{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"java-mcp-server","version":"0.1.0"},"spec":{"agents_md":"---\ndescription: 'Best practices and patterns for building Model Context Protocol (MCP) servers in Java using the official MCP Java SDK with reactive streams and Spring integration.'\napplyTo: \"**/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts\"\n---\n\n# Java MCP Server Development Guidelines\n\nWhen building MCP servers in Java, follow these best practices and patterns using the official Java SDK.\n\n## Dependencies\n\nAdd the MCP Java SDK to your Maven project:\n\n```xml\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.modelcontextprotocol.sdk\u003c/groupId\u003e\n        \u003cartifactId\u003emcp\u003c/artifactId\u003e\n        \u003cversion\u003e0.14.1\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\nOr for Gradle:\n\n```kotlin\ndependencies {\n    implementation(\"io.modelcontextprotocol.sdk:mcp:0.14.1\")\n}\n```\n\n## Server Setup\n\nCreate an MCP server using the builder pattern:\n\n```java\nimport io.mcp.server.McpServer;\nimport io.mcp.server.McpServerBuilder;\nimport io.mcp.server.transport.StdioServerTransport;\n\nMcpServer server = McpServerBuilder.builder()\n    .serverInfo(\"my-server\", \"1.0.0\")\n    .capabilities(capabilities -\u003e capabilities\n        .tools(true)\n        .resources(true)\n        .prompts(true))\n    .build();\n\n// Start with stdio transport\nStdioServerTransport transport = new StdioServerTransport();\nserver.start(transport).subscribe();\n```\n\n## Adding Tools\n\nRegister tool handlers with the server:\n\n```java\nimport io.mcp.server.tool.Tool;\nimport io.mcp.server.tool.ToolHandler;\nimport reactor.core.publisher.Mono;\n\n// Define a tool\nTool searchTool = Tool.builder()\n    .name(\"search\")\n    .description(\"Search for information\")\n    .inputSchema(JsonSchema.object()\n        .property(\"query\", JsonSchema.string()\n            .description(\"Search query\")\n            .required(true))\n        .property(\"limit\", JsonSchema.integer()\n            .description(\"Maximum results\")\n            .defaultValue(10)))\n    .build();\n\n// Register tool handler\nserver.addToolHandler(\"search\", (arguments) -\u003e {\n    String query = arguments.get(\"query\").asText();\n    int limit = arguments.has(\"limit\") \n        ? arguments.get(\"limit\").asInt() \n        : 10;\n    \n    // Perform search\n    List\u003cString\u003e results = performSearch(query, limit);\n    \n    return Mono.just(ToolResponse.success()\n        .addTextContent(\"Found \" + results.size() + \" results\")\n        .build());\n});\n```\n\n## Adding Resources\n\nImplement resource handlers for data access:\n\n```java\nimport io.mcp.server.resource.Resource;\nimport io.mcp.server.resource.ResourceHandler;\n\n// Register resource list handler\nserver.addResourceListHandler(() -\u003e {\n    List\u003cResource\u003e resources = List.of(\n        Resource.builder()\n            .name(\"Data File\")\n            .uri(\"resource://data/example.txt\")\n            .description(\"Example data file\")\n            .mimeType(\"text/plain\")\n            .build()\n    );\n    return Mono.just(resources);\n});\n\n// Register resource read handler\nserver.addResourceReadHandler((uri) -\u003e {\n    if (uri.equals(\"resource://data/example.txt\")) {\n        String content = loadResourceContent(uri);\n        return Mono.just(ResourceContent.text(content, uri));\n    }\n    throw new ResourceNotFoundException(uri);\n});\n\n// Register resource subscribe handler\nserver.addResourceSubscribeHandler((uri) -\u003e {\n    subscriptions.add(uri);\n    log.info(\"Client subscribed to {}\", uri);\n    return Mono.empty();\n});\n```\n\n## Adding Prompts\n\nImplement prompt handlers for templated conversations:\n\n```java\nimport io.mcp.server.prompt.Prompt;\nimport io.mcp.server.prompt.PromptMessage;\nimport io.mcp.server.prompt.PromptArgument;\n\n// Register prompt list handler\nserver.addPromptListHandler(() -\u003e {\n    List\u003cPrompt\u003e prompts = List.of(\n        Prompt.builder()\n            .name(\"analyze\")\n            .description(\"Analyze a topic\")\n            .argument(PromptArgument.builder()\n                .name(\"topic\")\n                .description(\"Topic to analyze\")\n                .required(true)\n                .build())\n            .argument(PromptArgument.builder()\n                .name(\"depth\")\n                .description(\"Analysis depth\")\n                .required(false)\n                .build())\n            .build()\n    );\n    return Mono.just(prompts);\n});\n\n// Register prompt get handler\nserver.addPromptGetHandler((name, arguments) -\u003e {\n    if (name.equals(\"analyze\")) {\n        String topic = arguments.getOrDefault(\"topic\", \"general\");\n        String depth = arguments.getOrDefault(\"depth\", \"basic\");\n        \n        List\u003cPromptMessage\u003e messages = List.of(\n            PromptMessage.user(\"Please analyze this topic: \" + topic),\n            PromptMessage.assistant(\"I'll provide a \" + depth + \" analysis of \" + topic)\n        );\n        \n        return Mono.just(PromptResult.builder()\n            .description(\"Analysis of \" + topic + \" at \" + depth + \" level\")\n            .messages(messages)\n            .build());\n    }\n    throw new PromptNotFoundException(name);\n});\n```\n\n## Reactive Streams Pattern\n\nThe Java SDK uses Reactive Streams (Project Reactor) for asynchronous processing:\n\n```java\n// Return Mono for single results\nserver.addToolHandler(\"process\", (args) -\u003e {\n    return Mono.fromCallable(() -\u003e {\n        String result = expensiveOperation(args);\n        return ToolResponse.success()\n            .addTextContent(result)\n            .build();\n    }).subscribeOn(Schedulers.boundedElastic());\n});\n\n// Return Flux for streaming results\nserver.addResourceListHandler(() -\u003e {\n    return Flux.fromIterable(getResources())\n        .map(r -\u003e Resource.builder()\n            .uri(r.getUri())\n            .name(r.getName())\n            .build())\n        .collectList();\n});\n```\n\n## Synchronous Facade\n\nFor blocking use cases, use the synchronous API:\n\n```java\nimport io.mcp.server.McpSyncServer;\n\nMcpSyncServer syncServer = server.toSyncServer();\n\n// Blocking tool handler\nsyncServer.addToolHandler(\"greet\", (args) -\u003e {\n    String name = args.get(\"name\").asText();\n    return ToolResponse.success()\n        .addTextContent(\"Hello, \" + name + \"!\")\n        .build();\n});\n```\n\n## Transport Configuration\n\n### Stdio Transport\n\nFor local subprocess communication:\n\n```java\nimport io.mcp.server.transport.StdioServerTransport;\n\nStdioServerTransport transport = new StdioServerTransport();\nserver.start(transport).block();\n```\n\n### HTTP Transport (Servlet)\n\nFor HTTP-based servers:\n\n```java\nimport io.mcp.server.transport.ServletServerTransport;\nimport jakarta.servlet.http.HttpServlet;\n\npublic class McpServlet extends HttpServlet {\n    private final McpServer server;\n    private final ServletServerTransport transport;\n    \n    public McpServlet() {\n        this.server = createMcpServer();\n        this.transport = new ServletServerTransport();\n    }\n    \n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {\n        transport.handleRequest(server, req, resp).block();\n    }\n}\n```\n\n## Spring Boot Integration\n\nUse the Spring Boot starter for seamless integration:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.modelcontextprotocol.sdk\u003c/groupId\u003e\n    \u003cartifactId\u003emcp-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e0.14.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nConfigure the server with Spring:\n\n```java\nimport org.springframework.context.annotation.Configuration;\nimport io.mcp.spring.McpServerConfigurer;\n\n@Configuration\npublic class McpConfiguration {\n    \n    @Bean\n    public McpServerConfigurer mcpServerConfigurer() {\n        return server -\u003e server\n            .serverInfo(\"spring-server\", \"1.0.0\")\n            .capabilities(cap -\u003e cap\n                .tools(true)\n                .resources(true)\n                .prompts(true));\n    }\n}\n```\n\nRegister handlers as Spring beans:\n\n```java\nimport org.springframework.stereotype.Component;\nimport io.mcp.spring.ToolHandler;\n\n@Component\npublic class SearchToolHandler implements ToolHandler {\n    \n    @Override\n    public String getName() {\n        return \"search\";\n    }\n    \n    @Override\n    public Tool getTool() {\n        return Tool.builder()\n            .name(\"search\")\n            .description(\"Search for information\")\n            .inputSchema(JsonSchema.object()\n                .property(\"query\", JsonSchema.string().required(true)))\n            .build();\n    }\n    \n    @Override\n    public Mono\u003cToolResponse\u003e handle(JsonNode arguments) {\n        String query = arguments.get(\"query\").asText();\n        return Mono.just(ToolResponse.success()\n            .addTextContent(\"Search results for: \" + query)\n            .build());\n    }\n}\n```\n\n## Error Handling\n\nUse proper error handling with MCP exceptions:\n\n```java\nserver.addToolHandler(\"risky\", (args) -\u003e {\n    return Mono.fromCallable(() -\u003e {\n        try {\n            String result = riskyOperation(args);\n            return ToolResponse.success()\n                .addTextContent(result)\n                .build();\n        } catch (ValidationException e) {\n            return ToolResponse.error()\n                .message(\"Invalid input: \" + e.getMessage())\n                .build();\n        } catch (Exception e) {\n            log.error(\"Unexpected error\", e);\n            return ToolResponse.error()\n                .message(\"Internal error occurred\")\n                .build();\n        }\n    });\n});\n```\n\n## JSON Schema Construction\n\nUse the fluent schema builder:\n\n```java\nimport io.mcp.json.JsonSchema;\n\nJsonSchema schema = JsonSchema.object()\n    .property(\"name\", JsonSchema.string()\n        .description(\"User's name\")\n        .minLength(1)\n        .maxLength(100)\n        .required(true))\n    .property(\"age\", JsonSchema.integer()\n        .description(\"User's age\")\n        .minimum(0)\n        .maximum(150))\n    .property(\"email\", JsonSchema.string()\n        .description(\"Email address\")\n        .format(\"email\")\n        .required(true))\n    .property(\"tags\", JsonSchema.array()\n        .items(JsonSchema.string())\n        .uniqueItems(true))\n    .additionalProperties(false)\n    .build();\n```\n\n## Logging and Observability\n\nUse SLF4J for logging:\n\n```java\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nprivate static final Logger log = LoggerFactory.getLogger(MyMcpServer.class);\n\nserver.addToolHandler(\"process\", (args) -\u003e {\n    log.info(\"Tool called: process, args: {}\", args);\n    \n    return Mono.fromCallable(() -\u003e {\n        String result = process(args);\n        log.debug(\"Processing completed successfully\");\n        return ToolResponse.success()\n            .addTextContent(result)\n            .build();\n    }).doOnError(error -\u003e {\n        log.error(\"Processing failed\", error);\n    });\n});\n```\n\nPropagate context with Reactor:\n\n```java\nimport reactor.util.context.Context;\n\nserver.addToolHandler(\"traced\", (args) -\u003e {\n    return Mono.deferContextual(ctx -\u003e {\n        String traceId = ctx.get(\"traceId\");\n        log.info(\"Processing with traceId: {}\", traceId);\n        \n        return Mono.just(ToolResponse.success()\n            .addTextContent(\"Processed\")\n            .build());\n    });\n});\n```\n\n## Testing\n\nWrite tests using the synchronous API:\n\n```java\nimport org.junit.jupiter.api.Test;\nimport static org.assertj.core.Assertions.assertThat;\n\nclass McpServerTest {\n    \n    @Test\n    void testToolHandler() {\n        McpServer server = createTestServer();\n        McpSyncServer syncServer = server.toSyncServer();\n        \n        JsonNode args = objectMapper.createObjectNode()\n            .put(\"query\", \"test\");\n        \n        ToolResponse response = syncServer.callTool(\"search\", args);\n        \n        assertThat(response.isError()).isFalse();\n        assertThat(response.getContent()).hasSize(1);\n    }\n}\n```\n\n## Jackson Integration\n\nThe SDK uses Jackson for JSON serialization. Customize as needed:\n\n```java\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\n\nObjectMapper mapper = new ObjectMapper();\nmapper.registerModule(new JavaTimeModule());\n\n// Use custom mapper with server\nMcpServer server = McpServerBuilder.builder()\n    .objectMapper(mapper)\n    .build();\n```\n\n## Content Types\n\nSupport multiple content types in responses:\n\n```java\nimport io.mcp.server.content.Content;\n\nserver.addToolHandler(\"multi\", (args) -\u003e {\n    return Mono.just(ToolResponse.success()\n        .addTextContent(\"Plain text response\")\n        .addImageContent(imageBytes, \"image/png\")\n        .addResourceContent(\"resource://data\", \"application/json\", jsonData)\n        .build());\n});\n```\n\n## Server Lifecycle\n\nProperly manage server lifecycle:\n\n```java\nimport reactor.core.Disposable;\n\nDisposable serverDisposable = server.start(transport).subscribe();\n\n// Graceful shutdown\nRuntime.getRuntime().addShutdownHook(new Thread(() -\u003e {\n    log.info(\"Shutting down MCP server\");\n    serverDisposable.dispose();\n    server.stop().block();\n}));\n```\n\n## Common Patterns\n\n### Request Validation\n\n```java\nserver.addToolHandler(\"validate\", (args) -\u003e {\n    if (!args.has(\"required_field\")) {\n        return Mono.just(ToolResponse.error()\n            .message(\"Missing required_field\")\n            .build());\n    }\n    \n    return processRequest(args);\n});\n```\n\n### Async Operations\n\n```java\nserver.addToolHandler(\"async\", (args) -\u003e {\n    return Mono.fromCallable(() -\u003e callExternalApi(args))\n        .timeout(Duration.ofSeconds(30))\n        .onErrorResume(TimeoutException.class, e -\u003e \n            Mono.just(ToolResponse.error()\n                .message(\"Operation timed out\")\n                .build()))\n        .subscribeOn(Schedulers.boundedElastic());\n});\n```\n\n### Resource Caching\n\n```java\nprivate final Map\u003cString, String\u003e cache = new ConcurrentHashMap\u003c\u003e();\n\nserver.addResourceReadHandler((uri) -\u003e {\n    return Mono.fromCallable(() -\u003e \n        cache.computeIfAbsent(uri, this::loadResource))\n        .map(content -\u003e ResourceContent.text(content, uri));\n});\n```\n\n## Best Practices\n\n1. **Use Reactive Streams** for async operations and backpressure\n2. **Leverage Spring Boot** starter for enterprise applications\n3. **Implement proper error handling** with specific error messages\n4. **Use SLF4J** for logging, not System.out\n5. **Validate inputs** in tool and prompt handlers\n6. **Support graceful shutdown** with proper resource cleanup\n7. **Use bounded elastic scheduler** for blocking operations\n8. **Propagate context** for observability in reactive chains\n9. **Test with synchronous API** for simplicity\n10. **Follow Java naming conventions** (camelCase for methods, PascalCase for classes)\n","description":"Best practices and patterns for building Model Context Protocol (MCP) servers in Java using the official MCP Java SDK with reactive streams and Spring integration.","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/java-mcp-server.instructions.md"},"manifest":{}},"content_hash":[11,9,55,13,103,54,115,126,78,62,192,143,8,123,61,30,171,204,73,46,26,200,93,198,59,12,188,53,157,62,166,2],"trust_level":"unsigned","yanked":false}
