{"id":34596034,"url":"https://github.com/kliushnichenko/jooby-mcp","last_synced_at":"2026-03-14T02:38:52.787Z","repository":{"id":299070715,"uuid":"1001527100","full_name":"kliushnichenko/jooby-mcp","owner":"kliushnichenko","description":" Jooby module that lets you run a Model Context Protocol (MCP) server","archived":false,"fork":false,"pushed_at":"2026-02-16T10:34:41.000Z","size":317,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-16T17:38:36.969Z","etag":null,"topics":["java","jooby","mcp","mcp-server","microservice"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kliushnichenko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-13T14:31:33.000Z","updated_at":"2026-02-16T10:34:46.000Z","dependencies_parsed_at":"2025-06-14T14:20:26.920Z","dependency_job_id":"acc95df3-bfe5-4aae-822c-150ef6ae7bed","html_url":"https://github.com/kliushnichenko/jooby-mcp","commit_stats":null,"previous_names":["kliushnichenko/jooby-mcp"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/kliushnichenko/jooby-mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kliushnichenko%2Fjooby-mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kliushnichenko%2Fjooby-mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kliushnichenko%2Fjooby-mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kliushnichenko%2Fjooby-mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kliushnichenko","download_url":"https://codeload.github.com/kliushnichenko/jooby-mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kliushnichenko%2Fjooby-mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30486267,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-14T01:54:10.014Z","status":"online","status_checked_at":"2026-03-14T02:00:06.612Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","jooby","mcp","mcp-server","microservice"],"created_at":"2025-12-24T11:41:06.671Z","updated_at":"2026-03-14T02:38:52.756Z","avatar_url":"https://github.com/kliushnichenko.png","language":"Java","readme":"[![Maven Central](https://img.shields.io/maven-central/v/io.github.kliushnichenko/jooby-mcp.svg)](https://central.sonatype.com/search?q=jooby-mcp)\n[![Javadoc](https://javadoc.io/badge/io.github.kliushnichenko/jooby-mcp/jooby-mcp.svg)](https://javadoc.io/doc/io.github.kliushnichenko/jooby-mcp/latest)\n[![Github](https://github.com/kliushnichenko/jooby-mcp/actions/workflows/maven.yml/badge.svg)](https://github.com/kliushnichenko/jooby-mcp/actions)\n\n## jooby-mcp\n\nThis module integrates the official [Java MCP SDK](https://github.com/modelcontextprotocol/java-sdk) with [Jooby](https://github.com/jooby-project/jooby)’s routing, server features, and dependency injection.  \n\nThis module enables declarative (annotation-based) registration of tools, prompts, and resources.\nAnnotations are discovered at build time using APT, eliminating the need for runtime reflection.\nTo use it, add the annotation processor alongside the module dependency.\n\nCompatibility:\n\n| Jooby Version | Jooby MCP Version |\n|---------------|-------------------|\n| 3.x           | 1.x               |\n| 4.x           | 2.x               |\n\nFeatures:\n\n- [X] SSE, Streamable-HTTP and Stateless Streamable-HTTP transport\n- [X] Multiple servers support\n- [X] Tools\n- [X] Prompts\n- [X] Resources\n- [X] Resource Templates\n- [X] Prompt Completions\n- [X] Resource Template Completions\n- [X] Required input arguments validation in tools\n- [X] Build time method signature and return type validation\n- [X] Elicitation, Sampling, Progress support via exchange object \n- [X] MCP Inspector integration module\n\nTable of Contents:\n\n- [Quick Start](#quick-start)\n- [Tools \u0026 Prompts Example](#tools--prompts-example)\n- [Resource Example](#resource-example)\n- [Resource Template Example](#resource-template-example)\n- [Prompt Completion Example](#prompt-completion-example)\n- [Exchange Object](#exchange-object)\n- [Multiple Servers Support](#multiple-servers-support)\n- [Customizing Default Server Name and Package](#customizing-default-server-name-and-package)\n- [MCP Inspector Module](#mcp-inspector-module)\n- [Appendix: Supported Return Types](#appendix-supported-return-types)\n\n## Quick Start\n\n1. Add the dependency to your `pom.xml`:\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.github.kliushnichenko\u003c/groupId\u003e\n        \u003cartifactId\u003ejooby-mcp\u003c/artifactId\u003e\n        \u003cversion\u003e${jooby.mcp.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n   ```\n2. Add annotation processor\n   ```xml\n   \u003cplugin\u003e\n       \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n       \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n       \u003cconfiguration\u003e\n           \u003cannotationProcessorPaths\u003e\n               \u003cpath\u003e\n                   \u003cgroupId\u003eio.github.kliushnichenko\u003c/groupId\u003e\n                   \u003cartifactId\u003ejooby-mcp-apt\u003c/artifactId\u003e\n                   \u003cversion\u003e${jooby.mcp.version}\u003c/version\u003e\n               \u003c/path\u003e\n           \u003c/annotationProcessorPaths\u003e\n       \u003c/configuration\u003e\n   \u003c/plugin\u003e\n   ```\n\n3. Add configuration to `application.conf`   \n   ⚠️ Since version `1.4.0` default transport was changed from `SSE` to `Streamable HTTP`\n   ```\n   mcp.default {                       # `default` is the server key, can be customized over compiler arguments\n     name: \"my-awesome-mcp-server\"     # Required\n     version: \"0.1.0\"                  # Required\n     transport: \"sse\"                  # Optional (default: streamable-http)\n     sseEndpoint: \"/mcp/sse\"           # Optional (default: /mcp/sse), applicable only to SSE transport\n     messageEndpoint: \"/mcp/message\"   # Optional (default: /mcp/message), applicable only to SSE transport\n   }\n   ```\n\n   Full config for `Streamable HTTP` transport:\n   ```\n    mcp.default {\n      name: \"my-awesome-mcp-server\"    # Required\n      version: \"0.1.0\"                 # Required\n      transport: \"streamable-http\"     # Optional (default: streamable-http)\n      mcpEndpoint: \"/mcp/streamable\"   # Optional (default: /mcp), applicable only to Streamable HTTP transport\n      disallowDelete: true             # Optional (default: false)\n      keepAliveInterval: 45            # Optional (default: N/A), in seconds\n      instructions: \"...\"              # Optional \n    }\n    ```\n\n   Full config for `Stateless Streamable HTTP` transport:\n   ```\n    mcp.default {\n      name: \"my-awesome-mcp-server\"    \n      version: \"0.1.0\"                 \n      transport: \"stateless-streamable-http\"    \n      mcpEndpoint: \"/mcp/stateless-streamable\"   # Optional (default: /mcp)\n    }\n    ```\n\n   *`keepAliveInterval` - enables sending periodic keep-alive messages to the client.  \n   Disabled by default to avoid excessive network overhead. Set to a positive integer value (in seconds) to enable.\n   \n   *`instructions` - Sets the server instructions that will be shared with clients during connection initialization. These instructions provide guidance to the client on how to interact with this server.\n\n4. Implement your features (tools, prompts, resources, etc.), see examples below or in\n   the [example-project](https://github.com/kliushnichenko/jooby-mcp/blob/1.x/jooby-mcp-example/src/main/java/io/github/kliushnichenko/mcp/example)\n\n5. Install the module. After compilation, you can observe generated `DefaultMcpServer` class. Now register its instance in the module:\n   ```java\n   {\n      install(new JacksonModule());                    // a JSON encode/decoder is required for JSONRPC\n      install(AvajeInjectModule.of());                 // or other DI module of your choice\n      install(new McpModule(new DefaultMcpServer());   // register MCP server\n   }\n   ```\n\n### Tools \u0026 Prompts Example\n\n```java\nimport io.github.kliushnichenko.jooby.mcp.annotation.Tool;\nimport io.github.kliushnichenko.jooby.mcp.annotation.ToolArg;\nimport io.github.kliushnichenko.jooby.mcp.annotation.Prompt;\n\n@Singleton\npublic class ToolsAndPromptsExample {\n\n    @Tool(name = \"add\", description = \"Adds two numbers together\")\n    public String add(\n            @ToolArg(name = \"first\", description = \"First number to add\") int a,\n            @ToolArg(name = \"second\", description = \"Second number to add\") int b\n    ) {\n        int result = a + b;\n        return String.valueOf(result);\n    }\n\n    @Tool\n    public String subtract(int a, int b) {\n        int result = a - b;\n        return String.valueOf(result);\n    }\n\n    @Prompt(name = \"summarizeText\", description = \"Summarizes the provided text into a specified number of sentences\")\n    public String summarizeText(@PromptArg(name = \"text\") String text, String maxSentences) {\n        return String.format(\"\"\"\n                Please provide a clear and concise summary of the following text in no more than %s sentences:\n                %s\n                \"\"\", maxSentences, text);\n    }\n} \n```\n\n#### Tools Output Schema\n\nThe output schema is automatically generated based on the return type of the method.\nFor example, for the `find_pet` tool below\n\n```java\n@Tool(name = \"find_pet\", description = \"Finds a pet by its ID\")\npublic Pet findPet(String petId) {\n    ...\n}\n```\nthe generated output schema will reflect the `Pet` class structure.  \n\nIf the return type is one of the reserved types (`String`, `McpSchema.CallToolResult`, `McpSchema.Content`) \nauto-generation step is omitted. \nUse `@OutputSchema` annotation to explicitly define the output schema in such cases:  \n- `@OutputSchema.From(Pet.class)` - to use schema generated from a specific class\n- `@OutputSchema.ArrayOf(Pet.class)` - to use array of specific class as output schema\n- `@OutputSchema.MapOf(Pet.class)` - to use map of specific class as output schema\n\nExample:\n```java\n@Tool(name = \"find_pet\", description = \"Finds a pet by its ID\")\n@OutputSchema.From(Pet.class)\npublic McpSchema.CallToolResult findPet(String petId) {\n    ...\n    \n    return new McpSchema.CallToolResult(pet, false);\n}\n```\n\n**Tip**: You can use `@OutputSchema.Suppressed` to skip output schema generation from return type.\n\n### How to enrich json schema?\n\nWhether the schema is generated from method arguments or return type, you can enrich it using OpenAPI annotations. At the moment, generator will respect `description` and `required` attributes from `@Schema` annotation. Also, if `@JsonProperty` is present on a field, its `value` will be used as the property name in the generated schema.\n\n```java\nimport io.swagger.v3.oas.annotations.media.Schema;  \nclass User {\n    @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = \"The user's middle name\")\n    @JsonProperty(\"middle-name\")\n    private String middleName;\n}\n```\n\n### Resource Example\n\n```java\nimport io.github.kliushnichenko.jooby.mcp.annotation.Resource;\n\n@Singleton\npublic class ResourceExamples {\n\n    @Resource(uri = \"file:///project/README.md\", name = \"README.md\", title = \"README\", mimeType = \"text/markdown\")\n    public McpSchema.TextResourceContents textResource() {\n        String content = \"\"\"\n                # Project Title\n\n                This is an example README file for the project.\n\n                ## Features\n\n                - Feature 1\n                - Feature 2\n                - Feature 3\n\n                \"\"\";\n        return new McpSchema.TextResourceContents(\"file:///project/README.md\", \"text/markdown\", content);\n    }\n}\n```\n\nFind more examples in the [example-project](https://github.com/kliushnichenko/jooby-mcp/blob/1.x/jooby-mcp-example/src/main/java/io/github/kliushnichenko/mcp/example/ResourceExamples.java)\n\n### Resource Template Example\n\n```java\nimport io.github.kliushnichenko.jooby.mcp.annotation.ResourceTemplate;\nimport io.github.kliushnichenko.jooby.mcp.annotation.CompleteResourceTemplate;\n\n@Singleton\npublic class ResourceTemplateExamples {\n\n    private static final Map\u003cString, String\u003e PROJECTS = Map.of(\n            \"project-alpha\", \"This is Project Alpha.\",\n            \"project-beta\", \"This is Project Beta.\",\n            \"project-gamma\", \"This is Project Gamma.\"\n    );\n\n    @ResourceTemplate(name = \"get_project\", uriTemplate = \"file:///project/{name}\")\n    public McpSchema.TextResourceContents getProject(String name, ResourceUri resourceUri) {\n        String content = PROJECTS.getOrDefault(name, \"\u003cProject not found\u003e\");\n        return new McpSchema.TextResourceContents(resourceUri.uri(), \"text/markdown\", content);\n    }\n\n    @CompleteResourceTemplate(\"get_project\")\n    public List\u003cString\u003e projectNameCompletion(@CompleteArg(name = \"name\") String partialInput) {\n        return PROJECTS.keySet()\n                .stream()\n                .filter(name -\u003e name.contains(partialInput))\n                .toList();\n    }\n}\n```\n\n### Prompt Completion Example\n\n```java\nimport io.github.kliushnichenko.jooby.mcp.annotation.CompleteArg;\nimport io.github.kliushnichenko.jooby.mcp.annotation.CompletePrompt;\nimport io.github.kliushnichenko.jooby.mcp.annotation.Prompt;\n\n@Singleton\npublic class PromptCompletionsExample {\n\n    private static final List\u003cString\u003e SUPPORTED_LANGUAGES = List.of(\"Java\", \"Python\", \"JavaScript\", \"Go\", \"TypeScript\");\n\n    @Prompt(name = \"code_review\", description = \"Code Review Prompt\")\n    public String codeReviewPrompt(String codeSnippet, String language) {\n        return \"\"\"\n                You are a senior software engineer tasked with reviewing the following %s code snippet:\n                   \n                %s\n                   \n                Please provide feedback on:\n                1. Code readability and maintainability.\n                2. Potential bugs or issues.\n                3. Suggestions for improvement.\n                 \"\"\".formatted(language, codeSnippet);\n    }\n\n    @CompletePrompt(\"code_review\")\n    public List\u003cString\u003e completeCodeReviewLang(@CompleteArg(name = \"language\") String partialInput) {\n        return SUPPORTED_LANGUAGES.stream()\n                .filter(lang -\u003e lang.toLowerCase().contains(partialInput.toLowerCase()))\n                .toList();\n    }\n}\n```\n### Exchange Object\n\nAn exchange object can be used to access request metadata, and support `Elicitation`, `Sampling` or `Progress` features.  \nAdd `McpSyncServerExchange` argument to your tool method:\n\n```java\npublic class ElicitationExample {\n    \n    @Tool(name = \"elicitation_example\")\n    public String elicitationExample(McpSyncServerExchange exchange) {\n        ...\n        exchange.createElicitation(request);\n        ...\n    }\n} \n```\nSee full example at [example-project](https://github.com/kliushnichenko/jooby-mcp/blob/1.x/jooby-mcp-example/src/main/java/io/github/kliushnichenko/mcp/example/ElicitationExample.java)\n\nIn the similar manner you can support Sampling, Progress and other features by using `McpSyncServerExchange` object.  \nExplore SDK documentation for more details: https://modelcontextprotocol.io/sdk/java/mcp-server#using-sampling-from-a-server\n\n### Multiple Servers Support\n\nUse `@McpServer` annotation to assign a tool or prompt to a specific server. Annotation can be applied at the class or\nmethod level.\n\n   ```java\n   import io.github.kliushnichenko.jooby.mcp.annotation.McpServer;\n\n@Singleton\n@McpServer(\"weather\")\npublic class WeatherService {\n\n    public record Coordinates(double latitude, double longitude) {\n    }\n\n    @Tool(name = \"get_weather\")\n    public String getWeather(Coordinates coordinates) {\n            ...\n    }\n}\n   ```\n\nAs a result, additional `WeatherMcpServer` class will be generated. Register it in the module:\n\n   ```java\n   {\n        install(new McpModule(new DefaultMcpServer(),new WeatherMcpServer()));\n   }\n   ```\n\nThe weather MCP server should have its own configuration section in `application.conf`:\n\n   ```\n   mcp.weather {\n     name: \"weather-mcp-server\"\n     version: \"0.1.0\"\n     mcpEndpoint: \"/mcp/weather\"\n   }\n   ```\n\n### Customizing Default Server Name and Package\n\nYou can customize the default server name and the package where the generated server classes will be placed by providing\ncompiler arguments. For example, to set the default server name to `CalculatorMcpServer` and the package\nto `com.acme.corp.mcp`, you can add the following configuration to your `pom.xml`:\n\n   ```xml\n\n\u003cplugin\u003e\n    \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n    \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n    \u003cconfiguration\u003e\n        \u003cannotationProcessorPaths\u003e\n            \u003cpath\u003e\n                \u003cgroupId\u003eio.github.kliushnichenko\u003c/groupId\u003e\n                \u003cartifactId\u003ejooby-mcp-apt\u003c/artifactId\u003e\n                \u003cversion\u003e${version}\u003c/version\u003e\n            \u003c/path\u003e\n        \u003c/annotationProcessorPaths\u003e\n        \u003ccompilerArgs\u003e\n            \u003carg\u003e-Amcp.default.server.key=calculator\u003c/arg\u003e\n            \u003carg\u003e-Amcp.target.package=com.acme.corp.mcp\u003c/arg\u003e\n        \u003c/compilerArgs\u003e\n    \u003c/configuration\u003e\n\u003c/plugin\u003e\n   ```\n\nMind, that `mcp.default.server.key` should match the configuration section in `application.conf`:\n\n   ```\n   mcp.calculator {\n     name: \"calculator-mcp-server\"\n     version: \"0.1.0\"\n     mcpEndpoint: \"/mcp/calculator\"\n   }\n   ```\n\n### MCP Inspector Module\n\nYou can use the `McpInspectorModule` to spin up [mcp-inspector-ui](https://github.com/modelcontextprotocol/inspector) right on jooby server\nand speed up your development process. Mind, that it works in `Direct` connection mode only, \naimed solely to test your local MCP server during development and requires `McpModule` to be installed.\nTo enable it, just install the module alongside `McpModule`:\n\n```java\n{\n    install(new McpInspectorModule());\n}\n```\n\nBy default, inspector will be available at `/mcp-inspector` path. And it will try to auto-connect to MCP server upon loading.    \nYou can customize both options over corresponding mutators:\n\n```java\n{\n    install(new McpInspectorModule()\n                .path(\"/custom-inspector-path\")\n                .autoConnect(false);\n    );\n}\n```\n\nIf  you have more than one MCP server registered, you can specify which one to connect to by default:\n\n```java\n{\n    install(new McpInspectorModule()\n                .defaultMcpServer(\"weather-mcp-server\")  // server name from application.conf settings\n    );\n}\n```\n\nDependency:\n```xml\n \u003cdependency\u003e\n    \u003cgroupId\u003eio.github.kliushnichenko\u003c/groupId\u003e\n    \u003cartifactId\u003ejooby-mcp-inspector\u003c/artifactId\u003e\n    \u003cversion\u003e${jooby.mcp.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Appendix: Supported Return Types\n\n##### Supported return types in Tools\n\n- `String`\n- `McpSchema.CallToolResult`\n- `McpSchema.Content`\n- `McpSchema.TextContent`\n- POJO (will be serialized to JSON)\n\n##### Supported return types in Prompts\n\n- `McpSchema.GetPromptResult`\n- `McpSchema.PromptMessage`\n- `List\u003cMcpSchema.PromptMessage\u003e`\n- `McpSchema.Content`\n- `String`\n- POJO (`toString()` method will be invoked to get the string representation)\n\n##### Supported return types in Resources and Resource Templates\n\n- `McpSchema.ReadResourceResult`\n- `McpSchema.ResourceContents`\n- `List\u003cMcpSchema.ResourceContents\u003e`\n- `McpSchema.TextResourceContents`\n- `McpSchema.BlobResourceContents`\n- POJO (will be serialized to JSON)\n\n##### Supported return types in Completions\n\n- `McpSchema.CompleteResult`\n- `McpSchema.CompleteResult.CompleteCompletion`\n- `List\u003cString\u003e`\n- `String`\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkliushnichenko%2Fjooby-mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkliushnichenko%2Fjooby-mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkliushnichenko%2Fjooby-mcp/lists"}