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 38a16bd7e..bedae1590 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java @@ -48,6 +48,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static io.modelcontextprotocol.spec.McpError.RESOURCE_NOT_FOUND; + /** * The Model Context Protocol (MCP) server implementation that provides asynchronous * communication using Project Reactor's Mono and Flux types. @@ -638,24 +640,23 @@ private List getResourceTemplates() { } private McpRequestHandler resourcesReadRequestHandler() { - return (exchange, params) -> { - McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params, - new TypeRef() { - }); + return (ex, params) -> { + McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params, new TypeRef<>() { + }); var resourceUri = resourceRequest.uri(); - - McpServerFeatures.AsyncResourceSpecification specification = this.resources.values() - .stream() - .filter(resourceSpecification -> this.uriTemplateManagerFactory - .create(resourceSpecification.resource().uri()) - .matches(resourceUri)) - .findFirst() - .orElseThrow(() -> new McpError("Resource not found: " + resourceUri)); - - return Mono.defer(() -> specification.readHandler().apply(exchange, resourceRequest)); + return asyncResourceSpecification(resourceUri) + .map(spec -> Mono.defer(() -> spec.readHandler().apply(ex, resourceRequest))) + .orElseGet(() -> Mono.error(RESOURCE_NOT_FOUND.apply(resourceUri))); }; } + private Optional asyncResourceSpecification(String uri) { + return resources.values() + .stream() + .filter(spec -> uriTemplateManagerFactory.create(spec.resource().uri()).matches(uri)) + .findFirst(); + } + // --------------------------------------- // Prompt Management // --------------------------------------- @@ -846,7 +847,7 @@ private McpRequestHandler completionCompleteRequestHan if (type.equals("ref/resource") && request.ref() instanceof McpSchema.ResourceReference resourceReference) { McpServerFeatures.AsyncResourceSpecification resourceSpec = this.resources.get(resourceReference.uri()); if (resourceSpec == null) { - return Mono.error(new McpError("Resource not found: " + resourceReference.uri())); + return Mono.error(RESOURCE_NOT_FOUND.apply(resourceReference.uri())); } if (!uriTemplateManagerFactory.create(resourceSpec.resource().uri()) .getVariableNames() diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java index 8f79d8c68..1dde58d69 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java @@ -33,6 +33,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiFunction; +import static io.modelcontextprotocol.spec.McpError.RESOURCE_NOT_FOUND; + /** * A stateless MCP server implementation for use with Streamable HTTP transport types. It * allows simple horizontal scalability since it does not maintain a session and does not @@ -478,23 +480,21 @@ private List getResourceTemplates() { private McpStatelessRequestHandler resourcesReadRequestHandler() { return (ctx, params) -> { - McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params, - new TypeRef() { - }); + McpSchema.ReadResourceRequest resourceRequest = jsonMapper.convertValue(params, new TypeRef<>() { + }); var resourceUri = resourceRequest.uri(); - - McpStatelessServerFeatures.AsyncResourceSpecification specification = this.resources.values() - .stream() - .filter(resourceSpecification -> this.uriTemplateManagerFactory - .create(resourceSpecification.resource().uri()) - .matches(resourceUri)) - .findFirst() - .orElseThrow(() -> new McpError("Resource not found: " + resourceUri)); - - return specification.readHandler().apply(ctx, resourceRequest); + return asyncResourceSpecification(resourceUri).map(spec -> spec.readHandler().apply(ctx, resourceRequest)) + .orElseGet(() -> Mono.error(RESOURCE_NOT_FOUND.apply(resourceUri))); }; } + private Optional asyncResourceSpecification(String uri) { + return resources.values() + .stream() + .filter(spec -> uriTemplateManagerFactory.create(spec.resource().uri()).matches(uri)) + .findFirst(); + } + // --------------------------------------- // Prompt Management // --------------------------------------- @@ -612,10 +612,10 @@ private McpStatelessRequestHandler completionCompleteR } if (type.equals("ref/resource") && request.ref() instanceof McpSchema.ResourceReference resourceReference) { - McpStatelessServerFeatures.AsyncResourceSpecification resourceSpec = this.resources + McpStatelessServerFeatures.AsyncResourceSpecification resourceSpec = resources .get(resourceReference.uri()); if (resourceSpec == null) { - return Mono.error(new McpError("Resource not found: " + resourceReference.uri())); + return Mono.error(RESOURCE_NOT_FOUND.apply(resourceReference.uri())); } if (!uriTemplateManagerFactory.create(resourceSpec.resource().uri()) .getVariableNames() 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 6172d8637..4f717306a 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpError.java @@ -7,8 +7,19 @@ import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse.JSONRPCError; import io.modelcontextprotocol.util.Assert; +import java.util.Map; +import java.util.function.Function; + public class McpError extends RuntimeException { + /** + * Resource + * Error Handling + */ + public static final Function RESOURCE_NOT_FOUND = resourceUri -> new McpError(new JSONRPCError( + McpSchema.ErrorCodes.RESOURCE_NOT_FOUND, "Resource not found", Map.of("uri", resourceUri))); + private JSONRPCError jsonRpcError; public McpError(JSONRPCError 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 40c23d2fb..de72968e0 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -143,6 +143,11 @@ public static final class ErrorCodes { */ public static final int INTERNAL_ERROR = -32603; + /** + * Resource not found. + */ + public static final int RESOURCE_NOT_FOUND = -32002; + } public sealed interface Request diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java new file mode 100644 index 000000000..84d650ab3 --- /dev/null +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java @@ -0,0 +1,21 @@ +package io.modelcontextprotocol.spec; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class McpErrorTest { + + @Test + void testNotFound() { + String uri = "file:///nonexistent.txt"; + McpError mcpError = McpError.RESOURCE_NOT_FOUND.apply(uri); + assertNotNull(mcpError.getJsonRpcError()); + assertEquals(-32002, mcpError.getJsonRpcError().code()); + assertEquals("Resource not found", mcpError.getJsonRpcError().message()); + assertEquals(Map.of("uri", uri), mcpError.getJsonRpcError().data()); + } + +} \ No newline at end of file