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 66e0b9d44..be4e4cf97 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 @@ -185,19 +185,6 @@ public static class Builder { // Default constructor } - /** - * Creates a new builder with the specified base URI. - * @param baseUri the base URI of the MCP server - * @deprecated Use {@link HttpClientSseClientTransport#builder(String)} instead. - * This constructor is deprecated and will be removed or made {@code protected} or - * {@code private} in a future release. - */ - @Deprecated(forRemoval = true) - public Builder(String baseUri) { - Assert.hasText(baseUri, "baseUri must not be empty"); - this.baseUri = baseUri; - } - /** * Sets the base URI. * @param baseUri the base URI diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java index d1b55f594..660a15e6a 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java @@ -32,7 +32,9 @@ public Mono handleRequest(McpTransportContext transpo McpSchema.JSONRPCRequest request) { McpStatelessRequestHandler requestHandler = this.requestHandlers.get(request.method()); if (requestHandler == null) { - return Mono.error(new McpError("Missing handler for request type: " + request.method())); + return Mono.error(McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND) + .message("Missing handler for request type: " + request.method()) + .build()); } return requestHandler.handle(transportContext, request.params()) .map(result -> new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), result, null)) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java index 23285d514..32256987a 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java @@ -326,7 +326,7 @@ public Mono addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica if (toolSpecification.tool() == null) { return Mono.error(new IllegalArgumentException("Tool must not be null")); } - if (toolSpecification.call() == null && toolSpecification.callHandler() == null) { + if (toolSpecification.callHandler() == null) { return Mono.error(new IllegalArgumentException("Tool call handler must not be null")); } if (this.serverCapabilities.tools() == null) { @@ -869,32 +869,6 @@ private McpRequestHandler promptsGetRequestHandler() // Logging Management // --------------------------------------- - /** - * This implementation would, incorrectly, broadcast the logging message to all - * connected clients, using a single minLoggingLevel for all of them. Similar to the - * sampling and roots, the logging level should be set per client session and use the - * ServerExchange to send the logging message to the right client. - * @param loggingMessageNotification The logging message to send - * @return A Mono that completes when the notification has been sent - * @deprecated Use - * {@link McpAsyncServerExchange#loggingNotification(LoggingMessageNotification)} - * instead. - */ - @Deprecated - public Mono loggingNotification(LoggingMessageNotification loggingMessageNotification) { - - if (loggingMessageNotification == null) { - return Mono.error(new McpError("Logging message must not be null")); - } - - if (loggingMessageNotification.level().level() < minLoggingLevel.level()) { - return Mono.empty(); - } - - return this.mcpTransportProvider.notifyClients(McpSchema.METHOD_NOTIFICATION_MESSAGE, - loggingMessageNotification); - } - private McpRequestHandler setLoggerRequestHandler() { return (exchange, params) -> { return Mono.defer(() -> { diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java index a15c58cd5..40a76045b 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java @@ -49,28 +49,6 @@ public class McpAsyncServerExchange { public static final TypeRef OBJECT_TYPE_REF = new TypeRef<>() { }; - /** - * Create a new asynchronous exchange with the client. - * @param session The server session representing a 1-1 interaction. - * @param clientCapabilities The client capabilities that define the supported - * features and functionality. - * @param clientInfo The client implementation information. - * @deprecated Use - * {@link #McpAsyncServerExchange(String, McpLoggableSession, McpSchema.ClientCapabilities, McpSchema.Implementation, McpTransportContext)} - */ - @Deprecated - public McpAsyncServerExchange(McpSession session, McpSchema.ClientCapabilities clientCapabilities, - McpSchema.Implementation clientInfo) { - this.sessionId = null; - if (!(session instanceof McpLoggableSession)) { - throw new IllegalArgumentException("Expecting session to be a McpLoggableSession instance"); - } - this.session = (McpLoggableSession) session; - this.clientCapabilities = clientCapabilities; - this.clientInfo = clientInfo; - this.transportContext = McpTransportContext.EMPTY; - } - /** * Create a new asynchronous exchange with the client. * @param session The server session representing a 1-1 interaction. @@ -142,10 +120,11 @@ public String sessionId() { */ public Mono createMessage(McpSchema.CreateMessageRequest createMessageRequest) { if (this.clientCapabilities == null) { - return Mono.error(new McpError("Client must be initialized. Call the initialize method first!")); + return Mono + .error(new IllegalStateException("Client must be initialized. Call the initialize method first!")); } if (this.clientCapabilities.sampling() == null) { - return Mono.error(new McpError("Client must be configured with sampling capabilities")); + return Mono.error(new IllegalStateException("Client must be configured with sampling capabilities")); } return this.session.sendRequest(McpSchema.METHOD_SAMPLING_CREATE_MESSAGE, createMessageRequest, CREATE_MESSAGE_RESULT_TYPE_REF); @@ -167,10 +146,11 @@ public Mono createMessage(McpSchema.CreateMessage */ public Mono createElicitation(McpSchema.ElicitRequest elicitRequest) { if (this.clientCapabilities == null) { - return Mono.error(new McpError("Client must be initialized. Call the initialize method first!")); + return Mono + .error(new IllegalStateException("Client must be initialized. Call the initialize method first!")); } if (this.clientCapabilities.elicitation() == null) { - return Mono.error(new McpError("Client must be configured with elicitation capabilities")); + return Mono.error(new IllegalStateException("Client must be configured with elicitation capabilities")); } return this.session.sendRequest(McpSchema.METHOD_ELICITATION_CREATE, elicitRequest, ELICITATION_RESULT_TYPE_REF); @@ -215,7 +195,7 @@ public Mono listRoots(String cursor) { public Mono loggingNotification(LoggingMessageNotification loggingMessageNotification) { if (loggingMessageNotification == null) { - return Mono.error(new McpError("Logging message must not be null")); + return Mono.error(new IllegalStateException("Logging message must not be null")); } return Mono.defer(() -> { @@ -234,7 +214,7 @@ public Mono loggingNotification(LoggingMessageNotification loggingMessageN */ public Mono progressNotification(McpSchema.ProgressNotification progressNotification) { if (progressNotification == null) { - return Mono.error(new McpError("Progress notification must not be null")); + return Mono.error(new IllegalStateException("Progress notification must not be null")); } return this.session.sendNotification(McpSchema.METHOD_NOTIFICATION_PROGRESS, progressNotification); } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java index 7fe9ef2a2..360eb607d 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java @@ -66,9 +66,9 @@ * Example of creating a basic synchronous server:
{@code
  * McpServer.sync(transportProvider)
  *     .serverInfo("my-server", "1.0.0")
- *     .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
- *           (exchange, args) -> CallToolResult.builder()
- *                   .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
+ *     .toolCall(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
+ *           (exchange, request) -> CallToolResult.builder()
+ *                   .content(List.of(new McpSchema.TextContent("Result: " + calculate(request.arguments()))))
  *                   .isError(false)
  *                   .build())
  *     .build();
@@ -77,8 +77,8 @@
  * Example of creating a basic asynchronous server: 
{@code
  * McpServer.async(transportProvider)
  *     .serverInfo("my-server", "1.0.0")
- *     .tool(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
- *           (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
+ *     .toolCall(Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
+ *           (exchange, request) -> Mono.fromSupplier(() -> calculate(request.arguments()))
  *               .map(result -> CallToolResult.builder()
  *                   .content(List.of(new McpSchema.TextContent("Result: " + result)))
  *                   .isError(false)
@@ -441,46 +441,6 @@ public AsyncSpecification capabilities(McpSchema.ServerCapabilities serverCap
 			return this;
 		}
 
-		/**
-		 * Adds a single tool with its implementation handler to the server. This is a
-		 * convenience method for registering individual tools without creating a
-		 * {@link McpServerFeatures.AsyncToolSpecification} explicitly.
-		 *
-		 * 

- * Example usage:

{@code
-		 * .tool(
-		 *     Tool.builder().name("calculator").title("Performs calculations").inputSchema(schema).build(),
-		 *     (exchange, args) -> Mono.fromSupplier(() -> calculate(args))
-		 *         .map(result -> CallToolResult.builder()
-		 *                   .content(List.of(new McpSchema.TextContent("Result: " + result)))
-		 *                   .isError(false)
-		 *                   .build()))
-		 * )
-		 * }
- * @param tool The tool definition including name, description, and schema. Must - * not be null. - * @param handler The function that implements the tool's logic. Must not be null. - * The function's first argument is an {@link McpAsyncServerExchange} upon which - * the server can interact with the connected client. The second argument is the - * map of arguments passed to the tool. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if tool or handler is null - * @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool - * calls that require a request object. - */ - @Deprecated - public AsyncSpecification tool(McpSchema.Tool tool, - BiFunction, Mono> handler) { - Assert.notNull(tool, "Tool must not be null"); - Assert.notNull(handler, "Handler must not be null"); - validateToolName(tool.name()); - assertNoDuplicateTool(tool.name()); - - this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler)); - - return this; - } - /** * Adds a single tool with its implementation handler to the server. This is a * convenience method for registering individual tools without creating a @@ -1064,45 +1024,6 @@ public SyncSpecification capabilities(McpSchema.ServerCapabilities serverCapa return this; } - /** - * Adds a single tool with its implementation handler to the server. This is a - * convenience method for registering individual tools without creating a - * {@link McpServerFeatures.SyncToolSpecification} explicitly. - * - *

- * Example usage:

{@code
-		 * .tool(
-		 *     Tool.builder().name("calculator").title("Performs calculations".inputSchema(schema).build(),
-		 *     (exchange, args) -> CallToolResult.builder()
-		 *                   .content(List.of(new McpSchema.TextContent("Result: " + calculate(args))))
-		 *                   .isError(false)
-		 *                   .build())
-		 * )
-		 * }
- * @param tool The tool definition including name, description, and schema. Must - * not be null. - * @param handler The function that implements the tool's logic. Must not be null. - * The function's first argument is an {@link McpSyncServerExchange} upon which - * the server can interact with the connected client. The second argument is the - * list of arguments passed to the tool. - * @return This builder instance for method chaining - * @throws IllegalArgumentException if tool or handler is null - * @deprecated Use {@link #toolCall(McpSchema.Tool, BiFunction)} instead for tool - * calls that require a request object. - */ - @Deprecated - public SyncSpecification tool(McpSchema.Tool tool, - BiFunction, McpSchema.CallToolResult> handler) { - Assert.notNull(tool, "Tool must not be null"); - Assert.notNull(handler, "Handler must not be null"); - validateToolName(tool.name()); - assertNoDuplicateTool(tool.name()); - - this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler)); - - return this; - } - /** * Adds a single tool with its implementation handler to the server. This is a * convenience method for registering individual tools without creating a @@ -1123,7 +1044,7 @@ public SyncSpecification toolCall(McpSchema.Tool tool, validateToolName(tool.name()); assertNoDuplicateTool(tool.name()); - this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, null, handler)); + this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler)); return this; } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java index fe0608b1c..a0cbae0f2 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpServerFeatures.java @@ -223,19 +223,8 @@ record Sync(McpSchema.Implementation serverInfo, McpSchema.ServerCapabilities se * map of tool arguments. */ public record AsyncToolSpecification(McpSchema.Tool tool, - @Deprecated BiFunction, Mono> call, BiFunction> callHandler) { - /** - * @deprecated Use {@link AsyncToolSpecification(McpSchema.Tool, null, - * BiFunction)} instead. - **/ - @Deprecated - public AsyncToolSpecification(McpSchema.Tool tool, - BiFunction, Mono> call) { - this(tool, call, (exchange, toolReq) -> call.apply(exchange, toolReq.arguments())); - } - static AsyncToolSpecification fromSync(SyncToolSpecification syncToolSpec) { return fromSync(syncToolSpec, false); } @@ -247,13 +236,6 @@ static AsyncToolSpecification fromSync(SyncToolSpecification syncToolSpec, boole return null; } - BiFunction, Mono> deprecatedCall = (syncToolSpec - .call() != null) ? (exchange, map) -> { - var toolResult = Mono - .fromCallable(() -> syncToolSpec.call().apply(new McpSyncServerExchange(exchange), map)); - return immediate ? toolResult : toolResult.subscribeOn(Schedulers.boundedElastic()); - } : null; - BiFunction> callHandler = ( exchange, req) -> { var toolResult = Mono @@ -261,7 +243,7 @@ static AsyncToolSpecification fromSync(SyncToolSpecification syncToolSpec, boole return immediate ? toolResult : toolResult.subscribeOn(Schedulers.boundedElastic()); }; - return new AsyncToolSpecification(syncToolSpec.tool(), deprecatedCall, callHandler); + return new AsyncToolSpecification(syncToolSpec.tool(), callHandler); } /** @@ -304,7 +286,7 @@ public AsyncToolSpecification build() { Assert.notNull(tool, "Tool must not be null"); Assert.notNull(callHandler, "Call handler function must not be null"); - return new AsyncToolSpecification(tool, null, callHandler); + return new AsyncToolSpecification(tool, callHandler); } } @@ -523,26 +505,16 @@ static AsyncCompletionSpecification fromSync(SyncCompletionSpecification complet * }
* * @param tool The tool definition including name, description, and parameter schema - * @param call (Deprected) The function that implements the tool's logic, receiving - * arguments and returning results. The function's first argument is an - * {@link McpSyncServerExchange} upon which the server can interact with the connected * @param callHandler The function that implements the tool's logic, receiving a * {@link McpSyncServerExchange} and a * {@link io.modelcontextprotocol.spec.McpSchema.CallToolRequest} and returning * results. The function's first argument is an {@link McpSyncServerExchange} upon - * which the server can interact with the client. The second arguments is a map of - * arguments passed to the tool. + * which the server can interact with the client. The second argument is a request + * object containing the arguments passed to the tool. */ public record SyncToolSpecification(McpSchema.Tool tool, - @Deprecated BiFunction, McpSchema.CallToolResult> call, BiFunction callHandler) { - @Deprecated - public SyncToolSpecification(McpSchema.Tool tool, - BiFunction, McpSchema.CallToolResult> call) { - this(tool, call, (exchange, toolReq) -> call.apply(exchange, toolReq.arguments())); - } - /** * Builder for creating SyncToolSpecification instances. */ @@ -583,7 +555,7 @@ public SyncToolSpecification build() { Assert.notNull(tool, "Tool must not be null"); Assert.notNull(callHandler, "CallTool function must not be null"); - return new SyncToolSpecification(tool, null, callHandler); + return new SyncToolSpecification(tool, callHandler); } } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java index 10f0e5a31..d33299d02 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java @@ -230,21 +230,6 @@ public void notifyPromptsListChanged() { this.asyncServer.notifyPromptsListChanged().block(); } - /** - * This implementation would, incorrectly, broadcast the logging message to all - * connected clients, using a single minLoggingLevel for all of them. Similar to the - * sampling and roots, the logging level should be set per client session and use the - * ServerExchange to send the logging message to the right client. - * @param loggingMessageNotification The logging message to send - * @deprecated Use - * {@link McpSyncServerExchange#loggingNotification(LoggingMessageNotification)} - * instead. - */ - @Deprecated - public void loggingNotification(LoggingMessageNotification loggingMessageNotification) { - this.asyncServer.loggingNotification(loggingMessageNotification).block(); - } - /** * Close the server gracefully. */ 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 d84518778..7037ff293 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 @@ -343,7 +343,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - String jsonError = jsonMapper.writeValueAsString(new McpError("Session ID missing in message endpoint")); + String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND) + .message("Session ID missing in message endpoint") + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -356,7 +358,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_NOT_FOUND); - String jsonError = jsonMapper.writeValueAsString(new McpError("Session not found: " + sessionId)); + String jsonError = jsonMapper.writeValueAsString(McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Session not found: " + sessionId) + .build()); PrintWriter writer = response.getWriter(); writer.write(jsonError); writer.flush(); @@ -383,7 +387,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Error processing message: {}", e.getMessage()); try { - McpError mcpError = new McpError(e.getMessage()); + McpError mcpError = McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message(e.getMessage()) + .build(); response.setContentType(APPLICATION_JSON); response.setCharacterEncoding(UTF_8); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index af25df28e..047aeebe8 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -147,7 +147,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) String accept = request.getHeader(ACCEPT); if (accept == null || !(accept.contains(APPLICATION_JSON) && accept.contains(TEXT_EVENT_STREAM))) { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Both application/json and text/event-stream required in Accept header")); + McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND) + .message("Both application/json and text/event-stream required in Accept header") + .build()); return; } @@ -180,7 +182,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Failed to handle request: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to handle request: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle request: " + e.getMessage()) + .build()); } } else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { @@ -193,22 +197,29 @@ else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { catch (Exception e) { logger.error("Failed to handle notification: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to handle notification: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to handle notification: " + e.getMessage()) + .build()); } } else { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("The server accepts either requests or notifications")); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("The server accepts either requests or notifications") + .build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError("Invalid message format")); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Invalid message format").build()); } catch (Exception e) { logger.error("Unexpected error handling message: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Unexpected error: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Unexpected error: " + e.getMessage()) + .build()); } } 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 ccd1d1ccb..d7561188c 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 @@ -282,7 +282,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND).message(combinedMessage).build()); return; } @@ -430,7 +431,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) && jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) { if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND).message(combinedMessage).build()); return; } @@ -460,7 +462,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) catch (Exception e) { logger.error("Failed to initialize session: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Failed to initialize session: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Failed to initialize session: " + e.getMessage()) + .build()); return; } } @@ -473,7 +477,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (!badRequestErrors.isEmpty()) { String combinedMessage = String.join("; ", badRequestErrors); - this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, new McpError(combinedMessage)); + this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, + McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND).message(combinedMessage).build()); return; } @@ -481,7 +486,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) if (session == null) { this.responseError(response, HttpServletResponse.SC_NOT_FOUND, - new McpError("Session not found: " + sessionId)); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Session not found: " + sessionId) + .build()); return; } @@ -523,19 +530,23 @@ else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { } else { this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Unknown message type")); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Unknown message type").build()); } } catch (IllegalArgumentException | IOException e) { logger.error("Failed to deserialize message: {}", e.getMessage()); this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Invalid message format: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST) + .message("Invalid message format: " + e.getMessage()) + .build()); } catch (Exception e) { logger.error("Error handling message: {}", e.getMessage()); try { this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError("Error processing message: " + e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR) + .message("Error processing message: " + e.getMessage()) + .build()); } catch (IOException ex) { logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); @@ -584,7 +595,9 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response if (request.getHeader(HttpHeaders.MCP_SESSION_ID) == null) { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, - new McpError("Session ID required in mcp-session-id header")); + McpError.builder(McpSchema.ErrorCodes.METHOD_NOT_FOUND) + .message("Session ID required in mcp-session-id header") + .build()); return; } @@ -605,7 +618,7 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response logger.error("Failed to delete session {}: {}", sessionId, e.getMessage()); try { this.responseError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - new McpError(e.getMessage())); + McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message(e.getMessage()).build()); } catch (IOException ex) { logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/StdioServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/StdioServerTransportProvider.java index 68be62931..d288ea3d6 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/StdioServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/StdioServerTransportProvider.java @@ -98,7 +98,7 @@ public void setSessionFactory(McpServerSession.Factory sessionFactory) { @Override public Mono notifyClients(String method, Object params) { if (this.session == null) { - return Mono.error(new McpError("No session to close")); + return Mono.error(new IllegalStateException("No session to close")); } return this.session.sendNotification(method, params) .doOnError(e -> logger.error("Failed to send notification: {}", e.getMessage())); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java index 0ba7ab3b8..80b5ae246 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java @@ -96,21 +96,6 @@ public interface NotificationHandler { } - /** - * Creates a new McpClientSession with the specified configuration and handlers. - * @param requestTimeout Duration to wait for responses - * @param transport Transport implementation for message exchange - * @param requestHandlers Map of method names to request handlers - * @param notificationHandlers Map of method names to notification handlers - * @deprecated Use - * {@link #McpClientSession(Duration, McpClientTransport, Map, Map, Function)} - */ - @Deprecated - public McpClientSession(Duration requestTimeout, McpClientTransport transport, - Map> requestHandlers, Map notificationHandlers) { - this(requestTimeout, transport, requestHandlers, notificationHandlers, Function.identity()); - } - /** * Creates a new McpClientSession with the specified configuration and handlers. * @param requestTimeout Duration to wait for responses diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java index d6e549fdc..a3e7890e6 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java @@ -27,11 +27,6 @@ public McpError(JSONRPCError jsonRpcError) { this.jsonRpcError = jsonRpcError; } - @Deprecated - public McpError(Object error) { - super(error.toString()); - } - public JSONRPCError getJsonRpcError() { return jsonRpcError; } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 97bde0b10..bb9cead7e 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -41,9 +41,6 @@ public final class McpSchema { private McpSchema() { } - @Deprecated - public static final String LATEST_PROTOCOL_VERSION = ProtocolVersions.MCP_2025_11_25; - public static final String JSONRPC_VERSION = "2.0"; public static final String FIRST_PAGE = null; @@ -798,35 +795,6 @@ public record Resource( // @formatter:off @JsonProperty("annotations") Annotations annotations, @JsonProperty("_meta") Map meta) implements ResourceContent { // @formatter:on - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link Resource#builder()} instead. - */ - @Deprecated - public Resource(String uri, String name, String title, String description, String mimeType, Long size, - Annotations annotations) { - this(uri, name, title, description, mimeType, size, annotations, null); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link Resource#builder()} instead. - */ - @Deprecated - public Resource(String uri, String name, String description, String mimeType, Long size, - Annotations annotations) { - this(uri, name, null, description, mimeType, size, annotations, null); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link Resource#builder()} instead. - */ - @Deprecated - public Resource(String uri, String name, String description, String mimeType, Annotations annotations) { - this(uri, name, null, description, mimeType, null, annotations, null); - } - public static Builder builder() { return new Builder(); } @@ -1592,36 +1560,6 @@ public record CallToolResult( // @formatter:off @JsonProperty("structuredContent") Object structuredContent, @JsonProperty("_meta") Map meta) implements Result { // @formatter:on - /** - * @deprecated use the builder instead. - */ - @Deprecated - public CallToolResult(List content, Boolean isError) { - this(content, isError, (Object) null, null); - } - - /** - * @deprecated use the builder instead. - */ - @Deprecated - public CallToolResult(List content, Boolean isError, Map structuredContent) { - this(content, isError, structuredContent, null); - } - - /** - * Creates a new instance of {@link CallToolResult} with a string containing the - * tool result. - * @param content The content of the tool result. This will be mapped to a - * one-sized list with a {@link TextContent} element. - * @param isError If true, indicates that the tool execution failed and the - * content contains error information. If false or absent, indicates successful - * execution. - */ - @Deprecated - public CallToolResult(String content, Boolean isError) { - this(List.of(new TextContent(content)), isError, null); - } - /** * Creates a builder for {@link CallToolResult}. * @return a new builder instance @@ -2619,33 +2557,6 @@ public TextContent(Annotations annotations, String text) { public TextContent(String content) { this(null, content, null); } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link TextContent#TextContent(Annotations, String)} instead. - */ - @Deprecated - public TextContent(List audience, Double priority, String content) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, content, null); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link TextContent#annotations()} instead. - */ - @Deprecated - public List audience() { - return annotations == null ? null : annotations.audience(); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link TextContent#annotations()} instead. - */ - @Deprecated - public Double priority() { - return annotations == null ? null : annotations.priority(); - } } /** @@ -2668,34 +2579,6 @@ public record ImageContent( // @formatter:off public ImageContent(Annotations annotations, String data, String mimeType) { this(annotations, data, mimeType, null); } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link ImageContent#ImageContent(Annotations, String, String)} instead. - */ - @Deprecated - public ImageContent(List audience, Double priority, String data, String mimeType) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, data, mimeType, - null); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link ImageContent#annotations()} instead. - */ - @Deprecated - public List audience() { - return annotations == null ? null : annotations.audience(); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link ImageContent#annotations()} instead. - */ - @Deprecated - public Double priority() { - return annotations == null ? null : annotations.priority(); - } } /** @@ -2742,34 +2625,6 @@ public record EmbeddedResource( // @formatter:off public EmbeddedResource(Annotations annotations, ResourceContents resource) { this(annotations, resource, null); } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link EmbeddedResource#EmbeddedResource(Annotations, ResourceContents)} - * instead. - */ - @Deprecated - public EmbeddedResource(List audience, Double priority, ResourceContents resource) { - this(audience != null || priority != null ? new Annotations(audience, priority) : null, resource, null); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link EmbeddedResource#annotations()} instead. - */ - @Deprecated - public List audience() { - return annotations == null ? null : annotations.audience(); - } - - /** - * @deprecated Only exists for backwards-compatibility purposes. Use - * {@link EmbeddedResource#annotations()} instead. - */ - @Deprecated - public Double priority() { - return annotations == null ? null : annotations.priority(); - } } /** diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java index 241f7d8b5..ecb1dafd8 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java @@ -86,34 +86,6 @@ public McpServerSession(String id, Duration requestTimeout, McpServerTransport t this.notificationHandlers = notificationHandlers; } - /** - * Creates a new server session with the given parameters and the transport to use. - * @param id session id - * @param transport the transport to use - * @param initHandler called when a - * {@link io.modelcontextprotocol.spec.McpSchema.InitializeRequest} is received by the - * server - * @param initNotificationHandler called when a - * {@link io.modelcontextprotocol.spec.McpSchema#METHOD_NOTIFICATION_INITIALIZED} is - * received. - * @param requestHandlers map of request handlers to use - * @param notificationHandlers map of notification handlers to use - * @deprecated Use - * {@link #McpServerSession(String, Duration, McpServerTransport, McpInitRequestHandler, Map, Map)} - */ - @Deprecated - public McpServerSession(String id, Duration requestTimeout, McpServerTransport transport, - McpInitRequestHandler initHandler, InitNotificationHandler initNotificationHandler, - Map> requestHandlers, - Map notificationHandlers) { - this.id = id; - this.requestTimeout = requestTimeout; - this.transport = transport; - this.initRequestHandler = initHandler; - this.requestHandlers = requestHandlers; - this.notificationHandlers = notificationHandlers; - } - /** * Retrieve the session id. * @return session id @@ -355,23 +327,6 @@ public void close() { this.transport.close(); } - /** - * Request handler for the initialization request. - * - * @deprecated Use {@link McpInitRequestHandler} - */ - @Deprecated - public interface InitRequestHandler { - - /** - * Handles the initialization request. - * @param initializeRequest the initialization request by the client - * @return a Mono that will emit the result of the initialization - */ - Mono handle(McpSchema.InitializeRequest initializeRequest); - - } - /** * Notification handler for the initialization notification from the client. */ @@ -385,46 +340,6 @@ public interface InitNotificationHandler { } - /** - * A handler for client-initiated notifications. - * - * @deprecated Use {@link McpNotificationHandler} - */ - @Deprecated - public interface NotificationHandler { - - /** - * Handles a notification from the client. - * @param exchange the exchange associated with the client that allows calling - * back to the connected client or inspecting its capabilities. - * @param params the parameters of the notification. - * @return a Mono that completes once the notification is handled. - */ - Mono handle(McpAsyncServerExchange exchange, Object params); - - } - - /** - * A handler for client-initiated requests. - * - * @param the type of the response that is expected as a result of handling the - * request. - * @deprecated Use {@link McpRequestHandler} - */ - @Deprecated - public interface RequestHandler { - - /** - * Handles a request from the client. - * @param exchange the exchange associated with the client that allows calling - * back to the connected client or inspecting its capabilities. - * @param params the parameters of the request. - * @return a Mono that will emit the response to the request. - */ - Mono handle(McpAsyncServerExchange exchange, Object params); - - } - /** * Factory for creating server sessions which delegate to a provided 1:1 transport * with a connected client. diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java b/mcp-core/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java index f9fc41b7a..061a95e69 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java @@ -9,10 +9,10 @@ import java.util.function.BiConsumer; import java.util.function.Function; -import io.modelcontextprotocol.json.McpJsonDefaults; import io.modelcontextprotocol.json.TypeRef; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.ProtocolVersions; import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; import reactor.core.publisher.Mono; @@ -29,7 +29,7 @@ public class MockMcpClientTransport implements McpClientTransport { private final BiConsumer interceptor; - private String protocolVersion = McpSchema.LATEST_PROTOCOL_VERSION; + private String protocolVersion = ProtocolVersions.MCP_2025_11_25; public MockMcpClientTransport() { this((t, msg) -> { diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java index c16b06a45..897ae2ccc 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java @@ -56,7 +56,6 @@ void builderShouldCreateValidAsyncToolSpecification() { assertThat(specification).isNotNull(); assertThat(specification.tool()).isEqualTo(tool); assertThat(specification.callHandler()).isNotNull(); - assertThat(specification.call()).isNull(); // deprecated field should be null } @Test @@ -107,12 +106,8 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder() .tool(tool) - .callHandler((exchange, request) -> { - return Mono.just(CallToolResult.builder() - .content(List.of(new TextContent(expectedResult))) - .isError(false) - .build()); - }) + .callHandler((exchange, request) -> Mono.just( + CallToolResult.builder().content(List.of(new TextContent(expectedResult))).isError(false).build())) .build(); CallToolRequest request = new CallToolRequest("calculator", Map.of()); @@ -127,54 +122,6 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { }).verifyComplete(); } - @Test - @SuppressWarnings("deprecation") - void deprecatedConstructorShouldWorkCorrectly() { - Tool tool = McpSchema.Tool.builder() - .name("deprecated-tool") - .title("A deprecated tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - String expectedResult = "deprecated result"; - - // Test the deprecated constructor that takes a 'call' function - McpServerFeatures.AsyncToolSpecification specification = new McpServerFeatures.AsyncToolSpecification(tool, - (exchange, - arguments) -> Mono.just(CallToolResult.builder() - .content(List.of(new TextContent(expectedResult))) - .isError(false) - .build())); - - assertThat(specification).isNotNull(); - assertThat(specification.tool()).isEqualTo(tool); - assertThat(specification.call()).isNotNull(); // deprecated field should be set - assertThat(specification.callHandler()).isNotNull(); // should be automatically - // created - - // Test that the callTool function works (it should delegate to the call function) - CallToolRequest request = new CallToolRequest("deprecated-tool", Map.of("arg1", "value1")); - Mono resultMono = specification.callHandler().apply(null, request); - - StepVerifier.create(resultMono).assertNext(result -> { - assertThat(result).isNotNull(); - assertThat(result.content()).hasSize(1); - assertThat(result.content().get(0)).isInstanceOf(TextContent.class); - assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult); - assertThat(result.isError()).isFalse(); - }).verifyComplete(); - - // Test that the deprecated call function also works directly - Mono callResultMono = specification.call().apply(null, request.arguments()); - - StepVerifier.create(callResultMono).assertNext(result -> { - assertThat(result).isNotNull(); - assertThat(result.content()).hasSize(1); - assertThat(result.content().get(0)).isInstanceOf(TextContent.class); - assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult); - assertThat(result.isError()).isFalse(); - }).verifyComplete(); - } - @Test void fromSyncShouldConvertSyncToolSpecificationCorrectly() { Tool tool = McpSchema.Tool.builder() @@ -200,8 +147,6 @@ void fromSyncShouldConvertSyncToolSpecificationCorrectly() { assertThat(asyncSpec).isNotNull(); assertThat(asyncSpec.tool()).isEqualTo(tool); assertThat(asyncSpec.callHandler()).isNotNull(); - assertThat(asyncSpec.call()).isNull(); // should be null since sync spec doesn't - // have deprecated call // Test that the converted async specification works correctly CallToolRequest request = new CallToolRequest("sync-tool", Map.of("param", "value")); @@ -216,59 +161,6 @@ void fromSyncShouldConvertSyncToolSpecificationCorrectly() { }).verifyComplete(); } - @Test - @SuppressWarnings("deprecation") - void fromSyncShouldConvertSyncToolSpecificationWithDeprecatedCallCorrectly() { - Tool tool = McpSchema.Tool.builder() - .name("sync-deprecated-tool") - .title("A sync tool with deprecated call") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - String expectedResult = "sync deprecated result"; - McpAsyncServerExchange nullExchange = null; // Mock or create a suitable exchange - // if needed - - // Create a sync tool specification using the deprecated constructor - McpServerFeatures.SyncToolSpecification syncSpec = new McpServerFeatures.SyncToolSpecification(tool, - (exchange, arguments) -> CallToolResult.builder() - .content(List.of(new TextContent(expectedResult))) - .isError(false) - .build()); - - // Convert to async using fromSync - McpServerFeatures.AsyncToolSpecification asyncSpec = McpServerFeatures.AsyncToolSpecification - .fromSync(syncSpec); - - assertThat(asyncSpec).isNotNull(); - assertThat(asyncSpec.tool()).isEqualTo(tool); - assertThat(asyncSpec.callHandler()).isNotNull(); - assertThat(asyncSpec.call()).isNotNull(); // should be set since sync spec has - // deprecated call - - // Test that the converted async specification works correctly via callTool - CallToolRequest request = new CallToolRequest("sync-deprecated-tool", Map.of("param", "value")); - Mono resultMono = asyncSpec.callHandler().apply(nullExchange, request); - - StepVerifier.create(resultMono).assertNext(result -> { - assertThat(result).isNotNull(); - assertThat(result.content()).hasSize(1); - assertThat(result.content().get(0)).isInstanceOf(TextContent.class); - assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult); - assertThat(result.isError()).isFalse(); - }).verifyComplete(); - - // Test that the deprecated call function also works - Mono callResultMono = asyncSpec.call().apply(nullExchange, request.arguments()); - - StepVerifier.create(callResultMono).assertNext(result -> { - assertThat(result).isNotNull(); - assertThat(result.content()).hasSize(1); - assertThat(result.content().get(0)).isInstanceOf(TextContent.class); - assertThat(((TextContent) result.content().get(0)).text()).isEqualTo(expectedResult); - assertThat(result.isError()).isFalse(); - }).verifyComplete(); - } - @Test void fromSyncShouldReturnNullWhenSyncSpecIsNull() { assertThat(McpServerFeatures.AsyncToolSpecification.fromSync(null)).isNull(); @@ -302,7 +194,8 @@ void tearDown() { void defaultShouldThrowOnInvalidName() { Tool invalidTool = Tool.builder().name("invalid tool name").build(); - assertThatThrownBy(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null)) + assertThatThrownBy( + () -> McpServer.async(transportProvider).toolCall(invalidTool, (exchange, request) -> null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("invalid characters"); } @@ -312,7 +205,7 @@ void lenientDefaultShouldLogOnInvalidName() { System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false"); Tool invalidTool = Tool.builder().name("invalid tool name").build(); - assertThatCode(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null)) + assertThatCode(() -> McpServer.async(transportProvider).toolCall(invalidTool, (exchange, request) -> null)) .doesNotThrowAnyException(); assertThat(logAppender.list).hasSize(1); } @@ -323,7 +216,7 @@ void lenientConfigurationShouldLogOnInvalidName() { assertThatCode(() -> McpServer.async(transportProvider) .strictToolNameValidation(false) - .tool(invalidTool, (exchange, args) -> null)).doesNotThrowAnyException(); + .toolCall(invalidTool, (exchange, request) -> null)).doesNotThrowAnyException(); assertThat(logAppender.list).hasSize(1); } @@ -334,7 +227,7 @@ void serverConfigurationShouldOverrideDefault() { assertThatThrownBy(() -> McpServer.async(transportProvider) .strictToolNameValidation(true) - .tool(invalidTool, (exchange, args) -> null)).isInstanceOf(IllegalArgumentException.class) + .toolCall(invalidTool, (exchange, request) -> null)).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("invalid characters"); } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java index 640d34c9c..e6161a59f 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java @@ -215,7 +215,7 @@ void testGetClientInfo() { @Test void testLoggingNotificationWithNullMessage() { StepVerifier.create(exchange.loggingNotification(null)).verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class).hasMessage("Logging message must not be null"); + assertThat(error).isInstanceOf(IllegalStateException.class).hasMessage("Logging message must not be null"); }); } @@ -301,7 +301,8 @@ void testLoggingNotificationWithSessionError() { @Test void testCreateElicitationWithNullCapabilities() { // Given - Create exchange with null capabilities - McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange(mockSession, null, clientInfo); + McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", mockSession, + null, clientInfo, McpTransportContext.EMPTY); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() .message("Please provide your name") @@ -309,7 +310,7 @@ void testCreateElicitationWithNullCapabilities() { StepVerifier.create(exchangeWithNullCapabilities.createElicitation(elicitRequest)) .verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class) + assertThat(error).isInstanceOf(IllegalStateException.class) .hasMessage("Client must be initialized. Call the initialize method first!"); }); @@ -324,15 +325,15 @@ void testCreateElicitationWithoutElicitationCapabilities() { .roots(true) .build(); - McpAsyncServerExchange exchangeWithoutElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithoutElicitation, clientInfo); + McpAsyncServerExchange exchangeWithoutElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithoutElicitation, clientInfo, McpTransportContext.EMPTY); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() .message("Please provide your name") .build(); StepVerifier.create(exchangeWithoutElicitation.createElicitation(elicitRequest)).verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class) + assertThat(error).isInstanceOf(IllegalStateException.class) .hasMessage("Client must be configured with elicitation capabilities"); }); @@ -348,8 +349,8 @@ void testCreateElicitationWithComplexRequest() { .elicitation() .build(); - McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); // Create a complex elicit request with schema java.util.Map requestedSchema = new java.util.HashMap<>(); @@ -391,8 +392,8 @@ void testCreateElicitationWithDeclineAction() { .elicitation() .build(); - McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() .message("Please provide sensitive information") @@ -418,8 +419,8 @@ void testCreateElicitationWithCancelAction() { .elicitation() .build(); - McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() .message("Please provide your information") @@ -445,8 +446,8 @@ void testCreateElicitationWithSessionError() { .elicitation() .build(); - McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange exchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() .message("Please provide your name") @@ -467,7 +468,8 @@ void testCreateElicitationWithSessionError() { @Test void testCreateMessageWithNullCapabilities() { - McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange(mockSession, null, clientInfo); + McpAsyncServerExchange exchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", mockSession, + null, clientInfo, McpTransportContext.EMPTY); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() .messages(Arrays @@ -476,7 +478,7 @@ void testCreateMessageWithNullCapabilities() { StepVerifier.create(exchangeWithNullCapabilities.createMessage(createMessageRequest)) .verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class) + assertThat(error).isInstanceOf(IllegalStateException.class) .hasMessage("Client must be initialized. Call the initialize method first!"); }); @@ -492,8 +494,8 @@ void testCreateMessageWithoutSamplingCapabilities() { .roots(true) .build(); - McpAsyncServerExchange exchangeWithoutSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithoutSampling, clientInfo); + McpAsyncServerExchange exchangeWithoutSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithoutSampling, clientInfo, McpTransportContext.EMPTY); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() .messages(Arrays @@ -501,7 +503,7 @@ void testCreateMessageWithoutSamplingCapabilities() { .build(); StepVerifier.create(exchangeWithoutSampling.createMessage(createMessageRequest)).verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class) + assertThat(error).isInstanceOf(IllegalStateException.class) .hasMessage("Client must be configured with sampling capabilities"); }); @@ -517,8 +519,8 @@ void testCreateMessageWithBasicRequest() { .sampling() .build(); - McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange(mockSession, capabilitiesWithSampling, - clientInfo); + McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() .messages(Arrays @@ -553,8 +555,8 @@ void testCreateMessageWithImageContent() { .sampling() .build(); - McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange(mockSession, capabilitiesWithSampling, - clientInfo); + McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); // Create request with image content McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -588,8 +590,8 @@ void testCreateMessageWithSessionError() { .sampling() .build(); - McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange(mockSession, capabilitiesWithSampling, - clientInfo); + McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() .messages(Arrays @@ -612,8 +614,8 @@ void testCreateMessageWithIncludeContext() { .sampling() .build(); - McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange(mockSession, capabilitiesWithSampling, - clientInfo); + McpAsyncServerExchange exchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() .messages(Arrays.asList(new McpSchema.SamplingMessage(McpSchema.Role.USER, @@ -662,7 +664,7 @@ void testPingWithSuccessfulResponse() { @Test void testPingWithMcpError() { // Given - Mock an MCP-specific error during ping - McpError mcpError = new McpError("Server unavailable"); + McpError mcpError = McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message("Server unavailable").build(); when(mockSession.sendRequest(eq(McpSchema.METHOD_PING), eq(null), any(TypeRef.class))) .thenReturn(Mono.error(mcpError)); diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/McpSyncServerExchangeTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/McpSyncServerExchangeTests.java index 069d0f896..fba733c9a 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/McpSyncServerExchangeTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/McpSyncServerExchangeTests.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpServerSession; @@ -54,7 +55,8 @@ void setUp() { clientInfo = new McpSchema.Implementation("test-client", "1.0.0"); - asyncExchange = new McpAsyncServerExchange(mockSession, clientCapabilities, clientInfo); + asyncExchange = new McpAsyncServerExchange("testSessionId", mockSession, clientCapabilities, clientInfo, + McpTransportContext.EMPTY); exchange = new McpSyncServerExchange(asyncExchange); } @@ -212,7 +214,7 @@ void testGetClientInfo() { @Test void testLoggingNotificationWithNullMessage() { - assertThatThrownBy(() -> exchange.loggingNotification(null)).isInstanceOf(McpError.class) + assertThatThrownBy(() -> exchange.loggingNotification(null)).isInstanceOf(IllegalStateException.class) .hasMessage("Logging message must not be null"); } @@ -294,8 +296,8 @@ void testLoggingNotificationWithSessionError() { @Test void testCreateElicitationWithNullCapabilities() { // Given - Create exchange with null capabilities - McpAsyncServerExchange asyncExchangeWithNullCapabilities = new McpAsyncServerExchange(mockSession, null, - clientInfo); + McpAsyncServerExchange asyncExchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", + mockSession, null, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithNullCapabilities = new McpSyncServerExchange( asyncExchangeWithNullCapabilities); @@ -304,7 +306,7 @@ void testCreateElicitationWithNullCapabilities() { .build(); assertThatThrownBy(() -> exchangeWithNullCapabilities.createElicitation(elicitRequest)) - .isInstanceOf(McpError.class) + .isInstanceOf(IllegalStateException.class) .hasMessage("Client must be initialized. Call the initialize method first!"); // Verify that sendRequest was never called due to null capabilities @@ -318,8 +320,8 @@ void testCreateElicitationWithoutElicitationCapabilities() { .roots(true) .build(); - McpAsyncServerExchange asyncExchangeWithoutElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithoutElicitation, clientInfo); + McpAsyncServerExchange asyncExchangeWithoutElicitation = new McpAsyncServerExchange("testSessionId", + mockSession, capabilitiesWithoutElicitation, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithoutElicitation = new McpSyncServerExchange(asyncExchangeWithoutElicitation); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() @@ -327,7 +329,7 @@ void testCreateElicitationWithoutElicitationCapabilities() { .build(); assertThatThrownBy(() -> exchangeWithoutElicitation.createElicitation(elicitRequest)) - .isInstanceOf(McpError.class) + .isInstanceOf(IllegalStateException.class) .hasMessage("Client must be configured with elicitation capabilities"); // Verify that sendRequest was never called due to missing elicitation @@ -342,8 +344,8 @@ void testCreateElicitationWithComplexRequest() { .elicitation() .build(); - McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation); // Create a complex elicit request with schema @@ -386,8 +388,8 @@ void testCreateElicitationWithDeclineAction() { .elicitation() .build(); - McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() @@ -414,8 +416,8 @@ void testCreateElicitationWithCancelAction() { .elicitation() .build(); - McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() @@ -442,8 +444,8 @@ void testCreateElicitationWithSessionError() { .elicitation() .build(); - McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange(mockSession, - capabilitiesWithElicitation, clientInfo); + McpAsyncServerExchange asyncExchangeWithElicitation = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithElicitation = new McpSyncServerExchange(asyncExchangeWithElicitation); McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder() @@ -465,8 +467,8 @@ void testCreateElicitationWithSessionError() { @Test void testCreateMessageWithNullCapabilities() { - McpAsyncServerExchange asyncExchangeWithNullCapabilities = new McpAsyncServerExchange(mockSession, null, - clientInfo); + McpAsyncServerExchange asyncExchangeWithNullCapabilities = new McpAsyncServerExchange("testSessionId", + mockSession, null, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithNullCapabilities = new McpSyncServerExchange( asyncExchangeWithNullCapabilities); @@ -476,7 +478,7 @@ void testCreateMessageWithNullCapabilities() { .build(); assertThatThrownBy(() -> exchangeWithNullCapabilities.createMessage(createMessageRequest)) - .isInstanceOf(McpError.class) + .isInstanceOf(IllegalStateException.class) .hasMessage("Client must be initialized. Call the initialize method first!"); // Verify that sendRequest was never called due to null capabilities @@ -491,8 +493,8 @@ void testCreateMessageWithoutSamplingCapabilities() { .roots(true) .build(); - McpAsyncServerExchange asyncExchangeWithoutSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithoutSampling, clientInfo); + McpAsyncServerExchange asyncExchangeWithoutSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithoutSampling, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithoutSampling = new McpSyncServerExchange(asyncExchangeWithoutSampling); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -501,7 +503,7 @@ void testCreateMessageWithoutSamplingCapabilities() { .build(); assertThatThrownBy(() -> exchangeWithoutSampling.createMessage(createMessageRequest)) - .isInstanceOf(McpError.class) + .isInstanceOf(IllegalStateException.class) .hasMessage("Client must be configured with sampling capabilities"); // Verify that sendRequest was never called due to missing sampling capabilities @@ -516,8 +518,8 @@ void testCreateMessageWithBasicRequest() { .sampling() .build(); - McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithSampling, clientInfo); + McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -553,8 +555,8 @@ void testCreateMessageWithImageContent() { .sampling() .build(); - McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithSampling, clientInfo); + McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling); // Create request with image content @@ -589,8 +591,8 @@ void testCreateMessageWithSessionError() { .sampling() .build(); - McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithSampling, clientInfo); + McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -614,8 +616,8 @@ void testCreateMessageWithIncludeContext() { .sampling() .build(); - McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange(mockSession, - capabilitiesWithSampling, clientInfo); + McpAsyncServerExchange asyncExchangeWithSampling = new McpAsyncServerExchange("testSessionId", mockSession, + capabilitiesWithSampling, clientInfo, McpTransportContext.EMPTY); McpSyncServerExchange exchangeWithSampling = new McpSyncServerExchange(asyncExchangeWithSampling); McpSchema.CreateMessageRequest createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -662,7 +664,7 @@ void testPingWithSuccessfulResponse() { @Test void testPingWithMcpError() { // Given - Mock an MCP-specific error during ping - McpError mcpError = new McpError("Server unavailable"); + McpError mcpError = McpError.builder(McpSchema.ErrorCodes.INTERNAL_ERROR).message("Server unavailable").build(); when(mockSession.sendRequest(eq(McpSchema.METHOD_PING), eq(null), any(TypeRef.class))) .thenReturn(Mono.error(mcpError)); diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/ResourceTemplateListingTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/ResourceTemplateListingTest.java index 61703c306..993ca717e 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/ResourceTemplateListingTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/ResourceTemplateListingTest.java @@ -41,10 +41,26 @@ void testTemplateResourcesFilteredFromRegularListing() { void testResourceListingWithMixedResources() { // Create resource list with both regular and template resources List allResources = List.of( - new McpSchema.Resource("file:///test/doc1.txt", "Document 1", "text/plain", null, null), - new McpSchema.Resource("file:///test/doc2.txt", "Document 2", "text/plain", null, null), - new McpSchema.Resource("file:///test/{type}/document.txt", "Typed Document", "text/plain", null, null), - new McpSchema.Resource("file:///users/{userId}/files/{fileId}", "User File", "text/plain", null, null)); + McpSchema.Resource.builder() + .uri("file:///test/doc1.txt") + .name("Document 1") + .mimeType("text/plain") + .build(), + McpSchema.Resource.builder() + .uri("file:///test/doc2.txt") + .name("Document 2") + .mimeType("text/plain") + .build(), + McpSchema.Resource.builder() + .uri("file:///test/{type}/document.txt") + .name("Typed Document") + .mimeType("text/plain") + .build(), + McpSchema.Resource.builder() + .uri("file:///users/{userId}/files/{fileId}") + .name("User File") + .mimeType("text/plain") + .build()); // Apply the filter logic from McpAsyncServer line 438 List filteredResources = allResources.stream() @@ -61,9 +77,16 @@ void testResourceListingWithMixedResources() { void testResourceTemplatesListedSeparately() { // Create mixed resources List resources = List.of( - new McpSchema.Resource("file:///test/regular.txt", "Regular Resource", "text/plain", null, null), - new McpSchema.Resource("file:///test/user/{userId}/profile.txt", "User Profile", "text/plain", null, - null)); + McpSchema.Resource.builder() + .uri("file:///test/regular.txt") + .name("Regular Resource") + .mimeType("text/plain") + .build(), + McpSchema.Resource.builder() + .uri("file:///test/user/{userId}/profile.txt") + .name("User Profile") + .mimeType("text/plain") + .build()); // Create explicit resource template McpSchema.ResourceTemplate explicitTemplate = new McpSchema.ResourceTemplate( diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java index a2030e468..54c45e561 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java @@ -51,7 +51,6 @@ void builderShouldCreateValidSyncToolSpecification() { assertThat(specification).isNotNull(); assertThat(specification.tool()).isEqualTo(tool); assertThat(specification.callHandler()).isNotNull(); - assertThat(specification.call()).isNull(); // deprecated field should be null } @Test @@ -140,7 +139,8 @@ void tearDown() { void defaultShouldThrowOnInvalidName() { Tool invalidTool = Tool.builder().name("invalid tool name").build(); - assertThatThrownBy(() -> McpServer.sync(transportProvider).tool(invalidTool, (exchange, args) -> null)) + assertThatThrownBy( + () -> McpServer.sync(transportProvider).toolCall(invalidTool, (exchange, request) -> null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("invalid characters"); } @@ -150,7 +150,7 @@ void lenientDefaultShouldLogOnInvalidName() { System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false"); Tool invalidTool = Tool.builder().name("invalid tool name").build(); - assertThatCode(() -> McpServer.sync(transportProvider).tool(invalidTool, (exchange, args) -> null)) + assertThatCode(() -> McpServer.sync(transportProvider).toolCall(invalidTool, (exchange, request) -> null)) .doesNotThrowAnyException(); assertThat(logAppender.list).hasSize(1); } @@ -161,7 +161,7 @@ void lenientConfigurationShouldLogOnInvalidName() { assertThatCode(() -> McpServer.sync(transportProvider) .strictToolNameValidation(false) - .tool(invalidTool, (exchange, args) -> null)).doesNotThrowAnyException(); + .toolCall(invalidTool, (exchange, request) -> null)).doesNotThrowAnyException(); assertThat(logAppender.list).hasSize(1); } @@ -172,7 +172,7 @@ void serverConfigurationShouldOverrideDefault() { assertThatThrownBy(() -> McpServer.sync(transportProvider) .strictToolNameValidation(true) - .tool(invalidTool, (exchange, args) -> null)).isInstanceOf(IllegalArgumentException.class) + .toolCall(invalidTool, (exchange, request) -> null)).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("invalid characters"); } diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java deleted file mode 100644 index 4c69e9d34..000000000 --- a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapper.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2025 - 2025 the original author or authors. - */ - -package io.modelcontextprotocol.json.jackson; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.json.McpJsonMapper; -import io.modelcontextprotocol.json.TypeRef; - -import java.io.IOException; - -/** - * Jackson-based implementation of JsonMapper. Wraps a Jackson ObjectMapper but keeps the - * SDK decoupled from Jackson at the API level. - * - * @deprecated since 18.0.0, use - * {@link io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapper} instead. Will be - * removed in 19.0.0 - */ -@Deprecated(forRemoval = true, since = "18.0.0") -public final class JacksonMcpJsonMapper implements McpJsonMapper { - - private final ObjectMapper objectMapper; - - /** - * Constructs a new JacksonMcpJsonMapper instance with the given ObjectMapper. - * @param objectMapper the ObjectMapper to be used for JSON serialization and - * deserialization. Must not be null. - * @throws IllegalArgumentException if the provided ObjectMapper is null. - */ - public JacksonMcpJsonMapper(ObjectMapper objectMapper) { - if (objectMapper == null) { - throw new IllegalArgumentException("ObjectMapper must not be null"); - } - this.objectMapper = objectMapper; - } - - /** - * Returns the underlying Jackson {@link ObjectMapper} used for JSON serialization and - * deserialization. - * @return the ObjectMapper instance - */ - public ObjectMapper getObjectMapper() { - return objectMapper; - } - - @Override - public T readValue(String content, Class type) throws IOException { - return objectMapper.readValue(content, type); - } - - @Override - public T readValue(byte[] content, Class type) throws IOException { - return objectMapper.readValue(content, type); - } - - @Override - public T readValue(String content, TypeRef type) throws IOException { - JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType()); - return objectMapper.readValue(content, javaType); - } - - @Override - public T readValue(byte[] content, TypeRef type) throws IOException { - JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType()); - return objectMapper.readValue(content, javaType); - } - - @Override - public T convertValue(Object fromValue, Class type) { - return objectMapper.convertValue(fromValue, type); - } - - @Override - public T convertValue(Object fromValue, TypeRef type) { - JavaType javaType = objectMapper.getTypeFactory().constructType(type.getType()); - return objectMapper.convertValue(fromValue, javaType); - } - - @Override - public String writeValueAsString(Object value) throws IOException { - return objectMapper.writeValueAsString(value); - } - - @Override - public byte[] writeValueAsBytes(Object value) throws IOException { - return objectMapper.writeValueAsBytes(value); - } - -} diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java deleted file mode 100644 index 8a7c0f42a..000000000 --- a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/jackson/JacksonMcpJsonMapperSupplier.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025 - 2025 the original author or authors. - */ - -package io.modelcontextprotocol.json.jackson; - -import io.modelcontextprotocol.json.McpJsonMapper; -import io.modelcontextprotocol.json.McpJsonMapperSupplier; - -/** - * A supplier of {@link McpJsonMapper} instances that uses the Jackson library for JSON - * serialization and deserialization. - *

- * This implementation provides a {@link McpJsonMapper} backed by a Jackson - * {@link com.fasterxml.jackson.databind.ObjectMapper}. - * - * @deprecated since 18.0.0, use - * {@link io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapperSupplier} instead. - * Will be removed in 19.0.0. - */ -@Deprecated(forRemoval = true, since = "18.0.0") -public class JacksonMcpJsonMapperSupplier implements McpJsonMapperSupplier { - - /** - * Returns a new instance of {@link McpJsonMapper} that uses the Jackson library for - * JSON serialization and deserialization. - *

- * The returned {@link McpJsonMapper} is backed by a new instance of - * {@link com.fasterxml.jackson.databind.ObjectMapper}. - * @return a new {@link McpJsonMapper} instance - */ - @Override - public McpJsonMapper get() { - return new JacksonMcpJsonMapper(new com.fasterxml.jackson.databind.ObjectMapper()); - } - -} diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java deleted file mode 100644 index 002a9d2a9..000000000 --- a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - */ -package io.modelcontextprotocol.json.schema.jackson; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.Schema; -import com.networknt.schema.SchemaRegistry; -import com.networknt.schema.Error; -import com.networknt.schema.dialect.Dialects; -import io.modelcontextprotocol.json.schema.JsonSchemaValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Default implementation of the {@link JsonSchemaValidator} interface. This class - * provides methods to validate structured content against a JSON schema. It uses the - * NetworkNT JSON Schema Validator library for validation. - * - * @author Christian Tzolov - * @deprecated since 18.0.0, use - * {@link io.modelcontextprotocol.json.schema.jackson2.DefaultJsonSchemaValidator} - * instead. Will be removed in 19.0.0. - */ -@Deprecated(forRemoval = true, since = "18.0.0") -public class DefaultJsonSchemaValidator implements JsonSchemaValidator { - - private static final Logger logger = LoggerFactory.getLogger(DefaultJsonSchemaValidator.class); - - private final ObjectMapper objectMapper; - - private final SchemaRegistry schemaFactory; - - // TODO: Implement a strategy to purge the cache (TTL, size limit, etc.) - private final ConcurrentHashMap schemaCache; - - public DefaultJsonSchemaValidator() { - this(new ObjectMapper()); - } - - public DefaultJsonSchemaValidator(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - this.schemaFactory = SchemaRegistry.withDialect(Dialects.getDraft202012()); - this.schemaCache = new ConcurrentHashMap<>(); - } - - @Override - public ValidationResponse validate(Map schema, Object structuredContent) { - - if (schema == null) { - throw new IllegalArgumentException("Schema must not be null"); - } - if (structuredContent == null) { - throw new IllegalArgumentException("Structured content must not be null"); - } - - try { - - JsonNode jsonStructuredOutput = (structuredContent instanceof String) - ? this.objectMapper.readTree((String) structuredContent) - : this.objectMapper.valueToTree(structuredContent); - - List validationResult = this.getOrCreateJsonSchema(schema).validate(jsonStructuredOutput); - - // Check if validation passed - if (!validationResult.isEmpty()) { - return ValidationResponse - .asInvalid("Validation failed: structuredContent does not match tool outputSchema. " - + "Validation errors: " + validationResult); - } - - return ValidationResponse.asValid(jsonStructuredOutput.toString()); - - } - catch (JsonProcessingException e) { - logger.error("Failed to validate CallToolResult: Error parsing schema: {}", e); - return ValidationResponse.asInvalid("Error parsing tool JSON Schema: " + e.getMessage()); - } - catch (Exception e) { - logger.error("Failed to validate CallToolResult: Unexpected error: {}", e); - return ValidationResponse.asInvalid("Unexpected validation error: " + e.getMessage()); - } - } - - /** - * Gets a cached Schema or creates and caches a new one. - * @param schema the schema map to convert - * @return the compiled Schema - * @throws JsonProcessingException if schema processing fails - */ - private Schema getOrCreateJsonSchema(Map schema) throws JsonProcessingException { - // Generate cache key based on schema content - String cacheKey = this.generateCacheKey(schema); - - // Try to get from cache first - Schema cachedSchema = this.schemaCache.get(cacheKey); - if (cachedSchema != null) { - return cachedSchema; - } - - // Create new schema if not in cache - Schema newSchema = this.createJsonSchema(schema); - - // Cache the schema - Schema existingSchema = this.schemaCache.putIfAbsent(cacheKey, newSchema); - return existingSchema != null ? existingSchema : newSchema; - } - - /** - * Creates a new Schema from the given schema map. - * @param schema the schema map - * @return the compiled Schema - * @throws JsonProcessingException if schema processing fails - */ - private Schema createJsonSchema(Map schema) throws JsonProcessingException { - // Convert schema map directly to JsonNode (more efficient than string - // serialization) - JsonNode schemaNode = this.objectMapper.valueToTree(schema); - - // Handle case where ObjectMapper might return null (e.g., in mocked scenarios) - if (schemaNode == null) { - throw new JsonProcessingException("Failed to convert schema to JsonNode") { - }; - } - - return this.schemaFactory.getSchema(schemaNode); - } - - /** - * Generates a cache key for the given schema map. - * @param schema the schema map - * @return a cache key string - */ - protected String generateCacheKey(Map schema) { - if (schema.containsKey("$id")) { - // Use the (optional) "$id" field as the cache key if present - return "" + schema.get("$id"); - } - // Fall back to schema's hash code as a simple cache key - // For more sophisticated caching, could use content-based hashing - return String.valueOf(schema.hashCode()); - } - - /** - * Clears the schema cache. Useful for testing or memory management. - */ - public void clearCache() { - this.schemaCache.clear(); - } - - /** - * Returns the current size of the schema cache. - * @return the number of cached schemas - */ - public int getCacheSize() { - return this.schemaCache.size(); - } - -} diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java deleted file mode 100644 index ae16d66e9..000000000 --- a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/JacksonJsonSchemaValidatorSupplier.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2025 - 2025 the original author or authors. - */ - -package io.modelcontextprotocol.json.schema.jackson; - -import io.modelcontextprotocol.json.schema.JsonSchemaValidator; -import io.modelcontextprotocol.json.schema.JsonSchemaValidatorSupplier; - -/** - * A concrete implementation of {@link JsonSchemaValidatorSupplier} that provides a - * {@link JsonSchemaValidator} instance based on the Jackson library. - * - * @see JsonSchemaValidatorSupplier - * @see JsonSchemaValidator - * @deprecated since 18.0.0, use - * {@link io.modelcontextprotocol.json.schema.jackson2.JacksonJsonSchemaValidatorSupplier} - * instead. Will be removed in 19.0.0. - */ -public class JacksonJsonSchemaValidatorSupplier implements JsonSchemaValidatorSupplier { - - /** - * Returns a new instance of {@link JsonSchemaValidator} that uses the Jackson library - * for JSON schema validation. - * @return A {@link JsonSchemaValidator} instance. - */ - @Override - public JsonSchemaValidator get() { - return new DefaultJsonSchemaValidator(); - } - -} diff --git a/mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/jackson/DefaultJsonSchemaValidatorTests.java b/mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/jackson/DefaultJsonSchemaValidatorTests.java deleted file mode 100644 index 66cba09b8..000000000 --- a/mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/jackson/DefaultJsonSchemaValidatorTests.java +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright 2024-2024 the original author or authors. - */ - -package io.modelcontextprotocol.json.jackson; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.modelcontextprotocol.json.schema.JsonSchemaValidator.ValidationResponse; -import io.modelcontextprotocol.json.schema.jackson.DefaultJsonSchemaValidator; - -/** - * Tests for {@link DefaultJsonSchemaValidator}. - * - * @author Christian Tzolov - */ -@Deprecated(forRemoval = true) -class DefaultJsonSchemaValidatorTests { - - private DefaultJsonSchemaValidator validator; - - private ObjectMapper objectMapper; - - @Mock - private ObjectMapper mockObjectMapper; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - validator = new DefaultJsonSchemaValidator(); - objectMapper = new ObjectMapper(); - } - - /** - * Utility method to convert JSON string to Map - */ - private Map toMap(String json) { - try { - return objectMapper.readValue(json, new TypeReference>() { - }); - } - catch (Exception e) { - throw new RuntimeException("Failed to parse JSON: " + json, e); - } - } - - private List> toListMap(String json) { - try { - return objectMapper.readValue(json, new TypeReference>>() { - }); - } - catch (Exception e) { - throw new RuntimeException("Failed to parse JSON: " + json, e); - } - } - - @Test - void testDefaultConstructor() { - DefaultJsonSchemaValidator defaultValidator = new DefaultJsonSchemaValidator(); - - String schemaJson = """ - { - "type": "object", - "properties": { - "test": {"type": "string"} - } - } - """; - String contentJson = """ - { - "test": "value" - } - """; - - ValidationResponse response = defaultValidator.validate(toMap(schemaJson), toMap(contentJson)); - assertTrue(response.valid()); - } - - @Test - void testConstructorWithObjectMapper() { - ObjectMapper customMapper = new ObjectMapper(); - DefaultJsonSchemaValidator customValidator = new DefaultJsonSchemaValidator(customMapper); - - String schemaJson = """ - { - "type": "object", - "properties": { - "test": {"type": "string"} - } - } - """; - String contentJson = """ - { - "test": "value" - } - """; - - ValidationResponse response = customValidator.validate(toMap(schemaJson), toMap(contentJson)); - assertTrue(response.valid()); - } - - @Test - void testValidateWithValidStringSchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"] - } - """; - - String contentJson = """ - { - "name": "John Doe", - "age": 30 - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - assertNotNull(response.jsonStructuredOutput()); - } - - @Test - void testValidateWithValidNumberSchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "price": {"type": "number", "minimum": 0}, - "quantity": {"type": "integer", "minimum": 1} - }, - "required": ["price", "quantity"] - } - """; - - String contentJson = """ - { - "price": 19.99, - "quantity": 5 - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithValidArraySchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": {"type": "string"} - } - }, - "required": ["items"] - } - """; - - String contentJson = """ - { - "items": ["apple", "banana", "cherry"] - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithValidArraySchemaTopLevelArray() { - String schemaJson = """ - { - "$schema" : "https://json-schema.org/draft/2020-12/schema", - "type" : "array", - "items" : { - "type" : "object", - "properties" : { - "city" : { - "type" : "string" - }, - "summary" : { - "type" : "string" - }, - "temperatureC" : { - "type" : "number", - "format" : "float" - } - }, - "required" : [ "city", "summary", "temperatureC" ] - }, - "additionalProperties" : false - } - """; - - String contentJson = """ - [ - { - "city": "London", - "summary": "Generally mild with frequent rainfall. Winters are cool and damp, summers are warm but rarely hot. Cloudy conditions are common throughout the year.", - "temperatureC": 11.3 - }, - { - "city": "New York", - "summary": "Four distinct seasons with hot and humid summers, cold winters with snow, and mild springs and autumns. Precipitation is fairly evenly distributed throughout the year.", - "temperatureC": 12.8 - }, - { - "city": "San Francisco", - "summary": "Mild year-round with a distinctive Mediterranean climate. Famous for summer fog, mild winters, and little temperature variation throughout the year. Very little rainfall in summer months.", - "temperatureC": 14.6 - }, - { - "city": "Tokyo", - "summary": "Humid subtropical climate with hot, wet summers and mild winters. Experiences a rainy season in early summer and occasional typhoons in late summer to early autumn.", - "temperatureC": 15.4 - } - ] - """; - - Map schema = toMap(schemaJson); - - // Validate as JSON string - ValidationResponse response = validator.validate(schema, contentJson); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - - List> structuredContent = toListMap(contentJson); - - // Validate as List> - response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithInvalidTypeSchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"] - } - """; - - String contentJson = """ - { - "name": "John Doe", - "age": "thirty" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - assertTrue(response.errorMessage().contains("structuredContent does not match tool outputSchema")); - } - - @Test - void testValidateWithMissingRequiredField() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "integer"} - }, - "required": ["name", "age"] - } - """; - - String contentJson = """ - { - "name": "John Doe" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - } - - @Test - void testValidateWithAdditionalPropertiesNotAllowed() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"} - }, - "required": ["name"], - "additionalProperties": false - } - """; - - String contentJson = """ - { - "name": "John Doe", - "extraField": "should not be allowed" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - } - - @Test - void testValidateWithAdditionalPropertiesExplicitlyAllowed() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"} - }, - "required": ["name"], - "additionalProperties": true - } - """; - - String contentJson = """ - { - "name": "John Doe", - "extraField": "should be allowed" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithDefaultAdditionalProperties() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"} - }, - "required": ["name"], - "additionalProperties": true - } - """; - - String contentJson = """ - { - "name": "John Doe", - "extraField": "should be allowed" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithAdditionalPropertiesExplicitlyDisallowed() { - String schemaJson = """ - { - "type": "object", - "properties": { - "name": {"type": "string"} - }, - "required": ["name"], - "additionalProperties": false - } - """; - - String contentJson = """ - { - "name": "John Doe", - "extraField": "should not be allowed" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - } - - @Test - void testValidateWithEmptySchema() { - String schemaJson = """ - { - "additionalProperties": true - } - """; - - String contentJson = """ - { - "anything": "goes" - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithEmptyContent() { - String schemaJson = """ - { - "type": "object", - "properties": {} - } - """; - - String contentJson = """ - {} - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithNestedObjectSchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "person": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "address": { - "type": "object", - "properties": { - "street": {"type": "string"}, - "city": {"type": "string"} - }, - "required": ["street", "city"] - } - }, - "required": ["name", "address"] - } - }, - "required": ["person"] - } - """; - - String contentJson = """ - { - "person": { - "name": "John Doe", - "address": { - "street": "123 Main St", - "city": "Anytown" - } - } - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertTrue(response.valid()); - assertNull(response.errorMessage()); - } - - @Test - void testValidateWithInvalidNestedObjectSchema() { - String schemaJson = """ - { - "type": "object", - "properties": { - "person": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "address": { - "type": "object", - "properties": { - "street": {"type": "string"}, - "city": {"type": "string"} - }, - "required": ["street", "city"] - } - }, - "required": ["name", "address"] - } - }, - "required": ["person"] - } - """; - - String contentJson = """ - { - "person": { - "name": "John Doe", - "address": { - "street": "123 Main St" - } - } - } - """; - - Map schema = toMap(schemaJson); - Map structuredContent = toMap(contentJson); - - ValidationResponse response = validator.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - } - - @Test - void testValidateWithJsonProcessingException() throws Exception { - DefaultJsonSchemaValidator validatorWithMockMapper = new DefaultJsonSchemaValidator(mockObjectMapper); - - Map schema = Map.of("type", "object"); - Map structuredContent = Map.of("key", "value"); - - // This will trigger our null check and throw JsonProcessingException - when(mockObjectMapper.valueToTree(any())).thenReturn(null); - - ValidationResponse response = validatorWithMockMapper.validate(schema, structuredContent); - - assertFalse(response.valid()); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Error parsing tool JSON Schema")); - assertTrue(response.errorMessage().contains("Failed to convert schema to JsonNode")); - } - - @ParameterizedTest - @MethodSource("provideValidSchemaAndContentPairs") - void testValidateWithVariousValidInputs(Map schema, Map content) { - ValidationResponse response = validator.validate(schema, content); - - assertTrue(response.valid(), "Expected validation to pass for schema: " + schema + " and content: " + content); - assertNull(response.errorMessage()); - } - - @ParameterizedTest - @MethodSource("provideInvalidSchemaAndContentPairs") - void testValidateWithVariousInvalidInputs(Map schema, Map content) { - ValidationResponse response = validator.validate(schema, content); - - assertFalse(response.valid(), "Expected validation to fail for schema: " + schema + " and content: " + content); - assertNotNull(response.errorMessage()); - assertTrue(response.errorMessage().contains("Validation failed")); - } - - private static Map staticToMap(String json) { - try { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(json, new TypeReference>() { - }); - } - catch (Exception e) { - throw new RuntimeException("Failed to parse JSON: " + json, e); - } - } - - private static Stream provideValidSchemaAndContentPairs() { - return Stream.of( - // Boolean schema - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "flag": {"type": "boolean"} - } - } - """), staticToMap(""" - { - "flag": true - } - """)), - // String with additional properties allowed - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "name": {"type": "string"} - }, - "additionalProperties": true - } - """), staticToMap(""" - { - "name": "test", - "extra": "allowed" - } - """)), - // Array with specific items - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "numbers": { - "type": "array", - "items": {"type": "number"} - } - } - } - """), staticToMap(""" - { - "numbers": [1.0, 2.5, 3.14] - } - """)), - // Enum validation - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": ["active", "inactive", "pending"] - } - } - } - """), staticToMap(""" - { - "status": "active" - } - """))); - } - - private static Stream provideInvalidSchemaAndContentPairs() { - return Stream.of( - // Wrong boolean type - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "flag": {"type": "boolean"} - } - } - """), staticToMap(""" - { - "flag": "true" - } - """)), - // Array with wrong item types - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "numbers": { - "type": "array", - "items": {"type": "number"} - } - } - } - """), staticToMap(""" - { - "numbers": ["one", "two", "three"] - } - """)), - // Invalid enum value - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": ["active", "inactive", "pending"] - } - } - } - """), staticToMap(""" - { - "status": "unknown" - } - """)), - // Minimum constraint violation - Arguments.of(staticToMap(""" - { - "type": "object", - "properties": { - "age": {"type": "integer", "minimum": 0} - } - } - """), staticToMap(""" - { - "age": -5 - } - """))); - } - - @Test - void testValidationResponseToValid() { - String jsonOutput = "{\"test\":\"value\"}"; - ValidationResponse response = ValidationResponse.asValid(jsonOutput); - assertTrue(response.valid()); - assertNull(response.errorMessage()); - assertEquals(jsonOutput, response.jsonStructuredOutput()); - } - - @Test - void testValidationResponseToInvalid() { - String errorMessage = "Test error message"; - ValidationResponse response = ValidationResponse.asInvalid(errorMessage); - assertFalse(response.valid()); - assertEquals(errorMessage, response.errorMessage()); - assertNull(response.jsonStructuredOutput()); - } - - @Test - void testValidationResponseRecord() { - ValidationResponse response1 = new ValidationResponse(true, null, "{\"valid\":true}"); - ValidationResponse response2 = new ValidationResponse(false, "Error", null); - - assertTrue(response1.valid()); - assertNull(response1.errorMessage()); - assertEquals("{\"valid\":true}", response1.jsonStructuredOutput()); - - assertFalse(response2.valid()); - assertEquals("Error", response2.errorMessage()); - assertNull(response2.jsonStructuredOutput()); - - // Test equality - ValidationResponse response3 = new ValidationResponse(true, null, "{\"valid\":true}"); - assertEquals(response1, response3); - assertNotEquals(response1, response2); - } - -} diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java index 240732ebe..7755ce456 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java @@ -82,7 +82,10 @@ void testToolCallSuccess(String clientType) { var clientBuilder = clientBuilders.get(clientType); - var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); + var callResponse = McpSchema.CallToolResult.builder() + .content(List.of(new McpSchema.TextContent("CALL RESPONSE"))) + .isError(false) + .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) @@ -170,7 +173,10 @@ void testToolListChangeHandlingSuccess(String clientType) { var clientBuilder = clientBuilders.get(clientType); - var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); + var callResponse = McpSchema.CallToolResult.builder() + .content(List.of(new McpSchema.TextContent("CALL RESPONSE"))) + .isError(false) + .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index d6677ec9a..9cd1191d1 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -95,26 +95,6 @@ void testImmediateClose() { // --------------------------------------- // Tools Tests // --------------------------------------- - @Test - @Deprecated - void testAddTool() { - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") - .capabilities(ServerCapabilities.builder().tools(true).build()) - .build(); - - StepVerifier - .create(mcpAsyncServer.addTool(new McpServerFeatures.AsyncToolSpecification(newTool, - (exchange, args) -> Mono.just(CallToolResult.builder().content(List.of()).isError(false).build())))) - .verifyComplete(); - - assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); - } - @Test void testAddToolCall() { Tool newTool = McpSchema.Tool.builder() @@ -136,29 +116,6 @@ void testAddToolCall() { assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); } - @Test - @Deprecated - void testAddDuplicateTool() { - Tool duplicateTool = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - - var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") - .capabilities(ServerCapabilities.builder().tools(true).build()) - .tool(duplicateTool, - (exchange, args) -> Mono.just(CallToolResult.builder().content(List.of()).isError(false).build())) - .build(); - - StepVerifier - .create(mcpAsyncServer.addTool(new McpServerFeatures.AsyncToolSpecification(duplicateTool, - (exchange, args) -> Mono.just(CallToolResult.builder().content(List.of()).isError(false).build())))) - .verifyComplete(); - - assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); - } - @Test void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index 0a59d0aae..eee5f1a4d 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -99,25 +99,6 @@ void testGetAsyncServer() { // Tools Tests // --------------------------------------- - @Test - @Deprecated - void testAddTool() { - var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") - .capabilities(ServerCapabilities.builder().tools(true).build()) - .build(); - - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolSpecification(newTool, - (exchange, args) -> CallToolResult.builder().content(List.of()).isError(false).build()))) - .doesNotThrowAnyException(); - - assertThatCode(mcpSyncServer::closeGracefully).doesNotThrowAnyException(); - } - @Test void testAddToolCall() { var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -138,27 +119,6 @@ void testAddToolCall() { assertThatCode(mcpSyncServer::closeGracefully).doesNotThrowAnyException(); } - @Test - @Deprecated - void testAddDuplicateTool() { - Tool duplicateTool = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); - - var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") - .capabilities(ServerCapabilities.builder().tools(true).build()) - .tool(duplicateTool, (exchange, args) -> CallToolResult.builder().content(List.of()).isError(false).build()) - .build(); - - assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolSpecification(duplicateTool, - (exchange, args) -> CallToolResult.builder().content(List.of()).isError(false).build()))) - .doesNotThrowAnyException(); - - assertThatCode(mcpSyncServer::closeGracefully).doesNotThrowAnyException(); - } - @Test void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java b/mcp-test/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java index f93e760f1..4e74dac3e 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/MockMcpClientTransport.java @@ -13,6 +13,7 @@ import io.modelcontextprotocol.json.TypeRef; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.ProtocolVersions; import io.modelcontextprotocol.spec.McpSchema.JSONRPCNotification; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; import reactor.core.publisher.Mono; @@ -29,7 +30,7 @@ public class MockMcpClientTransport implements McpClientTransport { private final BiConsumer interceptor; - private String protocolVersion = McpSchema.LATEST_PROTOCOL_VERSION; + private String protocolVersion = ProtocolVersions.MCP_2025_11_25; public MockMcpClientTransport() { this((t, msg) -> { diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java index 612a65898..47a229afd 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java @@ -13,6 +13,7 @@ import io.modelcontextprotocol.json.TypeRef; import io.modelcontextprotocol.MockMcpClientTransport; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.ProtocolVersions; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.InitializeResult; import io.modelcontextprotocol.spec.McpSchema.PaginatedRequest; @@ -42,7 +43,7 @@ private static MockMcpClientTransport initializationEnabledTransport() { private static MockMcpClientTransport initializationEnabledTransport( McpSchema.ServerCapabilities mockServerCapabilities, McpSchema.Implementation mockServerInfo) { - McpSchema.InitializeResult mockInitResult = new McpSchema.InitializeResult(McpSchema.LATEST_PROTOCOL_VERSION, + McpSchema.InitializeResult mockInitResult = new McpSchema.InitializeResult(ProtocolVersions.MCP_2025_11_25, mockServerCapabilities, mockServerInfo, "Test instructions"); return new MockMcpClientTransport((t, message) -> { @@ -51,7 +52,7 @@ private static MockMcpClientTransport initializationEnabledTransport( r.id(), mockInitResult, null); t.simulateIncomingMessage(initResponse); } - }).withProtocolVersion(McpSchema.LATEST_PROTOCOL_VERSION); + }).withProtocolVersion(ProtocolVersions.MCP_2025_11_25); } @Test @@ -212,8 +213,12 @@ void testResourcesChangeNotificationHandling() { assertThat(asyncMcpClient.initialize().block()).isNotNull(); // Create a mock resources list that the server will return - McpSchema.Resource mockResource = new McpSchema.Resource("test://resource", "Test Resource", "A test resource", - "text/plain", null); + McpSchema.Resource mockResource = McpSchema.Resource.builder() + .uri("test://resource") + .name("Test Resource") + .description("A test resource") + .mimeType("text/plain") + .build(); McpSchema.ListResourcesResult mockResourcesResult = new McpSchema.ListResourcesResult(List.of(mockResource), null); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpClientProtocolVersionTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpClientProtocolVersionTests.java index a94b9b6a7..03f64aa64 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpClientProtocolVersionTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpClientProtocolVersionTests.java @@ -11,6 +11,7 @@ import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.InitializeResult; import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; +import io.modelcontextprotocol.spec.ProtocolVersions; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -68,7 +69,7 @@ void shouldNegotiateSpecificVersion() { .requestTimeout(REQUEST_TIMEOUT) .build(); - client.setProtocolVersions(List.of(oldVersion, McpSchema.LATEST_PROTOCOL_VERSION)); + client.setProtocolVersions(List.of(oldVersion, ProtocolVersions.MCP_2025_11_25)); try { Mono initializeResultMono = client.initialize(); @@ -77,7 +78,7 @@ void shouldNegotiateSpecificVersion() { McpSchema.JSONRPCRequest request = transport.getLastSentMessageAsRequest(); assertThat(request.params()).isInstanceOf(McpSchema.InitializeRequest.class); McpSchema.InitializeRequest initRequest = (McpSchema.InitializeRequest) request.params(); - assertThat(initRequest.protocolVersion()).isIn(List.of(oldVersion, McpSchema.LATEST_PROTOCOL_VERSION)); + assertThat(initRequest.protocolVersion()).isIn(List.of(oldVersion, ProtocolVersions.MCP_2025_11_25)); transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(), new McpSchema.InitializeResult(oldVersion, ServerCapabilities.builder().build(), @@ -123,7 +124,7 @@ void shouldFailForUnsupportedVersion() { void shouldUseHighestVersionWhenMultipleSupported() { String oldVersion = "0.1.0"; String middleVersion = "0.2.0"; - String latestVersion = McpSchema.LATEST_PROTOCOL_VERSION; + String latestVersion = ProtocolVersions.MCP_2025_11_25; MockMcpClientTransport transport = new MockMcpClientTransport(); McpAsyncClient client = McpClient.async(transport) diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index 8ddd54266..f88736a5d 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -8,6 +8,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.ProtocolVersions; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; @@ -78,7 +79,7 @@ void testRequestCustomizer() throws URISyntaxException { withTransport(transport, (t) -> { // Send test message - var initializeRequest = new McpSchema.InitializeRequest(McpSchema.LATEST_PROTOCOL_VERSION, + var initializeRequest = new McpSchema.InitializeRequest(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), new McpSchema.Implementation("MCP Client", "0.3.1")); var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, @@ -108,7 +109,7 @@ void testAsyncRequestCustomizer() throws URISyntaxException { withTransport(transport, (t) -> { // Send test message - var initializeRequest = new McpSchema.InitializeRequest(McpSchema.LATEST_PROTOCOL_VERSION, + var initializeRequest = new McpSchema.InitializeRequest(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), new McpSchema.Implementation("MCP Client", "0.3.1")); var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, @@ -131,7 +132,7 @@ void testCloseUninitialized() { StepVerifier.create(transport.closeGracefully()).verifyComplete(); - var initializeRequest = new McpSchema.InitializeRequest(McpSchema.LATEST_PROTOCOL_VERSION, + var initializeRequest = new McpSchema.InitializeRequest(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), new McpSchema.Implementation("MCP Client", "0.3.1")); var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, @@ -146,7 +147,7 @@ void testCloseUninitialized() { void testCloseInitialized() { var transport = HttpClientStreamableHttpTransport.builder(host).build(); - var initializeRequest = new McpSchema.InitializeRequest(McpSchema.LATEST_PROTOCOL_VERSION, + var initializeRequest = new McpSchema.InitializeRequest(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), new McpSchema.Implementation("MCP Client", "0.3.1")); var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java index 8b2dea462..ce381436d 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java @@ -132,8 +132,10 @@ public class AsyncServerMcpTransportContextIntegrationTests { private final BiFunction> asyncStatelessHandler = ( transportContext, request) -> { - return Mono - .just(new McpSchema.CallToolResult(transportContext.get("server-side-header-value").toString(), null)); + return Mono.just(McpSchema.CallToolResult.builder() + .addTextContent(transportContext.get("server-side-header-value").toString()) + .isError(false) + .build()); }; private final BiFunction> asyncStatefulHandler = ( @@ -198,7 +200,10 @@ void asyncClientStreamableServer() { var mcpServer = McpServer.async(streamableServerTransport) .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) - .tools(new McpServerFeatures.AsyncToolSpecification(tool, null, asyncStatefulHandler)) + .tools(McpServerFeatures.AsyncToolSpecification.builder() + .tool(tool) + .callHandler(asyncStatefulHandler) + .build()) .build(); StepVerifier.create(asyncStreamableClient.initialize()).assertNext(initResult -> { @@ -229,7 +234,10 @@ void asyncClientSseServer() { var mcpServer = McpServer.async(sseServerTransport) .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) - .tools(new McpServerFeatures.AsyncToolSpecification(tool, null, asyncStatefulHandler)) + .tools(McpServerFeatures.AsyncToolSpecification.builder() + .tool(tool) + .callHandler(asyncStatefulHandler) + .build()) .build(); StepVerifier.create(asyncSseClient.initialize()).assertNext(initResult -> { diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java index 614172b84..29eef1410 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/common/HttpClientStreamableHttpVersionNegotiationIntegrationTests.java @@ -47,12 +47,14 @@ class HttpClientStreamableHttpVersionNegotiationIntegrationTests { .build(); private final BiFunction toolHandler = ( - exchange, request) -> new McpSchema.CallToolResult( - exchange.transportContext().get("protocol-version").toString(), null); + exchange, request) -> McpSchema.CallToolResult.builder() + .addTextContent(exchange.transportContext().get("protocol-version").toString()) + .isError(false) + .build(); McpSyncServer mcpServer = McpServer.sync(transport) .capabilities(McpSchema.ServerCapabilities.builder().tools(false).build()) - .tools(new McpServerFeatures.SyncToolSpecification(toolSpec, null, toolHandler)) + .tools(McpServerFeatures.SyncToolSpecification.builder().tool(toolSpec).callHandler(toolHandler).build()) .build(); @AfterEach diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java index cc8f4c4be..563e2167d 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java @@ -78,8 +78,10 @@ public class SyncServerMcpTransportContextIntegrationTests { }; private final BiFunction statelessHandler = ( - transportContext, - request) -> new McpSchema.CallToolResult(transportContext.get("server-side-header-value").toString(), null); + transportContext, request) -> McpSchema.CallToolResult.builder() + .addTextContent(transportContext.get("server-side-header-value").toString()) + .isError(false) + .build(); private final BiFunction statefulHandler = ( exchange, request) -> statelessHandler.apply(exchange.transportContext(), request); @@ -172,7 +174,7 @@ void streamableServer() { var mcpServer = McpServer.sync(streamableServerTransport) .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) - .tools(new McpServerFeatures.SyncToolSpecification(tool, null, statefulHandler)) + .tools(McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(statefulHandler).build()) .build(); McpSchema.InitializeResult initResult = streamableClient.initialize(); @@ -198,7 +200,7 @@ void sseServer() { var mcpServer = McpServer.sync(sseServerTransport) .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) - .tools(new McpServerFeatures.SyncToolSpecification(tool, null, statefulHandler)) + .tools(McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(statefulHandler).build()) .build(); McpSchema.InitializeResult initResult = sseClient.initialize(); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java index cdd2bacb7..d9f899020 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/McpServerProtocolVersionTests.java @@ -10,6 +10,7 @@ import io.modelcontextprotocol.MockMcpServerTransport; import io.modelcontextprotocol.MockMcpServerTransportProvider; import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.ProtocolVersions; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,8 +37,7 @@ void shouldUseLatestVersionByDefault() { String requestId = UUID.randomUUID().toString(); - transportProvider - .simulateIncomingMessage(jsonRpcInitializeRequest(requestId, McpSchema.LATEST_PROTOCOL_VERSION)); + transportProvider.simulateIncomingMessage(jsonRpcInitializeRequest(requestId, ProtocolVersions.MCP_2025_11_25)); McpSchema.JSONRPCMessage response = serverTransport.getLastSentMessage(); assertThat(response).isInstanceOf(McpSchema.JSONRPCResponse.class); @@ -60,7 +60,7 @@ void shouldNegotiateSpecificVersion() { McpAsyncServer server = McpServer.async(transportProvider).serverInfo(SERVER_INFO).build(); - server.setProtocolVersions(List.of(oldVersion, McpSchema.LATEST_PROTOCOL_VERSION)); + server.setProtocolVersions(List.of(oldVersion, ProtocolVersions.MCP_2025_11_25)); String requestId = UUID.randomUUID().toString(); @@ -105,7 +105,7 @@ void shouldSuggestLatestVersionForUnsupportedVersion() { void shouldUseHighestVersionWhenMultipleSupported() { String oldVersion = "0.1.0"; String middleVersion = "0.2.0"; - String latestVersion = McpSchema.LATEST_PROTOCOL_VERSION; + String latestVersion = ProtocolVersions.MCP_2025_11_25; MockMcpServerTransport serverTransport = new MockMcpServerTransport(); var transportProvider = new MockMcpServerTransportProvider(serverTransport); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java index 873d48e36..5390cc4c2 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/transport/StdioServerTransportProviderTests.java @@ -185,7 +185,7 @@ void shouldHandleNotificationBeforeSessionFactoryIsSet() { // Send notification before setting session factory StepVerifier.create(transportProvider.notifyClients("testNotification", Map.of("key", "value"))) .verifyErrorSatisfies(error -> { - assertThat(error).isInstanceOf(McpError.class); + assertThat(error).isInstanceOf(IllegalStateException.class); }); } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index c732b1cc1..942e0a6e2 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -17,10 +17,10 @@ import java.util.List; import java.util.Map; +import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; -import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; import net.javacrumbs.jsonunit.core.Option; /** @@ -72,7 +72,7 @@ void testContentDeserializationWrongType() { @Test void testImageContent() throws Exception { - McpSchema.ImageContent test = new McpSchema.ImageContent(null, null, "base64encodeddata", "image/png"); + McpSchema.ImageContent test = new McpSchema.ImageContent(null, "base64encodeddata", "image/png"); String value = JSON_MAPPER.writeValueAsString(test); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -160,7 +160,7 @@ void testEmbeddedResource() throws Exception { McpSchema.TextResourceContents resourceContents = new McpSchema.TextResourceContents("resource://test", "text/plain", "Sample resource content"); - McpSchema.EmbeddedResource test = new McpSchema.EmbeddedResource(null, null, resourceContents); + McpSchema.EmbeddedResource test = new McpSchema.EmbeddedResource(null, resourceContents); String value = JSON_MAPPER.writeValueAsString(test); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -191,7 +191,7 @@ void testEmbeddedResourceWithBlobContents() throws Exception { McpSchema.BlobResourceContents resourceContents = new McpSchema.BlobResourceContents("resource://test", "application/octet-stream", "base64encodedblob"); - McpSchema.EmbeddedResource test = new McpSchema.EmbeddedResource(null, null, resourceContents); + McpSchema.EmbeddedResource test = new McpSchema.EmbeddedResource(null, resourceContents); String value = JSON_MAPPER.writeValueAsString(test); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -366,8 +366,13 @@ void testResource() throws Exception { McpSchema.Annotations annotations = new McpSchema.Annotations( Arrays.asList(McpSchema.Role.USER, McpSchema.Role.ASSISTANT), 0.8); - McpSchema.Resource resource = new McpSchema.Resource("resource://test", "Test Resource", "A test resource", - "text/plain", annotations); + McpSchema.Resource resource = McpSchema.Resource.builder() + .uri("resource://test") + .name("Test Resource") + .description("A test resource") + .mimeType("text/plain") + .annotations(annotations) + .build(); String value = JSON_MAPPER.writeValueAsString(resource); assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER) @@ -451,11 +456,19 @@ void testResourceTemplate() throws Exception { @Test void testListResourcesResult() throws Exception { - McpSchema.Resource resource1 = new McpSchema.Resource("resource://test1", "Test Resource 1", - "First test resource", "text/plain", null); + McpSchema.Resource resource1 = McpSchema.Resource.builder() + .uri("resource://test1") + .name("Test Resource 1") + .description("First test resource") + .mimeType("text/plain") + .build(); - McpSchema.Resource resource2 = new McpSchema.Resource("resource://test2", "Test Resource 2", - "Second test resource", "application/json", null); + McpSchema.Resource resource2 = McpSchema.Resource.builder() + .uri("resource://test2") + .name("Test Resource 2") + .description("Second test resource") + .mimeType("application/json") + .build(); Map meta = Map.of("metaKey", "metaValue"); @@ -1274,7 +1287,7 @@ void testCallToolResultBuilder() throws Exception { @Test void testCallToolResultBuilderWithMultipleContents() throws Exception { McpSchema.TextContent textContent = new McpSchema.TextContent("Text result"); - McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, null, "base64data", "image/png"); + McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, "base64data", "image/png"); McpSchema.CallToolResult result = McpSchema.CallToolResult.builder() .addContent(textContent) @@ -1295,7 +1308,7 @@ void testCallToolResultBuilderWithMultipleContents() throws Exception { @Test void testCallToolResultBuilderWithContentList() throws Exception { McpSchema.TextContent textContent = new McpSchema.TextContent("Text result"); - McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, null, "base64data", "image/png"); + McpSchema.ImageContent imageContent = new McpSchema.ImageContent(null, "base64data", "image/png"); List contents = Arrays.asList(textContent, imageContent); McpSchema.CallToolResult result = McpSchema.CallToolResult.builder().content(contents).isError(true).build(); @@ -1326,27 +1339,6 @@ void testCallToolResultBuilderWithErrorResult() throws Exception { {"content":[{"type":"text","text":"Error: Operation failed"}],"isError":true}""")); } - @Test - void testCallToolResultStringConstructor() throws Exception { - // Test the existing string constructor alongside the builder - McpSchema.CallToolResult result1 = new McpSchema.CallToolResult("Simple result", false); - McpSchema.CallToolResult result2 = McpSchema.CallToolResult.builder() - .addTextContent("Simple result") - .isError(false) - .build(); - - String value1 = JSON_MAPPER.writeValueAsString(result1); - String value2 = JSON_MAPPER.writeValueAsString(result2); - - // Both should produce the same JSON - assertThat(value1).isEqualTo(value2); - assertThatJson(value1).when(Option.IGNORING_ARRAY_ORDER) - .when(Option.IGNORING_EXTRA_ARRAY_ITEMS) - .isObject() - .isEqualTo(json(""" - {"content":[{"type":"text","text":"Simple result"}],"isError":false}""")); - } - // Sampling Tests @Test