Repository navigation
Conversation
… (T020-T022) FR-012 byte-identical tools/list across two connections in both eras over an unpinned era harness; direct-tools and aggregated-prompts list filters must ignore a session's set_profile selection (fails on main); a modern stdio request must not persist a profile selection despite stdio's constant session id (fails on main: it does).
… (T023-T025) Spec 058 US3. profileSessionIDFromContext gates the resolver's session tier on !server.IsModernRequest(ctx) (stdio reports the constant "stdio" in both eras) and on a list-only marker; resolveActiveProfileForList skips the tier so the direct-tools and aggregated-prompts list filters no longer vary per connection (FR-012). Both the v2 precedence and the Spec 108 v3 resolution read the tier through the one gate, and a list-only resolution records nothing on the session.
… and guides modern requests (T026) Spec 058 US3. An unknown slug is refused as unknown in both eras; a 2026-07-28 request (gated on the era, not the transport's session id, which stdio reports as the constant "stdio") is refused with machine-actionable guidance naming /mcp/p/<slug>, and nothing is stored.
…ern era variants (T027-T028) TestProfile_SetProfileSessionScoped keeps its name and pins the legacy era explicitly; TestProfile_SetProfileModernRequestIsStateless covers the 2026-07-28 era over the unpinned era harness; TestProfile_SetProfileUnknown asserts the unknown-slug message in both eras.
…a (T029) Documents mcp-go's inflightKey collision for 2026-07-28 requests (every modern request runs under session id "", so two clients using JSON-RPC id 1 share key ":1") and asserts mcpproxy's guard isolates them while a legacy session can still cancel its own request.
…ec 058 R1, T030) A 2026-07-28 request has no session, so a notifications/cancelled posted on its own cannot be correlated with any in-flight request; mcp-go keys it as ":<id>" and would cancel whichever client's call shares that id. The guard acknowledges (202) and drops such notifications in the /mcp* middleware chain; legacy requests pass through unchanged.
mark3labs/mcp-go restricts interactions to collaborators, so the report is kept in the spec folder until it can be filed.
…isolated with no session (T031) Spec 058 FR-013, server edition: bearer JWT → user record → ResolveAgentTokenOwner → AllowedServers, re-derived per modern request; tool lists and calls differ per identity while every request runs under an empty session id.
…(T032) Spec 058 US3: initialize never runs in the 2026-07-28 era, so the session store never learns a modern client's name. Adds ActivityAttribution.ClientVersion (metadata.client_version when the session resolver wrote none) and the failing unit + end-to-end tests.
…l info (T033) Spec 058 US3. activityAttribution falls back to RequestProtocolInfoFromContext (ctx).ClientInfo when no session names the client, and the BeforeAny hook feeds the Spec 044 activation funnel and client-seen presence from the same data, debounced per client identity, so both survive the loss of AfterInitialize in the 2026-07-28 era.
… false (T034) The 'stable session id, no synthetic fallback' comment on sessionIDFromContext was rewritten in T023 (the id is attribution-grade only; modern requests have "" and stdio a constant). The 'background inactivity cleanup calls RemoveSession' comment in session_store.go had already been removed by #1497; the remaining claim there (the closed-entry TTL matches the storage inactivity timeout) is true. The AfterInitialize hook comment now says it never fires in the modern era.
… (T036a) Spec 058 FR-028 closes the stdio era gap: mcp-go has no stdio equivalent of WithStreamableHTTPProtocolVersions and chooses the era per request, so a stdio client could opt into 2026-07-28 while every HTTP door refused it. An OnRequestInitialization hook rejects a declared version outside clientFacingProtocolVersions — the one constant set the HTTP pin now reads too — with INVALID_REQUEST; legacy stdio and the HTTP surfaces are untouched.
…sultEnvelope (T036b) FR-016b on /mcp/all: over the live direct routing fixture, an upstream _meta payload key survives the hop while the hop-scoped serverInfo and protocolVersion keys are dropped, proving the registered handler calls the envelope helper.
…T035-T036) Profiles docs gain a Protocol eras section (set_profile is legacy-era only; lists never vary per connection; stdio holds the same pin). research.md D11 records the US3 decisions, including per-session token stats staying legacy-only. tasks.md ticks T020-T036b; ROADMAP.md regenerated.
…ient recorder (review round 1) Cross-review findings, both latent until T041 lifts the pin: a modern stdio request (session id "stdio") could still write the session's latest profile resolution and mark it worked; and the per-identity debounce map grew without limit on client-supplied names. recordSessionResolution and markWorkIfToolCall now skip modern requests; the recorder is capped at 1024 identities with a stale sweep.
…de markSessionWorked (review round 1) Sol 6.1 findings: UsageAggregate keys per-client usage on metadata.client_name, which a request-carried identity never wrote; the stdio modern-selection test accepted any error; the collision test's skip branch was unreachable; the server-edition isolation test stated a broader scope than it exercised. Adds TestStatelessList_AgentTokensScopedPerRequest through the production auth middleware (SC-004) and moves the work-session era gate into markSessionWorked so every funnel agrees.
Deploying mcpproxy-docs with
|
| Latest commit: |
1c92711
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://412b9404.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://058-us3-stateless-list-surfa.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…session (review round 2) The earlier assertion ran over a session store with no storage manager, where EnsurePersisted returns "" regardless of the era gate. The new test uses the storage-backed work-session fixture with a counting resolver and a legacy control; removing the gate in markSessionWorked fails it.
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 39437845341 --repo smart-mcp-proxy/mcpproxy-go
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spec 058 (MCP 2026-07-28 upgrade), user story US3 — stateless readiness, landed under the FR-028 legacy pin. Tracking issue #532. Tasks T020–T036b in
specs/058-mcp-2026-upgrade/tasks.md; decisions recorded in research.md D11.Under the 2026-07-28 revision there are no sessions, so
*/listresults must not vary per connection (FR-012) and nothing a request does may persist per-connection state (FR-011). The one piece of connection state mcpproxy holds is the legacyset_profileselection. This PR makes the modern era clean, fixes the pre-existing FR-012 violation on legacy, and closes two defects the era exposes.Behaviour
profileSessionIDFromContextreturns""for a 2026-07-28 request whatever the transport reports — stdio reports the constant"stdio"in both eras, and before this a modern stdioset_profilepersisted a selection (proven byTestProfileEra_ModernStdioRequestCannotPersistSelection). Both resolvers (v2 precedence and Spec 108 v3) read the one gate.resolveActiveProfileForList/listOnlyProfileResolutionskip the session tier and record nothing on the session; the direct-tools filter uses it at list time only (call-time re-evaluation keeps the full precedence) and the aggregated-prompts filter uses it forprompts/list(prompts/getkeeps its handler gate). Fixes the pre-existingprompts/listFR-012 violation research D1 named.set_profilevalidates the slug before the session guard (an unknown slug is answeredunknown profile '<slug>'in both eras) and refuses a modern request with guidance naming/mcp/p/<slug>."<sessionID>:<id>"; every modern request has session id"", so two clients using JSON-RPC id 1 collide and one can cancel the other's call — reproduced inTestCancelIsolation_LibraryCollisionStillPresent. The notification is handled before any hook, so the guard is HTTP middleware (modernCancellationGuard): a cancellation notification on a modern POST is acknowledged (202) and dropped. Legacy flows are untouched (TestCancelIsolation_LegacySelfCancelStillWorks).rejectUnservedProtocolOnStdiorefuses a declared version outsideclientFacingProtocolVersions(), the constant set the HTTP option now reads too, so T041 lifts both in one place.initialize(T032/T033).activityAttributionfalls back toRequestProtocolInfoFromContext(ctx).ClientInfo(name + newClientVersion→metadata.client_version), and theBeforeAnyhook feeds the Spec 044 activation funnel and client-seen presence from the same data, debounced per client identity per minute./mcp/allis now asserted over the live direct routing fixture (T036b).TestEntitlementStateless_TwoUsersIsolatedWithoutSession(-tags server) proves two OAuth users with differing entitlements are isolated over modern requests with no session (FR-013, research D4's untested half).Era harness
Modern-era tests reach the proxy through
unpinnedProxyEndpoint: the proxy's own MCP server over an unpinned Streamable HTTP transport behind the production auth middleware.rawEraClientspeaks JSON-RPC by hand so byte-identity can be asserted on the wire. Every modern-era test proves it took the modern path (requireEra).Deliberate residual (documented)
filterProfileV3Tools(Spec 108 FR-017 / Spec 115 FR-011) still hides the built-in administration tools according to a legacy session's selection at list time — a Spec 108 contract pinned by its visibility matrix tests, out of US3's scope. It cannot happen in the modern era and disappears at T077 (deprecation-window closeout). research.md D11 and the profiles docs name it.Upstream issue
The mcp-go report for the inflight-key collision could not be filed:
mark3labs/mcp-gorestricts interactions to collaborators (2026-10-20). The draft is inspecs/058-mcp-2026-upgrade/upstream-issues/mcp-go-inflight-cancel-collision.md;inflightKeyis unchanged on mcp-gomain(v1.3.0).No new dependencies.
Verification
./scripts/test-api-e2e.shgo test -racefor./internal/server/... ./internal/runtime/...(bare) and./internal/serveredition/... ./internal/server/...(-tags server) with the unit-tests.yml-skipregex.github/.golangci.yml, bare and--build-tags server: no findings in touched files (remaining findings are pre-existing in untouched files)zcode login), so the round ran on opencodegithub-copilot/gpt-6.1-solin three chunks. 7 findings, 6 fixed (bounded client recorder; era gate on the session write paths;metadata.client_nameon modern records so per-client usage counts them; two test-vacuity fixes; an accurately scoped server-edition test plus a production-chain agent-token test), 1 declined with rationale (body buffering: mcp-go already buffers the whole POST before era detection). Round 2 (incremental, on the fixes): 1 finding (a vacuous work-session assertion), fixed with a storage-backed test proven to bite. Table in research.md D11.Closes nothing on its own; part of #532.
Verified on a live instance
Scratch core from this branch (
./mcpproxy serve --config <scratch>/mcp_config.json --data-dir <scratch>/data --listen 127.0.0.1:18731, two disabled upstreams, profilesresearch/deploy, API key auth), driven with raw JSON-RPC over curl; stdio via./mcpproxy serve --listen ":0"on a second scratch data dir.tools/list8c0242b4cb8e74e8…on both)set_profile("research")on A, thentools/listA vs Bprofile_source: session); lists differ bycredentials/profilesonly — the documented built-in-visibility residual (Spec 108, legacy era only)2026-07-28)tools/listandset_profileon the pinned endpoint-32022 unsupported protocol version— never dispatched, nothing persistednotifications/cancelledPOSTmodernCancellationGuard(the same endpoint answers a modern request with 400), A unaffected and still servingnotifications/cancelledwith A's session idtools/listwith_meta-32600 protocol version 2026-07-28 is not served on the stdio surface: the client-facing era is pinned to 2025-11-25, …initialize+tools/listprotocolVersion: 2025-11-25, tools listedThe modern-era handler behaviour itself (refusal naming
/mcp/p/<slug>, identity-scoped lists, attribution) is reachable only through the in-process era harness while FR-028 pins the listening endpoint; those are theforEachEra/unpinnedProxyEndpointtests in this PR.🤖 Generated with Claude Code