Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
05c9f0d
feat: use cors configuration also on mcp server
alepane21 Nov 25, 2025
584fcb3
chore: force required headers for MCP
alepane21 Nov 26, 2025
66fe77c
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 26, 2025
de05598
chore: improve CORS middleware implementation
alepane21 Nov 26, 2025
32e4977
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 26, 2025
d013248
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 26, 2025
bd81800
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 26, 2025
d86632c
Merge remote-tracking branch 'origin/ale/eng-8562-mcp-server-connecti…
alepane21 Nov 26, 2025
807e1bb
chore: add test to verify that custom CORS rules on router are also used
alepane21 Nov 26, 2025
a836c15
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 26, 2025
0ffe281
fix: duplication should be made on separate headers
alepane21 Nov 26, 2025
6ecc2fb
Merge remote-tracking branch 'origin/ale/eng-8562-mcp-server-connecti…
alepane21 Nov 26, 2025
097905b
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 27, 2025
b0d854f
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 28, 2025
d331637
fix: use cors middleware and fix tests
alepane21 Nov 28, 2025
2019560
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Nov 28, 2025
9275444
Merge branch 'main' into ale/eng-8562-mcp-server-connection-failure-o…
alepane21 Dec 1, 2025
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
1 change: 1 addition & 0 deletions router/core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ func (r *Router) bootstrap(ctx context.Context) error {
mcpserver.WithEnableArbitraryOperations(r.mcp.EnableArbitraryOperations),
mcpserver.WithExposeSchema(r.mcp.ExposeSchema),
mcpserver.WithStateless(r.mcp.Session.Stateless),
mcpserver.WithCORSConfig(r.corsOptions),
}

// Determine the router GraphQL endpoint
Expand Down
37 changes: 30 additions & 7 deletions router/pkg/mcpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/wundergraph/cosmo/router/internal/stringsx"
"github.com/wundergraph/cosmo/router/pkg/cors"
"github.com/wundergraph/cosmo/router/pkg/schemaloader"
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"github.com/wundergraph/graphql-go-tools/v2/pkg/astprinter"
Expand Down Expand Up @@ -91,6 +93,8 @@ type Options struct {
ExposeSchema bool
// Stateless determines whether the MCP server should be stateless
Stateless bool
// CorsConfig is the CORS configuration for the MCP server
CorsConfig *cors.Config
}

// GraphQLSchemaServer represents an MCP server that works with GraphQL schemas and operations
Expand All @@ -111,6 +115,7 @@ type GraphQLSchemaServer struct {
operationsManager *OperationsManager
schemaCompiler *SchemaCompiler
registeredTools []string
corsConfig *cors.Config
}

type graphqlRequest struct {
Expand Down Expand Up @@ -237,6 +242,7 @@ func NewGraphQLSchemaServer(routerGraphQLEndpoint string, opts ...func(*Options)
enableArbitraryOperations: options.EnableArbitraryOperations,
exposeSchema: options.ExposeSchema,
stateless: options.Stateless,
corsConfig: options.CorsConfig,
}

return gs, nil
Expand Down Expand Up @@ -302,6 +308,12 @@ func WithStateless(stateless bool) func(*Options) {
}
}

func WithCORSConfig(corsCfg *cors.Config) func(*Options) {
return func(o *Options) {
o.CorsConfig = corsCfg
}
}

// Serve starts the server with the configured options and returns a streamable HTTP server.
func (s *GraphQLSchemaServer) Serve() (*server.StreamableHTTPServer, error) {
// Create custom HTTP server
Expand All @@ -320,7 +332,7 @@ func (s *GraphQLSchemaServer) Serve() (*server.StreamableHTTPServer, error) {
server.WithHeartbeatInterval(10*time.Second),
)

corsMiddleware := WithCORS("GET", "POST", "PUT", "DELETE")
corsMiddleware := WithCORS(s.corsConfig)

mux := http.NewServeMux()

Expand Down Expand Up @@ -802,11 +814,17 @@ func (s *GraphQLSchemaServer) handleGetGraphQLSchema() func(ctx context.Context,
// - Access-Control-Allow-Methods: specified methods + OPTIONS
// - Access-Control-Allow-Headers: Content-Type, Authorization
// - Access-Control-Max-Age: 86400 (24 hours)
func WithCORS(allowedMethods ...string) func(http.Handler) http.Handler {
func WithCORS(corsConfig *cors.Config) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if corsConfig == nil || !corsConfig.Enabled {
// If CORS is not enabled, just call the next handler
next.ServeHTTP(w, req)
return
}

// Set CORS headers for all requests
setCORSHeaders(w, allowedMethods)
setCORSHeaders(w, *corsConfig)

// Handle preflight OPTIONS requests
if req.Method == http.MethodOptions {
Expand All @@ -822,9 +840,14 @@ func WithCORS(allowedMethods ...string) func(http.Handler) http.Handler {

// setCORSHeaders sets common CORS headers
// Only used for web browsers, not for API clients
func setCORSHeaders(w http.ResponseWriter, allowedMethods []string) {
func setCORSHeaders(w http.ResponseWriter, config cors.Config) {
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
w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS")
hdrs := stringsx.RemoveDuplicates(append(config.AllowHeaders, "Content-Type, Accept, Authorization, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id"))
w.Header().Set("Access-Control-Allow-Headers", strings.Join(hdrs, ", "))
maxAge := config.MaxAge
if maxAge <= 0 {
maxAge = 24 * time.Hour
}
w.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", int(maxAge.Seconds())))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Loading