Skip to content

feat(mcp): streamable HTTP support#2157

Merged
StarpTech merged 12 commits intomainfrom
dustin/streamable-http-transport-mcp
Aug 21, 2025
Merged

feat(mcp): streamable HTTP support#2157
StarpTech merged 12 commits intomainfrom
dustin/streamable-http-transport-mcp

Conversation

@StarpTech
Copy link
Copy Markdown
Contributor

@StarpTech StarpTech commented Aug 20, 2025

Summary by CodeRabbit

  • New Features

    • MCP server: stateless mode support, CORS handling, and migration from SSE-only to a streamable HTTP transport.
    • Logging adapter to accept Zap-style loggers; test environment exposes MCP server address.
  • Configuration

    • New MCP.session.stateless setting (defaults to true); schema and defaults updated.
  • Tests

    • Added comprehensive CORS test coverage (preflight and multiple methods).
  • Chores

    • Major dependency upgrades across telemetry, OCI/Docker, and GraphQL/subgraph tooling; removals of deprecated packages.

Checklist

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 20, 2025

Walkthrough

Adds MCP stateless-session support, migrates MCP transport from SSE to a streamable HTTP server with CORS and preflight handling, introduces a zap logger adapter, updates router bootstrap/config/schema/tests, switches MCP client in tests, adds CORS tests, and applies broad dependency upgrades across demo/router modules.

Changes

Cohort / File(s) Summary
Dependency updates
demo/go.mod, router/go.mod, router-tests/go.mod
Large dependency realignment: OTEL upgrades, cosmo component bumps, OCI/Docker/tooling swaps, added auto-instrumentation and other indirect deps; many removals and replacements.
MCP server refactor & HTTP transport
router/pkg/mcpserver/server.go
Replace SSE transport with StreamableHTTPServer, add Stateless option and WithStateless, add Serve() returning *server.StreamableHTTPServer, add CORS middleware and preflight handling, remove ServeSSE, update start/stop lifecycle.
MCP logging adapter
router/pkg/mcpserver/util.go
Add Logger interface, ZapAdapter and NewZapAdapter to adapt zap logger to MCP server logging.
Router integration & config
router/core/router.go, router/pkg/config/config.go, router/pkg/config/config.schema.json, router/pkg/config/testdata/config_defaults.json, router/pkg/config/testdata/config_full.json
Wire MCPSession Stateless flag into router bootstrap; add MCPSessionConfig type and Session field on MCPConfiguration; update JSON schema and test config files to include MCP.Session.stateless (default true).
Test environment & tests
router-tests/testenv/testenv.go, router-tests/mcp_test.go
Use mcpclient.NewStreamableHttpClient in test env, add Environment.GetMCPServerAddr() helper, and add extensive CORS tests (OPTIONS/GET/POST/PUT/DELETE) validating CORS headers and max-age.
Import tidy
demo/pkg/subgraphs/subgraphs.go
Reordered imports only; no functional changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dustin/streamable-http-transport-mcp

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@StarpTech StarpTech changed the title Dustin/streamable http transport mcp feat(mcp): Streamable HTTP support Aug 20, 2025
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 20, 2025

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-905845d2130d931fda3ff564cb681980bfc8fe6f

@StarpTech StarpTech changed the title feat(mcp): Streamable HTTP support feat(mcp): streamable HTTP support Aug 20, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
router/core/router.go (1)

856-866: Ensure Stateless mode isn’t unintentionally overridden in programmatic configurations

The current implementation always passes WithStateless(r.mcp.Session.Stateless), and since MCPSessionConfig.Stateless is a plain bool (zero-value false) when you build configs in code (e.g. via core.WithMCP(testConfig.MCP)), you end up forcing stateful mode off even though the intended default is stateless – and even though envDefault:"true" would set it to true when loaded via LoadConfig. We confirmed:

  • MCPSessionConfig is defined as a non-pointer bool with envDefault:"true" (router/pkg/config/config.go:961–963).
  • There are programmatic usages bypassing LoadConfig (router-tests/testenv/testenv.go:1454; router/core/supervisor_instance.go:236), so those always get the zero‐value false.
  • router/core/router.go:865 still has mcpserver.WithStateless(r.mcp.Session.Stateless), which unconditionally overrides the server default.

To address this, we need to:

  • Change MCPSessionConfig.Stateless to a *bool so we can distinguish “unset” (nil) from an explicit choice.
  • Update the router code to only append WithStateless when the pointer is non-nil, allowing the MCP server’s own default to apply otherwise.

Please apply these mandatory refactors:

• router/pkg/config/config.go

--- a/router/pkg/config/config.go
+++ b/router/pkg/config/config.go
@@ -961,7 +961,7 @@ type MCPSessionConfig struct {
-   Stateless bool `yaml:"stateless" envDefault:"true" env:"MCP_SESSION_STATELESS"`
+   Stateless *bool `yaml:"stateless" envDefault:"true" env:"MCP_SESSION_STATELESS"`
}

• router/core/router.go

--- a/router/core/router.go
+++ b/router/core/router.go
@@ -852,7 +852,12 @@ func newServer(r *router) {
         mcpserver.WithExposeSchema(r.mcp.ExposeSchema),
-        mcpserver.WithStateless(r.mcp.Session.Stateless),
     }

+    if r.mcp.Session.Stateless != nil {
+        mcpOpts = append(
+            mcpOpts,
+            mcpserver.WithStateless(*r.mcp.Session.Stateless),
+        )
+    }

