Repository navigation
Index: nine findings from one audit of a large MCP server against the specification #1257
Description
I maintain a GitLab MCP server built on this SDK: about 865 tools on its individual surface, three tool surfaces, stdio and streamable HTTP, resource subscriptions and OAuth. I audited it against the MCP specification and against the SDK, and recorded every finding that belongs upstream rather than in my own code. Nine of them concern this SDK and have no fix yet: each was reproduced against a real deployment or against the compiled binary driven over its own transport, each names the specification clause, documentation page or semantic convention it fails, and each is pinned by a test I can hand you.
I am filing them as separate issues so that each can be triaged, labelled and closed on its own. This one is the index, so you can see the set at once and see that it comes from one audit rather than from nine unrelated reports. I checked every finding against current main before filing; the individual issues give the version I first observed it on.
Specification conformance, with a fix offered
Each of these four is a conformance defect with a pull request against it: a minimal change in the SDK's own style, a test that fails before it and passes after, and go test ./... green. Two of them have merged.
- mcp: the declared protocol version, not the negotiated one, decides what a session is served #1258 mcp: the declared protocol version, not the negotiated one, decides what a session is served. A client that asks for a newer revision and is negotiated down to an older one is still served the newer interaction. The pull request is mcp: choose the interaction pattern from the negotiated protocol version #1266, open.
- A cancelled request is still answered #1259 A cancelled request is still answered. A response goes out for a request the peer cancelled, which the specification says a receiver should not send. The pull request is mcp: send no response for a cancelled request on 2026-07-28 stdio sessions #1267, open.
- mcp: an unknown protocol version in per-request
_metais not answered with UnsupportedProtocolVersionError #1260 mcp: an unknown protocol version in per-request_metais not answered withUnsupportedProtocolVersionError. An unrecognised version is misclassified instead of being refused with the error the versioning page requires. The pull request is mcp: classify the per-request protocol version by membership #1268, merged. - mcp: a receiving middleware cannot tell whether a request carried params #1261 mcp: a receiving middleware cannot tell whether a request carried params. The predicate that answers the question is unexported, so middleware outside the package cannot distinguish an omitted params member from a present one. The pull request is mcp: add HasParams so middleware can tell whether params are present #1269, merged.
Found during review of the pull requests above
Two defects that the review of #1266 turned up rather than the original audit, each filed with a pull request in the same shape as the four above. Both have merged.
- mcp: three methods are served on a legacy session before the initialize handshake #1271 mcp: three methods are served on a legacy session before the initialize handshake. The gate lives in one branch of the method switch, so
logging/setLevel,resources/subscribeandresources/unsubscribeskip it; subscribe is the one that reaches state. The pull request is mcp: refuse a call on a legacy session before initialize #1273, merged. - mcp: the negotiated protocol version is recorded on one path of four, so stdio accepts batches a SEP-2575 session forbids #1272 mcp: the negotiated protocol version is recorded on one path of four, so stdio accepts batches a SEP-2575 session forbids. The pull request is mcp: record the negotiated protocol version on every path that records one #1274, merged.
Already filed
- mcp: the reason of a cancelled notification is discarded before any handler sees it #1254, the cancellation reason discarded before any handler sees it. Its pull request, mcp: carry a cancelled notification's reason as the request's cancel cause #1255, is merged.
API the SDK does not expose, proposal only
These four cannot be closed without new exported API, so they went out as proposals rather than as pull requests: each body states the requirement, cites the SDK code that stands in the way, and offers a concrete Go signature together with the alternatives I considered. Three of them, #1263, #1264 and #1265, are waiting on your decision about the shape, and I will write the pull request once you say which one you want.
- mcp: an SSE response keeps http.Server.WriteTimeout armed, so a long-lived stream dies before its first notification #1262 mcp: an SSE response keeps
http.Server.WriteTimeoutarmed, so a long-lived stream dies before its first notification. A stream that stays idle is cut by the server's own write deadline before it ever sends anything. The shape was settled on the issue: mcp: add StreamableHTTPOptions.StreamKeepAlive for periodic SSE comments on idle streams #1232 added the keep-alive, and mcp: extend the write deadline on every write to a keep-alive stream #1293, open, moves the write deadline on every write to that stream. - mcp: a server cannot send the notifications/cancelled that 2026-07-28 requires when it tears down a subscriptions/listen stream #1263 mcp: a server cannot send the
notifications/cancelledthat 2026-07-28 requires when it tears down asubscriptions/listenstream. The protocol asks for that notification on teardown and application code has no way to send it. - proposal: mcp: expose the JSON-RPC request id to receiving middleware and handlers #1264 proposal: mcp: expose the JSON-RPC request id to receiving middleware and handlers. There is no accessor for the id of the message being handled.
- mcp: a resource update cannot be delivered to one session, and the caller's context never reaches the sending middleware #1265 mcp: a resource update cannot be delivered to one session, and the caller's context never reaches the sending middleware. Delivery goes to every session subscribed to the URI, and nothing a caller puts on its context arrives at the middleware that sends it.
Filed since
- mcp: the streamable client does not start a new session after a 404, which the 2025-11-25 transport requires #1299 mcp: the streamable client does not start a new session after a 404, which the 2025-11-25 transport requires.
- mcp: Unsubscribe followed by Subscribe can leave a 2026-07-28 session subscribed to nothing #1359 mcp: Unsubscribe followed by Subscribe can leave a 2026-07-28 session subscribed to nothing. A listen's teardown removes the subscription a later listen on the same session made. The pull request mcp: retire a listen's subscriptions only while they are its own #1360 has merged and fixes the unsubscribe and subscribe again case; the issue stays open on which shape you want for two listens held open on one URI.
Every finding above is now filed. The nine of the original audit link back here.
Activity
docs(upstream): record the go-sdk batch and the auto-merge cancel ann…
docs(upstream): record the go-sdk batch and the auto-merge cancel ann…
docs(upstream): record the go-sdk batch and the auto-merge cancel ann…
docs(upstream): record the go-sdk batch and the auto-merge cancel ann…
docs(upstream): record the go-sdk batch and the auto-merge cancel ann…
jmrplens commentedon Oct 5, 2026
@guglielmo-san, a short status note on my pull requests and proposals from this set that are still open, for whenever you have a moment.
notifySessionsandResourceUpdatedas you asked, and itsprotocolVersion()doc comment now matches what mcp: record the negotiated protocol version on every path that records one #1274 changed. I have answered every thread on it.All three were green when I last updated them on 2026-09-29, and they still merge into current
mainwithout conflicts. GitHub shows them behind again, as it will after every merge, so I have left them as they are; if you want any of them updated or rebased, tell me and I will do it.#1263, #1264 and #1265 are still waiting on your call about the shape. Each lists the alternatives I considered, and once you pick a shape for one of them I will write its pull request.