Skip to content

[MCP] server-side SSE support#56051

Merged
greedy52 merged 7 commits intomasterfrom
STeve/54705_part_12_serverside_sse_support
Aug 25, 2025
Merged

[MCP] server-side SSE support#56051
greedy52 merged 7 commits intomasterfrom
STeve/54705_part_12_serverside_sse_support

Conversation

@greedy52
Copy link
Copy Markdown
Contributor

@greedy52 greedy52 commented Jun 24, 2025

related:

changelog: added SSE support for MCP access. Note that SSE transport is currently only used between Teleport agent and the remote MCP server, and SSE is not used on client side, yet.

Flow: Claude ----(stdio)---> Teleport ------(sse)----> sse server

tsh:

$ tsh mcp ls
Name       Description                                 Type  Labels   
---------- ------------------------------------------- ----- -------- 
dev-files  Shared files for developers                 stdio team=dev 
everything Demo everything MCP server in SSE transport SSE   team=dev 

server log:

2025-06-25T16:26:45.888-04:00 INFO [MCP] Start processing messages client_ip:127.0.0.1:57639 app:everything user:ai_user session_id:f1ebcac1-87ff-4d07-8e2e-8b240727a69b sse:stdout transport:SSE mcputils/reader.go:141
2025-06-25T16:26:45.888-04:00 INFO [MCP] Start processing messages client_ip:127.0.0.1:57639 app:everything user:ai_user session_id:f1ebcac1-87ff-4d07-8e2e-8b240727a69b stdio:stdin transport:stdio mcputils/reader.go:141

(notice sse:stdout vs stdio:stdin)

audit with external mcp_session_id:
Screenshot 2025-06-25 at 4 29 28 PM

@greedy52 greedy52 added no-changelog Indicates that a PR does not require a changelog entry MCP MCP Server related labels Jun 24, 2025
@greedy52 greedy52 mentioned this pull request Jun 23, 2025
14 tasks
Base automatically changed from STeve/54705_refactor to master June 25, 2025 17:21
@greedy52 greedy52 force-pushed the STeve/54705_part_12_serverside_sse_support branch from 1028307 to 88cb6e0 Compare June 25, 2025 20:21
@greedy52 greedy52 self-assigned this Jun 25, 2025
@greedy52 greedy52 force-pushed the STeve/54705_part_12_serverside_sse_support branch from 88cb6e0 to a5513cd Compare June 25, 2025 20:39
@greedy52 greedy52 marked this pull request as ready for review June 25, 2025 20:39
@greedy52 greedy52 force-pushed the STeve/54705_part_12_serverside_sse_support branch from a5513cd to bf331f2 Compare June 25, 2025 20:43
Comment thread api/proto/teleport/legacy/types/events/events.proto Outdated
Comment thread api/types/app.go Outdated
Comment thread api/types/constants.go Outdated
Comment thread lib/srv/mcp/audit.go Outdated
Comment thread lib/srv/mcp/session.go Outdated
Comment thread lib/utils/mcputils/protocol.go
Comment thread lib/utils/mcputils/reader.go Outdated
Comment thread lib/utils/mcputils/sse.go Outdated
Comment thread lib/srv/mcp/sse.go
@greedy52 greedy52 requested a review from Tener June 26, 2025 18:52
Comment thread api/types/constants.go Outdated
Comment thread lib/srv/mcp/sse.go Outdated
Comment on lines +48 to +49
session.logger.InfoContext(s.cfg.ParentContext, "Started handling stdio to SSE session", "base_url", logutils.StringerAttr(baseURL))
defer session.logger.InfoContext(s.cfg.ParentContext, "Completed handling stdio to SSE session")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How come logs in this function are using the parent context instead of ctx?

Copy link
Copy Markdown
Contributor Author

@greedy52 greedy52 Jul 4, 2025

Choose a reason for hiding this comment

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

i've added a comment that input context can be canceled by connection monitor so we use parent context here to make we see the defer log.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why does that matter for logging though? Cancellation is not a concept that is respected by slog.Handlers. Changing the context will result in a loss of request scoped attributes. I suggest using the request context for all logs emitted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This also speaks to whether storing a parent context is the correct design choice, see https://go.dev/blog/context-and-structs.

