Migrate org-scoped backend routes to /:org URL prefix (follow-up to #3250)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 407
- Forks
- 57
- Avg merge
- 5h 6m
- Merged PRs (30d)
- 880
Description
Background
#3250 stopped persisting activeOrganizationId to the session row (it was leaking across browser tabs) and switched to a per-request x-org-id header. That fix works for normal MCP fetches via the SDK but exposed a class of bugs every time a callsite forgets the header:
- The OAuth probe in
isConnectionAuthenticatedwas a hand-rolledfetchwithoutx-org-id→ proxy returned 403,supportsOAuthcame backfalse, and the OAuth popup never opened. - The save path
POST /api/connections/:id/oauth-tokenhad the same gap → token landed onconnection.connection_token(fallback) instead ofDownstreamTokenStorage, breakingbuildCloneInfo("No GitHub token found"). /api/vm-eventsand/org/:orgId/watchare SSE —EventSourcecan't set custom headers at all, so the header convention couldn't carry org context. We worked around it with a GET-only query-param fallback (?x-org-id=...).
Each of these was patched individually. The pattern is: the header convention is invisible from the URL, easy to forget, and structurally incompatible with EventSource.
Proposal
Move org context into the URL path for every org-scoped backend route, removing the need for the x-org-id header (and the SSE query-param workaround).
There's already precedent in the codebase:
/org/:organizationId/watch/org/:organizationId/events/:type/api/:org/models/*
After the migration:
/mcp/:connectionId→/:org/mcp/:connectionId/api/connections/:id/oauth-token→/:org/api/connections/:id/oauth-token/api/vm-events?...→/:org/api/vm-events?...(no morex-org-idquery param)- ...and the rest of the org-scoped surface for consistency.
authenticateRequest resolves the org from the path segment instead of the header; membership is verified the same way. Multi-tab safety comes from the URL itself — different tabs are different URLs.
Decisions to make
- Scope — narrow (just the routes that currently break or are fragile: proxy
/mcp/*,/api/connections/:id/oauth-token*, the two SSE routes) or sweep (all org-scoped routes:/api/org-sso,/api/files, decopilot, KV, vm-events, etc.). Sweeping gives one consistent shape; narrow keeps the diff small. - Slug vs id in the path — the frontend URL already uses the slug (
/gimenes-local/...); reusing it reads nicer and matches what users see.authenticateRequest's membership query already supports either. - Compatibility window — keep the old paths aliased and
x-org-idheader working during a deprecation window (matters for API-key consumers of/mcp/:connectionIdand/api/connections/...)? Or hard cut?
Touched surface (rough)
Backend (Hono routes): apps/mesh/src/api/app.ts, apps/mesh/src/api/routes/proxy.ts, downstream-token.ts, vm-events.ts, plus the routes mounted under /api/....
SDK + frontend fetch helpers:
packages/mesh-sdk/src/hooks/use-mcp-client.ts— drop thex-org-idheader, build org-scoped URL.packages/mesh-sdk/src/lib/mcp-oauth.ts—isConnectionAuthenticatedandcheckOAuthTokenStatus.- All
oauth-tokenPOST/DELETE callsites inapps/mesh/src/web/.... - The three SSE consumers (
vm-events-context.tsx,use-decopilot-events.ts,use-workflow-sse.ts).
Existing tests that assert the 403 cross-tenant guard (apps/mesh/src/api/routes/proxy.test.ts) need to be updated for the new path shape.
Out of scope
The interim fixes already in the codebase (per-callsite x-org-id headers, the GET-only query-param fallback in authenticateRequest) stay until this migration lands.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with apps/mesh/src/api/app.ts, apps/mesh/src/api/routes/proxy.ts, downstream-token.ts, vm-events.ts, and the listed SDK and SSE consumers. Review the existing proxy tests and decide the migration scope, path identifier, and compatibility window before tracing each org-scoped route. Done means the selected routes, clients, consumers, and cross-tenant tests consistently use the new URL shape without the header or SSE query-parameter workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, frontend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100