With this in place, programmatic configs will inherit the server’s default when Stateless is unset, and tests (which don’t set it) will continue to default to stateless mode.

router/pkg/mcpserver/server.go (1)

696-714: Bug: constructing an error result with a nil error; also stray parenthesis in message

In both branches you pass err which is nil, resulting in an error result with no underlying error. Use a constructed error instead and clean up the message format.

-	if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
+	if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
 		// Concatenate all error messages
 		var errorMessages []string
 		for _, gqlErr := range graphqlResponse.Errors {
 			errorMessages = append(errorMessages, gqlErr.Message)
 		}
 
 		errorMessage := strings.Join(errorMessages, "; ")
 
 		// If there are errors but no data, return only the errors
 		if len(graphqlResponse.Data) == 0 || string(graphqlResponse.Data) == "null" {
-			return mcp.NewToolResultErrorFromErr("Response Error", err), nil
+			return mcp.NewToolResultErrorFromErr("Response Error", errors.New(errorMessage)), nil
 		}
 
 		// If we have both errors and data, include data in the error message
 		dataString := string(graphqlResponse.Data)
-		combinedErrorMsg := fmt.Sprintf("Response error with partial success, Error: %s, Data: %s)", errorMessage, dataString)
-		return mcp.NewToolResultErrorFromErr(combinedErrorMsg, err), nil
+		combinedErrorMsg := fmt.Sprintf("Response error with partial success. Error: %s, Data: %s", errorMessage, dataString)
+		return mcp.NewToolResultErrorFromErr(combinedErrorMsg, errors.New(errorMessage)), nil
 	}
🧹 Nitpick comments (14)
router-tests/testenv/testenv.go (1)

1801-1807: Nit: build MCP server URL with net/url for correctness and readability

Using fmt.Sprintf is fine, but net/url avoids subtle path issues and reads clearer.

-func (e *Environment) GetMCPServerAddr() string {
-  if e.cfg.MCP.Enabled {
-    return fmt.Sprintf("http://%s/mcp", e.cfg.MCP.Server.ListenAddr)
-  }
-  return ""
-}
+func (e *Environment) GetMCPServerAddr() string {
+  if !e.cfg.MCP.Enabled {
+    return ""
+  }
+  u := url.URL{Scheme: "http", Host: e.cfg.MCP.Server.ListenAddr, Path: "/mcp"}
+  return u.String()
+}
router/pkg/config/config.schema.json (1)

1966-1970: Update description to reflect the new streamable HTTP transport, not SSE

The current text still mentions “when SSE is used as primary transport”. With this PR, MCP uses streamable HTTP. Suggest adjusting the description for accuracy.

-              "description": "The base URL of the MCP server. This is the URL advertised to the LLM clients when SSE is used as primary transport. By default, the base URL is relative to the URL that the router is running on. The URL is specified as a string with the format 'scheme://host:port'.",
+              "description": "The base URL of the MCP server. This is the URL advertised to LLM clients for the streamable HTTP transport. By default, the base URL is relative to the URL that the router is running on. The URL is specified as a string with the format 'scheme://host:port'.",
router-tests/mcp_test.go (5)

5-7: Avoid unnecessary string allocation: use bytes.NewReader and drop strings import

Switch to bytes.NewReader for the POST body to avoid an extra allocation/conversion and remove the now-unused strings import.

 import (
 	"encoding/json"
-	"fmt"
-	"net/http"
-	"strings"
+	"fmt"
+	"net/http"
+	"bytes"
 	"testing"
 )

437-439: Use bytes.NewReader for the JSON body

This prevents an unnecessary string conversion of the marshalled JSON.

-				req, err := http.NewRequest("POST", mcpAddr, strings.NewReader(string(requestBody)))
+				req, err := http.NewRequest("POST", mcpAddr, bytes.NewReader(requestBody))

368-415: Optional: assert Vary headers once server sets them

To make CORS behavior cache-safe, the server should emit Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers. After adding these on the server, consider asserting them here to prevent regressions.


417-469: Optional: factor CORS header assertions into a helper to deduplicate test code

A small helper like assertMCPResponseHasCORSHeaders(t, resp) would make these subtests tighter and easier to extend (e.g., when adding Vary assertions).


514-554: Optional: table-drive across methods and assertion sets

You already loop over methods; consider table-driving headers/expectations too to add coverage for HEAD/OPTIONS or credentialed scenarios later without duplication.

router/pkg/mcpserver/server.go (7)

86-91: Rename sseServer for clarity

The field is now a *server.StreamableHTTPServer; consider renaming sseServer -> httpServer or streamServer to avoid confusion in future maintenance.


202-206: Honor configurable RequestTimeout instead of a fixed 60s

You already accept RequestTimeout via Options and store it on the struct; wire it into the http.Client to avoid diverging timeouts.

-	httpClient := retryClient.StandardClient()
-	httpClient.Timeout = 60 * time.Second
+	httpClient := retryClient.StandardClient()
+	// Use the configured request timeout (defaults applied in Options)
+	httpClient.Timeout = options.RequestTimeout

288-296: Update docstring: it's no longer returning an SSE server

The comment still refers to SSE. Recommend adjusting wording to avoid confusion.

-// Serve starts the server with the configured options and returns an SSE server.
+// Serve starts the server with the configured options and returns a streamable HTTP server.

344-351: Fix wording and add nil-guard when Start fails mid-flight

  • Error text still mentions “SSE server”.
  • Minor: if Serve fails after partial init, ensure Stop won’t panic by nil-checking the server field.
-	ss, err := s.Serve()
+	ss, err := s.Serve()
 	if err != nil {
-		return fmt.Errorf("failed to create SSE server: %w", err)
+		return fmt.Errorf("failed to start MCP HTTP server: %w", err)
 	}
 
 	s.sseServer = ss

381-392: Graceful shutdown: add nil-check and update message

Guard against nil in edge cases and update the message to match the new transport.

 	s.logger.Debug("shutting down MCP server")
@@
-	if err := s.sseServer.Shutdown(shutdownCtx); err != nil {
-		return fmt.Errorf("failed to gracefully shutdown SSE server: %w", err)
-	}
+	if s.sseServer != nil {
+		if err := s.sseServer.Shutdown(shutdownCtx); err != nil {
+			return fmt.Errorf("failed to gracefully shutdown MCP HTTP server: %w", err)
+		}
+	}

760-799: CORS: add cache-safe Vary headers and echo requested headers on preflight

  • Add Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers to avoid cache poisoning.
  • Optionally include Access-Control-Request-Headers in the allow list to maximize compatibility.
-func WithCORS(allowedMethods ...string) func(http.Handler) http.Handler {
+func WithCORS(allowedMethods ...string) func(http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-			// Set CORS headers for all requests
-			setCORSHeaders(w, allowedMethods)
+			// Set CORS headers for all requests
+			setCORSHeaders(w, req, allowedMethods)
 
 			// Handle preflight OPTIONS requests
 			if req.Method == http.MethodOptions {
 				w.WriteHeader(http.StatusNoContent)
 				return
 			}
 
 			// Call the next handler
 			next.ServeHTTP(w, req)
 		})
 	}
 }
 
-// setCORSHeaders sets common CORS headers
-// Only used for web browsers, not for API clients
-func setCORSHeaders(w http.ResponseWriter, allowedMethods []string) {
+// setCORSHeaders sets common CORS headers (browser-only)
+func setCORSHeaders(w http.ResponseWriter, req *http.Request, allowedMethods []string) {
 	w.Header().Set("Access-Control-Allow-Origin", "*")
 	w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allowedMethods, "OPTIONS"), ", "))
-	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id")
+	allowed := "Content-Type, Accept, Authorization, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id"
+	if acrh := req.Header.Get("Access-Control-Request-Headers"); acrh != "" {
+		allowed = allowed + ", " + acrh
+	}
+	w.Header().Set("Access-Control-Allow-Headers", allowed)
 	w.Header().Set("Access-Control-Max-Age", "86400") // 24 hours
+	w.Header().Add("Vary", "Origin")
+	w.Header().Add("Vary", "Access-Control-Request-Method")
+	w.Header().Add("Vary", "Access-Control-Request-Headers")
 }

305-313: Security/operability: consider configurable allowed origins for MCP CORS

Right now Access-Control-Allow-Origin is “*”. If teams need to send credentials/cookies or restrict who can call MCP from a browser, expose an option (e.g., Options.CORSAllowedOrigins or a functional option) to echo a specific Origin or validate against an allowlist.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bf88521 and 3861be4.

⛔ Files ignored due to path filters (3)
  • demo/go.sum is excluded by !**/*.sum
  • router-tests/go.sum is excluded by !**/*.sum
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • demo/go.mod (8 hunks)
  • demo/pkg/subgraphs/subgraphs.go (1 hunks)
  • router-tests/go.mod (5 hunks)
  • router-tests/mcp_test.go (2 hunks)
  • router-tests/testenv/testenv.go (3 hunks)
  • router/core/router.go (1 hunks)
  • router/go.mod (4 hunks)
  • router/pkg/config/config.go (1 hunks)
  • router/pkg/config/config.schema.json (1 hunks)
  • router/pkg/mcpserver/server.go (8 hunks)
  • router/pkg/mcpserver/util.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T10:08:17.857Z
Learnt from: endigma
PR: wundergraph/cosmo#2155
File: router/core/router.go:1857-1866
Timestamp: 2025-08-20T10:08:17.857Z
Learning: router/pkg/config/config.schema.json forbids null values for traffic_shaping.subgraphs: additionalProperties references $defs.traffic_shaping_subgraph_request_rule with type "object". Therefore, in core.NewSubgraphTransportOptions, dereferencing each subgraph rule pointer is safe under schema-validated configs, and a nil-check is unnecessary.

Applied to files:

  • demo/pkg/subgraphs/subgraphs.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build-router
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: image_scan
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: integration_test (./events)
  • GitHub Check: build_test
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (17)
demo/pkg/subgraphs/subgraphs.go (1)

17-18: LGTM!

The import reordering aligns with the broader module wiring and dependency updates in this PR. Moving these imports to directly follow the core import improves readability and maintains logical grouping.

router-tests/go.mod (3)

16-16: LGTM!

The upgrade from mcp-go v0.30.0 to v0.38.0 aligns with the new streamable HTTP MCP server implementation introduced in this PR. This version bump supports the stateless MCP features and HTTP transport changes.


28-28: LGTM!

The router version update to the latest commit (bf88521) correctly synchronizes with the MCP server changes in this PR, ensuring the test environment has access to the new streamable HTTP server functionality.


51-51: LGTM!

The new indirect dependencies are legitimate transitive dependencies introduced by the mcp-go upgrade:

  • bahlo/generic-list-go: JSON schema processing
  • invopop/jsonschema: JSON schema generation
  • mailru/easyjson: JSON marshaling optimization
  • wk8/go-ordered-map/v2: Ordered map implementation

These align with the enhanced MCP functionality in this PR.

Also applies to: 99-99, 107-107, 153-153

router/go.mod (2)

75-75: LGTM!

The upgrade from mcp-go v0.29.0 to v0.38.0 supports the new streamable HTTP transport and stateless session features introduced in the MCP server implementation.


92-92: LGTM!

These indirect dependencies are consistent with the mcp-go upgrade and support the enhanced MCP server functionality:

  • JSON schema processing and generation capabilities
  • Optimized JSON marshaling
  • Ordered map structures for maintaining insertion order

Also applies to: 122-122, 127-127, 160-160

router/pkg/mcpserver/util.go (3)

5-9: LGTM!

The Logger interface provides a clean abstraction with appropriate methods for MCP server logging needs. The method signatures follow Go conventions and align with typical structured logging patterns.


11-21: LGTM!

The ZapAdapter struct and constructor provide a clean bridge between Zap's structured logging and the MCP server's logging interface. The use of Sugar() is appropriate for the format-style logging methods.


23-31: LGTM!

The Infof and Errorf method implementations correctly delegate to the underlying SugaredLogger, maintaining the expected logging behavior while satisfying the Logger interface contract.

demo/go.mod (2)

13-19: LGTM!

The dependency updates to the latest commit version (bf88521) ensure the demo environment has access to the new MCP server features. The OpenTelemetry upgrades to v1.36.0 provide enhanced observability capabilities that complement the new streamable HTTP transport.


43-43: LGTM!

The extensive dependency updates reflect a comprehensive modernization effort:

  • Container/OCI tooling updates align with modern container runtime requirements
  • OpenTelemetry auto-instrumentation (v1.1.0) enhances observability
  • Updated protobuf and gRPC ecosystem components
  • New subgraphs project reference supports the broader GraphQL federation improvements

These changes are consistent with the MCP server enhancements and overall system modernization in this PR.

Also applies to: 50-52, 75-75, 77-77, 100-100, 107-107, 144-144, 146-147, 151-151, 157-157, 160-163, 177-177, 182-182

router-tests/testenv/testenv.go (2)

812-815: Good switch to the streamable HTTP client

Replacing NewSSEMCPClient with NewStreamableHttpClient aligns the test env with the new transport. Nothing else in this function relies on SSE semantics. LGTM.


1237-1241: Consistent client migration here as well

Same as above—consistent update in CreateTestEnv. Looks good.

router/pkg/config/config.go (1)

953-964: Manual Verification Required: Confirm No Hidden Boolean Dependencies