Copy link
Copy Markdown
Contributor Author

@greedy52 greedy52 Jul 10, 2025

Choose a reason for hiding this comment

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

Thanks for pointing out!! i always thought the log won't happen if cantext is canceled. i see now it's mentioned in the slog handler godoc. I will revert all the parent ctx (maybe except the end session event).

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.

Comment thread lib/srv/mcp/sse.go Outdated
// handleStdioToSSE proxies a stdio client connection to an SSE server.
func (s *Server) handleStdioToSSE(ctx context.Context, sessionCtx *SessionCtx) error {
// Prep.
ctx, cancel := context.WithCancel(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Under what conditions is this context to be used instead of the parent context and vice versa?

Comment thread lib/srv/mcp/sse.go Outdated
Comment thread lib/srv/mcp/sse_test.go
testCtx := setupTestContext(t, withAdminRole(t), withApp(app))
handleDoneCh := make(chan struct{}, 1)
defer close(handleDoneCh)
go func() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How is this goroutine cleaned up if HandleSession never returns?

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.

it uses t.Context as input. i've modified a little bit to make it safer in case go routine returns after cleanup starts.

Comment thread lib/srv/mcp/stdio.go Outdated
Comment thread lib/utils/mcputils/sse.go Outdated
Comment thread lib/utils/mcputils/sse.go Outdated
Comment thread lib/utils/mcputils/sse.go Outdated
Comment thread lib/utils/mcputils/sse.go Outdated
@marcoandredinis marcoandredinis removed their request for review July 4, 2025 14:16
@greedy52 greedy52 requested a review from rosstimothy July 4, 2025 19:45
@greedy52
Copy link
Copy Markdown
Contributor Author

greedy52 commented Jul 4, 2025

@gabrielcorado PTAL 🙏

Copy link
Copy Markdown
Contributor

@gabrielcorado gabrielcorado left a comment

Choose a reason for hiding this comment

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

LGTM, some minor comments. I've tested with a few MCP servers and worked nicely, great work.

Comment thread lib/utils/mcputils/sse.go Outdated
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, nil, trace.Errorf("unexpected status code: %d", resp.StatusCode)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about having a more detailed error message here? The error here indicates that the path or URI is wrong or the server doesn't support SSE (for example, it only supports Streamable HTTP). What about a message like: "Unexpected status code: %d. Ensure the server URL is reachable, and is serving an MCP SSE server on the specified path.".

What do you think?

Comment thread lib/utils/mcputils/sse.go Outdated
// endpoint used for posting client requests. If successful, the transport
// reader and message writer are returned.
func ConnectSSEServer(ctx context.Context, baseURL *url.URL) (*SSEResponseReader, *SSERequestWriter, error) {
httpClient, err := defaults.HTTPClient()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we support the same configuration for HTTP transport from regular HTTP apps?

tr, err := defaults.Transport()
if err != nil {
return nil, trace.Wrap(err)
}
// Add a timeout to control how long it takes to (start) getting a response
// from the target server. This allows Teleport to show the user a helpful
// error message when the target service is slow in responding.
tr.ResponseHeaderTimeout = requestTimeout
tr.TLSClientConfig, err = configureTLS(c)

@greedy52 greedy52 changed the title MCP access part 12: server-side SSE support [MCP] server-side SSE support Aug 25, 2025
# Conflicts:
#	api/types/app_test.go
#	api/types/events/events.pb.go
#	integration/appaccess/appaccess_test.go
#	lib/srv/mcp/memory.go
#	lib/srv/mcp/server.go
#	lib/srv/mcp/stdio_test.go
#	lib/utils/mcputils/protocol.go
#	lib/utils/mcputils/stdio.go
� Conflicts:
�	lib/srv/mcp/server.go
@greedy52 greedy52 force-pushed the STeve/54705_part_12_serverside_sse_support branch from dd2559a to e35a8b4 Compare August 25, 2025 14:56
@public-teleport-github-review-bot public-teleport-github-review-bot Bot removed the request for review from GavinFrazar August 25, 2025 14:56
@greedy52 greedy52 force-pushed the STeve/54705_part_12_serverside_sse_support branch from d23e50e to 407e7df Compare August 25, 2025 16:59
@greedy52 greedy52 removed the no-changelog Indicates that a PR does not require a changelog entry label Aug 25, 2025
@greedy52 greedy52 added this pull request to the merge queue Aug 25, 2025
Merged via the queue into master with commit 5a0a522 Aug 25, 2025
44 of 45 checks passed
@greedy52 greedy52 deleted the STeve/54705_part_12_serverside_sse_support branch August 25, 2025 18:17
mmcallister pushed a commit that referenced this pull request Sep 22, 2025
* MCP access part 12: server-side SSE support

# Conflicts:
#	api/types/app_test.go
#	api/types/events/events.pb.go
#	integration/appaccess/appaccess_test.go
#	lib/srv/mcp/memory.go
#	lib/srv/mcp/server.go
#	lib/srv/mcp/stdio_test.go
#	lib/utils/mcputils/protocol.go
#	lib/utils/mcputils/stdio.go

* parse uri for determining transport type

* fix pointer, atomic, and parse error

� Conflicts:
�	lib/srv/mcp/server.go

* fix schema, etc

* switch to golang internal mcp sse parsing

* remove ParentCtx from logging

* fix build and address comments
greedy52 added a commit that referenced this pull request Oct 23, 2025
* MCP access part 12: server-side SSE support

* parse uri for determining transport type

* fix pointer, atomic, and parse error

� Conflicts:
�	lib/srv/mcp/server.go

* fix schema, etc

* switch to golang internal mcp sse parsing

* remove ParentCtx from logging

* fix build and address comments
github-merge-queue Bot pushed a commit that referenced this pull request Oct 27, 2025
* [MCP] server-side SSE support (#56051)

* MCP access part 12: server-side SSE support

* parse uri for determining transport type

* fix pointer, atomic, and parse error

� Conflicts:
�	lib/srv/mcp/server.go

* fix schema, etc

* switch to golang internal mcp sse parsing

* remove ParentCtx from logging

* fix build and address comments

* [mcp] refactor jwt token and app header rewrite logic (#58601)

* [mcp] mcputils for streamable http (#58764)

* [mcp] mcputils for streamable http

* fix flaky test

* use utils.ReadAtMost and fmt.Appendf

* add a marshal function for event

* fix spell

* [mcp] update audit events for streamable HTTP transport (#59155)

* [mcp] update audit events for streamable HTTP transport

* nolint for unused functions for now, they will be used in next PR

* [mcp] server handler for streamable HTTP transport (#59499)

* [mcp] server handler for streamable hTTP transport

* review comments round 1

* add comments and fix flaky test

* [mcp] bump mcp-go version (#59500)

* [mcp] bump mcp-go version

* fix IO transport by explicit start

* [mcp] add server prometheus metrics (#59773)

* [mcp] add server prometheus metrics

* remove TODO and nolint

* use counter where possible and limit known methods

* move reporting test to individual tests

* nolint for "cancelled"

* Fix an issue docker container launched by MCP commands are not removed sometimes (#59879)

* Fix an issue docker container launched by MCP commands are not removed sometimes

* switch to math/rand/v2

* add "tsh proxy mcp" command (#59968)

* [refactor] client.NewMCPServerDialer (#60020)

* [refactor] client.NewMCPServerDialer

* TestVerifyTLSCertLeafExpiry

* TestMatchResourcesByFilters

* fix typo

* fix lint

* mcputils for streamable HTTP transport conversion (#60024)

* mcputils for streamable HTTP transport conversion

* remove need of context from mcptest functions

* add test for notification

* [mcp] "tsh mcp connect" support for streamable HTTP (#60120)

* implement "tsh mcp connect" for streamable HTTP

* wait for 5s just to be conservative

* [mcp] Web UI and Teleport Connect adjustments for SSE and Streamable HTTP MCP servers (#60281)

* [mcp] Web UI and Teleport Connect adjustments for SSE and Streamable HTTP MCP servers

* review comments

* [mcp] fix some edge cases for streamable HTTP (#60286)

* [mcp] add JWT and rewrite headers support for SSE MCP servers (#60320)

* fix go.mod to match master

* fix lint and ut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MCP MCP Server related size/lg

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants