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:<id>/<profile> 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.
This commit is contained in:
Maurice
2026-08-18 13:38:50 +02:00
committed by Maurice
parent e375f9e002
commit 02f7320c90
3 changed files with 8 additions and 5 deletions
@@ -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:<pluginId>/<profileId>". 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(),
+1 -1
View File
@@ -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:<pluginId>/<profileId>". 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(),
@@ -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
});
});