Skip to content

Commit ebde028

Browse files
committed
Merge branch 'main' into fix/stateless-sync-server-close-gracefully-return-type
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
2 parents e8d9c7b + 2756337 commit ebde028

15 files changed

Lines changed: 1839 additions & 249 deletions

File tree

MIGRATION-2.0.md

Lines changed: 160 additions & 91 deletions
Large diffs are not rendered by default.

conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import java.util.List;
66
import java.util.Map;
77

8+
import io.modelcontextprotocol.json.McpJsonDefaults;
9+
import io.modelcontextprotocol.json.TypeRef;
810
import io.modelcontextprotocol.server.McpServer;
911
import io.modelcontextprotocol.server.McpServerFeatures;
1012
import io.modelcontextprotocol.server.transport.DefaultServerTransportSecurityValidator;
1113
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
12-
import io.modelcontextprotocol.spec.McpSchema;
1314
import io.modelcontextprotocol.spec.McpSchema.AudioContent;
1415
import io.modelcontextprotocol.spec.McpSchema.BlobResourceContents;
1516
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
@@ -43,6 +44,16 @@
4344
import org.slf4j.Logger;
4445
import org.slf4j.LoggerFactory;
4546

47+
import static io.modelcontextprotocol.spec.McpSchema.EnumSchemaOption;
48+
import static io.modelcontextprotocol.spec.McpSchema.JSON_SCHEMA_DIALECT_2020_12;
49+
import static io.modelcontextprotocol.spec.McpSchema.LegacyTitledEnumSchema;
50+
import static io.modelcontextprotocol.spec.McpSchema.TitledMultiSelectEnumSchema;
51+
import static io.modelcontextprotocol.spec.McpSchema.TitledMultiSelectItems;
52+
import static io.modelcontextprotocol.spec.McpSchema.TitledSingleSelectEnumSchema;
53+
import static io.modelcontextprotocol.spec.McpSchema.UntitledMultiSelectEnumSchema;
54+
import static io.modelcontextprotocol.spec.McpSchema.UntitledMultiSelectItems;
55+
import static io.modelcontextprotocol.spec.McpSchema.UntitledSingleSelectEnumSchema;
56+
4657
public class ConformanceServlet {
4758

4859
private static final Logger logger = LoggerFactory.getLogger(ConformanceServlet.class);
@@ -141,6 +152,7 @@ private static Tomcat createEmbeddedTomcat(HttpServletStreamableServerTransportP
141152
return tomcat;
142153
}
143154

155+
@SuppressWarnings("deprecation")
144156
private static List<McpServerFeatures.SyncToolSpecification> createToolSpecs() {
145157
return List.of(
146158
// test_simple_text - Returns simple text content
@@ -406,8 +418,8 @@ private static List<McpServerFeatures.SyncToolSpecification> createToolSpecs() {
406418
// json_schema_2020_12_tool - SEP-1613 dialect/keyword preservation
407419
McpServerFeatures.SyncToolSpecification.builder()
408420
.tool(Tool
409-
.builder("json_schema_2020_12_tool", Map.of("$schema", McpSchema.JSON_SCHEMA_DIALECT_2020_12,
410-
"type", "object", "$defs",
421+
.builder("json_schema_2020_12_tool", Map.of("$schema", JSON_SCHEMA_DIALECT_2020_12, "type",
422+
"object", "$defs",
411423
Map.of("address",
412424
Map.of("type", "object", "properties",
413425
Map.of("street", Map.of("type", "string"), "city",
@@ -434,33 +446,44 @@ private static List<McpServerFeatures.SyncToolSpecification> createToolSpecs() {
434446
.callHandler((exchange, request) -> {
435447
logger.info("Tool 'test_elicitation_sep1330_enums' called");
436448

437-
// Create schema with all 5 enum variants
438-
Map<String, Object> requestedSchema = Map.of("type", "object", "properties", Map.of(
439-
// 1. Untitled single-select
440-
"untitledSingle",
441-
Map.of("type", "string", "enum", List.of("option1", "option2", "option3")),
442-
// 2. Titled single-select using oneOf with const/title
443-
"titledSingle",
444-
Map.of("type", "string", "oneOf",
445-
List.of(Map.of("const", "value1", "title", "First Option"),
446-
Map.of("const", "value2", "title", "Second Option"),
447-
Map.of("const", "value3", "title", "Third Option"))),
448-
// 3. Legacy titled using enumNames (deprecated)
449-
"legacyEnum",
450-
Map.of("type", "string", "enum", List.of("opt1", "opt2", "opt3"), "enumNames",
451-
List.of("Option One", "Option Two", "Option Three")),
452-
// 4. Untitled multi-select
453-
"untitledMulti",
454-
Map.of("type", "array", "items",
455-
Map.of("type", "string", "enum", List.of("option1", "option2", "option3"))),
456-
// 5. Titled multi-select using items.anyOf with
457-
// const/title
458-
"titledMulti",
459-
Map.of("type", "array", "items",
460-
Map.of("anyOf",
461-
List.of(Map.of("const", "value1", "title", "First Choice"),
462-
Map.of("const", "value2", "title", "Second Choice"),
463-
Map.of("const", "value3", "title", "Third Choice"))))),
449+
TypeRef<Map<String, Object>> mapType = new TypeRef<>() {
450+
};
451+
var mapper = McpJsonDefaults.getMapper();
452+
453+
// 1. Untitled single-select
454+
var untitledSingle = UntitledSingleSelectEnumSchema.builder()
455+
.enumValues("option1", "option2", "option3")
456+
.build();
457+
// 2. Titled single-select using oneOf with const/title
458+
var titledSingle = TitledSingleSelectEnumSchema.builder()
459+
.oneOf(new EnumSchemaOption("value1", "First Option"),
460+
new EnumSchemaOption("value2", "Second Option"),
461+
new EnumSchemaOption("value3", "Third Option"))
462+
.build();
463+
// 3. Legacy titled using enumNames (deprecated)
464+
var legacyEnum = LegacyTitledEnumSchema.builder()
465+
.enumValues("opt1", "opt2", "opt3")
466+
.enumNames("Option One", "Option Two", "Option Three")
467+
.build();
468+
// 4. Untitled multi-select
469+
var untitledMulti = UntitledMultiSelectEnumSchema.builder(
470+
UntitledMultiSelectItems.builder().enumValues("option1", "option2", "option3").build())
471+
.build();
472+
// 5. Titled multi-select using items.anyOf with const/title
473+
var titledMulti = TitledMultiSelectEnumSchema
474+
.builder(TitledMultiSelectItems.builder()
475+
.anyOf(new EnumSchemaOption("value1", "First Choice"),
476+
new EnumSchemaOption("value2", "Second Choice"),
477+
new EnumSchemaOption("value3", "Third Choice"))
478+
.build())
479+
.build();
480+
481+
Map<String, Object> requestedSchema = Map.of("type", "object", "properties",
482+
Map.of("untitledSingle", mapper.convertValue(untitledSingle, mapType), "titledSingle",
483+
mapper.convertValue(titledSingle, mapType), "legacyEnum",
484+
mapper.convertValue(legacyEnum, mapType), "untitledMulti",
485+
mapper.convertValue(untitledMulti, mapType), "titledMulti",
486+
mapper.convertValue(titledMulti, mapType)),
464487
"required", List.of("untitledSingle", "titledSingle", "legacyEnum", "untitledMulti",
465488
"titledMulti"));
466489

docs/client.md

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,21 @@ The client provides both synchronous and asynchronous APIs for flexibility in di
4747

4848
// Call a tool
4949
CallToolResult result = client.callTool(
50-
new CallToolRequest("calculator",
51-
Map.of("operation", "add", "a", 2, "b", 3))
50+
CallToolRequest.builder("calculator")
51+
.arguments(Map.of("operation", "add", "a", 2, "b", 3))
52+
.build()
5253
);
5354

5455
// List and read resources
5556
ListResourcesResult resources = client.listResources();
5657
ReadResourceResult resource = client.readResource(
57-
new ReadResourceRequest("resource://uri")
58+
ReadResourceRequest.builder("resource://uri").build()
5859
);
5960

6061
// List and use prompts
6162
ListPromptsResult prompts = client.listPrompts();
6263
GetPromptResult prompt = client.getPrompt(
63-
new GetPromptRequest("greeting", Map.of("name", "Spring"))
64+
GetPromptRequest.builder("greeting").arguments(Map.of("name", "Spring")).build()
6465
);
6566

6667
// Add/remove roots
@@ -102,24 +103,22 @@ The client provides both synchronous and asynchronous APIs for flexibility in di
102103
client.initialize()
103104
.flatMap(initResult -> client.listTools())
104105
.flatMap(tools -> {
105-
return client.callTool(new CallToolRequest(
106-
"calculator",
107-
Map.of("operation", "add", "a", 2, "b", 3)
108-
));
106+
return client.callTool(CallToolRequest.builder("calculator")
107+
.arguments(Map.of("operation", "add", "a", 2, "b", 3))
108+
.build());
109109
})
110110
.flatMap(result -> {
111111
return client.listResources()
112112
.flatMap(resources ->
113-
client.readResource(new ReadResourceRequest("resource://uri"))
113+
client.readResource(ReadResourceRequest.builder("resource://uri").build())
114114
);
115115
})
116116
.flatMap(resource -> {
117117
return client.listPrompts()
118118
.flatMap(prompts ->
119-
client.getPrompt(new GetPromptRequest(
120-
"greeting",
121-
Map.of("name", "Spring")
122-
))
119+
client.getPrompt(GetPromptRequest.builder("greeting")
120+
.arguments(Map.of("name", "Spring"))
121+
.build())
123122
);
124123
})
125124
.flatMap(prompt -> {
@@ -144,7 +143,7 @@ Creates transport for process-based communication using stdin/stdout:
144143
ServerParameters params = ServerParameters.builder("npx")
145144
.args("-y", "@modelcontextprotocol/server-everything", "dir")
146145
.build();
147-
McpTransport transport = new StdioClientTransport(params);
146+
McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMapper());
148147
```
149148

150149
### Streamable HTTP
@@ -184,7 +183,7 @@ McpTransport transport = new StdioClientTransport(params);
184183
Creates a framework-agnostic (pure Java API) SSE client transport. Included in the core `mcp` module:
185184

186185
```java
187-
McpTransport transport = new HttpClientSseClientTransport("http://your-mcp-server");
186+
McpTransport transport = HttpClientSseClientTransport.builder("http://your-mcp-server").build();
188187
```
189188
=== "SSE WebClient (external)"
190189

@@ -301,6 +300,17 @@ The `ElicitResult` supports three actions:
301300
- `DECLINE` - The user declined to provide the information
302301
- `CANCEL` - The operation was cancelled
303302

303+
You can optionally have the client fill in missing values from the schema's `default` declarations before returning an accepted result to the server:
304+
305+
```java
306+
var client = McpClient.sync(transport)
307+
.applyElicitationDefaults(true) // default is false
308+
.elicitation(formElicitationHandler)
309+
.build();
310+
```
311+
312+
When enabled, any keys absent from an accepted `ElicitResult.content` are populated with the `default` values declared in the request's `requestedSchema`.
313+
304314
#### URL Elicitation Required Handling
305315

306316
When a server requires out-of-band URL elicitation but the client has not negotiated support for it (or the server strictly requires out-of-band handling), the server may return a `URL_ELICITATION_REQUIRED` error during tool execution or prompt retrieval.
@@ -339,7 +349,7 @@ mcpClient.initialize();
339349
mcpClient.setLoggingLevel(McpSchema.LoggingLevel.INFO);
340350

341351
// Call the tool that sends logging notifications
342-
CallToolResult result = mcpClient.callTool(new CallToolRequest("logging-test", Map.of()));
352+
CallToolResult result = mcpClient.callTool(CallToolRequest.builder("logging-test").build());
343353
```
344354

345355
Clients can control the minimum logging level they receive through the `mcpClient.setLoggingLevel(level)` request. Messages below the set level will be filtered out.
@@ -371,11 +381,13 @@ Tools are server-side functions that clients can discover and execute. The MCP c
371381

372382
// Call a tool with a CallToolRequest
373383
CallToolResult result = client.callTool(
374-
new CallToolRequest("calculator", Map.of(
375-
"operation", "add",
376-
"a", 1,
377-
"b", 2
378-
))
384+
CallToolRequest.builder("calculator")
385+
.arguments(Map.of(
386+
"operation", "add",
387+
"a", 1,
388+
"b", 2
389+
))
390+
.build()
379391
);
380392
```
381393

@@ -389,11 +401,13 @@ Tools are server-side functions that clients can discover and execute. The MCP c
389401
.subscribe();
390402

391403
// Call a tool asynchronously
392-
client.callTool(new CallToolRequest("calculator", Map.of(
393-
"operation", "add",
394-
"a", 1,
395-
"b", 2
396-
)))
404+
client.callTool(CallToolRequest.builder("calculator")
405+
.arguments(Map.of(
406+
"operation", "add",
407+
"a", 1,
408+
"b", 2
409+
))
410+
.build())
397411
.subscribe();
398412
```
399413

@@ -420,7 +434,7 @@ Resources represent server-side data sources that clients can access using URI t
420434

421435
// Read a resource
422436
ReadResourceResult resource = client.readResource(
423-
new ReadResourceRequest("resource://uri")
437+
ReadResourceRequest.builder("resource://uri").build()
424438
);
425439
```
426440

@@ -434,7 +448,7 @@ Resources represent server-side data sources that clients can access using URI t
434448
.subscribe();
435449

436450
// Read a resource asynchronously
437-
client.readResource(new ReadResourceRequest("resource://uri"))
451+
client.readResource(ReadResourceRequest.builder("resource://uri").build())
438452
.subscribe();
439453
```
440454

@@ -457,10 +471,10 @@ Register a consumer on the client builder, then subscribe/unsubscribe at any tim
457471
client.initialize();
458472

459473
// Subscribe to a specific resource URI
460-
client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource"));
474+
client.subscribeResource(McpSchema.SubscribeRequest.builder("custom://resource").build());
461475

462476
// ... later, stop receiving updates
463-
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource"));
477+
client.unsubscribeResource(McpSchema.UnsubscribeRequest.builder("custom://resource").build());
464478
```
465479

466480
=== "Async API"
@@ -473,11 +487,11 @@ Register a consumer on the client builder, then subscribe/unsubscribe at any tim
473487
.build();
474488

475489
client.initialize()
476-
.then(client.subscribeResource(new McpSchema.SubscribeRequest("custom://resource")))
490+
.then(client.subscribeResource(McpSchema.SubscribeRequest.builder("custom://resource").build()))
477491
.subscribe();
478492

479493
// ... later, stop receiving updates
480-
client.unsubscribeResource(new McpSchema.UnsubscribeRequest("custom://resource"))
494+
client.unsubscribeResource(McpSchema.UnsubscribeRequest.builder("custom://resource").build())
481495
.subscribe();
482496
```
483497

@@ -493,7 +507,7 @@ The prompt system enables interaction with server-side prompt templates. These t
493507

494508
// Get a prompt with parameters
495509
GetPromptResult prompt = client.getPrompt(
496-
new GetPromptRequest("greeting", Map.of("name", "World"))
510+
GetPromptRequest.builder("greeting").arguments(Map.of("name", "World")).build()
497511
);
498512
```
499513

@@ -507,6 +521,6 @@ The prompt system enables interaction with server-side prompt templates. These t
507521
.subscribe();
508522

509523
// Get a prompt asynchronously
510-
client.getPrompt(new GetPromptRequest("greeting", Map.of("name", "World")))
524+
client.getPrompt(GetPromptRequest.builder("greeting").arguments(Map.of("name", "World")).build())
511525
.subscribe();
512526
```

docs/quickstart.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ Add the BOM to your project:
123123
<dependency>
124124
<groupId>io.modelcontextprotocol.sdk</groupId>
125125
<artifactId>mcp-bom</artifactId>
126-
<version>1.0.0</version>
126+
<version>2.0.0</version>
127127
<type>pom</type>
128128
<scope>import</scope>
129129
</dependency>
@@ -135,7 +135,7 @@ Add the BOM to your project:
135135

136136
```groovy
137137
dependencies {
138-
implementation platform("io.modelcontextprotocol.sdk:mcp-bom:1.0.0")
138+
implementation platform("io.modelcontextprotocol.sdk:mcp-bom:2.0.0")
139139
//...
140140
}
141141
```

0 commit comments

Comments
 (0)