Skip to content

mcp: expose Mcp-Session-Id via confighttp CORS exposed_headers#8128

Closed
Rajneesh180 wants to merge 1 commit into
jaegertracing:mainfrom
Rajneesh180:feat/configurable-cors-mcp-7827
Closed

mcp: expose Mcp-Session-Id via confighttp CORS exposed_headers#8128
Rajneesh180 wants to merge 1 commit into
jaegertracing:mainfrom
Rajneesh180:feat/configurable-cors-mcp-7827

Conversation

@Rajneesh180

@Rajneesh180 Rajneesh180 commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Which problem is this PR solving?

Part of #7827

The MCP protocol requires clients to read the Mcp-Session-Id response header, so the server must send Access-Control-Expose-Headers: Mcp-Session-Id for browser-based (cross-origin) clients to access it. confighttp.CORSConfig previously did not map the ExposedHeaders option from rs/cors, which is why an earlier revision of this PR carried a small middleware.

Per the review feedback, the capability now lives upstream: open-telemetry/opentelemetry-collector#15119 added ExposedHeaders to confighttp.CORSConfig, released in confighttp v0.154.0. This PR drops the middleware entirely and relies on standard confighttp configuration.

Description of the changes

  • Bump go.opentelemetry.io/collector/config/confighttp to v0.154.0 (with its interlocked collector modules), which adds cors::exposed_headers support
  • No custom CORS code left in jaeger — the previous middleware is removed; server.go is identical to main
  • Document the browser-client CORS setup (exposed_headers: ["Mcp-Session-Id"]) in the extension README and Config comment
  • CORS test coverage for the confighttp integration: preflight, configurable origins, allowed-headers auto-mirror, and exposed headers

How was this change tested?

  • TestCORSExposeHeadersMcpSessionID — verifies cors::exposed_headers surfaces Access-Control-Expose-Headers: Mcp-Session-Id on a real cross-origin request
  • TestCORSPreflight — preflight 204 + Access-Control-Allow-Origin + allowed methods
  • TestCORSConfigurableOrigins — wildcard, exact match, blocked, subdomain match/no-match, multiple origins, nil/empty
  • TestCORSCustomHeaders — allowed-headers auto-mirror covers MCP protocol headers
  • go test ./cmd/jaeger/internal/extension/jaegermcp/... — all packages pass; go build ./cmd/jaeger/... clean

Copilot AI review requested due to automatic review settings March 2, 2026 09:36
@Rajneesh180
Rajneesh180 requested a review from a team as a code owner March 2, 2026 09:36
@dosubot dosubot Bot added the enhancement label Mar 2, 2026

Copilot AI left a comment

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.

Pull request overview

Adds configurable CORS behavior to the Jaeger MCP server extension so deployments can restrict allowed origins and permit additional request headers, rather than always responding with Access-Control-Allow-Origin: *.

Changes:

  • Introduces CORSConfig (allowed origins + additional allowed headers) and wires it into the MCP server config.
  • Updates corsMiddleware to match/echo allowed origins and to always include required MCP protocol headers in Access-Control-Allow-Headers.
  • Expands/updates CORS-related tests with table-driven coverage for origin matching and custom headers.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
cmd/jaeger/internal/extension/jaegermcp/config.go Adds CORSConfig and exposes it on the extension Config via mapstructure:"cors".
cmd/jaeger/internal/extension/jaegermcp/factory.go Sets backward-compatible default CORS config (AllowedOrigins: ["*"]).
cmd/jaeger/internal/extension/jaegermcp/server.go Makes CORS behavior configurable; implements origin matching + configurable allowed headers.
cmd/jaeger/internal/extension/jaegermcp/server_test.go Updates preflight test expectations and adds new tests for configurable origins and custom headers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +16
// Use ["*"] to allow any origin. Supports exact matches and wildcard patterns
// (e.g., "http://*.example.com").

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The AllowedOrigins comment says it “supports ... wildcard patterns”, but the implementation in isOriginAllowed has specific (and somewhat permissive) wildcard semantics. To reduce misconfiguration risk, consider documenting the exact supported pattern(s) (e.g., only * and scheme://*.example.com with a single wildcard) and/or validating unsupported patterns early in Validate().

Suggested change
// Use ["*"] to allow any origin. Supports exact matches and wildcard patterns
// (e.g., "http://*.example.com").
// Use ["*"] to allow any origin. Literal origins (e.g., "https://example.com") are
// always matched exactly. Wildcard handling, if used, follows the MCP server's
// origin-matching implementation and should be configured with care.

Copilot uses AI. Check for mistakes.
Comment on lines +232 to +237
origin := r.Header.Get("Origin")
if origin != "" && isOriginAllowed(origin, cfg.AllowedOrigins) {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else if containsWildcard(cfg.AllowedOrigins) {
w.Header().Set("Access-Control-Allow-Origin", "*")
}

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

When Access-Control-Allow-Origin is set dynamically based on the request Origin, the response should also include Vary: Origin to prevent shared caches/CDNs from serving a response with the wrong CORS headers to a different origin (cache poisoning / data exposure risk). Consider adding Vary: Origin (and for preflights optionally Vary: Access-Control-Request-Method / Vary: Access-Control-Request-Headers).

Copilot uses AI. Check for mistakes.
Comment on lines +274 to +283
// Support wildcard subdomain matching: "http://*.example.com"
if strings.Contains(pattern, "*") {
// Split pattern at the wildcard and check prefix/suffix
parts := strings.SplitN(pattern, "*", 2)
if len(parts) == 2 &&
strings.HasPrefix(origin, parts[0]) &&
strings.HasSuffix(origin, parts[1]) {
return true
}
}

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

isOriginAllowed currently treats any * in the configured pattern as a wildcard and matches by simple prefix/suffix. This is more permissive than the doc comment implies (“leading wildcard to match subdomains”) and can easily lead to accidentally broad matches (e.g., patterns with * in unexpected positions). Suggest either (a) enforcing/validating the supported form (single wildcard only in the subdomain position like scheme://*.example.com) or (b) updating the documentation to match the actual matching semantics.

Copilot uses AI. Check for mistakes.
Comment on lines 233 to 241
if origin != "" && isOriginAllowed(origin, cfg.AllowedOrigins) {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else if containsWildcard(cfg.AllowedOrigins) {
w.Header().Set("Access-Control-Allow-Origin", "*")
}

w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID")
w.Header().Set("Access-Control-Allow-Headers", allowHeadersStr)
w.Header().Set("Access-Control-Expose-Headers", "Mcp-Session-Id")

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description mentions “correct CORS behavior for credentialed requests”, but this middleware never sets Access-Control-Allow-Credentials: true. Echoing the origin is necessary but not sufficient for credentialed browser requests; if credentials are in scope, this likely needs a configurable allow_credentials flag and to avoid returning * when credentials are enabled. Otherwise, consider adjusting wording/comments to avoid implying credential support.

Copilot uses AI. Check for mistakes.
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from 27acae3 to 9d809a0 Compare March 5, 2026 09:51
Copilot AI review requested due to automatic review settings March 6, 2026 01:45
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from 9d809a0 to c18ad93 Compare March 6, 2026 01:45

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 231 to 255
@@ -230,3 +254,38 @@ func corsMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
})

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

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

When an Origin header is present but not allowed, the handler still processes the request (and preflights still return 204) — it just omits Access-Control-Allow-Origin. If the intent is to "restrict which origins can access the MCP endpoint", this doesn’t actually prevent cross-site requests (it only prevents browsers from reading responses) and can still allow CSRF-style side effects or resource usage. Consider explicitly rejecting requests with a disallowed Origin (e.g., return 403 for OPTIONS and non-OPTIONS when Origin is present and not allowed), or clarify in docs/tests that this is only a browser read restriction, not access control.

Copilot uses AI. Check for mistakes.
defer resp.Body.Close()

assert.Equal(t, http.StatusNoContent, resp.StatusCode)
assert.Equal(t, tt.expectedOrigin, resp.Header.Get("Access-Control-Allow-Origin"))

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

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

The middleware now conditionally adds Vary: Origin when reflecting an allowed Origin, but the new CORS tests only assert Access-Control-Allow-Origin. To prevent regressions in caching behavior, consider extending the relevant subtests to assert Vary is set to Origin when expectedOrigin is a reflected origin (and absent when expectedOrigin is "*" or empty).

Suggested change
assert.Equal(t, tt.expectedOrigin, resp.Header.Get("Access-Control-Allow-Origin"))
assert.Equal(t, tt.expectedOrigin, resp.Header.Get("Access-Control-Allow-Origin"))
// When the origin is reflected, the middleware should set Vary: Origin
// to ensure correct caching behavior. For wildcard ("*") or empty
// expected origins, Vary should be absent.
if tt.expectedOrigin != "" && tt.expectedOrigin == tt.requestOrigin {
assert.Equal(t, "Origin", resp.Header.Get("Vary"))
} else {
assert.Equal(t, "", resp.Header.Get("Vary"))
}

Copilot uses AI. Check for mistakes.
)

// CORSConfig holds CORS configuration for the MCP server.
type CORSConfig struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doesn't confighttp.ServerConfig have CORS config?

@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Great catch — you're right, confighttp.ServerConfig already has CORS support via its CORS configoptional.Optional[CORSConfig] field. I've refactored to use it.

What changed:

  • Removed the custom CORSConfig struct entirely — CORS origins/headers are now configured through HTTP.CORS using configoptional.Some(confighttp.CORSConfig{...})
  • Replaced the manual net.Listen + http.Server setup with confighttp's ToListener() + ToServer(), which gives us TLS support, OTel instrumentation, decompression, and auth middleware for free
  • Deleted ~60 lines of custom corsMiddleware/containsWildcard/isOriginAllowed — all of that is now handled by rs/cors via confighttp
  • Kept a thin mcpExposeHeadersMiddleware (~7 lines) only for Access-Control-Expose-Headers: Mcp-Session-Id, since confighttp.CORSConfig doesn't expose that rs/cors option

This also addresses all of Copilot's review points (missing Vary: Origin, Allow-Credentials, permissive wildcard matching) since rs/cors handles all of those correctly.

Net result: -75 lines, and we're aligned with how other collector extensions (like jaegerquery) handle CORS.

AllowedHeaders: []string{
"Mcp-Session-Id",
"Mcp-Protocol-Version",
"Last-Event-ID",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why should this be in the default config? A default config can be overwritten by the users, then all these headers will become invalid

Copilot AI review requested due to automatic review settings March 9, 2026 05:05
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from 96c8c35 to 84c79d1 Compare March 9, 2026 05:05
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback:

  • Removed AllowedHeaders from default CORS config — When AllowedHeaders is empty/unset, rs/cors automatically mirrors all Access-Control-Request-Headers from the preflight request. This means MCP headers (Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID) work out of the box without being hardcoded in defaults, and users who override CORS config won't lose them.
  • Users can still explicitly restrict headers via allowed_headers in their config if needed.
  • Access-Control-Expose-Headers: Mcp-Session-Id is still handled by the dedicated middleware (mcpExposeHeadersMiddleware), independent of CORS config.
  • Fixed DCO sign-off on all commits.

@yurishkuro ready for re-review when you have a chance.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 733 to +735
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
assert.Equal(t, "*", resp.Header.Get("Access-Control-Allow-Origin"))
assert.Equal(t, "GET, POST, DELETE, OPTIONS", resp.Header.Get("Access-Control-Allow-Methods"))
}

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

TestCORSPreflight no longer asserts Access-Control-Allow-Methods (and it also doesn't validate allowed request headers). Since this test is the basic regression check for the CORS behavior, it should continue to verify the expected preflight response headers produced by the server.

Copilot uses AI. Check for mistakes.
allowedOrigins: []string{"http://localhost:3000", "http://localhost:8080"},
requestOrigin: "http://localhost:8080",
expectedOrigin: "http://localhost:8080",
},

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description mentions an "empty origins blocks all" scenario, but TestCORSConfigurableOrigins does not include a case where AllowedOrigins is empty/nil. Adding that case would help prevent regressions around the "block all" configuration behavior.

Suggested change
},
},
{
name: "nil origins blocks all",
allowedOrigins: nil,
requestOrigin: "http://example.com",
expectedOrigin: "",
},
{
name: "empty origins blocks all",
allowedOrigins: []string{},
requestOrigin: "http://example.com",
expectedOrigin: "",
},

Copilot uses AI. Check for mistakes.
Comment on lines +828 to +836
CORS: configoptional.Some(confighttp.CORSConfig{
AllowedOrigins: []string{"*"},
AllowedHeaders: []string{
"Mcp-Session-Id",
"Mcp-Protocol-Version",
"Last-Event-ID",
"X-Custom-Header",
},
}),

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

This test config explicitly includes the MCP protocol headers in AllowedHeaders. If the intended behavior is that MCP protocol headers are always permitted regardless of configuration, the test should avoid listing those headers and instead assert they are still allowed when only a custom header is configured (or update the implementation/description to match the actual behavior).

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 16
// HTTP contains the HTTP server configuration for the MCP protocol endpoint.
// CORS is configured via HTTP.CORS using confighttp's built-in support.
HTTP confighttp.ServerConfig `mapstructure:"http"`

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

The PR description references adding a new jaeger_mcp-specific CORSConfig struct, but the code change instead relies on confighttp.ServerConfig.CORS for CORS settings. Either update the PR description to reflect the actual implementation approach, or introduce the described struct if that was the intent.

Copilot uses AI. Check for mistakes.
Comment thread cmd/jaeger/internal/extension/jaegermcp/server.go
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(confighttp.CORSConfig{
AllowedOrigins: []string{"*"},

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

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

The default MCP CORS configuration only sets AllowedOrigins, but it no longer whitelists the MCP protocol request headers (e.g., Mcp-Session-Id / Mcp-Protocol-Version / Last-Event-ID) that were previously always allowed by the custom corsMiddleware. With confighttp/rs-cors defaults, preflight requests that include these headers can be rejected unless they are added to AllowedHeaders. To preserve backward compatibility, consider adding the MCP protocol headers to the default AllowedHeaders (and optionally merging any user-configured AllowedHeaders with these defaults).

Suggested change
AllowedOrigins: []string{"*"},
AllowedOrigins: []string{"*"},
AllowedHeaders: []string{"Mcp-Session-Id", "Mcp-Protocol-Version", "Last-Event-ID"},

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 13, 2026 17:58
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from c3f4012 to c401120 Compare March 13, 2026 17:58

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 224 to 231
// mcpExposeHeadersMiddleware adds the Access-Control-Expose-Headers header
// for MCP protocol headers. confighttp's CORSConfig does not expose this
// rs/cors option, so we handle it in a thin middleware.
func mcpExposeHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID")
w.Header().Set("Access-Control-Expose-Headers", "Mcp-Session-Id")

// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}

next.ServeHTTP(w, r)
})
Comment on lines 700 to +735
func TestCORSPreflight(t *testing.T) {
config := &Config{
HTTP: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(confighttp.CORSConfig{
AllowedOrigins: []string{"*"},
}),
},
ServerName: "jaeger-test",
ServerVersion: "1.0.0",
}

server := newServer(config, componenttest.NewNopTelemetrySettings())
host := newMockHost()
err := server.Start(context.Background(), host)
require.NoError(t, err)
defer server.Shutdown(context.Background())

addr := server.listener.Addr().String()
url := fmt.Sprintf("http://%s/mcp", addr)

req, err := http.NewRequest(http.MethodOptions, url, http.NoBody)
require.NoError(t, err)
req.Header.Set("Origin", "http://localhost:3000")
req.Header.Set("Access-Control-Request-Method", "POST")

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

assert.Equal(t, http.StatusNoContent, resp.StatusCode)
assert.Equal(t, "*", resp.Header.Get("Access-Control-Allow-Origin"))
assert.Equal(t, "GET, POST, DELETE, OPTIONS", resp.Header.Get("Access-Control-Allow-Methods"))
}
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Pushed a fix addressing the remaining review feedback.

Key change: mcpExposeHeadersMiddleware was using Header.Set which clobbers any Access-Control-Expose-Headers already set by confighttp's CORS handling — switched to Header.Add.

Also found a real issue with the default config: with empty AllowedHeaders, rs/cors does NOT auto-mirror request headers — it defaults to {accept, content-type, x-requested-with}. MCP protocol headers would silently fail in CORS preflight. The auto-mirror behavior only activates with AllowedHeaders: ["*"], so I added that to the default config. This addresses @yurishkuro's earlier concern about specific headers becoming invalid when users override defaults — wildcard can't be "lost" like a hardcoded list can.

Test improvements:

  • TestCORSPreflight now asserts Access-Control-Allow-Methods
  • Added nil/empty origins test cases to TestCORSConfigurableOrigins
  • TestCORSCustomHeaders tests the auto-mirror behavior instead of listing MCP headers explicitly

All tests pass locally. Ready for re-review.

@Rajneesh180

Copy link
Copy Markdown
Contributor Author

