Skip to content

Conversation

tzolov
Copy link
Contributor

@tzolov tzolov commented Sep 2, 2025

  • Add integration tests for transport context propagation between MCP clients and servers
  • Test both sync and async server implementations across all transport types (stateless, streamable, SSE)
  • Cover Spring WebFlux and WebMVC environments with dedicated test suites
  • Validate context flow through HTTP headers for authentication, correlation IDs, and metadata
  • Rename existing McpTransportContextIntegrationTests to SyncServerMcpTransportContextIntegrationTests for clarity

This change adds missing test coverage for the MCP transport context feature, which allows passing contextual information (like authentication tokens, correlation IDs, or custom metadata) between MCP clients and servers through HTTP headers. The tests ensure that context propagation works correctly across:

  • Different server types (sync/async)
  • Different transport mechanisms (stateless, streamable, SSE)
  • Different Spring environments (WebFlux, WebMVC)
  • Different client types (sync/async)

Breaking Changes

None. This PR only adds new test files and renames one existing test file for clarity. No production code or APIs are changed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update (in this case, test coverage which serves as usage documentation)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

- Add integration tests for transport context propagation between MCP clients and servers
- Test both sync and async server implementations across all transport types (stateless, streamable, SSE)
- Cover Spring WebFlux and WebMVC environments with dedicated test suites
- Validate context flow through HTTP headers for authentication, correlation IDs, and metadata
- Rename existing McpTransportContextIntegrationTests to SyncServerMcpTransportContextIntegrationTests for clarity

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
@tzolov tzolov added this to the 0.12.0 milestone Sep 2, 2025
Copy link
Contributor

@Kehrlann Kehrlann left a comment

Choose a reason for hiding this comment

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

Nice and thorough work! We have a much better confidence in transports with this. Thanks.

I wonder if we don't have a structural issue with these tests, even though that existed before.

We always build all server transports as the field of classes. And then each test only calls .close() on the server that it is using, which in turns closes the underlying server transport - but other transports in the class never have their .close() called. Is that a problem, or will everything be magically garbage-collected away? Maybe @chemicL has insights for us.

(Also, if a test fails, mcpServer.close() is never called).

Comment on lines 100 to 108
private static final ThreadLocal<String> CLIENT_SIDE_HEADER_VALUE_HOLDER = new ThreadLocal<>();

private static final String HEADER_NAME = "x-test";

private final Supplier<McpTransportContext> clientContextProvider = () -> {
var headerValue = CLIENT_SIDE_HEADER_VALUE_HOLDER.get();
return headerValue != null ? McpTransportContext.create(Map.of("client-side-header-value", headerValue))
: McpTransportContext.EMPTY;
};
Copy link
Contributor

@Kehrlann Kehrlann Sep 2, 2025

Choose a reason for hiding this comment

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

[suggestion] I don't think we need to test sync clients and async clients. Sync clients are already tested with Sync servers ; hopefully all servers and all clients are spec-compliant, and breaking something in a client should result in an issue, now matter what type of server is used (and vice-versa). Otherwise the matrix starts to get quite big.

So I suggest:

  • SyncServer tests -> use sync clients
  • AsyncServer test -> use async clients

This way they are "naturally" paired - even though sync server + async client and async server + sync client would also work.

Comment on lines 110 to 116
private final McpSyncHttpClientRequestCustomizer clientRequestCustomizer = (builder, method, endpoint, body,
context) -> {
var headerValue = context.get("client-side-header-value");
if (headerValue != null) {
builder.header(HEADER_NAME, headerValue.toString());
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] Let's use an async request customizer. It should change anything but serves as an example.

Comment on lines 105 to 117
@Configuration
static class TestCommonConfig {

@Bean
public McpTransportContextExtractor<ServerRequest> serverContextExtractor() {
return (ServerRequest r) -> {
String headerValue = r.servletRequest().getHeader(HEADER_NAME);
return headerValue != null ? McpTransportContext.create(Map.of("server-side-header-value", headerValue))
: McpTransportContext.EMPTY;
};
};

}
Copy link
Contributor

Choose a reason for hiding this comment

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

[question] does this need to be a bean wrapped in a config class? It holds a static function that could be used "as-is".

Comment on lines +187 to +192
private final McpSyncClient streamableClient = McpClient
.sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
.httpRequestCustomizer(clientRequestCustomizer)
.build())
.transportContextProvider(clientContextProvider)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] swap the order, the beans are between fields of this class.

Comment on lines 218 to 221
var mcpServer = McpServer.sync(statelessServerTransport)
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
.tools(new McpStatelessServerFeatures.SyncToolSpecification(tool, statelessHandler))
.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

[suggestion] Make the server a bean. This way, all the servery bits are in a config class.


// Async client context provider
ExchangeFilterFunction asyncClientContextProvider = (request, next) -> Mono.deferContextual(ctx -> {
var context = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] clarify distinction between reactor context (ctx) and McpTransportContext

Suggested change
var context = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);

Comment on lines 81 to 90
private static final ThreadLocal<String> SYNC_CLIENT_SIDE_HEADER_VALUE_HOLDER = new ThreadLocal<>();

private static final String HEADER_NAME = "x-test";

// Sync client context provider
private final Supplier<McpTransportContext> syncClientContextProvider = () -> {
var headerValue = SYNC_CLIENT_SIDE_HEADER_VALUE_HOLDER.get();
return headerValue != null ? McpTransportContext.create(Map.of("client-side-header-value", headerValue))
: McpTransportContext.EMPTY;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as in AsyncServerMcpTransportContextIntegrationTests : consider using only async clients here.

…ove resource cleanup

- Remove synchronous client tests from AsyncServerMcpTransportContextIntegrationTests
- Clean up unused sync client imports and ThreadLocal context providers
- Add proper resource cleanup for async clients in teardown methods
- Update documentation to reflect async-only client/server focus
- Refactor WebMVC tests to use Spring beans for MCP server configuration
- Simplify test architecture by separating sync and async concerns

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
@tzolov
Copy link
Contributor Author

tzolov commented Sep 3, 2025

@Kehrlann , thanks for the review!
Latest commit should address most of the recommendation.

@Kehrlann
Copy link
Contributor

Kehrlann commented Sep 3, 2025

Looks good. Thanks for the updates!

@tzolov tzolov merged commit 02b8c4b into modelcontextprotocol:main Sep 4, 2025
1 of 2 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.

2 participants