mcp: prototype implementation for SEP 1442
This PR is the result of spending a few hours going through and attempting to implement SEP 1442 (modelcontextprotocol/modelcontextprotocol#1442).
Implemented:
- skipping initialization
- new protocol version and session ID treatment
- server/discover
- unsupported version errors
- new _meta fields
- client capability embedding
Still TODO:
- client-initiated streams
- ergonomics and documentation for handling capabilities from application code
- many, many more tests (and corresponding bug fixes)
This is very much quick and dirty, though I learned a lot about the SEP in the process. Tests pass, albeit largely because I configure the client to keep initialization, or downgrade the protocol version.
Notably, server->client requests do work in the context of an uninitialized session.
Additional observations will be noted in PR comments.
Here are my raw, mostly unfiltered notes from implementing this SEP. (I did delete a few where I was just confused).
**Protocol Version negotiation **
- Is the client allowed to change protocol versions within a given session? I assume not. (Answer: yes!)
- Do all server->client requests made within the context of a client->server request have to have the same protocol version?
- What if I get an error saying use protocol version B, and then another (later) error saying to use protocol version C. Do we allow that?
- Why MUST the error message be “Unsupported protocol version”. The code and data describe the semantics.
Session negotiation
- Nit: InvalidSessionError and SessionRequiredError both duplicate UnsupportedVersionError. They should be distinguished, and have distinguished error codes.
- Why is InvalidSessionError a 400 over HTTP. Shouldn’t it be a 404 (Not Found)? In what cases would a session ID be invalid?
- What is the client supposed to do when it receives a SessionRequiredError? Previously streamable semantics were dictated by HTTP error codes, but 400 does not have clear semantics attached to it.
- The section on per-request sessions is a little vague. I assume that the client is permitted to use its own UUID for the session ID. What is the lifespan of that session? Does it need to be explicitly deleted by a DELETE request? For now, I hold it open only for the duration of the request that created it.
Capability negotiation
- I think it’s a mistake to have server capabilities sent in an initial response message in the “client/capabilities” stream, which is another type of standalone stream. This seems like mixing transport and protocol in an unclean way. It’s also inconsistent with other stream semantics: the standalone stream not having any responses on it, and the ordinary POST request streams terminate with their response. Why not just have the client initialize the session, in this case?