The repository search shows:

  • Only one direct runtime use of Session.Stateless in the bootstrap path (router/core/router.go #865), where it’s unconditionally passed to mcpserver.WithStateless
  • No .yaml or .yml files define session.stateless: anywhere in the tree
  • Programmatic instantiations via core.WithMCP(testConfig.MCP) (in router-tests/testenv/testenv.go and supervisor_instance.go) simply carry the zero‐value false today

However, we haven’t yet verified whether any of your test‐environment YAML fixtures or higher-level initialization code implicitly expects a non-pointer bool (and thus would break when switching to *bool). Please manually confirm:

  • That no test/fixture YAML defines session.stateless (or relies on its absence meaning “true”)
  • That router-tests/testenv/* or other bootstrappers don’t assume Session.Stateless is always non-nil
  • That any custom wrappers around core.WithMCP or config-loader pathways correctly handle a nil pointer (skipping the override) versus an explicit false

Once you’ve verified there are no accidental boolean dependencies in tests or example configs, you can safely proceed with the pointer-based refactor.

router/pkg/config/config.schema.json (1)

1990-2001: MCP session.stateless schema addition looks correct and consistent with code wiring

  • Shape, type, and default are sensible. Matches the new Options.Stateless and router bootstrap pass-through.
router/pkg/mcpserver/server.go (2)

71-73: Option field addition is aligned with defaults and routing

Adding Stateless to Options with a true default matches the config schema and router wiring.


281-286: WithStateless option looks good

Matches the schema and router bootstrap plumbing.

Comment thread router/pkg/mcpserver/server.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3861be4 and 884f3bf.

📒 Files selected for processing (3)
  • router/pkg/config/testdata/config_defaults.json (1 hunks)
  • router/pkg/config/testdata/config_full.json (1 hunks)
  • router/pkg/mcpserver/server.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/pkg/mcpserver/server.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
router/pkg/config/testdata/config_full.json (1)

161-163: Confirm default and expand session-mode test coverage

I verified that:

  • The Go config struct MCPSessionConfig has Stateless bool envDefault:"true"`, so the default code path is stateless=true.
  • The router passes r.mcp.Session.Stateless into mcpserver.WithStateless(...) in router/core/router.go.
  • The server option WithStateless(stateless bool) is implemented in router/pkg/mcpserver/server.go.
  • However, I couldn’t find a "default": true entry for stateless in the JSON schema (please confirm), and the existing full‐fixture (config_full.json) and router‐tests only exercise the stateless=true case—no assertions cover stateless=false.

To broaden coverage, you might update the full‐config fixture to flip the non‐default path and add a test for it:

--- a/router/pkg/config/testdata/config_full.json
+++ b/router/pkg/config/testdata/config_full.json
@@ Lines 161-163
-    "Session": {
-      "Stateless": true
-    },
+    "Session": {
+      "Stateless": false
+    },

And then add a corresponding router‐test that loads this fixture and asserts the server is instantiated with session state (i.e., stateless=false).

Let me know if you’d like me to open a follow-up PR to add the stateful fixture and test.

Comment thread router/pkg/config/testdata/config_defaults.json
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
router-tests/go.mod (3)

107-107: easyjson added indirectly—no codegen needed unless you add easyjson tags.

As an indirect dep, this is fine. If we later add types with easyjson:json tags in router-tests, we should add a tools.go and pin generator versions to keep CI reproducible.


28-28: Align pseudo-version with local router commit in router-tests/go.mod

The router-tests module imports github.com/wundergraph/cosmo/router directly (e.g. in router-tests/complexity_limits_test.go, router-tests/testenv/testenv.go, etc.), so its require directive still informs MVS even though the replace to ../router wins for builds. If the pseudo-version in the require diverges from the actual ../router commit, you’ll see churn from go mod tidy every time you update the local router checkout.

• File to update:

  • router-tests/go.mod (line 28): bump the require github.com/wundergraph/cosmo/router pseudo-version to match the current commit hash in ../router.

Keeping these in sync will eliminate unnecessary go.mod diffs when you switch router commits.


31-37: Simplify OTEL dependency version declarations in router-tests/go.mod

The tests under router-tests/ do import OTEL APIs directly, so pinning them via a replace to v1.28.0 is required to ensure compatibility. However, explicitly requiring v1.36.0 (lines 31–34) and the indirect v1.36.0 entry (line 166) is now redundant—and potentially confusing—since the replace already downgrades everything to v1.28.0. Consider:

• Removing these direct require lines:

  • go.opentelemetry.io/otel v1.36.0
  • go.opentelemetry.io/otel/sdk v1.36.0
  • go.opentelemetry.io/otel/sdk/metric v1.36.0
  • go.opentelemetry.io/otel/trace v1.36.0

• Also removing the indirect require:

  • go.opentelemetry.io/otel/metric v1.36.0 (line 166)

Then run go mod tidy to let Go infer the correct (replaced) versions. Finally, verify that the tests only rely on OTEL APIs available in v1.28.0 to prevent accidental usage of newer symbols.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7f5f1 and ae3a3d6.

⛔ Files ignored due to path filters (2)
  • router-tests/go.sum is excluded by !**/*.sum
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • router-tests/go.mod (5 hunks)
  • router/go.mod (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/go.mod
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T20:59:38.879Z
Learnt from: StarpTech
PR: wundergraph/cosmo#2157
File: router/pkg/mcpserver/server.go:0-0
Timestamp: 2025-08-20T20:59:38.879Z
Learning: The wundergraph/cosmo project uses github.com/mark3labs/mcp-go v0.38.0, not older versions like v0.8.2. Always check the actual go.mod files to verify dependency versions before analyzing API availability.

Applied to files:

  • router-tests/go.mod
📚 Learning: 2025-08-20T20:59:38.879Z
Learnt from: StarpTech
PR: wundergraph/cosmo#2157
File: router/pkg/mcpserver/server.go:0-0
Timestamp: 2025-08-20T20:59:38.879Z
Learning: Always verify the exact version of dependencies by checking go.mod files before analyzing API availability. The wundergraph/cosmo project uses github.com/mark3labs/mcp-go v0.38.0 which has NewStreamableHTTPServer, WithStreamableHTTPServer, and WithStateLess APIs available.

Applied to files:

  • router-tests/go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
router-tests/go.mod (3)

51-51: OK to add bahlo/generic-list-go as indirect.

Likely pulled in by upstream libs. No action needed.


153-153: OK to add go-ordered-map/v2 as indirect.

Commonly pulled in via jsonschema tooling; safe as-is.


99-99: No schema-based snapshots to update
The bump of github.com/invopop/jsonschema v0.13.0 in router-tests/go.mod is an indirect dependency and does not impact any existing golden fixtures. A scan of the router-tests directory shows only GraphQL-based snapshots managed by Goldie (e.g., under testdata/fixtures/query_plans, testdata/introspection, etc.) and no JSON Schema files (*.schema.json) to re-generate.

• Tests in router-tests use Goldie for GraphQL response snapshots—no schema-generation fixtures found.
• No config.schema.json or other JSON Schema golden files are present in router-tests/testdata.

You can safely ignore the suggestion to re-run schema-based goldens.

Likely an incorrect or invalid review comment.

Comment thread router-tests/go.mod
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
router/pkg/mcpserver/server.go (1)

697-715: Bug: nil error passed to NewToolResultErrorFromErr + stray ')'

When GraphQL returns errors, err is nil (it holds the json.Unmarshal error), so you pass a nil error into NewToolResultErrorFromErr. Also, there’s a stray closing parenthesis in the formatted message.

Apply:

-    if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
+    if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
         // Concatenate all error messages
-        var errorMessages []string
-        for _, gqlErr := range graphqlResponse.Errors {
-            errorMessages = append(errorMessages, gqlErr.Message)
-        }
-        errorMessage := strings.Join(errorMessages, "; ")
+        var errorMessages []string
+        for _, gqlErr := range graphqlResponse.Errors {
+            errorMessages = append(errorMessages, gqlErr.Message)
+        }
+        errorMessage := strings.Join(errorMessages, "; ")
@@
-        if len(graphqlResponse.Data) == 0 || string(graphqlResponse.Data) == "null" {
-            return mcp.NewToolResultErrorFromErr("Response Error", err), nil
-        }
+        if len(graphqlResponse.Data) == 0 || string(graphqlResponse.Data) == "null" {
+            return mcp.NewToolResultErrorFromErr("Response Error", errors.New(errorMessage)), nil
+        }
@@
-        combinedErrorMsg := fmt.Sprintf("Response error with partial success, Error: %s, Data: %s)", errorMessage, dataString)
-        return mcp.NewToolResultErrorFromErr(combinedErrorMsg, err), nil
+        combinedErrorMsg := fmt.Sprintf("Response error with partial success, Error: %s, Data: %s", errorMessage, dataString)
+        return mcp.NewToolResultErrorFromErr(combinedErrorMsg, errors.New(errorMessage)), nil
     }

Further hardening (optional but recommended):

  • Treat non-2xx HTTP status codes as errors.
  • Include response status and body snippet for easier debugging.

Supporting changes outside the edited hunk:

-    req, err := http.NewRequest("POST", s.routerGraphQLEndpoint, bytes.NewReader(graphqlRequestBytes))
+    req, err := http.NewRequestWithContext(ctx, "POST", s.routerGraphQLEndpoint, bytes.NewReader(graphqlRequestBytes))
+    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+        return mcp.NewToolResultErrorFromErr(
+            fmt.Sprintf("upstream returned non-2xx status: %d", resp.StatusCode),
+            fmt.Errorf("status %d", resp.StatusCode),
+        ), nil
+    }
🧹 Nitpick comments (6)
router/pkg/mcpserver/server.go (6)

86-86: Field name shadowing: httpServer means two different types here

The struct field httpServer is a *server.StreamableHTTPServer, while Serve() also creates a local httpServer of type *http.Server. This increases cognitive load and invites mistakes.

Recommend renaming the struct field for clarity, e.g. streamableSrv.

Apply this minimal rename:

-   httpServer                *server.StreamableHTTPServer
+   streamableSrv             *server.StreamableHTTPServer
-   s.httpServer = ss
+   s.streamableSrv = ss
-   if err := s.httpServer.Shutdown(shutdownCtx); err != nil {
+   if err := s.streamableSrv.Shutdown(shutdownCtx); err != nil {

Also applies to: 350-351, 388-390


288-305: Lifecycle coupling: you start net/http directly, but shut down via StreamableHTTPServer

You pass a custom *http.Server into NewStreamableHTTPServer, then:

  • start the net/http server yourself (ListenAndServe on the local httpServer), but
  • stop via streamableHTTPServer.Shutdown().

This relies on the MCP server to propagate Shutdown to the custom http.Server you booted externally. If the underlying version doesn’t do that consistently, shutdown may hang or leak.

Two safer options:

  • Centralize lifecycle in the streamable server (call streamableHTTPServer.Start and streamableHTTPServer.Shutdown).
  • Or store the underlying *http.Server on the struct and shut it down directly.

Here’s a minimal defensive improvement that keeps your current wiring but guarantees shutdown:

@@
 type GraphQLSchemaServer struct {
@@
-    httpServer                *server.StreamableHTTPServer
+    streamableSrv             *server.StreamableHTTPServer
+    netHTTPServer             *http.Server
@@
 func (s *GraphQLSchemaServer) Serve() (*server.StreamableHTTPServer, error) {
-    // Create custom HTTP server
-    httpServer := &http.Server{
+    // Create custom HTTP server
+    httpServer := &http.Server{
         Addr:         s.listenAddr,
         ReadTimeout:  30 * time.Second,
         WriteTimeout: 30 * time.Second,
         IdleTimeout:  60 * time.Second,
     }
@@
-    // Set the handler for the custom HTTP server
-    httpServer.Handler = mux
+    // Set the handler for the custom HTTP server
+    httpServer.Handler = mux
+    // Keep a reference for direct shutdown if needed
+    s.netHTTPServer = httpServer

And in Stop (see detailed comment below), check and fall back to shutting down s.netHTTPServer if needed.

If you prefer to centralize on Start/Shutdown of the streamable server, I can provide an alternative patch that mounts CORS around the streamable handler while letting the library own the http.Server lifecycle.


306-317: CORS: add Vary headers and narrow methods

  • Add standard Vary headers (Origin, Access-Control-Request-Method, Access-Control-Request-Headers) to avoid cache poisoning and proxy misbehavior.
  • The /mcp endpoint only needs GET/POST; allowing PUT/DELETE is unnecessary and expands the surface.

Apply:

- corsMiddleware := WithCORS("GET", "POST", "PUT", "DELETE")
+ corsMiddleware := WithCORS("GET", "POST")
 func setCORSHeaders(w http.ResponseWriter, allowedMethods []string) {
     w.Header().Set("Access-Control-Allow-Origin", "*")
     w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allowedMethods, "OPTIONS"), ", "))
     w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id")
+    w.Header().Add("Vary", "Origin")
+    w.Header().Add("Vary", "Access-Control-Request-Method")
+    w.Header().Add("Vary", "Access-Control-Request-Headers")
     w.Header().Set("Access-Control-Max-Age", "86400") // 24 hours
 }

Optionally, consider making allowed origins configurable (e.g., WithCORSOrigins(...)) if this gets exposed on shared domains.

Also applies to: 792-799


333-336: Logging message clarity

The error log says “failed to start HTTP server” but this goroutine runs the long-lived serve loop; post-start errors (e.g., accept loop failures) will also land here. Suggest “HTTP serve loop error” to reduce confusion in ops.

-            s.logger.Error("failed to start HTTP server", zap.Error(err))
+            s.logger.Error("HTTP serve loop error", zap.Error(err))

288-305: Honor configured RequestTimeout

You capture Options.RequestTimeout but the HTTP client’s Timeout is hard-coded elsewhere to 60s. Suggest using the configured timeout.

Supporting change (outside this hunk, shown for clarity):

-    httpClient.Timeout = 60 * time.Second
+    httpClient.Timeout = options.RequestTimeout

761-790: CORS middleware docstrings: good, consider brief security note

Docs are clear. Consider adding a short note that wildcard origins are acceptable because credentials are not allowed (no Access-Control-Allow-Credentials). This helps future readers avoid accidentally enabling credentials with "*".

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ae3a3d6 and 0c9c9c6.

📒 Files selected for processing (1)
  • router/pkg/mcpserver/server.go (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T22:13:25.182Z
Learnt from: StarpTech
PR: wundergraph/cosmo#2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.182Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Applied to files:

  • router/pkg/mcpserver/server.go
🧬 Code graph analysis (1)
router/pkg/mcpserver/server.go (2)
router/core/graph_server.go (1)
  • Server (68-71)
router/pkg/mcpserver/util.go (1)
  • NewZapAdapter (17-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build-router
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: build_test
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: build_push_image (nonroot)
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./events)
  • GitHub Check: image_scan
  • GitHub Check: image_scan (nonroot)
  • GitHub Check: build_test
🔇 Additional comments (4)
router/pkg/mcpserver/server.go (4)

71-73: Stateless option added — confirm cross-module defaults and behavior

Adding Options.Stateless is sensible and defaulting to stateless aligns with the PR intent. Please confirm:

  • The router config default (mcp.session.stateless) matches this true default to avoid surprise behavior changes.
  • Clients relying on session continuity won’t regress.

If both defaults diverge anywhere, consider documenting precedence or threading the config explicitly to the constructor.

Would you like a follow-up PR note and docs snippet to call out the stateless default?


184-185: Changing the default to stateless: double-check compatibility

Setting the default to Stateless: true is a behavior change. Ensure:

  • Test fixtures and examples assume stateless by default.
  • Any existing consumers that depended on stateful sessions are updated.

If you want, I can scan the repo for external references and tests that expect session reuse and surface them.


281-286: Option constructor LGTM

WithStateless is a clean, explicit toggle. No functional issues spotted.


345-351: Start returns before the socket is actually listening

Start invokes Serve and immediately returns, while ListenAndServe runs in a goroutine. For tests or bootstraps that need readiness, consider exposing a readiness channel or health check waiter to avoid flakes.

I can add a non-blocking readiness probe (e.g., tcp dial with timeout, or http HEAD on /mcp) for router-tests if helpful.

Comment thread router/pkg/mcpserver/server.go
Comment thread router/pkg/mcpserver/server.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
router/pkg/mcpserver/server.go (2)

701-719: Fix: returning tool errors with nil err loses context; also stray parenthesis in message

When GraphQL returns errors, you construct errorMessage but pass err (nil) into NewToolResultErrorFromErr, and the combined message has an extra “)” at the end. This drops the real failure context.

Apply:

@@
-	if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
+	if err := json.Unmarshal(body, &graphqlResponse); err == nil && len(graphqlResponse.Errors) > 0 {
 		// Concatenate all error messages
 		var errorMessages []string
 		for _, gqlErr := range graphqlResponse.Errors {
 			errorMessages = append(errorMessages, gqlErr.Message)
 		}
 
 		errorMessage := strings.Join(errorMessages, "; ")
 
 		// If there are errors but no data, return only the errors
 		if len(graphqlResponse.Data) == 0 || string(graphqlResponse.Data) == "null" {
-			return mcp.NewToolResultErrorFromErr("Response Error", err), nil
+			return mcp.NewToolResultErrorFromErr("Response Error", errors.New(errorMessage)), nil
 		}
 
 		// If we have both errors and data, include data in the error message
 		dataString := string(graphqlResponse.Data)
-		combinedErrorMsg := fmt.Sprintf("Response error with partial success, Error: %s, Data: %s)", errorMessage, dataString)
-		return mcp.NewToolResultErrorFromErr(combinedErrorMsg, err), nil
+		title := "Response error with partial success"
+		detail := fmt.Sprintf("errors: %s; data: %s", errorMessage, dataString)
+		return mcp.NewToolResultErrorFromErr(title, errors.New(detail)), nil
 	}

371-376: Reset registeredTools after deletion to avoid unbounded growth across reloads

DeleteTools removes server-side tools, but the slice keeps accumulating names. Reset the slice before re-registering to avoid repeated entries and unnecessary work on subsequent reloads.

 	s.server.DeleteTools(s.registeredTools...)
+	// Reset cached tool names before re-registering to avoid unbounded growth
+	s.registeredTools = s.registeredTools[:0]
🧹 Nitpick comments (8)
router/pkg/mcpserver/server.go (8)

202-206: Honor RequestTimeout option for the HTTP client

You hardcode 60s while also exposing RequestTimeout (default 30s). This is surprising and makes tuning ineffective.

 	httpClient := retryClient.StandardClient()
-	httpClient.Timeout = 60 * time.Second
+	httpClient.Timeout = options.RequestTimeout

342-343: Update comment: Start no longer loads operations

The comment is outdated—Start only starts the HTTP transport; tools are registered during Reload.

-// Start loads operations and starts the server
+// Start starts the MCP HTTP transport; operations/tools are loaded via Reload once a schema is available.

333-336: Surface startup failures from ListenAndServe to callers

Start returns before verifying the port is bound; binding failures will be logged asynchronously but the caller sees success. Consider a readiness/error channel to propagate early failures.

Example pattern:

 	go func() {
-		defer s.logger.Info("MCP server stopped")
-		err := httpServer.ListenAndServe()
+		defer s.logger.Info("MCP server stopped")
+		err := httpServer.ListenAndServe()
 		if err != nil && !errors.Is(err, http.ErrServerClosed) {
 			s.logger.Error("failed to start HTTP server", zap.Error(err))
 		}
 	}()

You can introduce:

  • a started chan error passed into the goroutine,
  • return the first non-nil error or nil after a small “listening” probe (e.g., net.Listen before Serve), or
  • pre-bind with ln, _ := net.Listen("tcp", s.listenAddr) then httpServer.Serve(ln) to fail fast synchronously.

382-394: Make Stop idempotent

Returning an error when the server was never started makes shutdown flows brittle. Prefer no-op with debug log.

-	if s.httpServer == nil {
-		return fmt.Errorf("server is not started")
-	}
+	if s.httpServer == nil {
+		s.logger.Debug("MCP streamable HTTP server not started; skipping shutdown")
+		return nil
+	}

765-804: CORS hardening: add Vary headers; verify header list

Great minimal CORS middleware. Add Vary headers so caches don’t serve the wrong variant. Also, consider whether you need Access-Control-Allow-Credentials; if you don’t, keeping it omitted is safer.

 func setCORSHeaders(w http.ResponseWriter, allowedMethods []string) {
 	w.Header().Set("Access-Control-Allow-Origin", "*")
 	w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allowedMethods, "OPTIONS"), ", "))
 	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id")
 	w.Header().Set("Access-Control-Max-Age", "86400") // 24 hours
+	// Ensure proper caching behavior for CORS preflights and varied responses
+	w.Header().Add("Vary", "Origin")
+	w.Header().Add("Vary", "Access-Control-Request-Method")
+	w.Header().Add("Vary", "Access-Control-Request-Headers")
 }

288-317: Expose the base path as an option and consider using BaseURL in responses

  • Path “/mcp” is hardcoded. Expose it via an option to avoid coupling and to support multiple mounts.
  • You capture BaseURL in options but don’t use it; it would be useful to advertise endpoints and generate guidance consistently.

Happy to draft a small WithPath(path string) option and wire BaseURL where you render endpoints (e.g., operation info).


86-90: Field naming: distinguish the streamable transport from net/http server

httpServer is a *server.StreamableHTTPServer, but reads like *http.Server. Consider renaming (e.g., streamableSrv) to avoid maintenance confusion with the actual *http.Server created in Serve.

If you rename, update Stop and assignments accordingly.


615-626: Use BaseURL (if provided) when rendering usage instructions

You currently always show s.routerGraphQLEndpoint. If BaseURL is configured to point clients to a public/externally-routed endpoint, prefer it in usage strings.

A simple helper like func (s *GraphQLSchemaServer) effectiveEndpoint() string that chooses BaseURL when set would centralize this.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9c9c6 and 127f0e1.

📒 Files selected for processing (1)
  • router/pkg/mcpserver/server.go (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-20T22:13:25.222Z
Learnt from: StarpTech
PR: wundergraph/cosmo#2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Applied to files:

  • router/pkg/mcpserver/server.go
🧬 Code graph analysis (1)
router/pkg/mcpserver/server.go (2)
router/core/graph_server.go (1)
  • Server (68-71)
router/pkg/mcpserver/util.go (1)
  • NewZapAdapter (17-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
router/pkg/mcpserver/server.go (1)

71-73: Missing WithStateless in mcp-go v0.36.0 – bump dependency

Based on the version scan and API lookup:

  • router/go.mod and router-tests/go.mod both pin github.com/mark3labs/mcp-go v0.36.0.
  • The functions NewStreamableHTTPServer, WithStreamableHTTPServer, and WithHTTPContextFunc are present in v0.36.0.
  • However, there is no top-level func WithStateless in mcp-go v0.36.0—attempts to locate it in server/streamable_http.go yielded no definition.
  • The demo module’s go.mod still pulls mcp-go v0.30.0 (indirect); if it exercises WithStateless, it will also break.

To fix:

  • Bump the mcp-go dependency in router/go.mod and router-tests/go.mod to a release that includes WithStateless (e.g., v0.37.0 or later), then run go mod tidy.
  • Update demo/go.mod’s indirect pin if demo code uses the new API.
⛔ Skipped due to learnings
Learnt from: StarpTech
PR: wundergraph/cosmo#2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants