Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ docker run --rm -i -e PROMETHEUS_MCP_SERVER_PROMETHEUS_URL="https://$yourPrometh
```

```shell
# Streamable HTTP transport (capable of SSE as well)
# Stateless HTTP transport (application/json request/response)

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 should be reduced to a s/Streamable/Stateless/

docker run --rm -p 8080:8080 ghcr.io/tjhop/prometheus-mcp-server:latest --prometheus.url "https://$yourPrometheus:9090" --mcp.transport "http" --web.listen-address ":8080"

# or using env vars
Expand Down
27 changes: 5 additions & 22 deletions cmd/prometheus-mcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,6 @@ var (
" Docs: https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-admin-apis",
).Default("false").Bool()

flagMcpKeepaliveInterval = kingpin.Flag(
"mcp.keepalive-interval",
"Interval for sending keepalive pings to connected MCP sessions."+
" If the peer fails to respond, the session is closed."+
" Most useful for HTTP transports to prevent idle connections from dropping.",
).Default("30s").Duration()

flagMcpSessionTimeout = kingpin.Flag(
"mcp.session-timeout",
"Idle session timeout for HTTP transport MCP sessions.",
).Default("10m").Duration()

Comment on lines -133 to -144

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.

Removing flags is user-facing and can cause unexpected breakages in other ways (ie, the binary bombing at starting when provided with unexpected flags that suddenly don't work).

We should keep the flags for now, turn them into no-ops, and deprecate them. Let's leave the flag declarations here and update the help descriptions/README with the deprecation warning. we can still continue to unwire the timeout/keepalive configs

flagDocsAutoUpdate = kingpin.Flag(
"docs.auto-update",
"Enable automatic documentation updates from the official prometheus/docs repository."+
Expand Down Expand Up @@ -219,7 +207,6 @@ func main() {
DocsFS: docsFs,
ToonOutputEnabled: *flagMcpToonOutputEnabled,
ClientLoggingEnabled: *flagMcpClientLogging,
KeepAlive: *flagMcpKeepaliveInterval,
})
if err != nil {
logger.Error("Failed to create MCP server", "err", err)
Expand Down Expand Up @@ -296,7 +283,7 @@ func main() {
case "http":
logger.Debug("starting MCP server", "transport", "http")

httpMcpHandler := mcp.NewStreamableHTTPHandler(mcpServer, logger, *flagMcpSessionTimeout)
httpMcpHandler := mcp.NewStreamableHTTPHandler(mcpServer, logger)
http.Handle("/mcp", httpMcpHandler)
<-cancel
default:
Expand Down Expand Up @@ -355,15 +342,11 @@ func main() {
func initHTTPServer(logger *slog.Logger) *http.Server {
server := &http.Server{
// These are TCP-level timeouts for individual HTTP
// request/response cycles, not MCP session timeouts. MCP
// sessions are long lived, tracked by session ID, and managed
// through the go-sdk separately from these HTTP server values.
//
// Important: Because SSE/HTTP transports are streams, the
// WriteTimeout must be disabled because the response "never
// finishes".
// request/response cycles. Stateless HTTP transport uses
// application/json responses that complete in a single round
// trip, so standard timeouts apply.
Comment on lines +345 to +347

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.

ReadTimeout: 30 * time.Second,
WriteTimeout: 0,
WriteTimeout: *flagPrometheusTimeout,

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.

Let's keep the WriteTimeout: 0, we need it disabled to continue to support stream responses.

We should also probably bump IdleTimeout: 120 * time.Second as well -- this should reduce HTTP connection churn for normal clients that are potentially above that idle timeout, such as longer prometheus scrape intervals/LB health checks. The latter of which is arguably more consequential, as I'm realizing now this probably also means potentially sporadic 502s.

IdleTimeout: 30 * time.Second,
Comment thread
iavael marked this conversation as resolved.
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/alpkeskin/gotoon v0.1.1
github.com/blevesearch/bleve/v2 v2.6.0
github.com/go-git/go-git/v5 v5.19.1
github.com/modelcontextprotocol/go-sdk v1.6.1
github.com/modelcontextprotocol/go-sdk v1.7.0
github.com/oklog/run v1.2.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.69.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A
github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg=
github.com/mdlayher/vsock v1.3.0 h1:bqQfZ1OznI03y6YiXp2sze05RVdzLn/zsfjnjd4+ivI=
github.com/mdlayher/vsock v1.3.0/go.mod h1:WsuksavOvwCnV5UqGHUkvAvCy+Dqy81y4goKQTzxxNY=
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44=
github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
20 changes: 8 additions & 12 deletions pkg/mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ type ServerConfig struct {
DocsFS fs.FS
ToonOutputEnabled bool
ClientLoggingEnabled bool
KeepAlive time.Duration
}

// NewServer creates a new MCP server using the official Go SDK.
Expand Down Expand Up @@ -204,7 +203,6 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*mcp.Server, *ServerConta
&mcp.ServerOptions{
Instructions: instrx,
Logger: logger.WithGroup("go_sdk_logger"),
KeepAlive: cfg.KeepAlive,
Capabilities: caps,
},
)
Expand All @@ -229,22 +227,20 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*mcp.Server, *ServerConta
return server, container, nil
}

// NewStreamableHTTPHandler creates an HTTP handler for the MCP server.
// NewStreamableHTTPHandler creates an HTTP handler for the MCP server using
// stateless HTTP transport. In stateless mode, each request is handled
// independently without session tracking, and responses are returned as
// application/json rather than text/event-stream.

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 comment is also inaccurate

// It wraps the handler with auth context middleware to forward Authorization headers.
func NewStreamableHTTPHandler(server *mcp.Server, logger *slog.Logger, sessionTimeout time.Duration) http.Handler {
if sessionTimeout == 0 {
// 0 value for session timeout means that sessions never close.
// Set a default if unset.
sessionTimeout = 1 * time.Hour
}

func NewStreamableHTTPHandler(server *mcp.Server, logger *slog.Logger) http.Handler {
handler := mcp.NewStreamableHTTPHandler(
func(r *http.Request) *mcp.Server {
return server
},
&mcp.StreamableHTTPOptions{
SessionTimeout: sessionTimeout,
Logger: logger,
Stateless: true,
JSONResponse: true,

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.

JSONResponse is independent of Stateless, and not required in order for it to work.

In fact, enabling it actually silently breaks client notification logging -- this switches response away from text/event-stream, which breaks the ability to send the notifications mid-stream/call.

Logger: logger,
},
)

Expand Down