Repository navigation
Description
Follow-up from your latest review on PR #649 (findings #2 and #3's remote half) — opening this separately since it's a design question about the multi-server/federation protocol, not a bug in that PR's diff.
The gap
When a tool call routes to a remote gortex daemon (Router.RouteToolCall → proxy path, reached from internal/server/handler.go's handleToolCall, internal/mcp/streamable/transport.go's tryRouteToolCall, and the daemon dispatcher's tryProxyToolCall), two things about the origin session are not currently transmitted to, or enforced by, the remote:
-
The origin session's tool-surface policy. A tool hidden by the calling session's
facade-v1/hide-mode policy is checked locally bycheckToolGateinside the wrapped handler — but a proxied request never reaches that handler locally.Router.RouteToolCallonly gates onremoteEnabledIn/IsEffectful(whether the remote is enabled at all, and whether the tool is read-only) before forwarding — nothing checks whether this session is even allowed to call the tool in the first place. The remote executes under its own default policy, whatever that happens to be. -
A trusted, enforceable workspace/cwd ceiling. The origin resolves a
cwd/workspace for routing purposes (peekRouteContext,RouteInputs.Cwd), but that's used only to pick a remote destination — it isn't carried as an authoritative boundary the remote must honor once it receives the request. A remote hosting multiple repositories could, in principle, answer using a different repo than the one the caller's resolved cwd pointed to.
Neither of these is a bug in a single line — the origin-side gate simply doesn't exist on the remote-proxy branch at all, by construction: the local branch relies entirely on the downstream handler enforcing policy, and there is no equivalent downstream handler on the remote-proxy branch to rely on.
Why I'm asking rather than guessing at a fix
This needs an actual protocol decision, not just a code patch:
- Where does the origin-policy check belong? The natural shared choke point is
ProxyDecision.Decide(used by all three call sites above), but it doesn't currently have access to the MCPServer's session-policy machinery (IsToolEnabledForSessionetc.) — that'd mean threading a callback/dependency intodaemon.Router/ProxyDecisionthe waylocalExecuteis already wired in. - For the workspace ceiling: does the origin refuse to route at all once it knows the resolved cwd doesn't match anything the target remote can honor, or does the remote need a new authenticated field (a resolved workspace identity) it's expected to enforce against its own tracked repos? The latter is a real trust-boundary addition to the wire protocol between daemons, not just an origin-side check.
- Whatever the shape, it should apply uniformly across all three entry points (HTTP
/v1/tools/*, the Streamable HTTP/mcptransport, and the stdio daemon dispatcher's proxy path) rather than being patched into each independently — that's the mistake that let the ctx-timing bug in Promote deferred MCP tools on a registry-miss instead of 404ing #649 hide in one of three near-identical call sites for as long as it did.
Happy to implement once there's a direction — just don't want to unilaterally invent a trust-boundary design for the federation protocol without your steer, especially given how much back-and-forth #649 already took on narrower issues.
Activity
timkjr commentedon Aug 29, 2026
Addendum, connecting this to a pattern I noticed while implementing the local-only fixes from your review (separate PR #649 comment coming once those land):
That review's finding #1 —
X-Gortex-Overlay-Sessionsilently substituting for the real session identity when evaluating tool policy — turned out to have a bigger blast radius than "overlay vs. policy" once traced properly. The session ID this repo carries viagortexmcp.WithSessionID/SessionIDFromContextisn't overlay-specific or policy-specific — it's the one universal identity consulted by roughly 30 call sites: policy gating, token-stats accounting, the agent registry, diagnostics/health/readiness/stale-refs subscriptions, localization state, query logging, and notes/memory scoping, plus the ~8 genuinely overlay-related ones. The fix I landed (a separateWithOverlayCohortID/OverlayCohortIDFromContext, falling back to the real session id when unset) only had to touch the overlay-specific call sites — but it underlined that this class of bug (one context value doing double duty for two purposes that should stay independent) is exactly the shape of gap this issue is about, one layer further out.Right now there are at least four independent context-carried values a request needs correctly attached at every dispatch entrypoint for its identity/authorization to be enforced correctly: session id, session cwd (workspace boundary), the (now separated) overlay cohort, and the authorized-tool-call marker. Each one has to be remembered independently at each of the several entrypoints (HTTP
/v1/tools/*, the Streamable HTTP transport's two dispatch paths, the daemon's stdio dispatcher, the daemon's Streamable dispatcher) — which is exactly how the ctx-timing bugs from PR #649 slipped in three separate times over four review rounds.Not proposing a design here, just flagging it as something worth factoring into whatever approach you land on for this issue: a single, structured request-identity type (bundling session id + cwd + overlay cohort, maybe more) constructed once per inbound request at each entrypoint, carried under one context key, would likely be the more durable foundation for the remote-enforcement fix this issue is asking for — rather than adding yet another independent field to the pile and hoping every entrypoint remembers to set it. Whether that's worth doing now or as a later follow-up is your call; wanted you to have the full picture before deciding.
Address second maintainer review: separate overlay-cohort identity, r…