feat!: enforce required MCP spec fields in McpSchema; lenient wire deserialization (#928)
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>
This commit is contained in:
parent
599b43cac4
commit
22a0efa577
64 changed files with 4258 additions and 1688 deletions
|
|
@ -77,22 +77,35 @@ git checkout -b feature/your-feature-name
|
|||
|
||||
## Evolving wire-serialized records
|
||||
|
||||
Records in `McpSchema` are serialized directly to the MCP JSON wire format. Follow these rules whenever you add a field to an existing record to keep the protocol forward- and backward-compatible.
|
||||
Records in `McpSchema` are serialized directly to the MCP JSON wire format. The rules differ depending on whether the field you are adding (or maintaining) is *optional* — Java code may legitimately leave it `null` and the wire may omit it — or *spec-required* by MCP. Follow **Case A** for optional fields and **Case B** for spec-required fields.
|
||||
|
||||
### Rules
|
||||
### Case A — Optional fields
|
||||
|
||||
1. **Add new components only at the end** of the record's component list. Never reorder or rename existing components.
|
||||
2. **Annotate every component** with `@JsonProperty("fieldName")` even when the Java name already matches. This survives local renames via refactoring tools.
|
||||
3. **Use boxed types** (`Boolean`, `Integer`, `Long`, `Double`) so the field can be absent on the wire without a special sentinel.
|
||||
4. **Default to `null`**, not an empty collection or neutral value, so the `@JsonInclude(NON_NULL)` rule omits the field for clients that don't know about it yet.
|
||||
4. **Default to `null`**, not an empty collection or neutral value, so the `@JsonInclude(NON_ABSENT)` rule omits the field for clients that don't know about it yet.
|
||||
5. **Keep existing constructors as source-compatible overloads** that delegate to the new canonical constructor and pass `null` for the new component. Do not remove them in the same release that adds the field.
|
||||
6. **Do not put `@JsonCreator` on the canonical constructor** unless strictly necessary. Jackson auto-detects record canonical constructors; adding `@JsonCreator` pins deserialization to that exact parameter order forever.
|
||||
7. **Do not convert `null` to a default value in the canonical constructor.** Null carries "absent" semantics and must be preserved through the serialization round-trip.
|
||||
6. **Do not put `@JsonCreator` on the canonical constructor** unless strictly necessary. Jackson auto-detects record canonical constructors; adding `@JsonCreator` pins deserialization to that exact parameter order forever. *(For records that also have spec-required fields, the `@JsonCreator` belongs on a separate static `fromJson` factory — see Case B, Rule 2.)*
|
||||
7. **Do not convert `null` to a default value in the canonical constructor.** Null carries "absent" semantics and must be preserved through the serialization round-trip. *(Spec-required fields are the exception — see Case B, Rule 1.)*
|
||||
8. **Add three tests per new field** (put them in the relevant test class in `mcp-test`):
|
||||
- Deserialize JSON *without* the field → succeeds, field is `null`.
|
||||
- Serialize an instance with the field unset (`null`) → the key is absent from output.
|
||||
- Deserialize JSON with an extra *unknown* field → succeeds.
|
||||
9. **An inner `Builder` subclass can be used.** This improves the developer experience since frequently not all fields are required.
|
||||
9. **An inner `Builder` subclass can be used.** This improves the developer experience since frequently not all fields are required.
|
||||
|
||||
### Case B — Spec-required fields
|
||||
|
||||
When the MCP specification marks a field as required, callers must not be able to construct a structurally invalid record, but the wire parser must still tolerate peers that fail to send it. Follow these rules in addition to the relevant Case A rules (annotation, naming, append-only).
|
||||
|
||||
1. **Reject `null` in the compact constructor.** Use `Assert.notNull` for required objects or `Assert.hasText` for required `String` identifiers (`name`, `uri`, `uriTemplate`, `version`). This throws `IllegalArgumentException` at construction time instead of producing a record that fails later in serialization or protocol handling. Overrides Case A Rule 7 for this field.
|
||||
2. **Add a `@JsonCreator` static `fromJson` factory** alongside the canonical constructor. When a required field is absent from the wire, substitute a documented safe default (`""` for strings, `[]` for collections, `{}` for maps, `0` / `0.0` for numerics, `INFO` for `LoggingLevel`, etc.) and log at `WARN` naming the field and the value used. The SDK must not halt the conversation because of a missing field. Place `@JsonCreator` on this `fromJson` factory, never on the canonical constructor (Case A Rule 6 still applies to the canonical constructor itself).
|
||||
- Exception: `JSONRPCResponse.JSONRPCError` fails fast on missing `code` / `message` because a malformed JSON-RPC error envelope is unrecoverable.
|
||||
3. **Provide a required-first builder factory** `builder(req1, req2, …)` and remove the corresponding setters from the `Builder`. A no-arg `builder()` factory must not exist on a record that has required fields. If one already exists for source compatibility, mark it `@Deprecated`.
|
||||
4. **Add tests per required field**:
|
||||
- Constructing the record with `null` for the field throws `IllegalArgumentException`.
|
||||
- Deserializing JSON *without* the field succeeds and yields the documented default.
|
||||
- Deserializing JSON with an extra *unknown* field still succeeds (Case A Rule 8 also applies).
|
||||
|
||||
### Example
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,97 @@ The `Tool` record now models `inputSchema` (and `outputSchema`) as arbitrary JSO
|
|||
- Java code that used `Tool.inputSchema()` as a `JsonSchema` must switch to `Map<String, Object>` (or copy into your own schema wrapper).
|
||||
- `Tool.Builder.inputSchema(JsonSchema)` remains as a **deprecated** helper that maps the old record into a map; prefer `inputSchema(Map)` or `inputSchema(McpJsonMapper, String)`.
|
||||
|
||||
### Required MCP spec fields are enforced at construction time
|
||||
|
||||
Every wire record in `McpSchema` whose fields are marked required by the MCP spec now asserts non-null (and non-empty for `String` identifiers like `name`, `uri`, `uriTemplate`, `version`) in its compact constructor. Passing `null` throws `IllegalArgumentException` immediately, instead of producing a structurally invalid object that fails later in serialization or protocol handling.
|
||||
|
||||
This applies to (non-exhaustive):
|
||||
|
||||
- JSON-RPC envelopes: `JSONRPCRequest`, `JSONRPCNotification`, `JSONRPCResponse`, `JSONRPCResponse.JSONRPCError`
|
||||
- Lifecycle: `InitializeRequest`, `InitializeResult`, `Implementation`
|
||||
- Resources: `Resource`, `ResourceTemplate`, `ListResourcesResult`, `ListResourceTemplatesResult`, `ReadResourceRequest`, `ReadResourceResult`, `SubscribeRequest`, `UnsubscribeRequest`, `ResourcesUpdatedNotification`, `TextResourceContents`, `BlobResourceContents`
|
||||
- Prompts: `Prompt`, `PromptArgument`, `PromptMessage`, `ListPromptsResult`, `GetPromptRequest`, `GetPromptResult`
|
||||
- Tools: `Tool`, `ListToolsResult`, `CallToolRequest`, `CallToolResult`
|
||||
- Sampling / elicitation: `SamplingMessage`, `CreateMessageRequest`, `CreateMessageResult`, `ElicitRequest`, `ElicitResult`
|
||||
- Misc: `ProgressNotification`, `SetLevelRequest`, `LoggingMessageNotification`, `CompleteRequest`, `CompleteResult`, `CompleteRequest.CompleteArgument`, content records (`TextContent`, `ImageContent`, `AudioContent`, `EmbeddedResource`), `Root`, `ListRootsResult`, `PromptReference`, `ResourceReference`
|
||||
|
||||
**Action:** Audit any code that constructs these records with potentially-null values and provide valid, non-null arguments.
|
||||
|
||||
**Wire deserialization is lenient.** Records expose a `@JsonCreator fromJson` factory that substitutes safe defaults (e.g. `[]`, `""`, `0`, `INFO`, `Action.CANCEL`) for any absent required field and logs a `WARN` naming the field and the substituted value. `JSONRPCResponse.JSONRPCError` is excluded — malformed JSON-RPC error envelopes still fail immediately.
|
||||
|
||||
**Note:** `LoggingMessageNotification`/`SetLevelRequest` default a *missing* `level` to `INFO`, but an *unrecognized* level string still deserializes to `null` (see the `LoggingLevel` section above) and will then fail the canonical constructor. Ensure clients and servers send only recognized level strings.
|
||||
|
||||
### `PromptReference` discriminator pinning and equality
|
||||
|
||||
`PromptReference` keeps its `(type, name, title)` record components, so positional construction from 1.x still compiles. Two behavioural changes:
|
||||
|
||||
- The compact constructor pins `type` to `ref/prompt`. Any non-null value other than `ref/prompt` is replaced with `ref/prompt` and a `WARN` is logged. The legacy two-arg `PromptReference(String type, String name)` constructor remains `@Deprecated` and routes through the canonical constructor, so it triggers the same WARN.
|
||||
- `equals`/`hashCode` now consider `name` only (title and type are ignored). Two refs with the same name but different titles compare equal.
|
||||
|
||||
**Action:** Audit any code that used `PromptReference` as a map key or in a `Set` — equality semantics changed. If your code constructed instances with a custom `type` string for testing, switch to `PromptReference.builder(name)` (or `new PromptReference(name)`); the WARN tells you which call sites still pass the discriminator.
|
||||
|
||||
`CompleteReference.identifier()` is `@Deprecated` and now returns `null` via a default method on the interface.
|
||||
|
||||
### `ResourceReference` record component reduced
|
||||
|
||||
Components changed from `(type, uri)` to `(uri)`. Positional construction with two arguments breaks. The legacy `ResourceReference(String type, String uri)` constructor stays `@Deprecated`; it ignores `type` and logs a `WARN`. Use `new ResourceReference(uri)` or `ResourceReference.builder(uri)`. The `type()` accessor still returns `ref/resource` and Jackson serializes it via `@JsonProperty("type")` on the accessor.
|
||||
|
||||
### Builder API: required-first factories; old setters/no-arg builders deprecated
|
||||
|
||||
Most records that have a builder have gained a required-first factory method (`builder(req1, req2, …)`) and the corresponding setters for required fields are removed from the builder. The old no-arg `builder()` factory and public no-arg `Builder()` constructor are kept but `@Deprecated` where they would allow constructing a builder without required state.
|
||||
|
||||
Examples:
|
||||
|
||||
| Type | Old (deprecated) | New |
|
||||
|------|-----------------|-----|
|
||||
| `Resource` | `Resource.builder().uri(u).name(n)…` | `Resource.builder(uri, name)…` |
|
||||
| `ResourceTemplate` | `ResourceTemplate.builder().uriTemplate(u).name(n)…` | `ResourceTemplate.builder(uriTemplate, name)…` |
|
||||
| `Implementation` | `new Implementation(name, version)` | `Implementation.builder(name, version)…` |
|
||||
| `InitializeRequest` / `InitializeResult` | `… .builder()…` | `… .builder(protocolVersion, capabilities, clientInfo/serverInfo)` |
|
||||
| `Tool` | `Tool.builder().name(n)…` | `Tool.builder(name)…` |
|
||||
| `Prompt` / `PromptArgument` / `GetPromptRequest` | `… .builder().name(n)…` | `… .builder(name)…` |
|
||||
| `PromptMessage` / `SamplingMessage` | `… .builder().role(r).content(c)…` | `… .builder(role, content)…` |
|
||||
| `CreateMessageRequest` | `… .builder().messages(m).maxTokens(n)…` | `… .builder(messages, maxTokens)…` |
|
||||
| `ElicitRequest` | `… .builder().message(m).requestedSchema(s)…` | `… .builder(message, requestedSchema)…` |
|
||||
| `LoggingMessageNotification` | `… .builder().level(l).data(d)…` | `… .builder(level, data)…` |
|
||||
| `ListResourcesResult` / `ListResourceTemplatesResult` / `ListPromptsResult` / `ListToolsResult` / `ListRootsResult` | `… .builder()…` | `… .builder(items)…` |
|
||||
| `ReadResourceRequest` / `SubscribeRequest` / `UnsubscribeRequest` / `ResourcesUpdatedNotification` / `Root` | n/a | `… .builder(uri)…` |
|
||||
| `ReadResourceResult` | n/a | `ReadResourceResult.builder(contents)…` |
|
||||
| `TextResourceContents` / `BlobResourceContents` | n/a | `… .builder(uri, text|blob)…` |
|
||||
| `TextContent` / `ImageContent` / `AudioContent` / `EmbeddedResource` | n/a | `… .builder(text \| data, mimeType \| resource)…` |
|
||||
| `CallToolResult` | unchanged | also: required-first content set via builder constructor remains optional |
|
||||
| `ProgressNotification` | n/a | `ProgressNotification.builder(progressToken, progress)` |
|
||||
| `JSONRPCResponse.JSONRPCError` | n/a | `JSONRPCError.builder(code, message)` |
|
||||
| `CompleteRequest` | n/a | `CompleteRequest.builder(ref, argument)` |
|
||||
| `Annotations` | n/a | `Annotations.builder()` |
|
||||
| Capabilities (`Sampling`, `Elicitation`, `Roots`, `LoggingCapabilities`, `CompletionCapabilities`, prompt/resource/tool capabilities) | n/a | `… .builder()…` |
|
||||
|
||||
### JSON-RPC envelope ergonomics
|
||||
|
||||
In 1.x, every envelope was constructed via the canonical record constructor and the literal `"2.0"` `jsonrpc` string had to be threaded through every call site:
|
||||
|
||||
```java
|
||||
new JSONRPCRequest("2.0", "tools/call", id, params);
|
||||
new JSONRPCNotification("2.0", "notifications/initialized", null);
|
||||
new JSONRPCResponse("2.0", id, result, null);
|
||||
new JSONRPCResponse("2.0", id, null, new JSONRPCError(code, message, null));
|
||||
```
|
||||
|
||||
2.0 adds defaulting constructors and static factories so the `"2.0"` constant and the unused `result`/`error` slot disappear from caller code:
|
||||
|
||||
```java
|
||||
new JSONRPCRequest("tools/call", id); // params optional
|
||||
new JSONRPCRequest("tools/call", id, params);
|
||||
new JSONRPCNotification("notifications/initialized"); // params optional
|
||||
new JSONRPCNotification("notifications/initialized", params);
|
||||
JSONRPCResponse.result(id, result);
|
||||
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.
|
||||
|
||||
The 1.x canonical 4-arg constructors continue to compile.
|
||||
|
||||
### Optional JSON Schema validation on `tools/call` (server)
|
||||
|
||||
When a `JsonSchemaValidator` is available (including the default from `McpJsonDefaults.getSchemaValidator()` when you do not configure one explicitly) and `validateToolInputs` is left at its default of `true`, the server validates incoming tool arguments against `tool.inputSchema()` before invoking the tool. Failed validation produces a `CallToolResult` with `isError` set and a textual error in the content.
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public class ConformanceJdkClientMcpClient {
|
|||
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl).build();
|
||||
|
||||
return McpClient.sync(transport)
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.clientInfo(McpSchema.Implementation.builder("test-client", "1.0.0").build())
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ public class ConformanceJdkClientMcpClient {
|
|||
var capabilities = McpSchema.ClientCapabilities.builder().elicitation().build();
|
||||
|
||||
return McpClient.sync(transport)
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.clientInfo(McpSchema.Implementation.builder("test-client", "1.0.0").build())
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.capabilities(capabilities)
|
||||
.elicitation(request -> {
|
||||
|
|
@ -120,7 +120,7 @@ public class ConformanceJdkClientMcpClient {
|
|||
}
|
||||
|
||||
// Return accept action with the defaults applied
|
||||
return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, content, null);
|
||||
return McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.ACCEPT).content(content).build();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ public class ConformanceJdkClientMcpClient {
|
|||
arguments.put("b", 3);
|
||||
|
||||
McpSchema.CallToolResult result = client
|
||||
.callTool(new McpSchema.CallToolRequest("add_numbers", arguments));
|
||||
.callTool(McpSchema.CallToolRequest.builder("add_numbers").arguments(arguments).build());
|
||||
|
||||
System.out.println("Successfully called add_numbers tool");
|
||||
if (result != null && result.content() != null) {
|
||||
|
|
@ -219,7 +219,9 @@ public class ConformanceJdkClientMcpClient {
|
|||
var arguments = new java.util.HashMap<String, Object>();
|
||||
|
||||
McpSchema.CallToolResult result = client
|
||||
.callTool(new McpSchema.CallToolRequest("test_client_elicitation_defaults", arguments));
|
||||
.callTool(McpSchema.CallToolRequest.builder("test_client_elicitation_defaults")
|
||||
.arguments(arguments)
|
||||
.build());
|
||||
|
||||
System.out.println("Successfully called test_client_elicitation_defaults tool");
|
||||
if (result != null && result.content() != null) {
|
||||
|
|
@ -264,8 +266,8 @@ public class ConformanceJdkClientMcpClient {
|
|||
// reconnection
|
||||
var arguments = new java.util.HashMap<String, Object>();
|
||||
|
||||
McpSchema.CallToolResult result = client
|
||||
.callTool(new McpSchema.CallToolRequest("test_reconnection", arguments));
|
||||
McpSchema.CallToolResult result = client.callTool(
|
||||
McpSchema.CallToolRequest.builder("test_reconnection").arguments(arguments).build());
|
||||
|
||||
System.out.println("Successfully called test_reconnection tool");
|
||||
if (result != null && result.content() != null) {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class DefaultScenario implements Scenario {
|
|||
|
||||
this.client = McpClient.sync(transport)
|
||||
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.clientInfo(McpSchema.Implementation.builder("test-client", "1.0.0").build())
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class PreRegistrationScenario implements Scenario {
|
|||
|
||||
var client = McpClient.sync(transport)
|
||||
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
|
||||
.clientInfo(new McpSchema.Implementation("test-client", "1.0.0"))
|
||||
.clientInfo(McpSchema.Implementation.builder("test-client", "1.0.0").build())
|
||||
.requestTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -144,15 +144,14 @@ public class ConformanceServlet {
|
|||
return List.of(
|
||||
// test_simple_text - Returns simple text content
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_simple_text")
|
||||
.tool(Tool.builder("test_simple_text", EMPTY_JSON_SCHEMA)
|
||||
.description("Returns simple text content for testing")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_simple_text' called");
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("This is a simple text response for testing.")))
|
||||
.content(
|
||||
List.of(TextContent.builder("This is a simple text response for testing.").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -160,15 +159,13 @@ public class ConformanceServlet {
|
|||
|
||||
// test_image_content - Returns image content
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_image_content")
|
||||
.tool(Tool.builder("test_image_content", EMPTY_JSON_SCHEMA)
|
||||
.description("Returns image content for testing")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_image_content' called");
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new ImageContent(null, RED_PIXEL_PNG, "image/png")))
|
||||
.content(List.of(ImageContent.builder(RED_PIXEL_PNG, "image/png").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -176,15 +173,13 @@ public class ConformanceServlet {
|
|||
|
||||
// test_audio_content - Returns audio content
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_audio_content")
|
||||
.tool(Tool.builder("test_audio_content", EMPTY_JSON_SCHEMA)
|
||||
.description("Returns audio content for testing")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_audio_content' called");
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new AudioContent(null, MINIMAL_WAV, "audio/wav")))
|
||||
.content(List.of(AudioContent.builder(MINIMAL_WAV, "audio/wav").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -192,36 +187,35 @@ public class ConformanceServlet {
|
|||
|
||||
// test_embedded_resource - Returns embedded resource content
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_embedded_resource")
|
||||
.tool(Tool.builder("test_embedded_resource", EMPTY_JSON_SCHEMA)
|
||||
.description("Returns embedded resource content for testing")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_embedded_resource' called");
|
||||
TextResourceContents resourceContents = new TextResourceContents("test://embedded-resource",
|
||||
"text/plain", "This is an embedded resource content.");
|
||||
EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
|
||||
TextResourceContents resourceContents = TextResourceContents
|
||||
.builder("test://embedded-resource", "This is an embedded resource content.")
|
||||
.mimeType("text/plain")
|
||||
.build();
|
||||
EmbeddedResource embeddedResource = EmbeddedResource.builder(resourceContents).build();
|
||||
return CallToolResult.builder().content(List.of(embeddedResource)).isError(false).build();
|
||||
})
|
||||
.build(),
|
||||
|
||||
// test_multiple_content_types - Returns multiple content types
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_multiple_content_types")
|
||||
.tool(Tool.builder("test_multiple_content_types", EMPTY_JSON_SCHEMA)
|
||||
.description("Returns multiple content types for testing")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_multiple_content_types' called");
|
||||
TextResourceContents resourceContents = new TextResourceContents(
|
||||
"test://mixed-content-resource", "application/json",
|
||||
"{\"test\":\"data\",\"value\":123}");
|
||||
EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
|
||||
TextResourceContents resourceContents = TextResourceContents
|
||||
.builder("test://mixed-content-resource", "{\"test\":\"data\",\"value\":123}")
|
||||
.mimeType("application/json")
|
||||
.build();
|
||||
EmbeddedResource embeddedResource = EmbeddedResource.builder(resourceContents).build();
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("Multiple content types test:"),
|
||||
new ImageContent(null, RED_PIXEL_PNG, "image/png"), embeddedResource))
|
||||
.content(List.of(TextContent.builder("Multiple content types test:").build(),
|
||||
ImageContent.builder(RED_PIXEL_PNG, "image/png").build(), embeddedResource))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -229,28 +223,22 @@ public class ConformanceServlet {
|
|||
|
||||
// test_tool_with_logging - Tool that sends log messages during execution
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_tool_with_logging")
|
||||
.tool(Tool.builder("test_tool_with_logging", EMPTY_JSON_SCHEMA)
|
||||
.description("Tool that sends log messages during execution")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_tool_with_logging' called");
|
||||
// Send log notifications
|
||||
exchange.loggingNotification(LoggingMessageNotification.builder()
|
||||
.level(LoggingLevel.INFO)
|
||||
.data("Tool execution started")
|
||||
.build());
|
||||
exchange.loggingNotification(LoggingMessageNotification.builder()
|
||||
.level(LoggingLevel.INFO)
|
||||
.data("Tool processing data")
|
||||
.build());
|
||||
exchange.loggingNotification(LoggingMessageNotification.builder()
|
||||
.level(LoggingLevel.INFO)
|
||||
.data("Tool execution completed")
|
||||
.build());
|
||||
exchange.loggingNotification(
|
||||
LoggingMessageNotification.builder(LoggingLevel.INFO, "Tool execution started")
|
||||
.build());
|
||||
exchange.loggingNotification(
|
||||
LoggingMessageNotification.builder(LoggingLevel.INFO, "Tool processing data").build());
|
||||
exchange.loggingNotification(
|
||||
LoggingMessageNotification.builder(LoggingLevel.INFO, "Tool execution completed")
|
||||
.build());
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("Tool execution completed with logging")))
|
||||
.content(List.of(TextContent.builder("Tool execution completed with logging").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -258,15 +246,14 @@ public class ConformanceServlet {
|
|||
|
||||
// test_error_handling - Tool that always returns an error
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_error_handling")
|
||||
.tool(Tool.builder("test_error_handling", EMPTY_JSON_SCHEMA)
|
||||
.description("Tool that returns an error for testing error handling")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_error_handling' called");
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("This tool intentionally returns an error for testing")))
|
||||
.content(List.of(TextContent.builder("This tool intentionally returns an error for testing")
|
||||
.build()))
|
||||
.isError(true)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -274,33 +261,34 @@ public class ConformanceServlet {
|
|||
|
||||
// test_tool_with_progress - Tool that reports progress
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_tool_with_progress")
|
||||
.tool(Tool.builder("test_tool_with_progress", EMPTY_JSON_SCHEMA)
|
||||
.description("Tool that reports progress notifications")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_tool_with_progress' called");
|
||||
Object progressToken = request.meta().get("progressToken");
|
||||
if (progressToken != null) {
|
||||
// Send progress notifications sequentially
|
||||
exchange.progressNotification(new ProgressNotification(progressToken, 0.0, 100.0, null));
|
||||
exchange.progressNotification(
|
||||
ProgressNotification.builder(progressToken, 0.0).total(100.0).build());
|
||||
// try {
|
||||
// Thread.sleep(50);
|
||||
// }
|
||||
// catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
exchange.progressNotification(new ProgressNotification(progressToken, 50.0, 100.0, null));
|
||||
exchange.progressNotification(
|
||||
ProgressNotification.builder(progressToken, 50.0).total(100.0).build());
|
||||
// try {
|
||||
// Thread.sleep(50);
|
||||
// }
|
||||
// catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
exchange.progressNotification(new ProgressNotification(progressToken, 100.0, 100.0, null));
|
||||
exchange.progressNotification(
|
||||
ProgressNotification.builder(progressToken, 100.0).total(100.0).build());
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("Tool execution completed with progress")))
|
||||
.content(List.of(TextContent.builder("Tool execution completed with progress").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -313,7 +301,8 @@ public class ConformanceServlet {
|
|||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent("Tool execution completed without progress")))
|
||||
.content(List
|
||||
.of(TextContent.builder("Tool execution completed without progress").build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -322,28 +311,28 @@ public class ConformanceServlet {
|
|||
|
||||
// test_sampling - Tool that requests LLM sampling from client
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_sampling")
|
||||
.description("Tool that requests LLM sampling from client")
|
||||
.inputSchema(Map.of("type", "object", "properties",
|
||||
.tool(Tool
|
||||
.builder("test_sampling", Map.of("type", "object", "properties",
|
||||
Map.of("prompt",
|
||||
Map.of("type", "string", "description", "The prompt to send to the LLM")),
|
||||
"required", List.of("prompt")))
|
||||
.description("Tool that requests LLM sampling from client")
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_sampling' called");
|
||||
String prompt = (String) request.arguments().get("prompt");
|
||||
|
||||
// Request sampling from client
|
||||
CreateMessageRequest samplingRequest = CreateMessageRequest.builder()
|
||||
.messages(List.of(new SamplingMessage(Role.USER, new TextContent(prompt))))
|
||||
.maxTokens(100)
|
||||
CreateMessageRequest samplingRequest = CreateMessageRequest
|
||||
.builder(List
|
||||
.of(SamplingMessage.builder(Role.USER, TextContent.builder(prompt).build()).build()),
|
||||
100)
|
||||
.build();
|
||||
|
||||
CreateMessageResult response = exchange.createMessage(samplingRequest);
|
||||
String responseText = "LLM response: " + ((TextContent) response.content()).text();
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent(responseText)))
|
||||
.content(List.of(TextContent.builder(responseText).build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -351,13 +340,12 @@ public class ConformanceServlet {
|
|||
|
||||
// test_elicitation - Tool that requests user input from client
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_elicitation")
|
||||
.description("Tool that requests user input from client")
|
||||
.inputSchema(Map.of("type", "object", "properties",
|
||||
.tool(Tool
|
||||
.builder("test_elicitation", Map.of("type", "object", "properties",
|
||||
Map.of("message",
|
||||
Map.of("type", "string", "description", "The message to show the user")),
|
||||
"required", List.of("message")))
|
||||
.description("Tool that requests user input from client")
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_elicitation' called");
|
||||
|
|
@ -369,13 +357,13 @@ public class ConformanceServlet {
|
|||
Map.of("type", "string", "description", "User's email address")),
|
||||
"required", List.of("username", "email"));
|
||||
|
||||
ElicitRequest elicitRequest = new ElicitRequest(message, requestedSchema);
|
||||
ElicitRequest elicitRequest = ElicitRequest.builder(message, requestedSchema).build();
|
||||
|
||||
ElicitResult response = exchange.createElicitation(elicitRequest);
|
||||
String responseText = "User response: action=" + response.action() + ", content="
|
||||
+ response.content();
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent(responseText)))
|
||||
.content(List.of(TextContent.builder(responseText).build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -384,10 +372,8 @@ public class ConformanceServlet {
|
|||
// test_elicitation_sep1034_defaults - Tool with default values for all
|
||||
// primitive types
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_elicitation_sep1034_defaults")
|
||||
.tool(Tool.builder("test_elicitation_sep1034_defaults", EMPTY_JSON_SCHEMA)
|
||||
.description("Tool that requests elicitation with default values for all primitive types")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_elicitation_sep1034_defaults' called");
|
||||
|
|
@ -402,14 +388,15 @@ public class ConformanceServlet {
|
|||
"verified", Map.of("type", "boolean", "default", true)),
|
||||
"required", List.of("name", "age", "score", "status", "verified"));
|
||||
|
||||
ElicitRequest elicitRequest = new ElicitRequest("Please provide your information with defaults",
|
||||
requestedSchema);
|
||||
ElicitRequest elicitRequest = ElicitRequest
|
||||
.builder("Please provide your information with defaults", requestedSchema)
|
||||
.build();
|
||||
|
||||
ElicitResult response = exchange.createElicitation(elicitRequest);
|
||||
String responseText = "Elicitation completed: action=" + response.action() + ", content="
|
||||
+ response.content();
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent(responseText)))
|
||||
.content(List.of(TextContent.builder(responseText).build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -417,10 +404,8 @@ public class ConformanceServlet {
|
|||
|
||||
// test_elicitation_sep1330_enums - Tool with enum schema improvements
|
||||
McpServerFeatures.SyncToolSpecification.builder()
|
||||
.tool(Tool.builder()
|
||||
.name("test_elicitation_sep1330_enums")
|
||||
.tool(Tool.builder("test_elicitation_sep1330_enums", EMPTY_JSON_SCHEMA)
|
||||
.description("Tool that requests elicitation with enum schema improvements")
|
||||
.inputSchema(EMPTY_JSON_SCHEMA)
|
||||
.build())
|
||||
.callHandler((exchange, request) -> {
|
||||
logger.info("Tool 'test_elicitation_sep1330_enums' called");
|
||||
|
|
@ -455,13 +440,14 @@ public class ConformanceServlet {
|
|||
"required", List.of("untitledSingle", "titledSingle", "legacyEnum", "untitledMulti",
|
||||
"titledMulti"));
|
||||
|
||||
ElicitRequest elicitRequest = new ElicitRequest("Select your preferences", requestedSchema);
|
||||
ElicitRequest elicitRequest = ElicitRequest.builder("Select your preferences", requestedSchema)
|
||||
.build();
|
||||
|
||||
ElicitResult response = exchange.createElicitation(elicitRequest);
|
||||
String responseText = "Elicitation completed: action=" + response.action() + ", content="
|
||||
+ response.content();
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new TextContent(responseText)))
|
||||
.content(List.of(TextContent.builder(responseText).build()))
|
||||
.isError(false)
|
||||
.build();
|
||||
})
|
||||
|
|
@ -471,113 +457,142 @@ public class ConformanceServlet {
|
|||
private static List<McpServerFeatures.SyncPromptSpecification> createPromptSpecs() {
|
||||
return List.of(
|
||||
// test_simple_prompt - Simple prompt without arguments
|
||||
new McpServerFeatures.SyncPromptSpecification(
|
||||
new Prompt("test_simple_prompt", null, "A simple prompt for testing", List.of()),
|
||||
(exchange, request) -> {
|
||||
logger.info("Prompt 'test_simple_prompt' requested");
|
||||
return new GetPromptResult(null, List.of(new PromptMessage(Role.USER,
|
||||
new TextContent("This is a simple prompt for testing."))));
|
||||
}),
|
||||
new McpServerFeatures.SyncPromptSpecification(Prompt.builder("test_simple_prompt")
|
||||
.description("A simple prompt for testing")
|
||||
.arguments(List.of())
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Prompt 'test_simple_prompt' requested");
|
||||
return GetPromptResult.builder(List.of(PromptMessage
|
||||
.builder(Role.USER, TextContent.builder("This is a simple prompt for testing.").build())
|
||||
.build())).build();
|
||||
}),
|
||||
|
||||
// test_prompt_with_arguments - Prompt with arguments
|
||||
new McpServerFeatures.SyncPromptSpecification(
|
||||
new Prompt("test_prompt_with_arguments", null, "A prompt with arguments for testing",
|
||||
List.of(new PromptArgument("arg1", "First test argument", true),
|
||||
new PromptArgument("arg2", "Second test argument", true))),
|
||||
(exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_arguments' requested");
|
||||
String arg1 = (String) request.arguments().get("arg1");
|
||||
String arg2 = (String) request.arguments().get("arg2");
|
||||
String text = String.format("Prompt with arguments: arg1='%s', arg2='%s'", arg1, arg2);
|
||||
return new GetPromptResult(null,
|
||||
List.of(new PromptMessage(Role.USER, new TextContent(text))));
|
||||
}),
|
||||
new McpServerFeatures.SyncPromptSpecification(Prompt.builder("test_prompt_with_arguments")
|
||||
.description("A prompt with arguments for testing")
|
||||
.arguments(List.of(
|
||||
PromptArgument.builder("arg1").description("First test argument").required(true).build(),
|
||||
PromptArgument.builder("arg2").description("Second test argument").required(true).build()))
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_arguments' requested");
|
||||
String arg1 = (String) request.arguments().get("arg1");
|
||||
String arg2 = (String) request.arguments().get("arg2");
|
||||
String text = String.format("Prompt with arguments: arg1='%s', arg2='%s'", arg1, arg2);
|
||||
return GetPromptResult
|
||||
.builder(List
|
||||
.of(PromptMessage.builder(Role.USER, TextContent.builder(text).build()).build()))
|
||||
.build();
|
||||
}),
|
||||
|
||||
// test_prompt_with_embedded_resource - Prompt with embedded resource
|
||||
new McpServerFeatures.SyncPromptSpecification(
|
||||
new Prompt("test_prompt_with_embedded_resource", null,
|
||||
"A prompt with embedded resource for testing",
|
||||
List.of(new PromptArgument("resourceUri", "URI of the resource to embed", true))),
|
||||
(exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_embedded_resource' requested");
|
||||
String resourceUri = (String) request.arguments().get("resourceUri");
|
||||
TextResourceContents resourceContents = new TextResourceContents(resourceUri, "text/plain",
|
||||
"Embedded resource content for testing.");
|
||||
EmbeddedResource embeddedResource = new EmbeddedResource(null, resourceContents);
|
||||
return new GetPromptResult(null,
|
||||
List.of(new PromptMessage(Role.USER, embeddedResource), new PromptMessage(Role.USER,
|
||||
new TextContent("Please process the embedded resource above."))));
|
||||
}),
|
||||
new McpServerFeatures.SyncPromptSpecification(Prompt.builder("test_prompt_with_embedded_resource")
|
||||
.description("A prompt with embedded resource for testing")
|
||||
.arguments(List.of(PromptArgument.builder("resourceUri")
|
||||
.description("URI of the resource to embed")
|
||||
.required(true)
|
||||
.build()))
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_embedded_resource' requested");
|
||||
String resourceUri = (String) request.arguments().get("resourceUri");
|
||||
TextResourceContents resourceContents = TextResourceContents
|
||||
.builder(resourceUri, "Embedded resource content for testing.")
|
||||
.mimeType("text/plain")
|
||||
.build();
|
||||
EmbeddedResource embeddedResource = EmbeddedResource.builder(resourceContents).build();
|
||||
return GetPromptResult
|
||||
.builder(List.of(PromptMessage.builder(Role.USER, embeddedResource).build(),
|
||||
PromptMessage
|
||||
.builder(Role.USER,
|
||||
TextContent.builder("Please process the embedded resource above.")
|
||||
.build())
|
||||
.build()))
|
||||
.build();
|
||||
}),
|
||||
|
||||
// test_prompt_with_image - Prompt with image content
|
||||
new McpServerFeatures.SyncPromptSpecification(new Prompt("test_prompt_with_image", null,
|
||||
"A prompt with image content for testing", List.of()), (exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_image' requested");
|
||||
return new GetPromptResult(null, List.of(
|
||||
new PromptMessage(Role.USER, new ImageContent(null, RED_PIXEL_PNG, "image/png")),
|
||||
new PromptMessage(Role.USER, new TextContent("Please analyze the image above."))));
|
||||
}));
|
||||
new McpServerFeatures.SyncPromptSpecification(Prompt.builder("test_prompt_with_image")
|
||||
.description("A prompt with image content for testing")
|
||||
.arguments(List.of())
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Prompt 'test_prompt_with_image' requested");
|
||||
return GetPromptResult.builder(List.of(
|
||||
PromptMessage
|
||||
.builder(Role.USER, ImageContent.builder(RED_PIXEL_PNG, "image/png").build())
|
||||
.build(),
|
||||
PromptMessage
|
||||
.builder(Role.USER, TextContent.builder("Please analyze the image above.").build())
|
||||
.build()))
|
||||
.build();
|
||||
}));
|
||||
}
|
||||
|
||||
private static List<McpServerFeatures.SyncResourceSpecification> createResourceSpecs() {
|
||||
return List.of(
|
||||
// test://static-text - Static text resource
|
||||
new McpServerFeatures.SyncResourceSpecification(Resource.builder()
|
||||
.uri("test://static-text")
|
||||
.name("Static Text Resource")
|
||||
.description("A static text resource for testing")
|
||||
.mimeType("text/plain")
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Resource 'test://static-text' requested");
|
||||
return new ReadResourceResult(List.of(new TextResourceContents("test://static-text",
|
||||
"text/plain", "This is the content of the static text resource.")));
|
||||
}),
|
||||
new McpServerFeatures.SyncResourceSpecification(
|
||||
Resource.builder("test://static-text", "Static Text Resource")
|
||||
.description("A static text resource for testing")
|
||||
.mimeType("text/plain")
|
||||
.build(),
|
||||
(exchange, request) -> {
|
||||
logger.info("Resource 'test://static-text' requested");
|
||||
return ReadResourceResult.builder(List.of(TextResourceContents
|
||||
.builder("test://static-text", "This is the content of the static text resource.")
|
||||
.mimeType("text/plain")
|
||||
.build())).build();
|
||||
}),
|
||||
|
||||
// test://static-binary - Static binary resource (image)
|
||||
new McpServerFeatures.SyncResourceSpecification(Resource.builder()
|
||||
.uri("test://static-binary")
|
||||
.name("Static Binary Resource")
|
||||
.description("A static binary resource for testing")
|
||||
.mimeType("image/png")
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Resource 'test://static-binary' requested");
|
||||
return new ReadResourceResult(
|
||||
List.of(new BlobResourceContents("test://static-binary", "image/png", RED_PIXEL_PNG)));
|
||||
}),
|
||||
new McpServerFeatures.SyncResourceSpecification(
|
||||
Resource.builder("test://static-binary", "Static Binary Resource")
|
||||
.description("A static binary resource for testing")
|
||||
.mimeType("image/png")
|
||||
.build(),
|
||||
(exchange, request) -> {
|
||||
logger.info("Resource 'test://static-binary' requested");
|
||||
return ReadResourceResult
|
||||
.builder(List.of(BlobResourceContents.builder("test://static-binary", RED_PIXEL_PNG)
|
||||
.mimeType("image/png")
|
||||
.build()))
|
||||
.build();
|
||||
}),
|
||||
|
||||
// test://watched-resource - Resource that can be subscribed to
|
||||
new McpServerFeatures.SyncResourceSpecification(Resource.builder()
|
||||
.uri("test://watched-resource")
|
||||
.name("Watched Resource")
|
||||
.description("A resource that can be subscribed to for updates")
|
||||
.mimeType("text/plain")
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Resource 'test://watched-resource' requested");
|
||||
return new ReadResourceResult(List.of(new TextResourceContents("test://watched-resource",
|
||||
"text/plain", "This is a watched resource content.")));
|
||||
}));
|
||||
new McpServerFeatures.SyncResourceSpecification(
|
||||
Resource.builder("test://watched-resource", "Watched Resource")
|
||||
.description("A resource that can be subscribed to for updates")
|
||||
.mimeType("text/plain")
|
||||
.build(),
|
||||
(exchange, request) -> {
|
||||
logger.info("Resource 'test://watched-resource' requested");
|
||||
return ReadResourceResult.builder(List.of(TextResourceContents
|
||||
.builder("test://watched-resource", "This is a watched resource content.")
|
||||
.mimeType("text/plain")
|
||||
.build())).build();
|
||||
}));
|
||||
}
|
||||
|
||||
private static List<McpServerFeatures.SyncResourceTemplateSpecification> createResourceTemplateSpecs() {
|
||||
return List.of(
|
||||
// test://template/{id}/data - Resource template with parameter
|
||||
// substitution
|
||||
new McpServerFeatures.SyncResourceTemplateSpecification(ResourceTemplate.builder()
|
||||
.uriTemplate("test://template/{id}/data")
|
||||
.name("Template Resource")
|
||||
.description("A resource template for testing parameter substitution")
|
||||
.mimeType("application/json")
|
||||
.build(), (exchange, request) -> {
|
||||
logger.info("Resource template 'test://template/{{id}}/data' requested for URI: {}",
|
||||
request.uri());
|
||||
// Extract id from URI
|
||||
String uri = request.uri();
|
||||
String id = uri.replaceAll("test://template/(.+)/data", "$1");
|
||||
String jsonContent = String
|
||||
.format("{\"id\":\"%s\",\"templateTest\":true,\"data\":\"Data for ID: %s\"}", id, id);
|
||||
return new ReadResourceResult(
|
||||
List.of(new TextResourceContents(uri, "application/json", jsonContent)));
|
||||
}));
|
||||
new McpServerFeatures.SyncResourceTemplateSpecification(
|
||||
ResourceTemplate.builder("test://template/{id}/data", "Template Resource")
|
||||
.description("A resource template for testing parameter substitution")
|
||||
.mimeType("application/json")
|
||||
.build(),
|
||||
(exchange, request) -> {
|
||||
logger.info("Resource template 'test://template/{{id}}/data' requested for URI: {}",
|
||||
request.uri());
|
||||
// Extract id from URI
|
||||
String uri = request.uri();
|
||||
String id = uri.replaceAll("test://template/(.+)/data", "$1");
|
||||
String jsonContent = String
|
||||
.format("{\"id\":\"%s\",\"templateTest\":true,\"data\":\"Data for ID: %s\"}", id, id);
|
||||
return ReadResourceResult.builder(List.of(TextResourceContents.builder(uri, jsonContent)
|
||||
.mimeType("application/json")
|
||||
.build())).build();
|
||||
}));
|
||||
}
|
||||
|
||||
private static List<McpServerFeatures.SyncCompletionSpecification> createCompletionSpecs() {
|
||||
|
|
|
|||
|
|
@ -302,8 +302,9 @@ class LifecycleInitializer {
|
|||
|
||||
String latestVersion = this.protocolVersions.get(this.protocolVersions.size() - 1);
|
||||
|
||||
McpSchema.InitializeRequest initializeRequest = new McpSchema.InitializeRequest(latestVersion,
|
||||
this.clientCapabilities, this.clientInfo);
|
||||
McpSchema.InitializeRequest initializeRequest = McpSchema.InitializeRequest
|
||||
.builder(latestVersion, this.clientCapabilities, this.clientInfo)
|
||||
.build();
|
||||
|
||||
Mono<McpSchema.InitializeResult> result = mcpClientSession.sendRequest(McpSchema.METHOD_INITIALIZE,
|
||||
initializeRequest, McpAsyncClient.INITIALIZE_RESULT_TYPE_REF);
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ public class McpAsyncClient {
|
|||
|
||||
List<Root> roots = this.roots.values().stream().toList();
|
||||
|
||||
return Mono.just(new McpSchema.ListRootsResult(roots));
|
||||
return Mono.just(McpSchema.ListRootsResult.builder(roots).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -633,10 +633,10 @@ public class McpAsyncClient {
|
|||
return this.listTools(McpSchema.FIRST_PAGE).expand(result -> {
|
||||
String next = result.nextCursor();
|
||||
return (next != null && !next.isEmpty()) ? this.listTools(next) : Mono.empty();
|
||||
}).reduce(new McpSchema.ListToolsResult(new ArrayList<>(), null), (allToolsResult, result) -> {
|
||||
allToolsResult.tools().addAll(result.tools());
|
||||
return allToolsResult;
|
||||
}).map(result -> new McpSchema.ListToolsResult(Collections.unmodifiableList(result.tools()), null));
|
||||
}).reduce(new ArrayList<McpSchema.Tool>(), (accumulated, result) -> {
|
||||
accumulated.addAll(result.tools());
|
||||
return accumulated;
|
||||
}).map(all -> McpSchema.ListToolsResult.builder(Collections.unmodifiableList(all)).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -719,11 +719,11 @@ public class McpAsyncClient {
|
|||
public Mono<McpSchema.ListResourcesResult> listResources() {
|
||||
return this.listResources(McpSchema.FIRST_PAGE)
|
||||
.expand(result -> (result.nextCursor() != null) ? this.listResources(result.nextCursor()) : Mono.empty())
|
||||
.reduce(new McpSchema.ListResourcesResult(new ArrayList<>(), null), (allResourcesResult, result) -> {
|
||||
allResourcesResult.resources().addAll(result.resources());
|
||||
return allResourcesResult;
|
||||
.reduce(new ArrayList<McpSchema.Resource>(), (accumulated, result) -> {
|
||||
accumulated.addAll(result.resources());
|
||||
return accumulated;
|
||||
})
|
||||
.map(result -> new McpSchema.ListResourcesResult(Collections.unmodifiableList(result.resources()), null));
|
||||
.map(all -> McpSchema.ListResourcesResult.builder(Collections.unmodifiableList(all)).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -774,7 +774,7 @@ public class McpAsyncClient {
|
|||
* @see McpSchema.ReadResourceResult
|
||||
*/
|
||||
public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.Resource resource) {
|
||||
return this.readResource(new McpSchema.ReadResourceRequest(resource.uri()));
|
||||
return this.readResource(McpSchema.ReadResourceRequest.builder(resource.uri()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -806,13 +806,11 @@ public class McpAsyncClient {
|
|||
return this.listResourceTemplates(McpSchema.FIRST_PAGE)
|
||||
.expand(result -> (result.nextCursor() != null) ? this.listResourceTemplates(result.nextCursor())
|
||||
: Mono.empty())
|
||||
.reduce(new McpSchema.ListResourceTemplatesResult(new ArrayList<>(), null),
|
||||
(allResourceTemplatesResult, result) -> {
|
||||
allResourceTemplatesResult.resourceTemplates().addAll(result.resourceTemplates());
|
||||
return allResourceTemplatesResult;
|
||||
})
|
||||
.map(result -> new McpSchema.ListResourceTemplatesResult(
|
||||
Collections.unmodifiableList(result.resourceTemplates()), null));
|
||||
.reduce(new ArrayList<McpSchema.ResourceTemplate>(), (accumulated, result) -> {
|
||||
accumulated.addAll(result.resourceTemplates());
|
||||
return accumulated;
|
||||
})
|
||||
.map(all -> McpSchema.ListResourceTemplatesResult.builder(Collections.unmodifiableList(all)).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -898,7 +896,7 @@ public class McpAsyncClient {
|
|||
new TypeRef<>() {
|
||||
});
|
||||
|
||||
return readResource(new McpSchema.ReadResourceRequest(resourcesUpdatedNotification.uri()))
|
||||
return readResource(McpSchema.ReadResourceRequest.builder(resourcesUpdatedNotification.uri()).build())
|
||||
.flatMap(readResourceResult -> Flux.fromIterable(resourcesUpdateConsumers)
|
||||
.flatMap(consumer -> consumer.apply(readResourceResult.contents()))
|
||||
.onErrorResume(error -> {
|
||||
|
|
@ -927,11 +925,11 @@ public class McpAsyncClient {
|
|||
public Mono<ListPromptsResult> listPrompts() {
|
||||
return this.listPrompts(McpSchema.FIRST_PAGE)
|
||||
.expand(result -> (result.nextCursor() != null) ? this.listPrompts(result.nextCursor()) : Mono.empty())
|
||||
.reduce(new ListPromptsResult(new ArrayList<>(), null), (allPromptsResult, result) -> {
|
||||
allPromptsResult.prompts().addAll(result.prompts());
|
||||
return allPromptsResult;
|
||||
.reduce(new ArrayList<McpSchema.Prompt>(), (accumulated, result) -> {
|
||||
accumulated.addAll(result.prompts());
|
||||
return accumulated;
|
||||
})
|
||||
.map(result -> new McpSchema.ListPromptsResult(Collections.unmodifiableList(result.prompts()), null));
|
||||
.map(all -> McpSchema.ListPromptsResult.builder(Collections.unmodifiableList(all)).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ public interface McpClient {
|
|||
|
||||
private ClientCapabilities capabilities;
|
||||
|
||||
private Implementation clientInfo = new Implementation("Java SDK MCP Client", "0.15.0");
|
||||
private Implementation clientInfo = Implementation.builder("Java SDK MCP Client", "0.15.0").build();
|
||||
|
||||
private final Map<String, Root> roots = new HashMap<>();
|
||||
|
||||
|
|
@ -525,7 +525,7 @@ public interface McpClient {
|
|||
|
||||
private ClientCapabilities capabilities;
|
||||
|
||||
private Implementation clientInfo = new Implementation("Java SDK MCP Client", "0.15.0");
|
||||
private Implementation clientInfo = Implementation.builder("Java SDK MCP Client", "0.15.0").build();
|
||||
|
||||
private final Map<String, Root> roots = new HashMap<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@ class McpClientFeatures {
|
|||
: new McpSchema.ClientCapabilities(null,
|
||||
!Utils.isEmpty(roots) ? new McpSchema.ClientCapabilities.RootCapabilities(false) : null,
|
||||
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null,
|
||||
elicitationHandler != null ? new McpSchema.ClientCapabilities.Elicitation() : null);
|
||||
elicitationHandler != null ? McpSchema.ClientCapabilities.Elicitation.builder().build()
|
||||
: null);
|
||||
this.roots = roots != null ? new ConcurrentHashMap<>(roots) : new ConcurrentHashMap<>();
|
||||
|
||||
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
||||
|
|
@ -256,7 +257,8 @@ class McpClientFeatures {
|
|||
: new McpSchema.ClientCapabilities(null,
|
||||
!Utils.isEmpty(roots) ? new McpSchema.ClientCapabilities.RootCapabilities(false) : null,
|
||||
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null,
|
||||
elicitationHandler != null ? new McpSchema.ClientCapabilities.Elicitation() : null);
|
||||
elicitationHandler != null ? McpSchema.ClientCapabilities.Elicitation.builder().build()
|
||||
: null);
|
||||
this.roots = roots != null ? new HashMap<>(roots) : new HashMap<>();
|
||||
|
||||
this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
|
||||
|
|
|
|||
|
|
@ -11,17 +11,15 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.modelcontextprotocol.util.Assert;
|
||||
|
||||
/**
|
||||
* Server parameters for stdio client.
|
||||
* Server parameters for stdio client. This is not a wire type; Jackson annotations are
|
||||
* intentionally omitted.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
public class ServerParameters {
|
||||
|
||||
// Environment variables to inherit by default
|
||||
|
|
@ -32,13 +30,10 @@ public class ServerParameters {
|
|||
"SYSTEMDRIVE", "SYSTEMROOT", "TEMP", "USERNAME", "USERPROFILE")
|
||||
: Arrays.asList("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER");
|
||||
|
||||
@JsonProperty("command")
|
||||
private String command;
|
||||
|
||||
@JsonProperty("args")
|
||||
private List<String> args = new ArrayList<>();
|
||||
|
||||
@JsonProperty("env")
|
||||
private Map<String, String> env;
|
||||
|
||||
private ServerParameters(String command, List<String> args, Map<String, String> env) {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class DefaultMcpStatelessServerHandler implements McpStatelessServerHandler {
|
|||
.build());
|
||||
}
|
||||
return requestHandler.handle(transportContext, request.params())
|
||||
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null))
|
||||
.map(result -> McpSchema.JSONRPCResponse.result(request.id(), result))
|
||||
.onErrorResume(t -> {
|
||||
McpSchema.JSONRPCResponse.JSONRPCError error;
|
||||
if (t instanceof McpError mcpError && mcpError.getJsonRpcError() != null) {
|
||||
|
|
@ -45,9 +45,9 @@ class DefaultMcpStatelessServerHandler implements McpStatelessServerHandler {
|
|||
}
|
||||
else {
|
||||
error = new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
|
||||
t.getMessage(), null);
|
||||
t.getMessage());
|
||||
}
|
||||
return Mono.just(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null, error));
|
||||
return Mono.just(McpSchema.JSONRPCResponse.error(request.id(), error));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ public class McpAsyncServer {
|
|||
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
|
||||
logger.warn(content);
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(content)))
|
||||
.content(List.of(McpSchema.TextContent.builder(content).build()))
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -425,7 +425,7 @@ public class McpAsyncServer {
|
|||
if (!validation.valid()) {
|
||||
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
|
||||
.content(List.of(McpSchema.TextContent.builder(validation.errorMessage()).build()))
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -438,7 +438,7 @@ public class McpAsyncServer {
|
|||
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
|
||||
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(validation.jsonStructuredOutput())))
|
||||
.content(List.of(McpSchema.TextContent.builder(validation.jsonStructuredOutput()).build()))
|
||||
.isError(result.isError())
|
||||
.structuredContent(result.structuredContent())
|
||||
.build();
|
||||
|
|
@ -529,7 +529,7 @@ public class McpAsyncServer {
|
|||
return (exchange, params) -> {
|
||||
List<Tool> tools = this.tools.stream().map(McpServerFeatures.AsyncToolSpecification::tool).toList();
|
||||
|
||||
return Mono.just(new McpSchema.ListToolsResult(tools, null));
|
||||
return Mono.just(McpSchema.ListToolsResult.builder(tools).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -781,7 +781,7 @@ public class McpAsyncServer {
|
|||
.stream()
|
||||
.map(McpServerFeatures.AsyncResourceSpecification::resource)
|
||||
.toList();
|
||||
return Mono.just(new McpSchema.ListResourcesResult(resourceList, null));
|
||||
return Mono.just(McpSchema.ListResourcesResult.builder(resourceList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -791,7 +791,7 @@ public class McpAsyncServer {
|
|||
.stream()
|
||||
.map(McpServerFeatures.AsyncResourceTemplateSpecification::resourceTemplate)
|
||||
.toList();
|
||||
return Mono.just(new McpSchema.ListResourceTemplatesResult(resourceList, null));
|
||||
return Mono.just(McpSchema.ListResourceTemplatesResult.builder(resourceList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -923,7 +923,7 @@ public class McpAsyncServer {
|
|||
.map(McpServerFeatures.AsyncPromptSpecification::prompt)
|
||||
.toList();
|
||||
|
||||
return Mono.just(new McpSchema.ListPromptsResult(promptList, null));
|
||||
return Mono.just(McpSchema.ListPromptsResult.builder(promptList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,13 +166,13 @@ public class McpAsyncServerExchange {
|
|||
return this.listRoots(McpSchema.FIRST_PAGE)
|
||||
.expand(result -> (result.nextCursor() != null) ?
|
||||
this.listRoots(result.nextCursor()) : Mono.empty())
|
||||
.reduce(new McpSchema.ListRootsResult(new ArrayList<>(), null),
|
||||
.reduce(McpSchema.ListRootsResult.builder(new ArrayList<>()).build(),
|
||||
(allRootsResult, result) -> {
|
||||
allRootsResult.roots().addAll(result.roots());
|
||||
return allRootsResult;
|
||||
})
|
||||
.map(result -> new McpSchema.ListRootsResult(Collections.unmodifiableList(result.roots()),
|
||||
result.nextCursor()));
|
||||
.map(result -> McpSchema.ListRootsResult.builder(Collections.unmodifiableList(result.roots()))
|
||||
.nextCursor(result.nextCursor()).build());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ import reactor.core.publisher.Mono;
|
|||
* Example of creating a basic synchronous server: <pre>{@code
|
||||
* McpServer.sync(transportProvider)
|
||||
* .serverInfo("my-server", "1.0.0")
|
||||
* .toolCall(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
|
||||
* .toolCall(Tool.builder("calculator", schema).title("Performs calculations").build(),
|
||||
* (exchange, request) -> CallToolResult.builder()
|
||||
* .content(List.of(new McpSchema.TextContent("Result: " + calculate(request.arguments()))))
|
||||
* .content(List.of(McpSchema.TextContent.builder("Result: " + calculate(request.arguments())).build()))
|
||||
* .isError(false)
|
||||
* .build())
|
||||
* .build();
|
||||
|
|
@ -77,10 +77,10 @@ import reactor.core.publisher.Mono;
|
|||
* Example of creating a basic asynchronous server: <pre>{@code
|
||||
* McpServer.async(transportProvider)
|
||||
* .serverInfo("my-server", "1.0.0")
|
||||
* .toolCall(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
|
||||
* .toolCall(Tool.builder("calculator", schema).title("Performs calculations").build(),
|
||||
* (exchange, request) -> Mono.fromSupplier(() -> calculate(request.arguments()))
|
||||
* .map(result -> CallToolResult.builder()
|
||||
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
|
||||
* .content(List.of(McpSchema.TextContent.builder("Result: " + result).build()))
|
||||
* .isError(false)
|
||||
* .build()))
|
||||
* .build();
|
||||
|
|
@ -97,7 +97,7 @@ import reactor.core.publisher.Mono;
|
|||
* .tool(calculatorTool)
|
||||
* .callTool((exchange, args) -> Mono.fromSupplier(() -> calculate(args.arguments()))
|
||||
* .map(result -> CallToolResult.builder()
|
||||
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
|
||||
* .content(List.of(McpSchema.TextContent.builder("Result: " + result).build()))
|
||||
* .isError(false)
|
||||
* .build()))
|
||||
*. .build(),
|
||||
|
|
@ -105,7 +105,7 @@ import reactor.core.publisher.Mono;
|
|||
* .tool((weatherTool)
|
||||
* .callTool((exchange, args) -> Mono.fromSupplier(() -> getWeather(args.arguments()))
|
||||
* .map(result -> CallToolResult.builder()
|
||||
* .content(List.of(new McpSchema.TextContent("Weather: " + result)))
|
||||
* .content(List.of(McpSchema.TextContent.builder("Weather: " + result).build()))
|
||||
* .isError(false)
|
||||
* .build()))
|
||||
* .build()
|
||||
|
|
@ -145,7 +145,8 @@ import reactor.core.publisher.Mono;
|
|||
*/
|
||||
public interface McpServer {
|
||||
|
||||
McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("Java SDK MCP Server", "0.15.0");
|
||||
McpSchema.Implementation DEFAULT_SERVER_INFO = McpSchema.Implementation.builder("Java SDK MCP Server", "0.15.0")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Starts building a synchronous MCP server that provides blocking operations.
|
||||
|
|
@ -395,7 +396,7 @@ public interface McpServer {
|
|||
public AsyncSpecification<S> serverInfo(String name, String version) {
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
Assert.hasText(version, "Version must not be null or empty");
|
||||
this.serverInfo = new McpSchema.Implementation(name, version);
|
||||
this.serverInfo = McpSchema.Implementation.builder(name, version).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -992,7 +993,7 @@ public interface McpServer {
|
|||
public SyncSpecification<S> serverInfo(String name, String version) {
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
Assert.hasText(version, "Version must not be null or empty");
|
||||
this.serverInfo = new McpSchema.Implementation(name, version);
|
||||
this.serverInfo = McpSchema.Implementation.builder(name, version).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -1531,7 +1532,7 @@ public interface McpServer {
|
|||
public StatelessAsyncSpecification serverInfo(String name, String version) {
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
Assert.hasText(version, "Version must not be null or empty");
|
||||
this.serverInfo = new McpSchema.Implementation(name, version);
|
||||
this.serverInfo = McpSchema.Implementation.builder(name, version).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -2028,7 +2029,7 @@ public interface McpServer {
|
|||
public StatelessSyncSpecification serverInfo(String name, String version) {
|
||||
Assert.hasText(name, "Name must not be null or empty");
|
||||
Assert.hasText(version, "Version must not be null or empty");
|
||||
this.serverInfo = new McpSchema.Implementation(name, version);
|
||||
this.serverInfo = McpSchema.Implementation.builder(name, version).build();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,10 +77,12 @@ public class McpServerFeatures {
|
|||
// logging
|
||||
// by
|
||||
// default
|
||||
!Utils.isEmpty(prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null,
|
||||
!Utils.isEmpty(prompts) ? McpSchema.ServerCapabilities.PromptCapabilities.builder().build()
|
||||
: null,
|
||||
!Utils.isEmpty(resources)
|
||||
? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) : null,
|
||||
!Utils.isEmpty(tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null);
|
||||
? McpSchema.ServerCapabilities.ResourceCapabilities.builder().build() : null,
|
||||
!Utils.isEmpty(tools) ? McpSchema.ServerCapabilities.ToolCapabilities.builder().build()
|
||||
: null);
|
||||
|
||||
this.tools = (tools != null) ? tools : List.of();
|
||||
this.resources = (resources != null) ? resources : Map.of();
|
||||
|
|
@ -192,10 +194,12 @@ public class McpServerFeatures {
|
|||
// logging
|
||||
// by
|
||||
// default
|
||||
!Utils.isEmpty(prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null,
|
||||
!Utils.isEmpty(prompts) ? McpSchema.ServerCapabilities.PromptCapabilities.builder().build()
|
||||
: null,
|
||||
!Utils.isEmpty(resources)
|
||||
? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) : null,
|
||||
!Utils.isEmpty(tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null);
|
||||
? McpSchema.ServerCapabilities.ResourceCapabilities.builder().build() : null,
|
||||
!Utils.isEmpty(tools) ? McpSchema.ServerCapabilities.ToolCapabilities.builder().build()
|
||||
: null);
|
||||
|
||||
this.tools = (tools != null) ? tools : new ArrayList<>();
|
||||
this.resources = (resources != null) ? resources : new HashMap<>();
|
||||
|
|
@ -487,20 +491,19 @@ public class McpServerFeatures {
|
|||
*
|
||||
* <pre>{@code
|
||||
* McpServerFeatures.SyncToolSpecification.builder()
|
||||
* .tool(Tool.builder()
|
||||
* .name("calculator")
|
||||
* .tool(Tool.builder("calculator",
|
||||
* Map.of("type", "object", "properties",
|
||||
* Map.of("expression", Map.of("type", "string")),
|
||||
* "required", List.of("expression")))
|
||||
* .title("Performs mathematical calculations")
|
||||
* .inputSchema(new JsonSchemaObject()
|
||||
* .required("expression")
|
||||
* .property("expression", JsonSchemaType.STRING))
|
||||
* .build()
|
||||
* .build())
|
||||
* .toolHandler((exchange, req) -> {
|
||||
* String expr = (String) req.arguments().get("expression");
|
||||
* return CallToolResult.builder()
|
||||
* .content(List.of(new McpSchema.TextContent("Result: " + evaluate(expr))))
|
||||
* .content(List.of(McpSchema.TextContent.builder("Result: " + evaluate(expr)).build()))
|
||||
* .isError(false)
|
||||
* .build();
|
||||
* }))
|
||||
* })
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ public class McpStatelessAsyncServer {
|
|||
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
|
||||
logger.warn(content);
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(content)))
|
||||
.content(List.of(McpSchema.TextContent.builder(content).build()))
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ public class McpStatelessAsyncServer {
|
|||
if (!validation.valid()) {
|
||||
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
|
||||
.content(List.of(McpSchema.TextContent.builder(validation.errorMessage()).build()))
|
||||
.isError(true)
|
||||
.build();
|
||||
}
|
||||
|
|
@ -308,7 +308,7 @@ public class McpStatelessAsyncServer {
|
|||
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
|
||||
|
||||
return CallToolResult.builder()
|
||||
.content(List.of(new McpSchema.TextContent(validation.jsonStructuredOutput())))
|
||||
.content(List.of(McpSchema.TextContent.builder(validation.jsonStructuredOutput()).build()))
|
||||
.isError(result.isError())
|
||||
.structuredContent(result.structuredContent())
|
||||
.build();
|
||||
|
|
@ -393,7 +393,7 @@ public class McpStatelessAsyncServer {
|
|||
List<Tool> tools = this.tools.stream()
|
||||
.map(McpStatelessServerFeatures.AsyncToolSpecification::tool)
|
||||
.toList();
|
||||
return Mono.just(new McpSchema.ListToolsResult(tools, null));
|
||||
return Mono.just(McpSchema.ListToolsResult.builder(tools).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -557,7 +557,7 @@ public class McpStatelessAsyncServer {
|
|||
.stream()
|
||||
.map(McpStatelessServerFeatures.AsyncResourceSpecification::resource)
|
||||
.toList();
|
||||
return Mono.just(new McpSchema.ListResourcesResult(resourceList, null));
|
||||
return Mono.just(McpSchema.ListResourcesResult.builder(resourceList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -567,7 +567,7 @@ public class McpStatelessAsyncServer {
|
|||
.stream()
|
||||
.map(AsyncResourceTemplateSpecification::resourceTemplate)
|
||||
.toList();
|
||||
return Mono.just(new McpSchema.ListResourceTemplatesResult(resourceList, null));
|
||||
return Mono.just(McpSchema.ListResourceTemplatesResult.builder(resourceList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -687,7 +687,7 @@ public class McpStatelessAsyncServer {
|
|||
.map(McpStatelessServerFeatures.AsyncPromptSpecification::prompt)
|
||||
.toList();
|
||||
|
||||
return Mono.just(new McpSchema.ListPromptsResult(promptList, null));
|
||||
return Mono.just(McpSchema.ListPromptsResult.builder(promptList).build());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,10 +71,12 @@ public class McpStatelessServerFeatures {
|
|||
: new McpSchema.ServerCapabilities(null, // completions
|
||||
null, // experimental
|
||||
null, // currently statless server doesn't support set logging
|
||||
!Utils.isEmpty(prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null,
|
||||
!Utils.isEmpty(prompts) ? McpSchema.ServerCapabilities.PromptCapabilities.builder().build()
|
||||
: null,
|
||||
!Utils.isEmpty(resources)
|
||||
? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) : null,
|
||||
!Utils.isEmpty(tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null);
|
||||
? McpSchema.ServerCapabilities.ResourceCapabilities.builder().build() : null,
|
||||
!Utils.isEmpty(tools) ? McpSchema.ServerCapabilities.ToolCapabilities.builder().build()
|
||||
: null);
|
||||
|
||||
this.tools = (tools != null) ? tools : List.of();
|
||||
this.resources = (resources != null) ? resources : Map.of();
|
||||
|
|
@ -172,10 +174,12 @@ public class McpStatelessServerFeatures {
|
|||
// logging
|
||||
// by
|
||||
// default
|
||||
!Utils.isEmpty(prompts) ? new McpSchema.ServerCapabilities.PromptCapabilities(false) : null,
|
||||
!Utils.isEmpty(prompts) ? McpSchema.ServerCapabilities.PromptCapabilities.builder().build()
|
||||
: null,
|
||||
!Utils.isEmpty(resources)
|
||||
? new McpSchema.ServerCapabilities.ResourceCapabilities(false, false) : null,
|
||||
!Utils.isEmpty(tools) ? new McpSchema.ServerCapabilities.ToolCapabilities(false) : null);
|
||||
? McpSchema.ServerCapabilities.ResourceCapabilities.builder().build() : null,
|
||||
!Utils.isEmpty(tools) ? McpSchema.ServerCapabilities.ToolCapabilities.builder().build()
|
||||
: null);
|
||||
|
||||
this.tools = (tools != null) ? tools : new ArrayList<>();
|
||||
this.resources = (resources != null) ? resources : new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -462,8 +462,8 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
|||
response.setHeader(HttpHeaders.MCP_SESSION_ID, init.session().getId());
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
|
||||
String jsonResponse = jsonMapper.writeValueAsString(new McpSchema.JSONRPCResponse(
|
||||
McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initResult, null));
|
||||
String jsonResponse = jsonMapper
|
||||
.writeValueAsString(McpSchema.JSONRPCResponse.result(jsonrpcRequest.id(), initResult));
|
||||
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(jsonResponse);
|
||||
|
|
|
|||
|
|
@ -154,12 +154,10 @@ public class McpClientSession implements McpSession {
|
|||
|
||||
McpSchema.JSONRPCResponse.JSONRPCError jsonRpcError = (error instanceof McpError mcpError
|
||||
&& mcpError.getJsonRpcError() != null) ? mcpError.getJsonRpcError()
|
||||
// TODO: add error message through the data field
|
||||
: new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INTERNAL_ERROR,
|
||||
error.getMessage(), McpError.aggregateExceptionMessages(error));
|
||||
|
||||
var errorResponse = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
|
||||
jsonRpcError);
|
||||
var errorResponse = McpSchema.JSONRPCResponse.error(request.id(), jsonRpcError);
|
||||
return Mono.just(errorResponse);
|
||||
}).flatMap(this.transport::sendMessage).onErrorComplete(t -> {
|
||||
logger.warn("Issue sending response to the client, ", t);
|
||||
|
|
@ -188,13 +186,13 @@ public class McpClientSession implements McpSession {
|
|||
var handler = this.requestHandlers.get(request.method());
|
||||
if (handler == null) {
|
||||
MethodNotFoundError error = getMethodNotFoundError(request.method());
|
||||
return Mono.just(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), null,
|
||||
new McpSchema.JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.METHOD_NOT_FOUND,
|
||||
error.message(), error.data())));
|
||||
return Mono
|
||||
.just(McpSchema.JSONRPCResponse.error(request.id(), new McpSchema.JSONRPCResponse.JSONRPCError(
|
||||
McpSchema.ErrorCodes.METHOD_NOT_FOUND, error.message(), error.data())));
|
||||
}
|
||||
|
||||
return handler.handle(request.params())
|
||||
.map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null));
|
||||
.map(result -> McpSchema.JSONRPCResponse.result(request.id(), result));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -251,8 +249,7 @@ public class McpClientSession implements McpSession {
|
|||
return Mono.deferContextual(ctx -> Mono.<McpSchema.JSONRPCResponse>create(pendingResponseSink -> {
|
||||
logger.debug("Sending message for method {}", method);
|
||||
this.pendingResponses.put(requestId, pendingResponseSink);
|
||||
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, method,
|
||||
requestId, requestParams);
|
||||
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest(method, requestId, requestParams);
|
||||
this.transport.sendMessage(jsonrpcRequest).contextWrite(ctx).subscribe(v -> {
|
||||
}, error -> {
|
||||
this.pendingResponses.remove(requestId);
|
||||
|
|
@ -282,8 +279,7 @@ public class McpClientSession implements McpSession {
|
|||
*/
|
||||
@Override
|
||||
public Mono<Void> sendNotification(String method, Object params) {
|
||||
McpSchema.JSONRPCNotification jsonrpcNotification = new McpSchema.JSONRPCNotification(McpSchema.JSONRPC_VERSION,
|
||||
method, params);
|
||||
McpSchema.JSONRPCNotification jsonrpcNotification = new McpSchema.JSONRPCNotification(method, params);
|
||||
return this.transport.sendMessage(jsonrpcNotification);
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue