Skip to content

Conversation

@graylog-internal-actions-access

Note: This is a backport of #24717 to 7.0.

This PR:

  • Implements spec-compliant version negotiation: silent fallback during initialization (i.e. 200 OK response with server-supported protocol version) and strict MCP-Protocol-Version header validation for subsequent requests (i.e. 400 Bad Request responses for invalid/unsupported protocol versions).
  • Removes custom McpException in favor of McpError in SDK.

This ensures proper error handling according to the Model Context Protocol specification and fixes compatibility issues with MCP clients like MCP Inspector and fastmcp.

Description

Spec Compliance

Per 2025-06-18:

If the server supports the requested protocol version, it MUST respond with the same version. Otherwise, the server MUST respond with another protocol version it supports. This SHOULD be the latest version supported by the server.

If the server receives a request with an invalid or unsupported MCP-Protocol-Version, it MUST respond with 400 Bad Request.
For backwards compatibility, if the server does not receive an MCP-Protocol-Version header, and has no other way to identify the version [...] the server SHOULD assume protocol version 2025-03-26.

File changes

McpException.java

Replaced with standard McpError from MCP SDK

McpRestResource.java

  • Protocol Version Handling:

    • Validate MCP-Protocol-Version header on all requests (when present).
    • Return 400 Bad Request for invalid/unsupported header values per spec.
    • Default to "2025-03-26" when header is missing per spec backwards compatibility requirement.
    • Added TODO for future session management to retrieve negotiated version from session state.
  • Error Handling:

    • Catch McpError specifically and return 400 Bad Request with proper JSON-RPC error structure.
    • Extract request ID early (before any errors) to ensure all error responses include the correct ID.
    • Improved invalid/unsupported error messages with structured data (i.e., supported vs requested versions).

McpService.java

  • Version Negotiation:

    • Changed supportedVersions to protected static final for proper access from REST resource.
    • Added FALLBACK_MCP_VERSION constant ("2025-03-26") per spec requirement.
    • Implements silent fallback during initialization: if client requests unsupported version, server responds with its latest supported version instead of throwing error.
  • Error Handling:

    • Changed method signatures from throws McpException to throws McpError.
    • Standardized error construction using McpError.builder().
    • Consistent error codes: METHOD_NOT_FOUND, RESOURCE_NOT_FOUND, INVALID_PARAMS.
    • Include structured error data (e.g., URI, supported versions) for better debugging.
  • API Changes:

    • Added overloaded handle() method accepting protocolVersion parameter for future multi-version support.
    • Added TODO indicating where version-specific handlers could be implemented.

McpServiceTest.java

  • Changed testInitializeWithUnsupportedVersion() to expect successful fallback instead of exception.
  • Verify response includes server's supported version when client requests unsupported version.
  • Updated error assertions from McpException to McpError.
  • Updated expected error messages to match new standardized messages.

Motivation and Context

Fixes #24701

The previous implementation had two main issues:

  1. Incorrect version negotiation: During initialization, the server was rejecting clients that requested unsupported protocol versions with a 400 Bad Request error. Per the MCP specification, servers must silently fall back to a supported version and return 200 OK, allowing the client to decide whether to continue.
  2. Custom exception handling: Used a custom McpException class instead of the standard McpError types provided by the MCP SDK, leading to inconsistent error codes and messages.

How Has This Been Tested?

  • All existing tests pass with updated expectations.
  • Tested with MCP Inspector and fastmcp/claude, with both now successfully complete initialization.
  • Version negotiation works correctly: clients requesting unsupported versions receive server's supported version and can proceed:
curl -v -X POST -H "Content-type:application/json" -H "X-Sent-By:me" http://admin:admin@localhost:9000/api/mcp -d '
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": false
      }
    }
  }
}
'
> POST /api/mcp HTTP/1.1
> Host: localhost:9000
> Authorization: Basic YWRtaW46YWRtaW4=
> User-Agent: curl/8.7.1
> Accept: */*
> Content-type:application/json
> X-Sent-By:me
> Content-Length: 199
> 
* upload completely sent off: 199 bytes
< HTTP/1.1 200 OK
< Mcp-Session-Id: d2e85481-c9ec-477a-a43c-84704a2ab2ad
< X-Frame-Options: DENY
< X-Content-Type-Options: nosniff
< X-Graylog-Node-ID: 10b4a276-d245-49de-a581-f21e391b2ed4
< Content-Type: application/json
< Content-Length: 256
< 
* Connection #0 to host localhost left intact
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-06-18","capabilities":{"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"Graylog","version":"7.1.0-SNAPSHOT"}}}

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactoring (non-breaking change)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have requested a documentation update.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.

* fix(mcp): protocol version negotiation

- Implements spec-compliant version negotiation:  silent fallback (i.e. 200 OK with server-supported protocol version) during initialization and strict MCP-Protocol-Version header validation for subsequent requests (i.e. 400 Bad Request for invalid protocol versions).
- Removes custom McpException in favor of McpError in SDK.

(cherry picked from commit 45d97f0)
@monrax monrax merged commit 8a128e4 into 7.0 Jan 19, 2026
16 checks passed
@monrax monrax deleted the backport-7.0/fix/mcp-protocol-negotiation branch January 19, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants