MCP connector
The Routerra Teams MCP server: endpoint and headers, the 18 tools and the REST endpoint each one wraps, and every place MCP behaves differently from REST.
The MCP connector puts the External API in front of an AI assistant. Same key, same data, same rules — the tools are thin wrappers over the REST endpoints, and the handful of places where the two transports genuinely differ are the reason this article exists.
The server is Streamable HTTP with no sessions: every call is a self-contained JSON-RPC POST, and there is no handshake to keep alive.
The facts
| Endpoint | POST /mcp · POST /mcp/{key} |
| Required header | Accept: application/json, text/event-stream |
| Tools | 18 — read-only ones carry readOnlyHint |
| Tool result | The same endpoint's JSON, in content[0].text |
curl -X POST https://teams-api.routerra.io/mcp \
-H "X-API-Key: rtk_…" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_plan","arguments":{"planId":315}}}'
The 18 tools
| Tool | REST endpoint | Notes |
|---|---|---|
get_team_context | GET /team | read-only |
list_drivers | GET /drivers | read-only |
update_driver | PATCH /drivers/{driverId} | |
search_address_book | GET /address-book | read-only |
optimize_plan | POST /optimize | |
list_plans | GET /plans | read-only |
get_plan | GET /plans/{planId} | read-only · view defaults to CONCISE |
cancel_plan | DELETE /plans/{planId} | confirm, propose |
duplicate_plan | POST /plans/{planId}/duplicate | |
add_stops | POST /plans/{planId}/stops | |
update_stop | PUT /plans/{planId}/stops/{stopId} | takes a patch argument |
remove_stops | POST /plans/{planId}/stops/remove | confirm |
dispatch_plan | POST /plans/{planId}/dispatch | confirm, propose |
approve_plan | POST /plans/{planId}/approve | propose |
revoke_plan | POST /plans/{planId}/revoke | confirm, propose |
get_delivery_stats | GET /statistics | read-only |
apply_change | POST /changes/{changeId}/apply | |
list_changes | GET /changes | read-only |
There is no MCP tool for POST …/preview or POST …/changes, and none is needed: on MCP the preview arrives on its own when confirm was not passed, and a proposal is made with propose: true on the tool itself.
Where MCP and REST diverge
| Difference | Details |
|---|---|
| Response envelope | cancel_plan, revoke_plan, remove_stops, dispatch_plan and approve_plan return {applied, result, preview, change} rather than the endpoint body directly. When applied is true the payload is in result. Empty members are omitted, not sent as null — a successful approve_plan returns exactly {"applied": true, "result": {…}}, so read a missing key as null |
confirm | MCP only, on cancel_plan, revoke_plan, remove_stops and dispatch_plan against an APPROVED plan. REST applies immediately |
propose | MCP only. The REST equivalent is POST …/changes |
autoRevoke | REST only. dispatch_plan drops the parameter — confirm=true is what stands in for consenting to the revoke |
| Missing scope | On MCP a key with write gets a proposal instead of a 403. In REST it is always 403 insufficient_scope |
| Preview | In REST it is a separate POST …/preview. On MCP it comes back by itself whenever confirm was not passed |
Default view | get_plan defaults to CONCISE; GET /plans/{planId} defaults to detailed |
| Rate-limit tier | Every MCP call is filtered in the shared tier (capacity 60), because the tier is picked from the URI and every tool call arrives at /mcp. The five-token optimize tier still applies on top: optimize_plan and dispatch_plan take an optimize token themselves, so an MCP optimization spends one token from each bucket. Over REST, /optimize and …/dispatch are filtered straight into the optimize tier and spend one token there |
The same call, two behaviours
The scope row above is the divergence that surprises people, so it is worth stating twice. A key holding write but not dispatch that tries to send routes gets:
- over REST —
403 insufficient_scope, and nothing happens; - over MCP —
applied: false, achangewithstatus: "PROPOSED", and a dispatcher who can apply it.
Nothing about the key changed. Only the transport did. A key with only read is refused on both, because it cannot even propose. See Proposed changes.
An MCP optimization spends two tokens, not one
What the assistant is told
The server ships its own instructions, so a connected model already knows the shape of the product before you prompt it: that dispatch computes routes and approve sends them; that editing stops on a computed plan gives HAS_CHANGES; to prefer the concise view; to show people the …Local times rather than the UTC ones; to ask the user before cancelling, removing stops, revoking, or dispatching over an APPROVED plan; to call get_team_context at the start of a session and dispatch only with drivers whose startLocation is near the stops; to name the missing scope and stop retrying when a call is refused; and, for proposals, to tell the user the change is queued and to call apply_change only after explicit agreement in that same conversation.
For the REST side of each tool, see Plan endpoints and Plan operation endpoints. For where a key's scopes come from, see API authentication & scopes.