@yurishkuro gentle ping — latest push from two weeks ago addressed the remaining feedback: switched mcpExposeHeadersMiddleware to Header.Add so it doesn't clobber existing expose headers from confighttp, set AllowedHeaders: ["*"] in defaults so MCP protocol headers work via rs/cors auto-mirror without the stale-list problem you flagged, and expanded test coverage (nil/empty origins, preflight method assertions, auto-mirror behavior). CI is green, ready for another look when you get a chance.

Endpoint: ports.PortToHostPort(ports.MCPHTTP),
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(confighttp.CORSConfig{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

remove these defaults, they are unsafe and it should be up to the user to provide such defaults if they want

@yurishkuro

Copy link
Copy Markdown
Member

there are merge conflicts

@github-actions github-actions Bot added the waiting-for-author PR is waiting for author to respond to maintainer's comments label Mar 31, 2026
Copilot AI review requested due to automatic review settings March 31, 2026 17:37
@github-actions github-actions Bot removed the waiting-for-author PR is waiting for author to respond to maintainer's comments label Mar 31, 2026
@github-actions github-actions Bot removed the waiting-for-author PR is waiting for author to respond to maintainer's comments label Apr 1, 2026
@Rajneesh180 Rajneesh180 changed the title feat: make CORS settings configurable for MCP server mcp: add expose-headers middleware and CORS test coverage Apr 1, 2026
Copilot AI review requested due to automatic review settings April 6, 2026 12:05
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from b6c407e to 690545c Compare April 6, 2026 12:05

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

require.NoError(t, err)
defer resp.Body.Close()

allowHeaders := resp.Header.Get("Access-Control-Allow-Headers")

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

Header names are case-insensitive, and rs/cors may canonicalize mirrored Access-Control-Request-Headers values (e.g., "Mcp-Session-Id" / "X-Custom-Header") before returning Access-Control-Allow-Headers. These assertions currently require lowercase substrings, which can make the test fail even when behavior is correct. Consider normalizing allowHeaders (e.g., strings.ToLower) before checking contents, or performing case-insensitive matching.

Suggested change
allowHeaders := resp.Header.Get("Access-Control-Allow-Headers")
allowHeaders := strings.ToLower(resp.Header.Get("Access-Control-Allow-Headers"))

Copilot uses AI. Check for mistakes.
Comment on lines +701 to +703
defer server.Shutdown(context.Background())

addr := server.listener.Addr().String()

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

The new CORS tests start the server and immediately issue HTTP requests without waiting for the Serve goroutine to start accepting connections. This can cause occasional connection-refused flakes (this file already has waitForServer/startTestServer helpers used elsewhere). Consider calling waitForServer(addr) after Start, and using t.Cleanup to ensure Shutdown runs even if an assertion fails.

Suggested change
defer server.Shutdown(context.Background())
addr := server.listener.Addr().String()
t.Cleanup(func() {
require.NoError(t, server.Shutdown(context.Background()))
})
addr := server.listener.Addr().String()
require.NoError(t, waitForServer(addr))

Copilot uses AI. Check for mistakes.
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from 690545c to 7606cfc Compare April 6, 2026 12:46
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Right, ToListener/ToServer and the hardcoded CORS values are already on main — this PR only adds one thing on top of that.

confighttp.CORSConfig maps AllowedOrigins, AllowedHeaders, and MaxAge to rs/cors, but it doesn't map ExposedHeaders. The MCP spec requires clients to read Mcp-Session-Id from response headers, and browsers block access to custom response headers unless the server sends Access-Control-Expose-Headers. So the PR adds a thin middleware (~7 lines) that does Header.Add("Access-Control-Expose-Headers", "Mcp-Session-Id") — uses Add not Set to avoid clobbering anything confighttp sets. The rest is CORS test coverage that didn't exist before.

@yurishkuro the diff is 3 files, ~12 lines of production code. Latest push rebased on current main and cleaned up the tests (switched to t.Cleanup for server shutdown, added waitForServer to avoid startup races, case-insensitive header assertions).

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +680 to +695
func TestCORSPreflight(t *testing.T) {
config := &Config{
HTTP: confighttp.ServerConfig{
NetAddr: confignet.AddrConfig{
Endpoint: "localhost:0",
Transport: confignet.TransportTypeTCP,
},
CORS: configoptional.Some(confighttp.CORSConfig{
AllowedOrigins: []string{"*"},
}),
},
ServerName: "jaeger-test",
ServerVersion: "1.0.0",
MaxSpanDetailsPerRequest: 20,
MaxSearchResults: 100,
}
Comment on lines +911 to +919
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"initialize"}`))
require.NoError(t, err)
req.Header.Set("Origin", "http://localhost:3000")
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

@yurishkuro

Copy link
Copy Markdown
Member

if otel CORS doesn't provide the ability to set those please create a PR in OTEL for that. The underlying cors library that OTEL uses does seem to support this header:

https://github.com/rs/cors/blob/c9e5260a4ed400c4d3f356c5100a79af02f48d8d/cors.go#L78

https://github.com/rs/cors/blob/c9e5260a4ed400c4d3f356c5100a79af02f48d8d/cors.go#L434

@github-actions github-actions Bot added the waiting-for-author PR is waiting for author to respond to maintainer's comments label Apr 14, 2026
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from bb24fc4 to b9cbff6 Compare April 15, 2026 07:33
@github-actions github-actions Bot removed the waiting-for-author PR is waiting for author to respond to maintainer's comments label Apr 15, 2026
pull Bot pushed a commit to moqimoqidea/opentelemetry-collector that referenced this pull request Jun 1, 2026
`CORSConfig` maps `AllowedOrigins`, `AllowedHeaders`, and `MaxAge` into
`cors.Options`, but doesn't map `ExposedHeaders` even though `rs/cors`
supports it. This means there's no way to set
`Access-Control-Expose-Headers` through receiver config — anyone who
needs it has to write a custom middleware.

This came up in jaegertracing/jaeger#8128 where the Jaeger UI needs
specific headers exposed over CORS but has to work around this gap with
a hand-rolled middleware.

### Changes
- Add `ExposedHeaders []string` to `CORSConfig`
- Wire it into `cors.Options` in `ToServer()`
- Add test
- Update README

Signed-off-by: Rajneesh Chaudhary <[email protected]>
Copilot AI review requested due to automatic review settings June 11, 2026 21:58
@Rajneesh180
Rajneesh180 force-pushed the feat/configurable-cors-mcp-7827 branch from b9cbff6 to 2721f23 Compare June 11, 2026 21:58
…rtracing#7827)

confighttp v0.154.0 added ExposedHeaders to CORSConfig
(open-telemetry/opentelemetry-collector#15119), so the MCP server no
longer needs a custom middleware to send Access-Control-Expose-Headers.
Browser-based MCP clients can read the Mcp-Session-Id response header
by listing it under cors::exposed_headers.

- bump confighttp to v0.154.0 along with its interlocked collector modules
- drop the custom expose-headers middleware
- document the CORS setup for browser clients in README and Config
- cover CORS preflight, origin matching, allowed-headers auto-mirror,
  and exposed_headers in server tests

Signed-off-by: Rajneesh Chaudhary <[email protected]>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Comment thread go.mod
Comment on lines +53 to +67
go.opentelemetry.io/collector/client v1.60.0
go.opentelemetry.io/collector/component v1.60.0
go.opentelemetry.io/collector/component/componentstatus v0.153.0
go.opentelemetry.io/collector/component/componenttest v0.153.0
go.opentelemetry.io/collector/config/configauth v1.59.0
go.opentelemetry.io/collector/component/componenttest v0.154.0
go.opentelemetry.io/collector/config/configauth v1.60.0
go.opentelemetry.io/collector/config/configgrpc v0.153.0
go.opentelemetry.io/collector/config/confighttp v0.153.0
go.opentelemetry.io/collector/config/confighttp v0.154.0
go.opentelemetry.io/collector/config/confighttp/xconfighttp v0.153.0
go.opentelemetry.io/collector/config/configmiddleware v1.59.0
go.opentelemetry.io/collector/config/confignet v1.59.0
go.opentelemetry.io/collector/config/configoptional v1.59.0
go.opentelemetry.io/collector/config/configmiddleware v1.60.0
go.opentelemetry.io/collector/config/confignet v1.60.0
go.opentelemetry.io/collector/config/configoptional v1.60.0
go.opentelemetry.io/collector/config/configretry v1.59.0
go.opentelemetry.io/collector/config/configtelemetry v0.153.0
go.opentelemetry.io/collector/config/configtls v1.59.0
go.opentelemetry.io/collector/confmap v1.59.0
go.opentelemetry.io/collector/config/configtls v1.60.0
go.opentelemetry.io/collector/confmap v1.60.0
Comment thread go.mod
Comment on lines +73 to +76
go.opentelemetry.io/collector/confmap/xconfmap v0.154.0
go.opentelemetry.io/collector/connector v0.153.0
go.opentelemetry.io/collector/connector/forwardconnector v0.153.0
go.opentelemetry.io/collector/consumer v1.59.0
go.opentelemetry.io/collector/consumer v1.60.0
Comment thread go.mod
Comment on lines +85 to 94
go.opentelemetry.io/collector/extension v1.60.0
go.opentelemetry.io/collector/extension/extensionauth v1.60.0
go.opentelemetry.io/collector/extension/extensioncapabilities v0.153.0
go.opentelemetry.io/collector/extension/zpagesextension v0.153.0
go.opentelemetry.io/collector/featuregate v1.59.0
go.opentelemetry.io/collector/featuregate v1.60.0
go.opentelemetry.io/collector/otelcol v0.153.0
go.opentelemetry.io/collector/pdata v1.59.0
go.opentelemetry.io/collector/pdata v1.60.0
go.opentelemetry.io/collector/pdata/xpdata v0.153.0
go.opentelemetry.io/collector/pipeline v1.59.0
go.opentelemetry.io/collector/processor v1.59.0
Comment on lines +731 to +732
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
Comment on lines +934 to +940
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

exposed := resp.Header.Get("Access-Control-Expose-Headers")
assert.Contains(t, exposed, "Mcp-Session-Id",
"exposed_headers config must surface Mcp-Session-Id so browsers can read it from cross-origin responses")
@Rajneesh180 Rajneesh180 changed the title mcp: add expose-headers middleware and CORS test coverage mcp: expose Mcp-Session-Id via confighttp CORS exposed_headers Jun 11, 2026
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.84%. Comparing base (260e647) to head (2721f23).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8128      +/-   ##
==========================================
- Coverage   96.90%   96.84%   -0.07%     
==========================================
  Files         336      336              
  Lines       17921    17921              
==========================================
- Hits        17367    17356      -11     
- Misses        401      413      +12     
+ Partials      153      152       -1     
Flag Coverage Δ
badger_direct 8.95% <ø> (ø)
badger_e2e 1.04% <ø> (ø)
cassandra-4.x-direct-manual 14.56% <ø> (ø)
cassandra-4.x-e2e-auto 1.03% <ø> (ø)
cassandra-4.x-e2e-manual 1.03% <ø> (ø)
cassandra-5.x-direct-manual 14.56% <ø> (ø)
cassandra-5.x-e2e-auto 1.03% <ø> (ø)
cassandra-5.x-e2e-manual 1.03% <ø> (ø)
clickhouse-direct 9.02% <ø> (ø)
clickhouse-e2e 1.18% <ø> (ø)
elasticsearch-6.x-direct 16.89% <ø> (ø)
elasticsearch-7.x-direct 16.92% <ø> (ø)
elasticsearch-8.x-direct ?
elasticsearch-8.x-e2e 1.04% <ø> (ø)
elasticsearch-9.x-e2e 1.04% <ø> (ø)
grpc_direct 7.90% <ø> (ø)
grpc_e2e 1.06% <ø> (ø)
kafka-3.x-v2 1.04% <ø> (ø)
memory_v2 1.06% <ø> (ø)
opensearch-1.x-direct 16.96% <ø> (ø)
opensearch-2.x-direct 16.96% <ø> (ø)
opensearch-2.x-e2e 1.04% <ø> (ø)
opensearch-3.x-e2e 1.04% <ø> (ø)
query 1.06% <ø> (ø)
tailsampling-processor 0.55% <ø> (ø)
unittests 95.24% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yurishkuro

Copy link
Copy Markdown
Member

I don't see any functional changes in this PR.

@yurishkuro yurishkuro closed this Jun 11, 2026
@github-actions github-actions Bot added the waiting-for-author PR is waiting for author to respond to maintainer's comments label Jun 11, 2026
@Rajneesh180
Rajneesh180 deleted the feat/configurable-cors-mcp-7827 branch June 13, 2026 17:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement waiting-for-author PR is waiting for author to respond to maintainer's comments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants