Skip to content

Fix MCP server 500 error on unknown client capability fields - #26008

Merged
monrax merged 8 commits into
masterfrom
fix/mcp-object-mapper
Jun 9, 2026
Merged

monrax merged 8 commits into
masterfrom
fix/mcp-object-mapper

Conversation

@monrax

@monrax monrax commented May 14, 2026

Copy link
Copy Markdown
Contributor

Description

Introduce a dedicated protocol-level ObjectMapper in McpService and McpRestResource for deserializing MCP SDK types (InitializeRequest, ReadResourceRequest, CallToolRequest, GetPromptRequest). This mapper is configured with FAIL_ON_UNKNOWN_PROPERTIES = false to tolerate forward-compatible fields from newer MCP clients.

The global Graylog ObjectMapper is retained in McpService only for serializing tool results (domain types like TabularResponse).

Motivation and Context

The MCP Java SDK has a known bug (modelcontextprotocol/java-sdk#766) where capability sub-records like ClientCapabilities.Sampling lack @JsonIgnoreProperties(ignoreUnknown = true). Combined with the global Graylog ObjectMapper being used for protocol deserialization (which adds SnakeCaseStrategy and other custom modules), this caused 500 Internal Server Errors when clients sent forward-compatible fields like capabilities.sampling.tools.

Fixes #25956

How Has This Been Tested?

  • McpServiceTest (15 tests) and McpRestResourceTest (2 tests) pass.
  • Manually verified against a running Graylog instance that {"capabilities":{"sampling":{"tools":{}}}} no longer causes a 500.

Screenshots (if appropriate):

N/A

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.

monrax added 8 commits May 13, 2026 15:35
The global Graylog ObjectMapper (with SnakeCaseStrategy and custom
modules) was being used to deserialize MCP SDK protocol types, which
bypassed the SDK's @JsonIgnoreProperties(ignoreUnknown = true)
annotations. This caused 500 errors when clients sent forward-compatible
capability fields like capabilities.sampling.tools.

Introduce a plain ObjectMapper qualified with @McpProtocolObjectMapper
for deserializing McpSchema.* types, while keeping the global mapper for
serializing Graylog domain objects in tool results.

Fixes: #25956

Co-Authored-By: Claude Opus 4.6 <[EMAIL_ADDRESS_REDACTED]>
Drop the @McpProtocolObjectMapper annotation and Guice binding in favor
of directly instantiating a plain ObjectMapper as a field in McpService
and McpRestResource.

Co-Authored-By: Claude Opus 4.6 <[EMAIL_ADDRESS_REDACTED]>
The MCP Java SDK has a known bug (modelcontextprotocol/java-sdk#766)
where capability sub-records like ClientCapabilities.Sampling lack
@JsonIgnoreProperties(ignoreUnknown = true). As a temporary workaround
we explicitly disable FAIL_ON_UNKNOWN_PROPERTIES to tolerate
forward-compatible fields from newer MCP clients.

Co-Authored-By: Claude Opus 4.6 <[EMAIL_ADDRESS_REDACTED]>
…RTIES

Re-introduce the @McpProtocolObjectMapper qualifier annotation and Guice
binding (from the first commit of this PR) instead of inline new
ObjectMapper() instances. This centralizes configuration in
McpServerModule and makes the mapper injectable/testable.

The binding now includes FAIL_ON_UNKNOWN_PROPERTIES=false to handle
forward-compatible fields from newer MCP clients.

Workaround for modelcontextprotocol/java-sdk#766
Remove once the SDK adds @JsonIgnoreProperties(ignoreUnknown=true) to
its capability records.

Co-Authored-By: Claude Opus 4.6 <[EMAIL_ADDRESS_REDACTED]>
Verifies that the protocol ObjectMapper tolerates forward-compatible
fields like capabilities.sampling.tools that the SDK's empty Sampling
record doesn't declare.

Regression test for #25956

Co-Authored-By: Claude Opus 4.6 <[EMAIL_ADDRESS_REDACTED]>
@monrax
monrax requested a review from janheise June 4, 2026 14:08
* Workaround for <a href="https://github.com/modelcontextprotocol/java-sdk/issues/766">java-sdk#766</a>.
* Remove once the SDK adds @JsonIgnoreProperties(ignoreUnknown=true) to its capability records.
*/
@BindingAnnotation

@janheise janheise Jun 5, 2026 •

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.

according to https://github.com/google/guice/wiki/BindingAnnotations, @BindingAnnotation is "old" and @Qualifier the current way to tag the interface

@janheise
janheise self-requested a review June 5, 2026 12:04

@janheise janheise 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.

LGTM, just one small comment

@monrax
monrax merged commit 767c512 into master Jun 9, 2026
22 checks passed
@monrax
monrax deleted the fix/mcp-object-mapper branch June 9, 2026 19:21
monrax added a commit that referenced this pull request Jul 2, 2026
* Use MCP SDK's own JSON mapper for protocol (de)serialization

mcp-sdk 2.0.0 adds @JsonIgnoreProperties(ignoreUnknown=true) to every
McpSchema wire record (including the ClientCapabilities sub-records), which
fixes java-sdk#766 -- the bug that forced the FAIL_ON_UNKNOWN_PROPERTIES=false
workaround introduced in #26008.

Drop that workaround and the bespoke @McpProtocolObjectMapper qualifier, and
inject the SDK's own McpJsonMapper (JacksonMcpJsonMapperSupplier) instead. The
distinct McpJsonMapper type disambiguates it from the global Graylog
ObjectMapper -- whose SnakeCaseStrategy and custom modules must not touch
protocol messages -- so no qualifier is needed. The four protocol convertValue
sites target plain (non-generic) records, so the inferred TypeReference is
replaced with the explicit target class.

The bug #2 regression test (testInitializeToleratesUnknownCapabilityFields)
now exercises the SDK mapper directly, proving the workaround is no longer
required.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Migrate deprecated McpSchema constructors to builders

mcp-sdk 2.0.0 deprecates the all-args constructors and no-arg builders of its
McpSchema wire records in favour of required-first builder factories
(builder(req1, req2, ...)). Migrate the deprecated construction sites:

- McpService: Implementation, ListResourcesResult, TextResourceContents,
  ReadResourceResult, ResourceTemplate, ListResourceTemplatesResult,
  ListToolsResult, TextContent (x4), ListPromptsResult.
- Stream/Dashboard/EventDefinition resource providers: Resource, in both the
  list() (deprecated 8-arg ctor) and read() (deprecated no-arg builder() +
  uri()/name() setters) paths.

Behaviour is unchanged; the builders delegate to the same canonical
constructors. Canonical (non-deprecated) constructors are left as-is:
InitializeResult (5-arg), ServerCapabilities and its capability sub-records,
and JSONRPCResponse/JSONRPCError.

The Tool builder (Tool.builder() + inputSchema(JsonSchema)) is intentionally
left for the follow-up inputSchema -> Map refactor, since its non-deprecated
factory requires the input schema as a Map up front.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Model Tool inputSchema as Map instead of deprecated JsonSchema

mcp-sdk 2.0.0 deprecates the McpSchema.JsonSchema record (java-sdk#749) in
favour of modelling a tool's inputSchema as an arbitrary JSON Schema
Map<String, Object>, matching how outputSchema is already handled.

Change Tool's inputSchema field/accessor from McpSchema.JsonSchema to
Map<String, Object>, generated the same way as outputSchema. Since the MCP spec
requires every tool to declare an input schema (the Tool record's inputSchema
is non-null), inputSchema() now returns a non-null Map, defaulting an empty
generated schema to {"type":"object"}.

This lets McpService use the non-deprecated Tool.builder(name, inputSchema)
factory, clearing the last two MCP deprecation warnings in production code
(Tool.builder() no-arg and inputSchema(JsonSchema)). JsonSchema is no longer
referenced anywhere in our code.

Wire change: the emitted inputSchema now includes any top-level JSON Schema
keywords that the old JsonSchema record dropped -- in particular the
"$schema":"https://json-schema.org/draft/2020-12/schema" dialect declaration
our victools generator produces. Previously convertValue(node, JsonSchema.class)
narrowed the document to {type, properties, required, additionalProperties,
$defs, definitions} and the deprecated builder.inputSchema(JsonSchema) helper
rebuilt the map from only those fields, silently stripping $schema. The map
path preserves it, so input schemas now advertise their dialect just like
output schemas already did (outputSchema has always gone through
convertValue(node, Map)). The change is additive and spec-aligned: SEP-1613
treats a missing $schema as 2020-12, which is exactly the dialect now declared.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Validate tool-call arguments against the tool input schema

mcp-sdk 2.0.0 added server-side validation of tool arguments against the
declared inputSchema (java-sdk#873), but that lives in the SDK server runtime,
which we don't use -- we hand-roll JSON-RPC dispatch in McpService. Add the
equivalent ourselves so a tools/call with arguments that don't match the
tool's input schema is rejected with a CallToolResult(isError=true) carrying
the validation message, matching the SDK's behaviour, instead of being
silently coerced by Jackson.

Validation uses the SDK's own (non-deprecated) JsonSchemaValidator
(io.modelcontextprotocol.json.schema; the DefaultJsonSchemaValidator caches
compiled schemas), so arguments are checked against the same 2020-12 dialect
our schemas now declare. The tool's advertised inputSchema (Map) is the schema
used for validation.

Gated behind a new McpConfiguration.enableInputValidation flag, defaulting to
false to mirror the existing enable_output_schema opt-in (and so existing
stored configs, which lack the field, keep current behaviour). When disabled,
McpService behaves exactly as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Pass McpConfiguration into McpService.handle instead of re-reading it

McpService.handle re-fetched McpConfiguration from ClusterConfigService on
every tools/call (to read enableInputValidation), which is an uncached MongoDB
lookup -- and McpRestResource had already read the same document earlier in the
same request to check enableRemoteAccess. That was a redundant per-request DB
round-trip.

Thread the already-read McpConfiguration from McpRestResource into handle() and
use it for the validation gate, so the config is read once per request.
McpService no longer depends on ClusterConfigService. The no-arg-config handle
overload used by tests defaults to McpConfiguration.DEFAULT_VALUES (validation
off), preserving existing behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Parse JSON-RPC envelope in McpService; drop the mapper from McpRestResource

McpRestResource held its own McpJsonMapper purely to turn the request body into
a JSONRPCRequest, duplicating the mapper McpService already owns. It also
hand-rolled envelope inspection (containsAnyRequests) and then blindly
convertValue'd the payload into a JSONRPCRequest -- which would coerce a
notification or response into a half-null request.

Add McpService.parseMessage(JsonNode), which uses the SDK's
McpSchema.deserializeJsonRpcMessage to parse the body into a typed
JSONRPCMessage (request / notification / response). McpRestResource now
delegates parsing to it and dispatches on the type: requests are handled,
notifications and responses get 202 Accepted. This makes McpService the sole
owner of the protocol mapper, so McpRestResource no longer depends on it.

Parsing runs in its own try/catch: an unrecognisable payload returns 400 Bad
Request instead of being silently accepted (as the old containsAnyRequests path
did). This also keeps the request-handling path free of null ids -- a parsed
JSONRPCRequest always carries one -- so we never try to build an error
JSONRPCResponse with a null id, which 2.0.0's constructor rejects.

HTTP behaviour is otherwise preserved (403 remote-access, 202
notification/response, 405 SSE, 200 result, 400 McpError, 500 otherwise).

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Fall back to the entity id for blank-titled resources on read

The resource providers' read() built McpSchema.Resource with the entity title
as the resource name, with no fallback. Since the REST API allows a blank
title, a blank-titled stream/dashboard/event definition made the SDK 2.0.0
Resource constructor throw "name must not be empty", which McpService maps to
RESOURCE_NOT_FOUND -- so a readable entity looked missing.

Apply the same Strings.isNullOrEmpty(title) ? id : title fallback that list()
already uses, so read() and list() agree and blank-titled entities remain
readable.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Drop the now-unused throws IOException from McpRestResource.post

Since envelope parsing moved into its own try/catch, the only checked
IOException (from McpService.parseMessage) is caught locally and never
propagates out of post(). Remove the dead throws clause and its import.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Add resource provider read() tests

The Stream/Dashboard/EventDefinition resource providers had no unit tests, so
the read() name-fallback fix (blank title -> entity id) was only covered live.
Add focused tests per provider for read(): blank title falls back to the id,
a present title is used as-is, and missing/forbidden entities return empty.

ViewDTO and EventDefinitionDto are @autovalue; they're mocked here (the
DoNotMockAutoValue lint is a warning the codebase already tolerates) since we
only need their id/title/description accessors.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Inline the protocol mapper and schema validator in McpService

Both the McpJsonMapper and the JsonSchemaValidator are now used only by
McpService (McpRestResource stopped using the mapper when envelope parsing moved
into McpService). They are stateless, dependency-free SDK helpers constructed
from a no-arg supplier, so binding them in Guice and injecting them was DI
ceremony with no payoff -- no shared instance, no alternative implementation, and
the test seam went unused (the validation tests use the real validator).

Construct them as private final fields in McpService and drop the two
McpServerModule bindings and the two constructor parameters. Behaviour is
unchanged; McpServiceTest no longer has to supply them.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Add UI switch for enable_input_validation option

* Drop the test-only 3-arg McpService.handle overload

The 3-arg handle(permissionHelper, request, sessionId) overload existed only as
test convenience -- the sole production caller (McpRestResource) uses the full
handle(..., protocolVersion, config) signature. Remove it so the production API
is the single real method, and move the convenience into McpServiceTest as a
private handle(request) helper that supplies the test defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
monrax added a commit that referenced this pull request Jul 7, 2026
* Fit the MCP server to mcp-sdk 2.0.0 (#26526)

* Use MCP SDK's own JSON mapper for protocol (de)serialization

mcp-sdk 2.0.0 adds @JsonIgnoreProperties(ignoreUnknown=true) to every
McpSchema wire record (including the ClientCapabilities sub-records), which
fixes java-sdk#766 -- the bug that forced the FAIL_ON_UNKNOWN_PROPERTIES=false
workaround introduced in #26008.

Drop that workaround and the bespoke @McpProtocolObjectMapper qualifier, and
inject the SDK's own McpJsonMapper (JacksonMcpJsonMapperSupplier) instead. The
distinct McpJsonMapper type disambiguates it from the global Graylog
ObjectMapper -- whose SnakeCaseStrategy and custom modules must not touch
protocol messages -- so no qualifier is needed. The four protocol convertValue
sites target plain (non-generic) records, so the inferred TypeReference is
replaced with the explicit target class.

The bug #2 regression test (testInitializeToleratesUnknownCapabilityFields)
now exercises the SDK mapper directly, proving the workaround is no longer
required.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Migrate deprecated McpSchema constructors to builders

mcp-sdk 2.0.0 deprecates the all-args constructors and no-arg builders of its
McpSchema wire records in favour of required-first builder factories
(builder(req1, req2, ...)). Migrate the deprecated construction sites:

- McpService: Implementation, ListResourcesResult, TextResourceContents,
  ReadResourceResult, ResourceTemplate, ListResourceTemplatesResult,
  ListToolsResult, TextContent (x4), ListPromptsResult.
- Stream/Dashboard/EventDefinition resource providers: Resource, in both the
  list() (deprecated 8-arg ctor) and read() (deprecated no-arg builder() +
  uri()/name() setters) paths.

Behaviour is unchanged; the builders delegate to the same canonical
constructors. Canonical (non-deprecated) constructors are left as-is:
InitializeResult (5-arg), ServerCapabilities and its capability sub-records,
and JSONRPCResponse/JSONRPCError.

The Tool builder (Tool.builder() + inputSchema(JsonSchema)) is intentionally
left for the follow-up inputSchema -> Map refactor, since its non-deprecated
factory requires the input schema as a Map up front.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Model Tool inputSchema as Map instead of deprecated JsonSchema

mcp-sdk 2.0.0 deprecates the McpSchema.JsonSchema record (java-sdk#749) in
favour of modelling a tool's inputSchema as an arbitrary JSON Schema
Map<String, Object>, matching how outputSchema is already handled.

Change Tool's inputSchema field/accessor from McpSchema.JsonSchema to
Map<String, Object>, generated the same way as outputSchema. Since the MCP spec
requires every tool to declare an input schema (the Tool record's inputSchema
is non-null), inputSchema() now returns a non-null Map, defaulting an empty
generated schema to {"type":"object"}.

This lets McpService use the non-deprecated Tool.builder(name, inputSchema)
factory, clearing the last two MCP deprecation warnings in production code
(Tool.builder() no-arg and inputSchema(JsonSchema)). JsonSchema is no longer
referenced anywhere in our code.

Wire change: the emitted inputSchema now includes any top-level JSON Schema
keywords that the old JsonSchema record dropped -- in particular the
"$schema":"https://json-schema.org/draft/2020-12/schema" dialect declaration
our victools generator produces. Previously convertValue(node, JsonSchema.class)
narrowed the document to {type, properties, required, additionalProperties,
$defs, definitions} and the deprecated builder.inputSchema(JsonSchema) helper
rebuilt the map from only those fields, silently stripping $schema. The map
path preserves it, so input schemas now advertise their dialect just like
output schemas already did (outputSchema has always gone through
convertValue(node, Map)). The change is additive and spec-aligned: SEP-1613
treats a missing $schema as 2020-12, which is exactly the dialect now declared.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Validate tool-call arguments against the tool input schema

mcp-sdk 2.0.0 added server-side validation of tool arguments against the
declared inputSchema (java-sdk#873), but that lives in the SDK server runtime,
which we don't use -- we hand-roll JSON-RPC dispatch in McpService. Add the
equivalent ourselves so a tools/call with arguments that don't match the
tool's input schema is rejected with a CallToolResult(isError=true) carrying
the validation message, matching the SDK's behaviour, instead of being
silently coerced by Jackson.

Validation uses the SDK's own (non-deprecated) JsonSchemaValidator
(io.modelcontextprotocol.json.schema; the DefaultJsonSchemaValidator caches
compiled schemas), so arguments are checked against the same 2020-12 dialect
our schemas now declare. The tool's advertised inputSchema (Map) is the schema
used for validation.

Gated behind a new McpConfiguration.enableInputValidation flag, defaulting to
false to mirror the existing enable_output_schema opt-in (and so existing
stored configs, which lack the field, keep current behaviour). When disabled,
McpService behaves exactly as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Pass McpConfiguration into McpService.handle instead of re-reading it

McpService.handle re-fetched McpConfiguration from ClusterConfigService on
every tools/call (to read enableInputValidation), which is an uncached MongoDB
lookup -- and McpRestResource had already read the same document earlier in the
same request to check enableRemoteAccess. That was a redundant per-request DB
round-trip.

Thread the already-read McpConfiguration from McpRestResource into handle() and
use it for the validation gate, so the config is read once per request.
McpService no longer depends on ClusterConfigService. The no-arg-config handle
overload used by tests defaults to McpConfiguration.DEFAULT_VALUES (validation
off), preserving existing behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Parse JSON-RPC envelope in McpService; drop the mapper from McpRestResource

McpRestResource held its own McpJsonMapper purely to turn the request body into
a JSONRPCRequest, duplicating the mapper McpService already owns. It also
hand-rolled envelope inspection (containsAnyRequests) and then blindly
convertValue'd the payload into a JSONRPCRequest -- which would coerce a
notification or response into a half-null request.

Add McpService.parseMessage(JsonNode), which uses the SDK's
McpSchema.deserializeJsonRpcMessage to parse the body into a typed
JSONRPCMessage (request / notification / response). McpRestResource now
delegates parsing to it and dispatches on the type: requests are handled,
notifications and responses get 202 Accepted. This makes McpService the sole
owner of the protocol mapper, so McpRestResource no longer depends on it.

Parsing runs in its own try/catch: an unrecognisable payload returns 400 Bad
Request instead of being silently accepted (as the old containsAnyRequests path
did). This also keeps the request-handling path free of null ids -- a parsed
JSONRPCRequest always carries one -- so we never try to build an error
JSONRPCResponse with a null id, which 2.0.0's constructor rejects.

HTTP behaviour is otherwise preserved (403 remote-access, 202
notification/response, 405 SSE, 200 result, 400 McpError, 500 otherwise).

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Fall back to the entity id for blank-titled resources on read

The resource providers' read() built McpSchema.Resource with the entity title
as the resource name, with no fallback. Since the REST API allows a blank
title, a blank-titled stream/dashboard/event definition made the SDK 2.0.0
Resource constructor throw "name must not be empty", which McpService maps to
RESOURCE_NOT_FOUND -- so a readable entity looked missing.

Apply the same Strings.isNullOrEmpty(title) ? id : title fallback that list()
already uses, so read() and list() agree and blank-titled entities remain
readable.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Drop the now-unused throws IOException from McpRestResource.post

Since envelope parsing moved into its own try/catch, the only checked
IOException (from McpService.parseMessage) is caught locally and never
propagates out of post(). Remove the dead throws clause and its import.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Add resource provider read() tests

The Stream/Dashboard/EventDefinition resource providers had no unit tests, so
the read() name-fallback fix (blank title -> entity id) was only covered live.
Add focused tests per provider for read(): blank title falls back to the id,
a present title is used as-is, and missing/forbidden entities return empty.

ViewDTO and EventDefinitionDto are @autovalue; they're mocked here (the
DoNotMockAutoValue lint is a warning the codebase already tolerates) since we
only need their id/title/description accessors.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Inline the protocol mapper and schema validator in McpService

Both the McpJsonMapper and the JsonSchemaValidator are now used only by
McpService (McpRestResource stopped using the mapper when envelope parsing moved
into McpService). They are stateless, dependency-free SDK helpers constructed
from a no-arg supplier, so binding them in Guice and injecting them was DI
ceremony with no payoff -- no shared instance, no alternative implementation, and
the test seam went unused (the validation tests use the real validator).

Construct them as private final fields in McpService and drop the two
McpServerModule bindings and the two constructor parameters. Behaviour is
unchanged; McpServiceTest no longer has to supply them.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>

* Add UI switch for enable_input_validation option

* Drop the test-only 3-arg McpService.handle overload

The 3-arg handle(permissionHelper, request, sessionId) overload existed only as
test convenience -- the sole production caller (McpRestResource) uses the full
handle(..., protocolVersion, config) signature. Remove it so the production API
is the single real method, and move the convenience into McpServiceTest as a
private handle(request) helper that supplies the test defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
(cherry picked from commit 1757bc5)
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.

MCP Server Rejects Valid Clients — No Version Negotiation, Unrecognized Capability Crashes Server

2 participants