25
Backend
04
Security
08
iOS
07
Infra

Serve the planning engine as deterministic MCP tools from the GraphQL binary

ADR-0062 ACCEPTED · 2026-04-11
Serve the planning engine as deterministic MCP tools from the GraphQL binary

Decision

Serve MCP from the same axum binary that serves GraphQL, on its own route, using the rmcp crate. Each tool is a thin wrapper over the services the GraphQL resolvers call. A write over MCP stores a modification through the same path as the app, applies the same effects and publishes the same trip signal, so an open app updates live (ADR-0041).

Keep the tools deterministic and let the user's model be the agent. The client's model reads the tool descriptions, chooses which tools to call and in what order, and composes them. The server runs no agent loop and does no intent parsing on this path. The one tool that reaches a model on our side is neighbourhood discovery, which calls Exa (ADR-0038).

Tools return structured JSON, and the server also offers an MCP Apps trip card that capable clients render inline. Clients sign in with OAuth 2.1 (ADR-0063), because that is what MCP clients implement.

Why

The planning engine (ADR-0033) was reachable only through the iOS app, where our own model turns typed requests into effects (ADR-0032). General AI clients such as Claude Desktop and ChatGPT are becoming where people start a task, and MCP is how those clients call tools. We bet that planning a trip in one of them can bring people to the app for the trip itself: explore in a chat client, then travel with the app.

The user's client already has a capable model and pays for it. A server-side agent would duplicate that model and its tuning. Deterministic tools keep our side testable and cheap, and they put the product's quality in the tool descriptions, which we can evaluate (ADR-0064).

Rejected alternatives

  • Our own agent behind a chat endpoint. We would pay for and tune a model the user already has in their client.
  • An integration per assistant. MCP reaches every compliant client through one interface.

Consequences

Any MCP client can plan trips with no per-client work on our side. We now run an OAuth sign-in flow beside the app's PASETO tokens, and an MCP route beside GraphQL. We depend on a young protocol whose clients differ in what they support, such as inline UI. Tool descriptions are now API surface, and a badly worded one makes models call the wrong tool.