Skip to content

Fix hosted stateless follow-up path for large directions/isochrone results - #257

Open
mattpodwysocki wants to merge 1 commit into
mainfrom
fix/directions-hosted-stateless-followup
Open

Fix hosted stateless follow-up path for large directions/isochrone results#257
mattpodwysocki wants to merge 1 commit into
mainfrom
fix/directions-hosted-stateless-followup

Conversation

@mattpodwysocki

Copy link
Copy Markdown
Contributor

Summary

directions_tool and isochrone_tool stored large responses in temporaryResourceManager (an in-process Map, 30-minute TTL) and returned a mapbox://temp/{id} URI for a client to resources/read afterward. The hosted deployment is fully stateless — every POST /mcp can land on a different one of several ECS tasks behind a load balancer with no session stickiness — so a follow-up read landing on a different task than the one that computed the route found nothing, surfacing as "Resource not found or expired."

This was most visible on long routes (e.g. London→Scotland with geometries="geojson"), where the cleaned response crosses the 50KB inline threshold. It read like "the Directions API is still computing" or "requested too soon," but Directions is synchronous and had already finished by the time the URI was returned — the actual cause was which ECS task happened to serve the second HTTP request.

Fix

Both tools now encode the full response directly into a new self-describing mapbox://inline-response/{tool}?data=... ref, following the same zero-server-state pattern already used by mapbox://compute/, mapbox://selffetch/, and mapbox://inline/payload in this codebase. Resolving the ref is a pure function of the URI string — no store, no TTL, no owner check — so it works identically no matter which task or process handles the follow-up resources/read.

static_map_image_tool's equivalent large-payload fallback still uses temporaryResourceManager and is intentionally left as-is here: it stores base64-encoded binary image data, not JSON, so naively applying the same scheme would double-base64-encode it (~77% size overhead vs. ~33%). Tracked as a fast follow-up.

hosted-mcp-server imports @mapbox/mcp-server as a plain npm dependency and creates a fresh MCP server per request via MCPManager/getMCPServer() — this fix requires no code changes there, only a version bump once released.

Acceptance criteria

  • A follow-up read for a large directions_tool/isochrone_tool result works when handled by a different process/ECS task — proven with test/integration/hostedMultiTaskFollowup.test.ts, which computes the route in one spawned server process, kills it outright, and resolves the ref from a second, completely independent process via a real resources/read call.
  • No "still computing"/"requested too soon" framing anywhere — the ref is resolvable immediately and unconditionally; there is no polling or job-status concept because Directions/Isochrone are synchronous.
  • Reproduced against a real multi-process setup, not just a single local process (see integration test above). I also verified the test is meaningful by stashing the fix, confirming the test fails against the old mapbox://temp/ code path, then restoring the fix and confirming it passes.
  • Chat-style directions (distance/duration, render_map_tool preview via the self-fetch ref) are untouched for long routes — only the large-geometry inline-vs-ref fallback path changed.
  • CHANGELOG.md updated under Unreleased.

Test plan

  • npm run build
  • npx vitest run — 949 tests passing, including new unit tests for inlineResponseRef.ts/InlineResponseResource.ts and updated large-response tests for both tools
  • npx eslint --fix on all changed/new files — clean
  • Real two-process integration test proving the cross-task fix (see above)

🤖 Generated with Claude Code

… refs

directions_tool and isochrone_tool stored large responses in
temporaryResourceManager (an in-process Map, 30-min TTL) and returned a
mapbox://temp/{id} URI for a follow-up resources/read. The hosted
deployment is fully stateless across several ECS tasks with no session
stickiness, so a follow-up landing on a different task than the one that
computed the route found nothing - surfacing as "Resource not found or
expired" and easily misread as "Directions still computing" on long
routes, when the API had already finished.

Both tools now encode the full response into a new self-describing
mapbox://inline-response/{tool}?data=... ref, following the same
zero-server-state pattern already used by mapbox://compute/,
mapbox://selffetch/, and mapbox://inline/payload. Resolving the ref is a
pure function of the URI, so it works from any task/process - proven with
a real two-process integration test that computes the route in one
spawned server, kills it, and resolves the ref from a second, independent
process.

static_map_image_tool's equivalent fallback still uses
temporaryResourceManager and is intentionally left as-is (base64 binary
payload needs its own encoding to avoid double-base64 overhead) -
tracked as a fast follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant