Skip to content

Support server returning only JSON on requests - #299

Merged
ihrpr merged 9 commits into
mainfrom
ihrpr/response-returns-json
Apr 10, 2025
Merged

Support server returning only JSON on requests#299
ihrpr merged 9 commits into
mainfrom
ihrpr/response-returns-json

Conversation

@ihrpr

@ihrpr ihrpr commented Apr 9, 2025

Copy link
Copy Markdown
Contributor

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
Comment thread src/server/streamableHttp.test.ts Outdated
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

@jerome3o-anthropic jerome3o-anthropic left a comment

Copy link
Copy Markdown
Member

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

Comment thread src/examples/README.md Outdated

```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
Copy Markdown
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
Copy Markdown
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 thread src/examples/README.md Outdated
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
Copy Markdown
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

Comment thread src/server/.streamableHttp.test.ts.swp Outdated

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

Comment thread src/server/streamableHttp.test.ts Outdated
await jsonResponseTransport.handleRequest(req, mockResponse);

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

Copy link
Copy Markdown
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
Copy Markdown
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

Comment thread src/server/streamableHttp.test.ts Outdated
it("should return JSON response for a single request", async () => {
const requestMessage: JSONRPCMessage = {
jsonrpc: "2.0",
method: "test",

Copy link
Copy Markdown
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?

@ihrpr ihrpr Apr 10, 2025

Copy link
Copy Markdown
Contributor Author

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

Comment thread src/server/streamableHttp.test.ts Outdated
await jsonResponseTransport.handleRequest(req, mockResponse);

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

Copy link
Copy Markdown
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
Copy Markdown
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

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