Skip to content

feat: add CORS support with configurable allowed headers in MCP server#6295

Merged
478320 merged 4 commits intomasterfrom
feat/mcp_server_support_header
Feb 25, 2026
Merged

feat: add CORS support with configurable allowed headers in MCP server#6295
478320 merged 4 commits intomasterfrom
feat/mcp_server_support_header

Conversation

@Aias00
Copy link
Contributor

@Aias00 Aias00 commented Feb 10, 2026

add CORS support with configurable allowed headers in MCP server

Make sure that:

  • You have read the contribution guidelines.
  • You submit test cases (unit or integration tests) that back your changes.
  • Your local test passed ./mvnw clean install -Dmaven.javadoc.skip=true.

Copilot AI review requested due to automatic review settings February 10, 2026 02:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds CORS handling to the MCP server plugin/transport so cross-origin clients can call MCP endpoints with configurable Access-Control-Allow-Headers sourced from shenyu.cross.allowedHeaders.

Changes:

  • Wires shenyu.cross.allowedHeaders from Spring Boot ShenyuConfig into the MCP plugin and MCP server manager.
  • Extends the Streamable HTTP transport provider/builder to accept configured allowed headers and applies CORS headers to responses (including OPTIONS preflight).
  • Updates MCP plugin request handling to respond to OPTIONS preflight and to emit CORS headers using configurable allowed headers.

Reviewed changes

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

Show a summary per file
File Description
shenyu-spring-boot-starter-plugin-mcp-server/McpServerPluginConfiguration.java Injects ShenyuConfig and passes configured CORS allowed headers into MCP manager/plugin beans.
shenyu-plugin-mcp-server/transport/StreamableHttpProviderBuilder.java Adds allowedHeaders to builder and passes it into the transport provider constructor.
shenyu-plugin-mcp-server/transport/ShenyuStreamableHttpServerTransportProvider.java Adds configurable CORS allow-headers and centralizes CORS header application for Streamable HTTP responses.
shenyu-plugin-mcp-server/manager/ShenyuMcpServerManager.java Stores configured CORS allowed headers and passes them into Streamable HTTP transport creation.
shenyu-plugin-mcp-server/McpServerPlugin.java Adds OPTIONS preflight handling and resolves CORS headers (origin/allowed headers/methods) per request.

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

exchange.getResponse().getHeaders().set("Access-Control-Allow-Origin", resolveAllowOrigin(exchange));
exchange.getResponse().getHeaders().set("Access-Control-Allow-Headers", resolveAllowHeaders(exchange));
exchange.getResponse().getHeaders().set("Access-Control-Allow-Methods", CORS_ALLOW_METHODS);
exchange.getResponse().getHeaders().set("Vary", "Origin, Access-Control-Request-Headers");
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

set("Vary", ...) overwrites any existing Vary header that may already be set by the framework (e.g., Accept-Encoding), which can break caching semantics. Prefer adding/merging the Vary values instead of replacing the header entirely.

Suggested change
exchange.getResponse().getHeaders().set("Vary", "Origin, Access-Control-Request-Headers");
// Merge CORS-related Vary values with any existing Vary header entries
final List<String> existingVary = exchange.getResponse().getHeaders().getVary();
final Set<String> varyValues = new LinkedHashSet<>(existingVary);
varyValues.add("Origin");
varyValues.add("Access-Control-Request-Headers");
exchange.getResponse().getHeaders().setVary(new java.util.ArrayList<>(varyValues));

Copilot uses AI. Check for mistakes.
Comment on lines 222 to 226
if ("OPTIONS".equalsIgnoreCase(request.methodName())) {
// Handle CORS preflight requests
return ServerResponse.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id, Authorization, Mcp-Protocol-Version")
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
return applyCorsHeaders(request, ServerResponse.ok(), CORS_ALLOW_METHODS)
.header("Access-Control-Max-Age", "3600")
.build();
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

This transport returns 405 for GET, but the CORS preflight response advertises Access-Control-Allow-Methods: GET, POST, OPTIONS. Align the advertised methods with what the endpoint actually supports (likely POST, OPTIONS) to avoid misleading clients and incorrect CORS behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +684 to +693
allowedHeaders.add(header.trim());
}
final String requestedHeaders = request.headers().firstHeader("Access-Control-Request-Headers");
if (Objects.nonNull(requestedHeaders) && !requestedHeaders.isBlank()) {
for (String requestedHeader : requestedHeaders.split(",")) {
final String header = requestedHeader.trim();
if (!header.isEmpty()) {
allowedHeaders.add(header);
}
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

This logic unions the configured allow-list with all client-supplied Access-Control-Request-Headers, which effectively allows any requested header and makes the configuration non-enforcing. Consider returning only the configured allow-list (or intersecting configured vs requested) so disallowed requested headers fail preflight.

Suggested change
allowedHeaders.add(header.trim());
}
final String requestedHeaders = request.headers().firstHeader("Access-Control-Request-Headers");
if (Objects.nonNull(requestedHeaders) && !requestedHeaders.isBlank()) {
for (String requestedHeader : requestedHeaders.split(",")) {
final String header = requestedHeader.trim();
if (!header.isEmpty()) {
allowedHeaders.add(header);
}
}
final String trimmed = header.trim();
if (!trimmed.isEmpty()) {
allowedHeaders.add(trimmed);
}
}
final String requestedHeaders = request.headers().firstHeader("Access-Control-Request-Headers");
if (Objects.nonNull(requestedHeaders) && !requestedHeaders.isBlank()) {
final Set<String> effectiveHeaders = new LinkedHashSet<>();
for (String requestedHeader : requestedHeaders.split(",")) {
final String header = requestedHeader.trim();
if (!header.isEmpty() && allowedHeaders.contains(header)) {
effectiveHeaders.add(header);
}
}
if (!effectiveHeaders.isEmpty()) {
return String.join(", ", effectiveHeaders);
}

Copilot uses AI. Check for mistakes.
Comment on lines +312 to +316
private Mono<Void> handleCorsPreflight(final ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.OK);
setCorsHeaders(exchange);
exchange.getResponse().getHeaders().set("Access-Control-Max-Age", "3600");
return exchange.getResponse().setComplete();
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

CORS behavior was added/changed (preflight handling + configurable allowed headers), but there are no corresponding tests covering OPTIONS requests and the resulting Access-Control-* headers. Please add unit/integration tests that assert the preflight response headers for both configured and default allowed-headers scenarios.

Copilot uses AI. Check for mistakes.
Aias00 and others added 3 commits February 10, 2026 10:57
@478320 478320 merged commit 6f82818 into master Feb 25, 2026
54 of 58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants