Skip to content

fix: improve compatibility with non-compliant MCP servers #413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

tzolov
Copy link
Contributor

@tzolov tzolov commented Jul 18, 2025

Addresses issues with servers like Shopify that violate MCP/HTTP specs:

  • Prioritize application/json in Accept headers to fix content-type issues
  • Handle empty notification bodies ({}) that should be bodiless per spec
  • Add status code validation and null safety improvements

Resolves #406

Improves HTTP transport robustness to handle non-compliant MCP servers that violate MCP and HTTP specifications.

Motivation and Context

This change addresses compatibility issues with real-world MCP servers that don't strictly follow MCP and HTTP specifications. Specifically, the Shopify MCP server exhibits two problematic behaviors:

  1. Incorrect content-type negotiation: Returns text/streamable content-type when the Accept header prioritizes text/stream over application/json, but correctly returns application/json when the latter is first
  2. MCP spec violation: Sends empty JSON bodies ({}) for notification messages that should be bodiless according to the MCP specification

These violations cause the Java SDK to fail when communicating with such servers, limiting its real-world applicability.

How Has This Been Tested?

  • Tested against the Shopify MCP server that exhibits the problematic behaviors
  • Verified that existing compliant servers continue to work correctly
  • Tested both WebFlux and standard HTTP client transport implementations
  • Validated proper handling of empty responses and status code validation

Breaking Changes

No breaking changes. This is a backward-compatible improvement that maintains existing API contracts while adding robustness.

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

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

…xtprotocol#413)

Addresses issues with servers like Shopify that violate MCP/HTTP specs:
- Prioritize application/json in Accept headers to fix content-type issues
- Handle empty notification bodies ({}) that should be bodiless per spec
- Add status code validation and null safety improvements

Resolves modelcontextprotocol#406

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
@tzolov tzolov changed the title fix: improve compatibility with non-compliant MCP servers (#406) fix: improve compatibility with non-compliant MCP servers (#413) Jul 18, 2025
@tzolov tzolov changed the title fix: improve compatibility with non-compliant MCP servers (#413) fix: improve compatibility with non-compliant MCP servers Jul 18, 2025
s.next(List.of(jsonRpcResponse));
}
else {
logger.warn("Received empty response message: {}", responseMessage);
Copy link
Member

@chemicL chemicL Jul 18, 2025

Choose a reason for hiding this comment

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

I am a bit worried that if the request body was an MCP Request then we will never complete a pending operation that awaits for it. Please consider adding a check whether the original message was a request and if so send an error to the Flux (using handle's sink).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately if we receive an empty response message (e.g. null, " " or {}) there is no way to determine if it is a bogus response from a previous request, compliment notification response or bogus request.
Here we assume that if an empty even is received thread it as incorrect notification response.

else {
// No content type means no response body
logger.debug("No content type returned for POST in session {}", sessionRepresentation);
return Mono.empty();
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as for the WebClient implementation.

Comment on lines 174 to 176
System.out.println("Received non-successful response: " + this.responseInfo.statusCode());
SseEvent sseEvent = new SseEvent(null, null, null);
this.sink.next(new SseResponseEvent(responseInfo, sseEvent));
Copy link
Member

Choose a reason for hiding this comment

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

This looks unfinished. Did you intend to use the logger and call sink.error ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeh good catch. I've mistakenly commit some experiments. Above logic is incorrect.

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
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.

Streamable HTTP transport hangs during initialization
2 participants