From 02f7320c902517a334029fc20eda686fc8da35cc Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 18 Aug 2026 13:38:50 +0200 Subject: [PATCH] fix(mcp): name the route profile keys the router actually accepts Both tool descriptions offered "transit" as an example. RouteCalculator only knows driving, walking and cycling plus the plugin:/ form, and maps anything else to driving when it draws the route, so an agent that set "transit" got a success and a car route with a car icon. Name the real keys and say plainly what happens to anything else. The incoming-leg test used "transit" too. It now uses walking, and the outgoing test seeds the incoming column first, so its "must not touch incoming" assertion proves something the factory did not already give it. --- server/src/nest/assignments/assignments.mcp.ts | 2 +- server/src/nest/days/days.mcp.ts | 2 +- server/tests/unit/mcp/tools-leg-transport-mode.test.ts | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/src/nest/assignments/assignments.mcp.ts b/server/src/nest/assignments/assignments.mcp.ts index ee549f83d..f0eb426a3 100644 --- a/server/src/nest/assignments/assignments.mcp.ts +++ b/server/src/nest/assignments/assignments.mcp.ts @@ -117,7 +117,7 @@ export class AssignmentsMcp { @Tool({ name: 'set_leg_transport_mode', - description: 'Set the travel mode of a route leg for a place assignment. Use direction "outgoing" (default) for the common case: the leg leaving this stop toward the next. Use direction "incoming" ONLY when this stop\'s arriving leg originates from something that is not itself a place assignment (e.g. a flight/train booking arrival, or a morning hotel departure) – setting "incoming" on an ordinary place-to-place leg is stored but has no effect on route rendering, because it targets a column that is only read for non-place origins. transport_mode is a route profile key (e.g. "driving", "walking", "cycling", "transit"); null clears it so the leg inherits the day default.', + description: 'Set the travel mode of a route leg for a place assignment. Use direction "outgoing" (default) for the common case: the leg leaving this stop toward the next. Use direction "incoming" ONLY when this stop\'s arriving leg originates from something that is not itself a place assignment (e.g. a flight/train booking arrival, or a morning hotel departure) – setting "incoming" on an ordinary place-to-place leg is stored but has no effect on route rendering, because it targets a column that is only read for non-place origins. transport_mode is a route profile key: "driving", "walking", "cycling", or a plugin profile written as "plugin:/". Any other value is stored but drawn as a driving route. null clears it so the leg inherits the day default.', inputSchema: { tripId: z.number().int().positive(), assignmentId: z.number().int().positive(), diff --git a/server/src/nest/days/days.mcp.ts b/server/src/nest/days/days.mcp.ts index 13ef856d5..430099d36 100644 --- a/server/src/nest/days/days.mcp.ts +++ b/server/src/nest/days/days.mcp.ts @@ -106,7 +106,7 @@ export class DaysMcp { @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.', + description: 'Set the whole-day default travel mode for a day. transport_mode is a route profile key: "driving", "walking", "cycling", or a plugin profile written as "plugin:/". Any other value is stored but drawn as a driving route. null clears the default. Per-segment leg modes still override this.', inputSchema: { tripId: z.number().int().positive(), dayId: z.number().int().positive(), diff --git a/server/tests/unit/mcp/tools-leg-transport-mode.test.ts b/server/tests/unit/mcp/tools-leg-transport-mode.test.ts index f52cb2b47..00f95c59e 100644 --- a/server/tests/unit/mcp/tools-leg-transport-mode.test.ts +++ b/server/tests/unit/mcp/tools-leg-transport-mode.test.ts @@ -70,6 +70,9 @@ describe('Tool: set_leg_transport_mode', () => { const day = createDay(testDb, trip.id); const place = createPlace(testDb, trip.id); const assignment = createDayAssignment(testDb, day.id, place.id); + // Seed the incoming column so the assertion below proves it survives; the factory + // leaves it NULL, so without this it would pass whether or not the tool touched it. + testDb.prepare('UPDATE day_assignments SET incoming_leg_transport_mode = ? WHERE id = ?').run('walking', assignment.id); await withHarness(user.id, async (h) => { const result = await h.client.callTool({ @@ -78,7 +81,7 @@ describe('Tool: set_leg_transport_mode', () => { }); const data = parseToolResult(result) as any; expect(data.assignment.leg_transport_mode).toBe('cycling'); - expect(data.assignment.incoming_leg_transport_mode ?? null).toBeNull(); // outgoing must not touch incoming (panel item 4) + expect(data.assignment.incoming_leg_transport_mode).toBe('walking'); // outgoing must not touch incoming (panel item 4) expect(broadcastMock).toHaveBeenCalledWith(trip.id, 'assignment:updated', expect.any(Object)); }); }); @@ -95,10 +98,10 @@ describe('Tool: set_leg_transport_mode', () => { await withHarness(user.id, async (h) => { const result = await h.client.callTool({ name: 'set_leg_transport_mode', - arguments: { tripId: trip.id, assignmentId: assignment.id, transport_mode: 'transit', direction: 'incoming' }, + arguments: { tripId: trip.id, assignmentId: assignment.id, transport_mode: 'walking', direction: 'incoming' }, }); const data = parseToolResult(result) as any; - expect(data.assignment.incoming_leg_transport_mode).toBe('transit'); + expect(data.assignment.incoming_leg_transport_mode).toBe('walking'); expect(data.assignment.leg_transport_mode).toBe('driving'); // outgoing untouched }); });