Skip to content

Implement legacy SSE client transport #101

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft

Conversation

mattt
Copy link
Contributor

@mattt mattt commented May 6, 2025

As discussed in #95 (comment)

See https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse

// Create an SSE client transport
// This implements the "HTTP with SSE" transport, an earlier specification.
// For new implementations, prefer HTTPClientTransport with streaming: true.
let sseTransport = SSEClientTransport(
    endpoint: URL(string: "http://localhost:8080/sse")! // Ensure endpoint is SSE-specific if needed
)
try await client.connect(transport: sseTransport)

@mattt mattt force-pushed the mattt/sse-transport branch 2 times, most recently from a403cc2 to 525389a Compare May 6, 2025 18:06
@mattt mattt marked this pull request as ready for review May 6, 2025 18:07
@mattt mattt force-pushed the mattt/sse-transport branch from a517191 to 5978c71 Compare May 6, 2025 18:23
@stallent
Copy link
Contributor

stallent commented May 7, 2025

Should we attempt to unify this with the existing http transport? I certainly don't want to but... The spec outlines how it is possible for a client to detect which its connecting to so it can support either. Its done like this...

Accept an MCP server URL from the user, which may point to either
a server using the old transport or the new transport.

Attempt to POST an InitializeRequest to the server URL, 
with an Accept header as defined above:

 - If it succeeds, the client can assume this is a server supporting the new Streamable HTTP transport.
 
 - If it fails with an HTTP 4xx status code (e.g., 405 Method Not Allowed or 404 Not Found):
           Issue a GET request to the server URL, expecting that this will open an SSE stream 
           and return an endpoint event as the first event.   

           When the endpoint event arrives, the client can assume this is a server running the
           old HTTP+SSE transport, and should use  that transport for all subsequent communication.

Can't say this would be "fun" but considering the spec outlines how, and there isn't an easy way for a dev to know which the server they are connecting to supports in many circumstances, maybe its worth seeing if the LOE is moderate.

@mattt
Copy link
Contributor Author

mattt commented May 7, 2025

@stallent Yeah, that's a great idea! We should totally do that instead.

@mattt mattt marked this pull request as draft May 7, 2025 12:53
@zats
Copy link
Contributor

zats commented May 7, 2025

Very excited for this direction, wondering what servers are you testing this against? Tried with https://mcp.zapier.com/mcp (SSE) and it won't work with or without streaming, fails with 405 method not allowed, where $ npx @modelcontextprotocol/inspector, curious if you have any thoughts what would be the issue.

@stallent
Copy link
Contributor

stallent commented May 7, 2025

I assume you tried on this branch with the SSETransport? You probably did, just double checking. Happy to debug this later this afternoon after a few dull meetings.

@zats
Copy link
Contributor

zats commented May 7, 2025

I believe so, but let me confirm again before you invest any time

Update:

I configured gmail zappier integration and using SSE without OAuth

image

SSE client fails with 404 when trying to post against messageURL when listing tools

image

I'm hopeful that zapier implementation is working since I tested with with $ npx @modelcontextprotocol/inspector
image

Another Update:

On the bright side, I was finally able to test streaming working against vercel (followed steps here https://vercel.com/templates/next.js/model-context-protocol-mcp-with-next-js)
On the less bright side, all the 1p SSE-based servers such as Zapier (or SSE version of vercel) does not seem to work.
Happy to poke provide more info if it'd help to debug, currently all SSE servers seem to fail with 405 on attempting to POST a message

@stallent
Copy link
Contributor

stallent commented May 8, 2025

Figured out the issue, but not sure the "right" way to fix. constructMessageURL is encoding a character it shouldn't be. Using the zapier example the endpoint message is this:

event: endpoint
data: /api/mcp/s/NjkhereAreSomeFakeCharactersRhYg==/message?sessionId=9f8325f6-3927-416a-ad60-c42cc09f7178

and the resulting components.url at end of constructMessageURL is encoding the ? to %3F. Lame. If you change the return line to this dumb solution

return URL(string: "\(components.scheme ?? "")://\(components.host ?? "")\(path)")

It fixes it, but hopefully @mattt can do a better fix.

@zats
Copy link
Contributor

zats commented May 8, 2025

Ace solution Stephen, I was printing URLs but somehow missed this, thanks!

@stallent
Copy link
Contributor

stallent commented May 8, 2025

this version sucks a little less. I'm sure there are things still needed but this isn't as dumb as the above.

private func constructMessageURL(from path: String) -> URL? {
    guard var baseEndpointComponents = URLComponents(url: endpoint, resolvingAgainstBaseURL: true) else { return nil }
    guard var messageEndpointComponents = URLComponents(string: path) else { return nil }
            
    // if the new path is a full url, return it.
    if messageEndpointComponents.scheme != nil {
         return messageEndpointComponents.url
    }
            
    return  messageEndpointComponents.url(relativeTo: baseEndpointComponents.url)
}

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