SSEClientTransport drops path prefix when endpoint URL is not on the HTTP root (similar to python-sdk#563)
维护者通常 1 天内回复
还没有人认领这个 Issue。
- #741 来自 @majiayu000 —— 已关闭,未合并
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 50/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 停滞
- 技术栈
- go
- 领域
- api, networking
调研方向
从 SSEClientTransport.Connect 开始,这里会从配置的 Endpoint 解析出第一个 Endpoint 事件,并将文档中记录的复现 URL 与得到的消息 URL 进行比较。为非根路径前缀以及以 / 开头的 Endpoint 事件添加覆盖;完成标准是后续 POSTs 保留路由前缀,同时不破坏正常的 Endpoint 解析。
由索引模型根据 Issue 内容生成。
描述
Describe the bug
SSEClientTransport drops the path prefix of the configured Endpoint when resolving the message endpoint from the first endpoint SSE event.
In Connect, the message endpoint is derived like this:
// Connect connects through the client endpoint.
func (c *SSEClientTransport) Connect(ctx context.Context) (Connection, error) {
parsedURL, err := url.Parse(c.Endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "GET", c.Endpoint, nil)
if err != nil {
return nil, err
}
httpClient := c.HTTPClient
if httpClient == nil {
httpClient = http.DefaultClient
}
req.Header.Set("Accept", "text/event-stream")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
msgEndpoint, err := func() (*url.URL, error) {
var evt Event
for evt, err = range scanEvents(resp.Body) {
break
}
if err != nil {
return nil, err
}
if evt.Name != "endpoint" {
return nil, fmt.Errorf("first event is %q, want %q", evt.Name, "endpoint")
}
raw := string(evt.Data)
return parsedURL.Parse(raw)
}()
if err != nil {
resp.Body.Close()
return nil, fmt.Errorf("missing endpoint: %v", err)
}
// From here on, the stream takes ownership of resp.Body.
s := &sseClientConn{
client: httpClient,
msgEndpoint: msgEndpoint,
incoming: make(chan []byte, 100),
body: resp.Body,
done: make(chan struct{}),
}
go func() {
defer s.Close() // close the transport when the GET exits
for evt, err := range scanEvents(resp.Body) {
if err != nil {
return
}
select {
case s.incoming <- evt.Data:
case <-s.done:
return
}
}
}()
return s, nil
}
When the server sends an endpoint event whose data starts with / (for example /messages?sessionId=123), parsedURL.Parse(raw) replaces the entire path of c.Endpoint instead of preserving the prefix.
So if c.Endpoint is:
http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/sse
and the server sends:
event: endpoint
data: /messages?sessionId=123
then msgEndpoint becomes:
http://192.168.1.57:8000/messages?sessionId=123
instead of:
http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/messages?sessionId=123
This makes it impossible to use SSEClientTransport with an MCP server that is mounted behind a path prefix or with a reverse proxy that encodes routing information in the path.
This is very similar to the issue reported in the Python SDK in modelcontextprotocol/python-sdk#563.
To Reproduce
Steps to reproduce the behavior:
-
Put an MCP SSE server behind a reverse proxy that exposes it at a non-root path, for example:
http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/sse -
Have the backend server send an
endpointevent like:event: endpoint data: /messages?sessionId=123 -
In a Go client, connect using
SSEClientTransport:transport := &mcp.SSEClientTransport{ Endpoint: "http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/sse", } client := mcp.NewClient(&mcp.Implementation{ Name: "mcp-client", Version: "dev", }, nil) ctx := context.Background() session, err := client.Connect(ctx, transport, nil) if err != nil { log.Fatalf("Connect failed: %v", err) } defer session.Close() -
Check the HTTP traffic on
192.168.1.57:8000(e.g. reverse proxy logs). You’ll see:- The initial
GETgoes to/mcp/http://192.168.1.30:8088/sse(correct). - Subsequent POSTs go to
/messages?sessionId=123(missing the/mcp/http://192.168.1.30:8088prefix), which breaks any path-based routing.
- The initial
Expected behavior
The message endpoint resolution should not silently drop the original path prefix of c.Endpoint when the server sends the endpoint event.
For example, for:
Endpoint = "http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/sse"endpointevent data/messages?sessionId=123
I would expect msgEndpoint to still include the prefix (or at least have a way to preserve it), so that path-based reverse proxies remain usable.
Logs
Example reverse-proxy logs (simplified):
# Initial SSE connection
192.168.1.100 - - "GET /mcp/http://192.168.1.30:8088/sse HTTP/1.1" 200 -
# Subsequent POST from go-sdk SSE client (missing prefix)
192.168.1.100 - - "POST /messages?sessionId=123 HTTP/1.1" 404 -
The proxy only routes /mcp/http://192.168.1.30:8088/..., so the POST to /messages fails.
Additional context
Two questions / possible directions where I’d really appreciate guidance from the maintainers:
-
Can / should servers return a “relative” path in the
endpointevent?
Right now many examples use a leading slash, e.g.data: /messages?sessionId=123.
If instead the server returned something likedata: messages?sessionId=123(no leading/), thenparsedURL.Parse(raw)would treat it as relative to the original endpoint path and the prefix would be preserved.- Is such a relative URI considered valid according to the MCP spec for
endpointevents? - If yes, should server implementations be encouraged to do this when they expect to live behind path prefixes?
- Is such a relative URI considered valid according to the MCP spec for
-
Would it make sense for the Go client to support configuring two paths / URLs?
For example:- One URL for the initial SSE
GET(Endpoint), and - Another explicit URL / base URL for the message POST endpoint (e.g.
MessageEndpointor similar), instead of always deriving it fromEndpoint+evt.Data.
This would make it much easier to support path-based reverse proxies (and setups where routing information is encoded in the path) without having to modify the MCP server itself.
- One URL for the initial SSE
Current workaround
As a temporary workaround, I’m using a rather ugly “double-prefixed” endpoint and a very permissive server handler:
-
Client-side
Endpointconfigured inSSEClientTransport:http://192.168.1.57:8000/mcp/http://192.168.1.30:8088/mcp/http://192.168.1.30:8088/
-
After the reverse proxy on
192.168.1.57:8000, this is forwarded to the backend as:http://192.168.1.30:8088/mcp/http://192.168.1.30:8088/ -
The MCP server (which also uses go-sdk) sends an
endpointevent like:event: endpoint data: /mcp/http://192.168.1.30:8088/?sessionid=xxx -
After the same proxy logic, the final POSTs end up at:
http://192.168.1.30:8088/?sessionid=xxx
On the server side I’m using the go-sdk SSE handler mounted at the root, without any path restrictions:
sseHandler := mcp.NewSSEHandler(s, nil)
err := http.ListenAndServe(address, sseHandler)
if !errors.Is(err, http.ErrServerClosed) {
slog.Error("failed to listen and serve", "error", err)
os.Exit(1)
}
Because NewSSEHandler is handling all incoming paths and only cares about the query parameter sessionid, this setup happens to work and the session ID is parsed correctly. However, it’s clearly a hacky workaround and illustrates how difficult it is to use SSEClientTransport cleanly when the server is behind a path-based reverse proxy.
- go-sdk version: v1.1.0
- Go version: 1.25.4
- OS: macOS
- 主要语言
- Go
- 星标
- 5.3k
- 派生
- 607
- 平均合并
- 1 天 17 小时
- 30 天内合并 PR
- 80
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
modelcontextprotocol/go-sdk 的其他 Issue
-
Expose generic `SendNotification` on `ServerSession` for custom protocol extensions可能重新可做 关联的 PR 已关闭且未合并。 未关闭needs investigation
难度 2/5 1-3 小时 新手友好度 72/100
modelcontextprotocol/go-sdk#745 · 13 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
mcp: a second subscriptions/listen for the same URI silences the first可能已有人在做 @jmrplens 今天认领。 未关闭
modelcontextprotocol/go-sdk#1489 · 3 条评论 · 已指派 1 人 ·
维护者通常 1 天内回复
-
mcp: ClientSession.CallTool omits Mcp-Param-* headers unless the tool is in the ListTools cache, so a call without a prior tools/list is rejected可能已有人在做 @huynle-dev 今天认领。 未关闭
难度 4/5 1-2 天 新手友好度 35/100
modelcontextprotocol/go-sdk#1481 · 2 条评论 · 已指派 1 人 ·
维护者通常 1 天内回复
-
mcp: list_changed can only be broadcast; a server cannot notify one session (2026-07-28 subscriptions/listen)可能已有人在做 @huynle-dev 今天认领。 未关闭
难度 4/5 1-2 天 新手友好度 32/100
modelcontextprotocol/go-sdk#1480 · 1 条评论 · 已指派 1 人 ·
维护者通常 1 天内回复
-
mcp: Client.Connect falls back to initialize after a 401 on server/discover, running the authorization flow twice可能已有人在做 @kerthans 于 12 天前认领。 未关闭
难度 3/5 1-2 天 新手友好度 35/100
modelcontextprotocol/go-sdk#1423 · 1 条评论 ·
维护者通常 1 天内回复
查看 modelcontextprotocol/go-sdk 的全部 Issue
相似的 Issue
-
date_time, url_encode, html_escape, to_unix_time, contains_all and contains_any panic when called without arguments可能已有人在做 @antcybersec 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
projectdiscovery/dsl#372 ·
维护者通常 1 天内回复
-
blocked-needs-validation
难度 2/5 1-3 小时 新手友好度 62/100
projectcapsule/capsule#2265 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
JuliusBrussee/caveman#1449 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 72/100
维护者通常 1 天内回复
-
bot-found bug priority: P3
难度 2/5 1-3 小时 新手友好度 72/100
madenvel/KalinkaPlayer#498 ·