diff --git a/ROADMAP.md b/ROADMAP.md index b5b7dc4..bc2a336 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -27,7 +27,7 @@ Once we catch up on the most recent MCP specification revision we aim to fully s ### v1.x Development -The Java SDK is currently in active development as v1.x, following a recent stable 1.0.0 release. The SDK provides: +The Java SDK is currently in active development as v1.x, following a recent stable 1.1.0 release. The SDK provides: - MCP protocol implementation - Synchronous and asynchronous programming models diff --git a/conformance-tests/VALIDATION_RESULTS.md b/conformance-tests/VALIDATION_RESULTS.md index 8edc7ad..cb38abe 100644 --- a/conformance-tests/VALIDATION_RESULTS.md +++ b/conformance-tests/VALIDATION_RESULTS.md @@ -87,7 +87,7 @@ cd conformance-tests/client-jdk-http-client # Run all scenarios for scenario in initialize tools_call elicitation-sep1034-client-defaults sse-retry; do npx @modelcontextprotocol/conformance client \ - --command "java -jar target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario $scenario done ``` @@ -104,7 +104,7 @@ cd conformance-tests/client-spring-http-client # Run auth suite npx @modelcontextprotocol/conformance@0.1.15 client \ --spec-version 2025-11-25 \ - --command "java -jar target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar target/client-spring-http-client-1.1.1-SNAPSHOT.jar" \ --suite auth ``` diff --git a/conformance-tests/client-jdk-http-client/README.md b/conformance-tests/client-jdk-http-client/README.md index ba5f4fe..787820e 100644 --- a/conformance-tests/client-jdk-http-client/README.md +++ b/conformance-tests/client-jdk-http-client/README.md @@ -54,7 +54,7 @@ cd conformance-tests/client-jdk-http-client This creates an executable JAR at: ``` -target/client-jdk-http-client-1.1.0-SNAPSHOT.jar +target/client-jdk-http-client-1.1.1-SNAPSHOT.jar ``` ## Running Tests @@ -65,19 +65,19 @@ Run a single scenario: ```bash npx @modelcontextprotocol/conformance client \ - --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario initialize npx @modelcontextprotocol/conformance client \ - --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario tools_call npx @modelcontextprotocol/conformance client \ - --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario elicitation-sep1034-client-defaults npx @modelcontextprotocol/conformance client \ - --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario sse-retry ``` @@ -85,7 +85,7 @@ Run with verbose output: ```bash npx @modelcontextprotocol/conformance client \ - --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar" \ --scenario initialize \ --verbose ``` @@ -96,7 +96,7 @@ You can also run the client manually if you have a test server: ```bash export MCP_CONFORMANCE_SCENARIO=initialize -java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.0-SNAPSHOT.jar http://localhost:3000/mcp +java -jar conformance-tests/client-jdk-http-client/target/client-jdk-http-client-1.1.1-SNAPSHOT.jar http://localhost:3000/mcp ``` ## Test Results diff --git a/conformance-tests/client-jdk-http-client/pom.xml b/conformance-tests/client-jdk-http-client/pom.xml index 54618f1..1a0a4cb 100644 --- a/conformance-tests/client-jdk-http-client/pom.xml +++ b/conformance-tests/client-jdk-http-client/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk conformance-tests - 1.1.0-SNAPSHOT + 1.1.2 client-jdk-http-client jar @@ -28,7 +28,7 @@ io.modelcontextprotocol.sdk mcp - 1.1.0-SNAPSHOT + 1.1.2 diff --git a/conformance-tests/client-spring-http-client/README.md b/conformance-tests/client-spring-http-client/README.md index afbf647..0abca0e 100644 --- a/conformance-tests/client-spring-http-client/README.md +++ b/conformance-tests/client-spring-http-client/README.md @@ -67,7 +67,7 @@ cd conformance-tests/client-spring-http-client This creates an executable JAR at: ``` -target/client-spring-http-client-1.1.0-SNAPSHOT.jar +target/client-spring-http-client-1.1.1-SNAPSHOT.jar ``` ## Running Tests @@ -79,7 +79,7 @@ Run the full auth suite: ```bash npx @modelcontextprotocol/conformance@0.1.15 client \ --spec-version 2025-11-25 \ - --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.1-SNAPSHOT.jar" \ --suite auth ``` @@ -88,7 +88,7 @@ Run a single scenario: ```bash npx @modelcontextprotocol/conformance@0.1.15 client \ --spec-version 2025-11-25 \ - --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.1-SNAPSHOT.jar" \ --scenario auth/metadata-default ``` @@ -97,7 +97,7 @@ Run with verbose output: ```bash npx @modelcontextprotocol/conformance@0.1.15 client \ --spec-version 2025-11-25 \ - --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \ + --command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.1-SNAPSHOT.jar" \ --scenario auth/metadata-default \ --verbose ``` @@ -108,7 +108,7 @@ You can also run the client manually if you have a test server: ```bash export MCP_CONFORMANCE_SCENARIO=auth/metadata-default -java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar http://localhost:3000/mcp +java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.1-SNAPSHOT.jar http://localhost:3000/mcp ``` ## Known Issues diff --git a/conformance-tests/client-spring-http-client/pom.xml b/conformance-tests/client-spring-http-client/pom.xml index 90ed576..a1cc0df 100644 --- a/conformance-tests/client-spring-http-client/pom.xml +++ b/conformance-tests/client-spring-http-client/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk conformance-tests - 1.1.0-SNAPSHOT + 1.1.2 client-spring-http-client jar diff --git a/conformance-tests/pom.xml b/conformance-tests/pom.xml index 7329fe8..fd7baf5 100644 --- a/conformance-tests/pom.xml +++ b/conformance-tests/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 conformance-tests pom diff --git a/conformance-tests/server-servlet/pom.xml b/conformance-tests/server-servlet/pom.xml index 289599a..942fd90 100644 --- a/conformance-tests/server-servlet/pom.xml +++ b/conformance-tests/server-servlet/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk conformance-tests - 1.1.0-SNAPSHOT + 1.1.2 server-servlet jar @@ -28,7 +28,7 @@ io.modelcontextprotocol.sdk mcp - 1.1.0-SNAPSHOT + 1.1.2 diff --git a/docs/quickstart.md b/docs/quickstart.md index e7e76bc..1cbc08b 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -123,7 +123,7 @@ Add the BOM to your project: io.modelcontextprotocol.sdk mcp-bom - 1.0.0 + 1.1.0 pom import @@ -135,7 +135,7 @@ Add the BOM to your project: ```groovy dependencies { - implementation platform("io.modelcontextprotocol.sdk:mcp-bom:1.0.0") + implementation platform("io.modelcontextprotocol.sdk:mcp-bom:1.1.0") //... } ``` diff --git a/mcp-bom/pom.xml b/mcp-bom/pom.xml index aa6cc79..3c5e91a 100644 --- a/mcp-bom/pom.xml +++ b/mcp-bom/pom.xml @@ -7,7 +7,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp-bom diff --git a/mcp-core/pom.xml b/mcp-core/pom.xml index 3f7fa0b..8d9c5e4 100644 --- a/mcp-core/pom.xml +++ b/mcp-core/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp-core jar diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java index 93fcc33..434c07a 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java @@ -303,7 +303,7 @@ public class McpAsyncClient { return Mono.empty(); } - return this.listToolsInternal(init, McpSchema.FIRST_PAGE).doOnNext(listToolsResult -> { + return this.listToolsInternal(init, McpSchema.FIRST_PAGE, null).doOnNext(listToolsResult -> { listToolsResult.tools() .forEach(tool -> logger.debug("Tool {} schema: {}", tool.name(), tool.outputSchema())); if (enableCallToolSchemaCaching && listToolsResult.tools() != null) { @@ -645,16 +645,27 @@ public class McpAsyncClient { * @return A Mono that emits the list of tools result */ public Mono listTools(String cursor) { - return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor)); + return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, null)); } - private Mono listToolsInternal(Initialization init, String cursor) { + /** + * Retrieves a paginated list of tools with optional metadata. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return A Mono that emits the list of tools result + */ + public Mono listTools(String cursor, Map meta) { + return this.initializer.withInitialization("listing tools", init -> this.listToolsInternal(init, cursor, meta)); + } + + private Mono listToolsInternal(Initialization init, String cursor, + Map meta) { if (init.initializeResult().capabilities().tools() == null) { return Mono.error(new IllegalStateException("Server does not provide tools capability")); } return init.mcpSession() - .sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor), + .sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor, meta), LIST_TOOLS_RESULT_TYPE_REF) .doOnNext(result -> { // Validate tool names (warn only) @@ -725,12 +736,30 @@ public class McpAsyncClient { * @see #readResource(McpSchema.Resource) */ public Mono listResources(String cursor) { + return this.listResourcesInternal(cursor, null); + } + + /** + * Retrieves a paginated list of resources provided by the server. Resources represent + * any kind of UTF-8 encoded data that an MCP server makes available to clients, such + * as database records, API responses, log files, and more. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return A Mono that completes with the list of resources result. + * @see McpSchema.ListResourcesResult + * @see #readResource(McpSchema.Resource) + */ + public Mono listResources(String cursor, Map meta) { + return this.listResourcesInternal(cursor, meta); + } + + private Mono listResourcesInternal(String cursor, Map meta) { return this.initializer.withInitialization("listing resources", init -> { if (init.initializeResult().capabilities().resources() == null) { return Mono.error(new IllegalStateException("Server does not provide the resources capability")); } return init.mcpSession() - .sendRequest(McpSchema.METHOD_RESOURCES_LIST, new McpSchema.PaginatedRequest(cursor), + .sendRequest(McpSchema.METHOD_RESOURCES_LIST, new McpSchema.PaginatedRequest(cursor, meta), LIST_RESOURCES_RESULT_TYPE_REF); }); } @@ -795,12 +824,30 @@ public class McpAsyncClient { * @see McpSchema.ListResourceTemplatesResult */ public Mono listResourceTemplates(String cursor) { + return this.listResourceTemplatesInternal(cursor, null); + } + + /** + * Retrieves a paginated list of resource templates provided by the server. Resource + * templates allow servers to expose parameterized resources using URI templates, + * enabling dynamic resource access based on variable parameters. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return A Mono that completes with the list of resource templates result. + * @see McpSchema.ListResourceTemplatesResult + */ + public Mono listResourceTemplates(String cursor, Map meta) { + return this.listResourceTemplatesInternal(cursor, meta); + } + + private Mono listResourceTemplatesInternal(String cursor, + Map meta) { return this.initializer.withInitialization("listing resource templates", init -> { if (init.initializeResult().capabilities().resources() == null) { return Mono.error(new IllegalStateException("Server does not provide the resources capability")); } return init.mcpSession() - .sendRequest(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST, new McpSchema.PaginatedRequest(cursor), + .sendRequest(McpSchema.METHOD_RESOURCES_TEMPLATES_LIST, new McpSchema.PaginatedRequest(cursor, meta), LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF); }); } @@ -895,8 +942,26 @@ public class McpAsyncClient { * @see #getPrompt(GetPromptRequest) */ public Mono listPrompts(String cursor) { - return this.initializer.withInitialization("listing prompts", init -> init.mcpSession() - .sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF)); + return this.listPromptsInternal(cursor, null); + } + + /** + * Retrieves a paginated list of prompts with optional metadata. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return A Mono that completes with the list of prompts result. + * @see McpSchema.ListPromptsResult + * @see #getPrompt(GetPromptRequest) + */ + public Mono listPrompts(String cursor, Map meta) { + return this.listPromptsInternal(cursor, meta); + } + + private Mono listPromptsInternal(String cursor, Map meta) { + return this.initializer.withInitialization("listing prompts", + init -> init.mcpSession() + .sendRequest(McpSchema.METHOD_PROMPT_LIST, new PaginatedRequest(cursor, meta), + LIST_PROMPTS_RESULT_TYPE_REF)); } /** diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index 7fdaa89..7e08f83 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.client; import java.time.Duration; +import java.util.Map; import java.util.function.Supplier; import org.slf4j.Logger; @@ -259,6 +260,18 @@ public class McpSyncClient implements AutoCloseable { } + /** + * Retrieves a paginated list of tools provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return The list of tools result containing: - tools: List of available tools, each + * with a name, description, and input schema - nextCursor: Optional cursor for + * pagination if more tools are available + */ + public McpSchema.ListToolsResult listTools(String cursor, Map meta) { + return withProvidedContext(this.delegate.listTools(cursor, meta)).block(); + } + // -------------------------- // Resources // -------------------------- @@ -282,6 +295,17 @@ public class McpSyncClient implements AutoCloseable { } + /** + * Retrieves a paginated list of resources with optional metadata. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return The list of resources result + */ + public McpSchema.ListResourcesResult listResources(String cursor, Map meta) { + return withProvidedContext(this.delegate.listResources(cursor, meta)).block(); + + } + /** * Send a resources/read request. * @param resource the resource to read @@ -324,6 +348,20 @@ public class McpSyncClient implements AutoCloseable { } + /** + * Resource templates allow servers to expose parameterized resources using URI + * templates. Arguments may be auto-completed through the completion API. + * + * Retrieves a paginated list of resource templates provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return The list of resource templates result. + */ + public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor, Map meta) { + return withProvidedContext(this.delegate.listResourceTemplates(cursor, meta)).block(); + + } + /** * Subscriptions. The protocol supports optional subscriptions to resource changes. * Clients can subscribe to specific resources and receive notifications when they @@ -370,6 +408,17 @@ public class McpSyncClient implements AutoCloseable { } + /** + * Retrieves a paginated list of prompts provided by the server. + * @param cursor Optional pagination cursor from a previous list request + * @param meta Optional metadata to include in the request (_meta field) + * @return The list of prompts result. + */ + public ListPromptsResult listPrompts(String cursor, Map meta) { + return withProvidedContext(this.delegate.listPrompts(cursor, meta)).block(); + + } + public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { return withProvidedContext(this.delegate.getPrompt(getPromptRequest)).block(); } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index be4e4cf..e8b66e3 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -241,10 +241,17 @@ public class HttpClientSseClientTransport implements McpClientTransport { } /** - * Customizes the HTTP client builder. + * Applies the given consumer to the shared {@link HttpRequest.Builder} once, + * at build time. Any headers set here are frozen into the template and + * cannot be updated after the transport is built. * @param requestCustomizer the consumer to customize the HTTP request builder * @return this builder + * @deprecated Use {@link #requestBuilder(HttpRequest.Builder)} for stable + * headers, or {@link #httpRequestCustomizer(McpSyncHttpClientRequestCustomizer)} + * / {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)} for + * dynamic per-request customization. */ + @Deprecated public Builder customizeRequest(final Consumer requestCustomizer) { Assert.notNull(requestCustomizer, "requestCustomizer must not be null"); requestCustomizer.accept(requestBuilder); @@ -456,7 +463,7 @@ public class HttpClientSseClientTransport implements McpClientTransport { return Mono.deferContextual(ctx -> { var builder = this.requestBuilder.copy() .uri(requestUri) - .header(HttpHeaders.CONTENT_TYPE, "application/json") + .header(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8") .header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION) .POST(HttpRequest.BodyPublishers.ofString(body)); var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 57a27a3..d5e3688 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -102,6 +102,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { private static final String APPLICATION_JSON = "application/json"; + private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8"; + private static final String TEXT_EVENT_STREAM = "text/event-stream"; public static int NOT_FOUND = 404; @@ -298,6 +300,10 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { "Authorization error connecting to SSE stream", responseEvent.responseInfo())); } + else if (statusCode == METHOD_NOT_ALLOWED) { + logger.debug("The server does not support SSE streams, using request-response mode."); + return Flux.empty(); + } if (!(responseEvent instanceof ResponseSubscribers.SseResponseEvent sseResponseEvent)) { return Flux.error(new McpTransportException( @@ -344,10 +350,6 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { return Flux.empty(); } } - else if (statusCode == METHOD_NOT_ALLOWED) { // NotAllowed - logger.debug("The server does not support SSE streams, using request-response mode."); - return Flux.empty(); - } else if (statusCode == NOT_FOUND) { if (transportSession != null && transportSession.sessionId().isPresent()) { @@ -477,7 +479,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { var builder = requestBuilder.uri(uri) .header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM) - .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8) .header(HttpHeaders.CACHE_CONTROL, "no-cache") .header(HttpHeaders.PROTOCOL_VERSION, ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION, @@ -738,10 +740,17 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { } /** - * Customizes the HTTP client builder. + * Applies the given consumer to the shared {@link HttpRequest.Builder} once, + * at build time. Any headers set here are frozen into the template and + * cannot be updated after the transport is built. * @param requestCustomizer the consumer to customize the HTTP request builder * @return this builder + * @deprecated Use {@link #requestBuilder(HttpRequest.Builder)} for stable + * headers, or {@link #httpRequestCustomizer(McpSyncHttpClientRequestCustomizer)} + * / {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)} for + * dynamic per-request customization. */ + @Deprecated public Builder customizeRequest(final Consumer requestCustomizer) { Assert.notNull(requestCustomizer, "requestCustomizer must not be null"); requestCustomizer.accept(requestBuilder); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index d3648a0..0fb2fa7 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -286,7 +286,6 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement response.setCharacterEncoding(UTF_8); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Connection", "keep-alive"); - response.setHeader("Access-Control-Allow-Origin", "*"); String sessionId = UUID.randomUUID().toString(); AsyncContext asyncContext = request.startAsync(); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 95edb63..fe38b25 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -315,7 +315,6 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet response.setCharacterEncoding(UTF_8); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Connection", "keep-alive"); - response.setHeader("Access-Control-Allow-Origin", "*"); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); @@ -522,7 +521,6 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet response.setCharacterEncoding(UTF_8); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Connection", "keep-alive"); - response.setHeader("Access-Control-Allow-Origin", "*"); AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); diff --git a/mcp-json-jackson2/pom.xml b/mcp-json-jackson2/pom.xml index d36762a..9f90e9c 100644 --- a/mcp-json-jackson2/pom.xml +++ b/mcp-json-jackson2/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp-json-jackson2 jar @@ -72,7 +72,7 @@ io.modelcontextprotocol.sdk mcp-core - 1.1.0-SNAPSHOT + 1.1.2 com.networknt diff --git a/mcp-json-jackson3/pom.xml b/mcp-json-jackson3/pom.xml index cd6ecaa..294a154 100644 --- a/mcp-json-jackson3/pom.xml +++ b/mcp-json-jackson3/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp-json-jackson3 jar @@ -66,7 +66,7 @@ io.modelcontextprotocol.sdk mcp-core - 1.1.0-SNAPSHOT + 1.1.2 tools.jackson.core diff --git a/mcp-test/pom.xml b/mcp-test/pom.xml index 53fb849..939523e 100644 --- a/mcp-test/pom.xml +++ b/mcp-test/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp-test jar @@ -24,7 +24,7 @@ io.modelcontextprotocol.sdk mcp-core - 1.1.0-SNAPSHOT + 1.1.2 @@ -159,7 +159,7 @@ io.modelcontextprotocol.sdk mcp-json-jackson3 - 1.1.0-SNAPSHOT + 1.1.2 test @@ -170,7 +170,7 @@ io.modelcontextprotocol.sdk mcp-json-jackson2 - 1.1.0-SNAPSHOT + 1.1.2 test diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java index e5d55c3..5c2d77f 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.json.McpJsonDefaults; import io.modelcontextprotocol.server.McpServer; import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.server.McpSyncServer; @@ -47,6 +48,7 @@ import io.modelcontextprotocol.spec.McpSchema.Root; import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; import io.modelcontextprotocol.spec.McpSchema.TextContent; import io.modelcontextprotocol.spec.McpSchema.Tool; +import io.modelcontextprotocol.util.McpJsonMapperUtils; import io.modelcontextprotocol.util.Utils; import net.javacrumbs.jsonunit.core.Option; import org.junit.jupiter.params.ParameterizedTest; @@ -914,6 +916,62 @@ public abstract class AbstractMcpClientServerIntegrationTests { } } + @ParameterizedTest(name = "{0} : {displayName} ") + @MethodSource("clientsForTesting") + void testToolWithNonAsciiCharacters(String clientType) { + var clientBuilder = clientBuilders.get(clientType); + + String inputSchema = """ + { + "type": "object", + "properties": { + "username": { "type": "string" } + }, + "required": ["username"] + } + """; + + McpServerFeatures.SyncToolSpecification nonAsciiTool = McpServerFeatures.SyncToolSpecification.builder() + .tool(Tool.builder() + .name("greeter") + .description("打招呼") + .inputSchema(McpJsonDefaults.getMapper(), inputSchema) + .build()) + .callHandler((exchange, request) -> { + String username = (String) request.arguments().get("username"); + return McpSchema.CallToolResult.builder() + .addContent(new McpSchema.TextContent("Hello " + username)) + .build(); + }) + .build(); + + var mcpServer = prepareSyncServerBuilder().capabilities(ServerCapabilities.builder().tools(true).build()) + .tools(nonAsciiTool) + .build(); + + try (var mcpClient = clientBuilder.build()) { + + InitializeResult initResult = mcpClient.initialize(); + assertThat(initResult).isNotNull(); + + var tools = mcpClient.listTools().tools(); + assertThat(tools).hasSize(1); + assertThat(tools.get(0).name()).isEqualTo("greeter"); + assertThat(tools.get(0).description()).isEqualTo("打招呼"); + + CallToolResult response = mcpClient + .callTool(new McpSchema.CallToolRequest("greeter", Map.of("username", "测试用户"))); + + assertThat(response).isNotNull(); + assertThat(response.isError()).isFalse(); + assertThat(response.content()).hasSize(1); + assertThat(((McpSchema.TextContent) response.content().get(0)).text()).isEqualTo("Hello 测试用户"); + } + finally { + mcpServer.closeGracefully(); + } + } + @ParameterizedTest(name = "{0} : {displayName} ") @MethodSource("clientsForTesting") void testToolListChangeHandlingSuccess(String clientType) { diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java index 26d6056..7fe7bd6 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java @@ -154,6 +154,19 @@ public abstract class AbstractMcpSyncClientTests { }); } + @Test + void testListToolsWithMeta() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + Map meta = java.util.Map.of("requestId", "test-123"); + ListToolsResult tools = mcpSyncClient.listTools(McpSchema.FIRST_PAGE, meta); + + assertThat(tools).isNotNull().satisfies(result -> { + assertThat(result.tools()).isNotNull().isNotEmpty(); + }); + }); + } + @Test void testListAllTools() { withClient(createMcpTransport(), mcpSyncClient -> { @@ -678,4 +691,43 @@ public abstract class AbstractMcpSyncClientTests { }); } + @Test + void testListResourcesWithMeta() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + Map meta = java.util.Map.of("requestId", "test-123"); + ListResourcesResult resources = mcpSyncClient.listResources(McpSchema.FIRST_PAGE, meta); + + assertThat(resources).isNotNull().satisfies(result -> { + assertThat(result.resources()).isNotNull(); + }); + }); + } + + @Test + void testListResourceTemplatesWithMeta() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + Map meta = java.util.Map.of("requestId", "test-123"); + ListResourceTemplatesResult result = mcpSyncClient.listResourceTemplates(McpSchema.FIRST_PAGE, meta); + + assertThat(result).isNotNull().satisfies(r -> { + assertThat(r.resourceTemplates()).isNotNull(); + }); + }); + } + + @Test + void testListPromptsWithMeta() { + withClient(createMcpTransport(), mcpSyncClient -> { + mcpSyncClient.initialize(); + Map meta = java.util.Map.of("requestId", "test-123"); + McpSchema.ListPromptsResult result = mcpSyncClient.listPrompts(McpSchema.FIRST_PAGE, meta); + + assertThat(result).isNotNull().satisfies(r -> { + assertThat(r.prompts()).isNotNull(); + }); + }); + } + } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java index 48bf1da..a349fca 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java @@ -239,72 +239,159 @@ class McpAsyncClientTests { } @Test - void testListToolsWithEmptyCursor() { - McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build(); - McpSchema.Tool subtractTool = McpSchema.Tool.builder() - .name("subtract") - .description("calculate subtract") - .build(); - McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool, subtractTool), ""); + void testListToolsWithCursorAndMeta() { + var transport = new TestMcpClientTransport(); + McpAsyncClient client = McpClient.async(transport).build(); - McpClientTransport transport = new McpClientTransport() { - Function, Mono> handler; + Map meta = Map.of("customKey", "customValue"); + McpSchema.ListToolsResult result = client.listTools("cursor-1", meta).block(); + assertThat(result).isNotNull(); + assertThat(result.tools()).hasSize(1); + assertThat(transport.getCapturedRequest()).isNotNull(); + assertThat(transport.getCapturedRequest().cursor()).isEqualTo("cursor-1"); + assertThat(transport.getCapturedRequest().meta()).containsEntry("customKey", "customValue"); + } - @Override - public Mono connect( - Function, Mono> handler) { - return Mono.deferContextual(ctx -> { - this.handler = handler; - return Mono.empty(); - }); - } + @Test + void testListResourcesWithCursorAndMeta() { + var transport = new TestMcpClientTransport(); + McpAsyncClient client = McpClient.async(transport).build(); - @Override - public Mono closeGracefully() { + Map meta = Map.of("customKey", "customValue"); + McpSchema.ListResourcesResult result = client.listResources("cursor-1", meta).block(); + assertThat(result).isNotNull(); + assertThat(result.resources()).hasSize(1); + assertThat(transport.getCapturedRequest()).isNotNull(); + assertThat(transport.getCapturedRequest().cursor()).isEqualTo("cursor-1"); + assertThat(transport.getCapturedRequest().meta()).containsEntry("customKey", "customValue"); + } + + @Test + void testListResourceTemplatesWithCursorAndMeta() { + var transport = new TestMcpClientTransport(); + McpAsyncClient client = McpClient.async(transport).build(); + + Map meta = Map.of("customKey", "customValue"); + McpSchema.ListResourceTemplatesResult result = client.listResourceTemplates("cursor-1", meta).block(); + assertThat(result).isNotNull(); + assertThat(result.resourceTemplates()).hasSize(1); + assertThat(transport.getCapturedRequest()).isNotNull(); + assertThat(transport.getCapturedRequest().cursor()).isEqualTo("cursor-1"); + assertThat(transport.getCapturedRequest().meta()).containsEntry("customKey", "customValue"); + } + + @Test + void testListPromptsWithCursorAndMeta() { + var transport = new TestMcpClientTransport(); + McpAsyncClient client = McpClient.async(transport).build(); + + Map meta = Map.of("customKey", "customValue"); + McpSchema.ListPromptsResult result = client.listPrompts("cursor-1", meta).block(); + assertThat(result).isNotNull(); + assertThat(result.prompts()).hasSize(1); + assertThat(transport.getCapturedRequest()).isNotNull(); + assertThat(transport.getCapturedRequest().cursor()).isEqualTo("cursor-1"); + assertThat(transport.getCapturedRequest().meta()).containsEntry("customKey", "customValue"); + + } + + static class TestMcpClientTransport implements McpClientTransport { + + private Function, Mono> handler; + + private McpSchema.PaginatedRequest capturedRequest = null; + + @Override + public Mono connect(Function, Mono> handler) { + return Mono.deferContextual(ctx -> { + this.handler = handler; + return Mono.empty(); + }); + } + + @Override + public Mono closeGracefully() { + return Mono.empty(); + } + + @Override + public Mono sendMessage(McpSchema.JSONRPCMessage message) { + if (!(message instanceof McpSchema.JSONRPCRequest request)) { return Mono.empty(); } + McpSchema.JSONRPCResponse response; + if (McpSchema.METHOD_INITIALIZE.equals(request.method())) { + McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder() + .prompts(false) + .resources(false, false) + .tools(false) + .build(); - @Override - public Mono sendMessage(McpSchema.JSONRPCMessage message) { - if (!(message instanceof McpSchema.JSONRPCRequest request)) { - return Mono.empty(); - } + McpSchema.InitializeResult initResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2024_11_05, + caps, MOCK_SERVER_INFO, null); - McpSchema.JSONRPCResponse response; - if (McpSchema.METHOD_INITIALIZE.equals(request.method())) { - response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), MOCK_INIT_RESULT, - null); - } - else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) { - response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult, - null); - } - else { - return Mono.empty(); - } - - return handler.apply(Mono.just(response)).then(); + response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), initResult, null); } + else if (McpSchema.METHOD_PROMPT_LIST.equals(request.method())) { + capturedRequest = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class); - @Override - public T unmarshalFrom(Object data, TypeRef typeRef) { - return JSON_MAPPER.convertValue(data, new TypeRef<>() { - @Override - public java.lang.reflect.Type getType() { - return typeRef.getType(); - } - }); + McpSchema.Prompt mockPrompt = new McpSchema.Prompt("test-prompt", "A test prompt", List.of()); + McpSchema.ListPromptsResult mockPromptResult = new McpSchema.ListPromptsResult(List.of(mockPrompt), + null); + response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockPromptResult, + null); } - }; + else if (McpSchema.METHOD_RESOURCES_TEMPLATES_LIST.equals(request.method())) { + capturedRequest = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class); - McpAsyncClient client = McpClient.async(transport).enableCallToolSchemaCaching(true).build(); + McpSchema.ResourceTemplate mockTemplate = new McpSchema.ResourceTemplate("file:///{name}", "template", + null, null, null); + McpSchema.ListResourceTemplatesResult mockResourceTemplateResult = new McpSchema.ListResourceTemplatesResult( + List.of(mockTemplate), null); + response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), + mockResourceTemplateResult, null); + } + else if (McpSchema.METHOD_RESOURCES_LIST.equals(request.method())) { + capturedRequest = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class); - Mono mono = client.listTools(); - McpSchema.ListToolsResult toolsResult = mono.block(); - assertThat(toolsResult).isNotNull(); + McpSchema.Resource mockResource = McpSchema.Resource.builder() + .uri("file:///test.txt") + .name("test.txt") + .build(); + McpSchema.ListResourcesResult mockResourceResult = new McpSchema.ListResourcesResult( + List.of(mockResource), null); + + response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockResourceResult, + null); + } + else if (McpSchema.METHOD_TOOLS_LIST.equals(request.method())) { + capturedRequest = JSON_MAPPER.convertValue(request.params(), McpSchema.PaginatedRequest.class); + + McpSchema.Tool addTool = McpSchema.Tool.builder().name("add").description("calculate add").build(); + McpSchema.ListToolsResult mockToolsResult = new McpSchema.ListToolsResult(List.of(addTool), null); + response = new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), mockToolsResult, + null); + } + else { + return Mono.empty(); + } + return handler.apply(Mono.just(response)).then(); + } + + @Override + public T unmarshalFrom(Object data, TypeRef typeRef) { + return JSON_MAPPER.convertValue(data, new TypeRef<>() { + @Override + public java.lang.reflect.Type getType() { + return typeRef.getType(); + } + }); + } + + public McpSchema.PaginatedRequest getCapturedRequest() { + return capturedRequest; + } - Set names = toolsResult.tools().stream().map(McpSchema.Tool::name).collect(Collectors.toSet()); - assertThat(names).containsExactlyInAnyOrder("subtract", "add"); } } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportErrorHandlingTest.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportErrorHandlingTest.java index c4857e5..d3793ca 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportErrorHandlingTest.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportErrorHandlingTest.java @@ -18,7 +18,6 @@ import java.util.function.Predicate; import com.sun.net.httpserver.HttpServer; import io.modelcontextprotocol.client.transport.customizer.McpHttpClientAuthorizationErrorHandler; import io.modelcontextprotocol.common.McpTransportContext; -import org.reactivestreams.Publisher; import io.modelcontextprotocol.server.transport.TomcatTestUtil; import io.modelcontextprotocol.spec.HttpHeaders; import io.modelcontextprotocol.spec.McpClientTransport; @@ -34,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -369,6 +369,26 @@ public class HttpClientStreamableHttpTransportErrorHandlingTest { StepVerifier.create(transport.closeGracefully()).verifyComplete(); } + @Test + void test405OnConnectReturnsEmptyFlux() { + serverSseResponseStatus.set(405); + AtomicReference capturedException = new AtomicReference<>(); + var transport = HttpClientStreamableHttpTransport.builder(HOST).openConnectionOnStartup(true).build(); + transport.setExceptionHandler(capturedException::set); + + var messages = new ArrayList(); + StepVerifier.create(transport.connect(msg -> msg.doOnNext(messages::add))).verifyComplete(); + + Awaitility.await() + .atMost(Duration.ofSeconds(1)) + .untilAsserted(() -> assertThat(processedSseConnectCount.get()).isEqualTo(1)); + + assertThat(messages).isEmpty(); + assertThat(capturedException.get()).isNull(); + + StepVerifier.create(transport.closeGracefully()).verifyComplete(); + } + @Nested class AuthorizationError { diff --git a/mcp/pom.xml b/mcp/pom.xml index 5dc8016..f805eb3 100644 --- a/mcp/pom.xml +++ b/mcp/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 mcp jar @@ -25,13 +25,13 @@ io.modelcontextprotocol.sdk mcp-json-jackson3 - 1.1.0-SNAPSHOT + 1.1.2 io.modelcontextprotocol.sdk mcp-core - 1.1.0-SNAPSHOT + 1.1.2 diff --git a/mkdocs.yml b/mkdocs.yml index 3e27c3f..5730940 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,8 +49,6 @@ nav: - Contributing Guide: contribute.md - Documentation: development.md - API Reference: https://javadoc.io/doc/io.modelcontextprotocol.sdk/mcp-core/latest - - News: - - blog/index.md markdown_extensions: - admonition diff --git a/pom.xml b/pom.xml index cdbeb25..7566d7d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.modelcontextprotocol.sdk mcp-parent - 1.1.0-SNAPSHOT + 1.1.2 pom https://github.com/modelcontextprotocol/java-sdk Internal server error - Forgejo: Beyond coding. We Forge. 500 Internal server error Forgejo version: 17.0.0-dev-603-6cb6155db7+gitea-1.22.0
Internal server error
Forgejo version: 17.0.0-dev-603-6cb6155db7+gitea-1.22.0