MCP embeds JSON Schema documents in three places — Tool.inputSchema,
Tool.outputSchema, and ElicitRequest.requestedSchema — and SEP-1613
mandates that these documents conform to JSON Schema 2020-12 by default.
Servers now reject malformed schemas at both build time (McpServer.build())
and runtime (McpAsyncServer/McpStatelessAsyncServer.addTool()), returning
an IllegalArgumentException that identifies the offending field and
references SEP-1613. Elicitation requests whose requestedSchema violates
the meta-schema are rejected before being sent to the client.
Schemas that explicitly declare a different dialect via $schema are accepted
without meta-schema validation, since 2020-12 is the default, not the only
permitted dialect.
Refine meta-schema loading and deprecate orphan JsonSchemaValidator
Resolves#700
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
Instead of fully breaking the API, this is a step
which depreactes the existing non-argument builder.
It allows the users to gradually migrate while preserving
the necessary checks.
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
Co-authored-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
Every wire-serialized record in McpSchema now validates spec-required
fields at construction time. Wire deserialization is intentionally
lenient: a missing required field is replaced with a documented default
and a WARN is logged, instead of failing the parse. A required-first
builder convention is introduced across the schema so it is no longer
possible to obtain a builder that is missing required state.
Required-field guards
---------------------
Every wire record's compact constructor asserts non-null (and non-empty
for String identifiers like name, uri, uriTemplate, version) on its
spec-required components. Passing null now throws
IllegalArgumentException at construction time instead of silently
producing invalid JSON via @JsonInclude(NON_ABSENT). This applies to
the JSON-RPC envelopes, lifecycle types, resource/prompt/tool requests
and results, sampling and elicitation, content records, root, complete,
logging and progress notifications, and the two CompleteReference
implementations. See MIGRATION-2.0.md for the full list.
Lenient wire deserialization
----------------------------
For each of those records (except JSONRPCResponse.JSONRPCError, which
still fails fast) a @JsonCreator static `fromJson` factory substitutes
a documented default for any absent required field — "" for strings,
[] for collections, {} for maps, 0 / 0.0 for numerics, INFO for
LoggingLevel, USER for SamplingMessage.role, ASSISTANT for
CreateMessageResult.role, CANCEL for ElicitResult.action, {values=[]}
for CompleteResult.completion — and logs a WARN naming the field and
the value used. Application code can still observe a malformed message
and react, but the SDK no longer halts the conversation.
Builder convention
------------------
Records that have a builder gain a required-first factory method
`builder(req1, req2, …)`; setters for required fields are removed from
the builder so it cannot be left in an invalid state. Existing no-arg
`builder()` factories and required-field setters are kept where source
compatibility demands it but are marked @Deprecated. New builders are
also added for several records that previously had none (ProgressNotification,
JSONRPCError, CompleteRequest, list/result types, content records, ...).
JSON-RPC envelope ergonomics
----------------------------
Previously every JSON-RPC envelope had to be constructed via the
canonical record constructor and the literal "2.0" string had to be
threaded through every call site, e.g.
new JSONRPCRequest("2.0", "tools/call", id, params)
new JSONRPCResponse("2.0", id, result, null)
new JSONRPCResponse("2.0", id, null, new JSONRPCError(code, message, null))
Now:
new JSONRPCRequest("tools/call", id, params) // jsonrpc defaulted
new JSONRPCNotification("notifications/initialized") // params optional
JSONRPCResponse.result(id, result) // factory
JSONRPCResponse.error(id, new JSONRPCError(code, message)) // 2-arg error
JSONRPCResponse's compact constructor additionally enforces the
JSON-RPC invariant that exactly one of `result` / `error` is set —
previously the SDK could build envelopes that violated the protocol.
CompleteReference changes
-------------------------
- PromptReference.equals/hashCode now key on `name` only (previously
derived from identifier()+type()). Two refs with the same name but
different titles now compare equal — code using PromptReference as a
map/set key should be audited.
- PromptReference's compact constructor pins `type` to "ref/prompt"
and logs a WARN if the caller supplies a different non-null value.
The legacy two-arg `PromptReference(String type, String name)`
constructor remains @Deprecated.
- ResourceReference's record components are reduced from (type, uri)
to (uri) — positional construction breaks. The legacy
`ResourceReference(String type, String uri)` constructor stays
@Deprecated and ignores `type`.
- CompleteReference.identifier() is @Deprecated and now returns null
via a default method on the interface.
Tests / refactor
----------------
Conformance harness, integration tests, sample apps, and internal
callers were migrated to the new builder factories and convenience
constructors. No semantic changes to client/server runtime behaviour
beyond the McpSchema changes above.
Docs
----
- MIGRATION-2.0.md: required-field section now covers the broader
record set and the lenient-deserialization behaviour; PromptReference
WARN behaviour and ResourceReference component reduction are
documented; the JSON-RPC envelope section is rewritten to compare the
pre-2.0 surface with the new one.
- CONTRIBUTING.md: the "Evolving wire-serialized records" recipe is
split into two cases — adding a new optional field (existing rules)
and adding/maintaining a spec-required field (new rules covering
Assert in compact constructor, @JsonCreator fromJson with defaults
and WARN, required-first builder factory).
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
Recent changes don't coerce null completion arguments to empty lists so we have
to check for null when handling prompt completions.
Resolves#932
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
The MCP specification evolves continuously; domain types must absorb
new fields and subtypes without breaking existing clients or servers.
On the 1.x line this is structurally prevented by sealed interfaces,
which make it impossible to add a permitted subtype without breaking
exhaustive pattern-match switch expressions in caller code. This
commit opens the 2.0 release line, where those constraints are lifted
and serialization is made self-contained — independent of any global
ObjectMapper configuration.
Breaking changes for users migrating from 1.x
- Sealed interfaces removed from JSONRPCMessage, Request, Result,
Notification, ResourceContents, CompleteReference and Content.
Exhaustive switch expressions over these types must add a default
branch.
- Prompt(name, description, null) no longer silently coerces null
arguments to an empty list. Use Prompt.withDefaults() to preserve
the previous behaviour.
- CompleteCompletion.total and .hasMore are now absent from the wire
when not set, rather than being emitted as null.
- ServerParameters no longer carries Jackson annotations; it is an
internal configuration class, not a wire type.
What now works that did not before
- CompleteReference polymorphic dispatch (PromptReference vs
ResourceReference) works through a plain readValue or convertValue
call — no hand-rolled map inspection required.
- LoggingLevel deserialization is lenient: unknown level strings
produce null instead of throwing.
- All domain records now tolerate unknown JSON fields, so a client
built against an older SDK version will not fail when a newer server
sends fields it does not yet recognise.
- Null optional fields are consistently absent from serialized output
regardless of ObjectMapper configuration.
Documentation
- CONTRIBUTING adds an "Evolving wire-serialized records" section: a
9-rule recipe and example for adding a field safely.
- MIGRATION-2.0 documents all breaking changes listed above.
Follow-up coming next
Several spec-required fields (e.g. JSONRPCError.code/message,
ProgressNotification.progress, CreateMessageRequest.maxTokens,
CallToolResult.content) are stored as nullable Java types without a
null guard. If constructed with null, the NON_ABSENT rule silently
omits them, producing invalid wire JSON without throwing. Fix: compact
canonical constructors with Assert.notNull, following the pattern
already in JSONRPCRequest.
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
Both HttpClientSseClientTransport and HttpClientStreamableHttpTransport
set Content-Type to 'application/json' without specifying the charset.
While Java's BodyPublishers.ofString() uses UTF-8 by default, the
missing charset in the header can cause the server to interpret the
request body using a different encoding (e.g., ISO-8859-1), corrupting
non-ASCII characters such as Chinese tool names.
Explicitly set Content-Type to 'application/json; charset=utf-8' in
POST requests on both client transports.
Fixes#260
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Daniel Garnier-Moiroux <git@garnier.wf>
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Deprecate Builder.customizeRequest() in HttpClientSseClientTransport and HttpClientStreamableHttpTransport
customizeRequest() executes its consumer once at build time, freezing
headers into the shared requestBuilder. This silently breaks OAuth
token refresh scenarios where the Authorization header needs to be
updated after the transport is built.
Add @Deprecated and update Javadoc to clarify the build-time-only
semantics and guide users toward httpRequestCustomizer() or
asyncHttpRequestCustomizer() which run on every request.
Closes#788
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
Clients can now subscribe to specific resources and receive targeted
notifications when those resources change. Previously, calling
`notifyResourceUpdated` on the server would broadcast the notification
to every connected client regardless of interest — now only sessions
that have explicitly subscribed to a given resource URI receive the
update, making resource change propagation both correct and efficient.
The subscription lifecycle is fully handled: clients can subscribe and
unsubscribe at any time, and the server cleans up subscription state
when a session closes.
Supersedes #838.
Resolves#837, #776.
---------
Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
Update project version across all POM files, documentation, and CI
workflow to prepare for the 1.0.x release
Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
As a follow-up of #779, this commit refines McpJsonDefaults
method names by shortening them to not repeat the qualifiers
already expressed at type level.
- The integration tests pass with both Jackson 2 and Jackson 3.
- Modified McpSchemaTests.testContentDeserializationWrongType to work
with both Jackson 2 and Jackson 3.
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This change adds Jackson 3 support and aligns package naming in the existing Jackson 2 module.
Specific changes:
* Deprecated classes for removal in `io.modelcontextprotocol.json.jackson`/`io.modelcontextprotocol.json.schema.jackson`
* Copied the above to `io.modelcontextprotocol.json.jackson2`/`io.modelcontextprotocol.json.schema.jackson2` in non-deprecated form.
* Added tests for the default McpJsonMapper and JsonSchemaValidator
* Pin `npx @modelcontextprotocol/server-everything` version to `2025.12.18`.
* Replace `tzolov/mcp-everything-server` Docker image with `node:lts-alpine` + `npx` command.
* Handle HTTP 202 special case.
* Fix test assertions.
The recent rollout of everything-server broke integration tests which take the latest version from the node registry.
This PR unifies the everything-server usage - the Testcontainers Docker setup uses the same version as the STDIO npx-based tests and no longer relies on tzolov/mcp-everything-server.
Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
Update the ClientCapabilities.Elicitation record to accept optional "form"
and "url" fields as defined in the MCP 2025-11-25 specification.
Previously, deserializing an InitializeRequest with
`{"capabilities":{"elicitation":{"form":{}}}}` would fail with
UnrecognizedPropertyException because the Elicitation record was empty.
Changes:
- Add nested Form and Url marker records to Elicitation
- Add no-arg constructor for backward compatibility (serializes to {})
- Add elicitation(boolean form, boolean url) builder method
- Add comprehensive tests for deserialization and serialization
Fixes#724
- We do not support full version negotiation, but this commit allows the
client to the send the correct version to match that of the server.
- There is one limitation: the GET /mcp request happens too early,
before we have deserialized the initialization response, so we do not
know the negotiated version yet. This request will have the client
latest version as Mcp-Protocol-Version value.
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>