Skip to content

Support server returning only JSON on requests #299

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

Merged
merged 9 commits into from
Apr 10, 2025

Conversation

ihrpr
Copy link
Contributor

@ihrpr ihrpr commented Apr 9, 2025

Spec states that servers must support either streaming or json response. Clients MUST support both.

If the input contains any number of JSON-RPC requests, the server MUST either return Content-Type: text/event-stream, to initiate an SSE stream, or Content-Type: application/json, to return one JSON object. The client MUST support both these cases.

We anticipate that TypeScript sdk will mostly be used with streaming, but for testing purpose adding support for returning JSON.
Returning JSON is more limited in functionality as we are loosing streaming tool notification/logging etc.

Example of a response calling to server that supports streaming:
image

Example of a response calling to server that support only JSON (loosing logging):
image

@ihrpr ihrpr requested a review from jspahrsummers April 9, 2025 20:55
@ihrpr ihrpr marked this pull request as ready for review April 9, 2025 21:14
beaulac and others added 3 commits April 9, 2025 21:18
* always send headers specified in requestInit option
* avoid doubled onerror call
* use for-await to iterate SSE stream
* remove outdated comments
* simplify requestId tracking
* throw error when response Content-Type is out of spec
StreamableHTTPClientTransport cleanup / fixes
Copy link
Member

@jerome3o-anthropic jerome3o-anthropic left a comment

Choose a reason for hiding this comment

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

looks mostly good - some doc updates + remove accidental .swp file

some non-blocking questions/nits, feel free to ignore


```bash
# Initialize the server and get the session ID from headers
SESSION_ID=$(curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" \
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't work for me - I get:

❯ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":"1"}' \
  -i http://localhost:3000/mcp 2>&1
HTTP/1.1 406 Not Acceptable
X-Powered-By: Express
Date: Thu, 10 Apr 2025 10:50:08 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{"jsonrpc":"2.0","error":{"code":-32000,"message":"Not Acceptable: Client must accept both application/json and text/event-stream"},"id":null}%   

Copy link
Member

Choose a reason for hiding this comment

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

This worked with the current implementation:

SESSION_ID=$(curl -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "capabilities": {},
      "protocolVersion": "2025-03-26", 
      "clientInfo": {
        "name": "test",
        "version": "1.0.0"
      }
    },
    "id": "1"
  }' \
  -i http://localhost:3000/mcp 2>&1 | grep -i "mcp-session-id" | cut -d' ' -f2 | tr -d '\r')
echo "Session ID: $SESSION_ID"

Comment on lines 29 to 32
curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" \
-H "mcp-session-id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","method":"mcp.call_tool","params":{"name":"greet","arguments":{"name":"World"}},"id":"2"}' \
http://localhost:3000/mcp
Copy link
Member

Choose a reason for hiding this comment

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

mcp.call_tool isn't correct, also I needed to have the Accept: text/event-stream header here too


curl -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Accept: text/event-stream" \
  -H "mcp-session-id: $SESSION_ID" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "greet",
      "arguments": {
        "name": "World"
      }
    },
    "id": "2"
  }' \
  http://localhost:3000/mcp

Copy link
Member

Choose a reason for hiding this comment

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

remove before merging

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ugh, how this got it here!

app.listen(PORT, () => {
console.log(`MCP Streamable HTTP Server with JSON responses listening on port ${PORT}`);
console.log(`Server is running. Press Ctrl+C to stop.`);
console.log(`Initialize with: curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":"1"}' http://localhost:${PORT}/mcp`);
Copy link
Member

Choose a reason for hiding this comment

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

looks like this may need the same treatment mentioned prior

await jsonResponseTransport.handleRequest(req, mockResponse);

// Wait for all promises to resolve
await new Promise(resolve => setTimeout(resolve, 50));
Copy link
Member

Choose a reason for hiding this comment

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

[non-blocking nit] is there a better way to do this?

Copy link
Member

Choose a reason for hiding this comment

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

seems like it would just slow down tests

it("should return JSON response for a single request", async () => {
const requestMessage: JSONRPCMessage = {
jsonrpc: "2.0",
method: "test",
Copy link
Member

Choose a reason for hiding this comment

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

[non-blocking question] should this use real MCP messages?

Copy link
Contributor Author

@ihrpr ihrpr Apr 10, 2025

Choose a reason for hiding this comment

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

yes, actually was thinking to use MCP server and rewrite tests, mocking request doesn't seem robust

await jsonResponseTransport.handleRequest(req, mockResponse);

// Wait for all promises to resolve - give it enough time
await new Promise(resolve => setTimeout(resolve, 100));
Copy link
Member

Choose a reason for hiding this comment

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

same nit as prior

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we actually don't even need it, was trying to make the test work, but the issues was in completely different place

@ihrpr ihrpr merged commit 75ec9de into main Apr 10, 2025
4 checks passed
@ihrpr ihrpr deleted the ihrpr/response-returns-json branch April 10, 2025 12:31
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