mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
feat(mcp): add set_day_default_transport_mode tool
Wrap DaysService.setDefaultTransportMode behind an MCP tool, mirroring the days/:id/transport REST route. Adapter only; per-segment leg modes still override the day default.
This commit is contained in:
@@ -104,6 +104,30 @@ export class DaysMcp {
|
||||
return ok({ success: true });
|
||||
}
|
||||
|
||||
@Tool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
description: 'Set the whole-day default travel mode for a day. transport_mode is a route profile key (e.g. "driving", "walking", "cycling", "transit"); null clears the default. Per-segment leg modes still override this.',
|
||||
inputSchema: {
|
||||
tripId: z.number().int().positive(),
|
||||
dayId: z.number().int().positive(),
|
||||
transport_mode: z.string().nullable().optional().describe('Route profile key (e.g. "driving"), or null to clear the day default'),
|
||||
},
|
||||
annotations: TOOL_ANNOTATIONS_WRITE,
|
||||
access: { group: 'trips', mode: 'write' },
|
||||
})
|
||||
async setDayDefaultTransportMode(
|
||||
{ tripId, dayId, transport_mode }: { tripId: number; dayId: number; transport_mode?: string | null },
|
||||
ctx: McpContext,
|
||||
) {
|
||||
if (this.auth.isDemoUser(ctx.userId)) return demoDenied();
|
||||
if (!this.days.verifyTripAccess(tripId, ctx.userId)) return noAccess();
|
||||
if (!this.guards.hasTripPermission('day_edit', tripId, ctx.userId)) return permissionDenied();
|
||||
if (!this.days.getDay(dayId, tripId)) return { content: [{ type: 'text' as const, text: 'Day not found.' }], isError: true };
|
||||
const day = this.days.setDefaultTransportMode(dayId, transport_mode ?? null);
|
||||
this.guards.safeBroadcast(tripId, 'day:updated', { day });
|
||||
return ok({ day });
|
||||
}
|
||||
|
||||
@ResourceTemplate({
|
||||
name: 'trip-days',
|
||||
uriTemplate: 'trek://trips/{tripId}/days',
|
||||
|
||||
@@ -182,3 +182,115 @@ describe('Tool: set_leg_transport_mode', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// set_day_default_transport_mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Tool: set_day_default_transport_mode', () => {
|
||||
it('sets the day default transport mode', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: day.id, transport_mode: 'driving' },
|
||||
});
|
||||
const data = parseToolResult(result) as any;
|
||||
expect(data.day.default_transport_mode).toBe('driving');
|
||||
expect(broadcastMock).toHaveBeenCalledWith(trip.id, 'day:updated', expect.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the day default with null', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
testDb.prepare('UPDATE days SET default_transport_mode = ? WHERE id = ?').run('driving', day.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: day.id, transport_mode: null },
|
||||
});
|
||||
const data = parseToolResult(result) as any;
|
||||
expect(data.day.default_transport_mode).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the day with its populated assignments array', async () => {
|
||||
// setDefaultTransportMode returns { ...day, assignments }; exercise the non-empty case (panel item 4).
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const assignment = createDayAssignment(testDb, day.id, place.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: day.id, transport_mode: 'driving' },
|
||||
});
|
||||
const data = parseToolResult(result) as any;
|
||||
expect(Array.isArray(data.day.assignments)).toBe(true);
|
||||
expect(data.day.assignments).toHaveLength(1);
|
||||
expect(data.day.assignments[0].id).toBe(assignment.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a day belonging to another trip (cross-trip join)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip1 = createTrip(testDb, user.id);
|
||||
const trip2 = createTrip(testDb, user.id);
|
||||
const day2 = createDay(testDb, trip2.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip1.id, dayId: day2.id, transport_mode: 'walking' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when day not found', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: 99999, transport_mode: 'walking' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns access denied for non-member', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: day.id, transport_mode: 'walking' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('blocks demo user', async () => {
|
||||
process.env.DEMO_MODE = 'true';
|
||||
const { user } = createUser(testDb, { email: 'demo@nomad.app' });
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
name: 'set_day_default_transport_mode',
|
||||
arguments: { tripId: trip.id, dayId: day.id, transport_mode: 'walking' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user