Skip to content

fix(core): deserialize responses as requested result type#717

Open
stationeros wants to merge 1 commit intomodelcontextprotocol:mainfrom
stationeros:main
Open

fix(core): deserialize responses as requested result type#717
stationeros wants to merge 1 commit intomodelcontextprotocol:mainfrom
stationeros:main

Conversation

@stationeros
Copy link
Copy Markdown

@stationeros stationeros commented Apr 24, 2026

This change fixes unsafe response handling in Protocol.request() by deserializing the JSON-RPC result into the requested RequestResult type instead of relying on a direct runtime cast. It also adds a regression test covering the case where the response payload is JSON-compatible with the expected result type but is not the same runtime subtype.

Motivation and Context

Protocol.request() currently completes requests with an unchecked cast from response.result to T. That is brittle for responses that are deserialized through a broader/result-polymorphic path, including the task-result scenario described in #601.

This change makes the request path decode the response payload as the requested type before completing the deferred result. That avoids ClassCastException-style failures when the wire payload matches the expected schema but the in-memory runtime subtype does not.

Fixes #601.

How Has This Been Tested?

Tested at the protocol/unit-test level.

Added/updated ProtocolTest coverage for:

  • preserving existing _meta when adding a progress token
  • creating _meta when none exists
  • leaving _meta unchanged when onProgress is absent
  • handling notification exceptions correctly
  • deserializing a response into the requested result type instead of casting the runtime subtype directly

I have not tested this in a real application yet.

Breaking Changes

Potentially yes.

This patch changes Protocol.request() to an inline/reified generic function in order to decode the response as the requested type. Existing Kotlin call sites should continue to look the same, but this is still a public API signature change and may require downstream consumers to recompile. It may also affect non-Kotlin/Java interop expectations.

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

Additional context

The response path now decodes response.result into the requested T before completing the request, rather than completing with response.result as T.

The regression test intentionally delivers a JSONRPCResponse containing a different RequestResult runtime subtype with the same JSON shape. Before this change, that fails because of the unsafe cast. After this change, the payload is decoded into the requested type and succeeds.

If we to avoid the public API signature change, the alternative would be to preserve the existing request() signature and thread an explicit serializer/decode strategy through the internal request/response pipeline instead.

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.

type-unsafe cast in Protocol result deserialization

1 participant