Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PoC of stateless HTTP transport #24

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

sd2k
Copy link
Collaborator

@sd2k sd2k commented Feb 13, 2025

This is an implementation of a stateless transport over HTTP for MCP. It's not part of the official spec, but something similar will probably be added in the future.

Functionally, it just presents a single HTTP endpoint (conventionally at /mcp) that accepts JSONRPC messages as POST bodies, and returns JSONRPC messages as responses.

Each inbound message is validated and gets its own 'server' instance. The handler then takes care of initializing the server (which in a stateless protocol is the responsibility of the client), then forwards the inbound message to the server, and returns the server's response.

We could easily add middleware for things like authentication, similar to in #21.

This also comes with a 'client' implementation which is similar to the stdio and sse clients in the official SDK and returns a pair of read/write streams for use with a ClientSession. This is useful for testing or writing example MCP clients.

The idea is that this transport will be compatible with the HTTP transport in the unofficial Go SDK, so we can start building examples of usage. Notably this client also supports custom headers, which will be useful for authentication.

You can test this out locally by running it like this:

uvx --with-editable . mcp-grafana --transport http

Then calling it like this:

curl 'localhost:8000/mcp' -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

sd2k added 2 commits February 13, 2025 12:33
This is an implementation of a stateless transport over HTTP
for MCP. It's not part of the official spec, but something
similar will probably be added in the future.

Functionally, it just presents a single HTTP endpoint
(conventionally at `/mcp`) that accepts JSONRPC messages
as POST bodies, and returns JSONRPC messages as responses.

Each inbound message is validated and gets its own 'server'
instance. The handler then takes care of initializing the
server (which in a stateless protocol is the responsibility
of the client), then forwards the inbound message to the
server, and returns the server's response.

We could easily add middleware for things like authentication,
similar to in #21.

This also comes with a 'client' implementation which is similar
to the stdio and sse clients in the official SDK and returns a
pair of read/write streams for use with a `ClientSession`. This
is useful for testing or writing example MCP clients.

The idea is that this transport will be compatible with the
[HTTP transport in the unofficial Go SDK][go-sdk], so we can
start building examples of usage. Notably this client also
supports custom headers, which will be useful for authentication.

[go-sdk]: https://github.com/metoro-io/mcp-golang/blob/main/examples/http_example/README.md
@sd2k sd2k force-pushed the poc-add-stateless-http-transport branch from b3ddf55 to 2ffc1de Compare February 14, 2025 09:24
sd2k added a commit to grafana/typescript-sdk that referenced this pull request Feb 14, 2025
This client transport is a stateless transport that uses JSONRPC
over HTTP POST, foregoing stateful communication and therefore
avoiding some of the complexity of the SSE transport (and the
need for persistent connections). It does mean that server-to-client
communication is not possible.

This transport is also implemented in Python in [this PR], and
the TypeScript client has been tested against the Python server.

[this PR]: grafana/mcp-grafana#24
@sd2k sd2k marked this pull request as ready for review February 18, 2025 16:58
@sd2k sd2k requested a review from a team as a code owner February 18, 2025 16:58
Copy link
Contributor

@annanay25 annanay25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not part of the official spec, but something similar will probably be added in the future.

Will it be a pain to maintain the unofficial client if this doesn't get merged into the official spec? I remember reading somewhere that we'd have to maintain our own implementation anyway, but just confirming we are ok with that outcome.

Everything else LGTM!

logger.debug("Waiting for request body")
body = await write_stream_reader.receive()

logger.debug(f"Connecting to HTTP endpoint: {url}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This has bitten me before when the URLs contain usernames/passwords 😬 but its behind debug so might be ok!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't say that's ok, we commonly enable debug logging in prod as needed. We should make sure to clean the url before writing it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants