Skip to content

Commit 3915314

Browse files
authored
Merge branch 'main' into taskHink
2 parents 9def53c + e6045f7 commit 3915314

23 files changed

+458
-229
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/client/LifecycleInitializer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ public <T> Mono<T> withInitialization(String actionName, Function<Initialization
287287
this.initializationRef.compareAndSet(newInit, null);
288288
return Mono.error(new RuntimeException("Client failed to initialize " + actionName, ex));
289289
})
290-
.flatMap(operation);
290+
.flatMap(res -> operation.apply(res)
291+
.contextWrite(c -> c.put(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
292+
res.initializeResult().protocolVersion())));
291293
});
292294
}
293295

@@ -319,6 +321,8 @@ private Mono<McpSchema.InitializeResult> doInitialize(DefaultInitialization init
319321
}
320322

321323
return mcpClientSession.sendNotification(McpSchema.METHOD_NOTIFICATION_INITIALIZED, null)
324+
.contextWrite(
325+
c -> c.put(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION, initializeResult.protocolVersion()))
322326
.thenReturn(initializeResult);
323327
}).flatMap(initializeResult -> {
324328
initialization.cacheResult(initializeResult);

mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public class McpAsyncClient {
106106
public static final TypeRef<McpSchema.ProgressNotification> PROGRESS_NOTIFICATION_TYPE_REF = new TypeRef<>() {
107107
};
108108

109+
public static final String NEGOTIATED_PROTOCOL_VERSION = "io.modelcontextprotocol.client.negotiated-protocol-version";
110+
109111
/**
110112
* Client capabilities.
111113
*/

mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.function.Consumer;
2121
import java.util.function.Function;
2222

23+
import io.modelcontextprotocol.client.McpAsyncClient;
2324
import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent;
2425
import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer;
2526
import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;
@@ -193,7 +194,9 @@ private Publisher<Void> createDelete(String sessionId) {
193194
.uri(uri)
194195
.header("Cache-Control", "no-cache")
195196
.header(HttpHeaders.MCP_SESSION_ID, sessionId)
196-
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
197+
.header(HttpHeaders.PROTOCOL_VERSION,
198+
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
199+
this.latestSupportedProtocolVersion))
197200
.DELETE();
198201
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
199202
return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null, transportContext));
@@ -264,7 +267,9 @@ private Mono<Disposable> reconnect(McpTransportStream<Disposable> stream) {
264267
var builder = requestBuilder.uri(uri)
265268
.header(HttpHeaders.ACCEPT, TEXT_EVENT_STREAM)
266269
.header("Cache-Control", "no-cache")
267-
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
270+
.header(HttpHeaders.PROTOCOL_VERSION,
271+
connectionCtx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
272+
this.latestSupportedProtocolVersion))
268273
.GET();
269274
var transportContext = connectionCtx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
270275
return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null, transportContext));
@@ -439,7 +444,9 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
439444
.header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
440445
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
441446
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
442-
.header(HttpHeaders.PROTOCOL_VERSION, this.latestSupportedProtocolVersion)
447+
.header(HttpHeaders.PROTOCOL_VERSION,
448+
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
449+
this.latestSupportedProtocolVersion))
443450
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
444451
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
445452
return Mono

mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
2525
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
2626
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
27-
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
2827
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
2928
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
3029
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
@@ -398,19 +397,23 @@ public Mono<CallToolResult> apply(McpAsyncServerExchange exchange, McpSchema.Cal
398397
// results that conform to this schema.
399398
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
400399
if (result.structuredContent() == null) {
401-
logger.warn(
402-
"Response missing structured content which is expected when calling tool with non-empty outputSchema");
403-
return new CallToolResult(
404-
"Response missing structured content which is expected when calling tool with non-empty outputSchema",
405-
true);
400+
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
401+
logger.warn(content);
402+
return CallToolResult.builder()
403+
.content(List.of(new McpSchema.TextContent(content)))
404+
.isError(true)
405+
.build();
406406
}
407407

408408
// Validate the result against the output schema
409409
var validation = this.jsonSchemaValidator.validate(outputSchema, result.structuredContent());
410410

411411
if (!validation.valid()) {
412412
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
413-
return new CallToolResult(validation.errorMessage(), true);
413+
return CallToolResult.builder()
414+
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
415+
.isError(true)
416+
.build();
414417
}
415418

416419
if (Utils.isEmpty(result.content())) {

mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@
6767
* McpServer.sync(transportProvider)
6868
* .serverInfo("my-server", "1.0.0")
6969
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
70-
* (exchange, args) -> new CallToolResult("Result: " + calculate(args)))
70+
* (exchange, args) -> CallToolResult.builder()
71+
* .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
72+
* .isError(false)
73+
* .build())
7174
* .build();
7275
* }</pre>
7376
*
@@ -76,7 +79,10 @@
7679
* .serverInfo("my-server", "1.0.0")
7780
* .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
7881
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
79-
* .map(result -> new CallToolResult("Result: " + result)))
82+
* .map(result -> CallToolResult.builder()
83+
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
84+
* .isError(false)
85+
* .build()))
8086
* .build();
8187
* }</pre>
8288
*
@@ -90,12 +96,18 @@
9096
* McpServerFeatures.AsyncToolSpecification.builder()
9197
* .tool(calculatorTool)
9298
* .callTool((exchange, args) -> Mono.fromSupplier(() -> calculate(args.arguments()))
93-
* .map(result -> new CallToolResult("Result: " + result))))
99+
* .map(result -> CallToolResult.builder()
100+
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
101+
* .isError(false)
102+
* .build()))
94103
*. .build(),
95104
* McpServerFeatures.AsyncToolSpecification.builder()
96105
* .tool((weatherTool)
97106
* .callTool((exchange, args) -> Mono.fromSupplier(() -> getWeather(args.arguments()))
98-
* .map(result -> new CallToolResult("Weather: " + result))))
107+
* .map(result -> CallToolResult.builder()
108+
* .content(List.of(new McpSchema.TextContent("Weather: " + result)))
109+
* .isError(false)
110+
* .build()))
99111
* .build()
100112
* )
101113
* // Register resources
@@ -425,7 +437,10 @@ public AsyncSpecification<S> capabilities(McpSchema.ServerCapabilities serverCap
425437
* .tool(
426438
* Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
427439
* (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
428-
* .map(result -> new CallToolResult("Result: " + result))
440+
* .map(result -> CallToolResult.builder()
441+
* .content(List.of(new McpSchema.TextContent("Result: " + result)))
442+
* .isError(false)
443+
* .build()))
429444
* )
430445
* }</pre>
431446
* @param tool The tool definition including name, description, and schema. Must
@@ -1022,7 +1037,10 @@ public SyncSpecification<S> capabilities(McpSchema.ServerCapabilities serverCapa
10221037
* Example usage: <pre>{@code
10231038
* .tool(
10241039
* Tool.builder().name("calculator").title("Performs calculations".inputSchema(schema).build(),
1025-
* (exchange, args) -> new CallToolResult("Result: " + calculate(args))
1040+
* (exchange, args) -> CallToolResult.builder()
1041+
* .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
1042+
* .isError(false)
1043+
* .build())
10261044
* )
10271045
* }</pre>
10281046
* @param tool The tool definition including name, description, and schema. Must

mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,13 @@ public static Builder builder() {
338338
*
339339
* <pre>{@code
340340
* new McpServerFeatures.AsyncResourceSpecification(
341-
* new Resource("docs", "Documentation files", "text/markdown"),
341+
* Resource.builder()
342+
* .uri("docs")
343+
* .name("Documentation files")
344+
* .title("Documentation files")
345+
* .mimeType("text/markdown")
346+
* .description("Markdown documentation files")
347+
* .build(),
342348
* (exchange, request) -> Mono.fromSupplier(() -> readFile(request.getPath()))
343349
* .map(ReadResourceResult::new))
344350
* }</pre>
@@ -512,7 +518,10 @@ static AsyncCompletionSpecification fromSync(SyncCompletionSpecification complet
512518
* .build()
513519
* .toolHandler((exchange, req) -> {
514520
* String expr = (String) req.arguments().get("expression");
515-
* return new CallToolResult("Result: " + evaluate(expr));
521+
* return CallToolResult.builder()
522+
* .content(List.of(new McpSchema.TextContent("Result: " + evaluate(expr))))
523+
* .isError(false)
524+
* .build();
516525
* }))
517526
* .build();
518527
* }</pre>
@@ -608,7 +617,13 @@ public static Builder builder() {
608617
*
609618
* <pre>{@code
610619
* new McpServerFeatures.SyncResourceSpecification(
611-
* new Resource("docs", "Documentation files", "text/markdown"),
620+
* Resource.builder()
621+
* .uri("docs")
622+
* .name("Documentation files")
623+
* .title("Documentation files")
624+
* .mimeType("text/markdown")
625+
* .description("Markdown documentation files")
626+
* .build(),
612627
* (exchange, request) -> {
613628
* String content = readFile(request.getPath());
614629
* return new ReadResourceResult(content);

mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
1515
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
1616
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
17-
import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
1817
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
1918
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
2019
import io.modelcontextprotocol.spec.McpSchema.Tool;
@@ -277,19 +276,23 @@ public Mono<CallToolResult> apply(McpTransportContext transportContext, McpSchem
277276
// results that conform to this schema.
278277
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
279278
if (result.structuredContent() == null) {
280-
logger.warn(
281-
"Response missing structured content which is expected when calling tool with non-empty outputSchema");
282-
return new CallToolResult(
283-
"Response missing structured content which is expected when calling tool with non-empty outputSchema",
284-
true);
279+
String content = "Response missing structured content which is expected when calling tool with non-empty outputSchema";
280+
logger.warn(content);
281+
return CallToolResult.builder()
282+
.content(List.of(new McpSchema.TextContent(content)))
283+
.isError(true)
284+
.build();
285285
}
286286

287287
// Validate the result against the output schema
288288
var validation = this.jsonSchemaValidator.validate(outputSchema, result.structuredContent());
289289

290290
if (!validation.valid()) {
291291
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
292-
return new CallToolResult(validation.errorMessage(), true);
292+
return CallToolResult.builder()
293+
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
294+
.isError(true)
295+
.build();
293296
}
294297

295298
if (Utils.isEmpty(result.content())) {

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2153,6 +2153,7 @@ public CallToolResult(List<Content> content, Boolean isError, Map<String, Object
21532153
* content contains error information. If false or absent, indicates successful
21542154
* execution.
21552155
*/
2156+
@Deprecated
21562157
public CallToolResult(String content, Boolean isError) {
21572158
this(List.of(new TextContent(content)), isError, null);
21582159
}
@@ -2291,7 +2292,7 @@ public Builder task(Task task) {
22912292
* @return a new CallToolResult instance
22922293
*/
22932294
public CallToolResult build() {
2294-
return new CallToolResult(content, isError, (Object) structuredContent, task, meta);
2295+
return new CallToolResult(content, isError, structuredContent, meta);
22952296
}
22962297

22972298
}

mcp-core/src/main/java/io/modelcontextprotocol/spec/McpTransportSession.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
package io.modelcontextprotocol.spec;
66

7-
import org.reactivestreams.Publisher;
8-
97
import java.util.Optional;
108

9+
import org.reactivestreams.Publisher;
10+
1111
/**
1212
* An abstraction of the session as perceived from the MCP transport layer. Not to be
1313
* confused with the {@link McpSession} type that operates at the level of the JSON-RPC

mcp-core/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ void usesLatestVersion() {
8989
}
9090

9191
@Test
92-
void usesCustomLatestVersion() {
92+
void usesServerSupportedVersion() {
9393
startTomcat();
9494

9595
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT)
@@ -101,19 +101,21 @@ void usesCustomLatestVersion() {
101101
McpSchema.CallToolResult response = client.callTool(new McpSchema.CallToolRequest("test-tool", Map.of()));
102102

103103
var calls = requestRecordingFilter.getCalls();
104-
105-
assertThat(calls).filteredOn(c -> !c.body().contains("\"method\":\"initialize\""))
106-
// GET /mcp ; POST notification/initialized ; POST tools/call
107-
.hasSize(3)
104+
// Initialize tells the server the Client's latest supported version
105+
// FIXME: Set the correct protocol version on GET /mcp
106+
assertThat(calls).filteredOn(c -> c.method().equals("POST") && !c.body().contains("\"method\":\"initialize\""))
107+
// POST notification/initialized ; POST tools/call
108+
.hasSize(2)
108109
.map(McpTestRequestRecordingServletFilter.Call::headers)
109-
.allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version", "2263-03-18"));
110+
.allSatisfy(headers -> assertThat(headers).containsEntry("mcp-protocol-version",
111+
ProtocolVersions.MCP_2025_06_18));
110112

111113
assertThat(response).isNotNull();
112114
assertThat(response.content()).hasSize(1)
113115
.first()
114116
.extracting(McpSchema.TextContent.class::cast)
115117
.extracting(McpSchema.TextContent::text)
116-
.isEqualTo("2263-03-18");
118+
.isEqualTo(ProtocolVersions.MCP_2025_06_18);
117119
mcpServer.close();
118120
}
119121

0 commit comments

Comments
 (0)