diff --git a/.github/workflows/hugo.yml b/.github/workflows/hugo.yml
new file mode 100644
index 0000000..314f31f
--- /dev/null
+++ b/.github/workflows/hugo.yml
@@ -0,0 +1,59 @@
+name: Deploy documentation to GitHub Pages
+
+on:
+ push:
+ branches: ["main"]
+ paths:
+ - "docs/**"
+ - ".github/workflows/hugo.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+jobs:
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Hugo
+ uses: peaceiris/actions-hugo@v3
+ with:
+ hugo-version: "latest"
+ hugo-extended: true
+
+ - name: Get Hugo modules
+ working-directory: docs
+ run: hugo mod get -u
+
+ - name: Build site
+ working-directory: docs
+ env:
+ # GA ID from repo secret GA_MEASUREMENT_ID (never commit the ID)
+ HUGO_SERVICES_GOOGLEANALYTICS_ID: ${{ secrets.GA_MEASUREMENT_ID }}
+ run: hugo --minify --environment production
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs/public
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 524f096..c2c0199 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,7 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+# Hugo
+docs/public/
+docs/resources/_gen/
\ No newline at end of file
diff --git a/README.md b/README.md
index fce6ce3..ca48182 100644
--- a/README.md
+++ b/README.md
@@ -1,477 +1,33 @@
[](https://central.sonatype.com/search?q=jooby-mcp)
-[](https://javadoc.io/doc/io.github.kliushnichenko/jooby-mcp/latest)
[](https://github.com/kliushnichenko/jooby-mcp/actions)
+[](https://kliushnichenko.github.io/jooby-mcp/)
+[](https://javadoc.io/doc/io.github.kliushnichenko/jooby-mcp/latest)
## jooby-mcp
-This 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.
-
-This module enables declarative (annotation-based) registration of tools, prompts, and resources.
-Annotations are discovered at build time using APT, eliminating the need for runtime reflection.
-To use it, add the annotation processor alongside the module dependency.
-
-Compatibility:
-
-| Jooby Version | Jooby MCP Version |
-|---------------|-------------------|
-| 3.x | 1.x |
-| 4.x | 2.x |
-
-Features:
-
-- [X] SSE, Streamable-HTTP and Stateless Streamable-HTTP transport
-- [X] Multiple servers support
-- [X] Tools
-- [X] Prompts
-- [X] Resources
-- [X] Resource Templates
-- [X] Prompt Completions
-- [X] Resource Template Completions
-- [X] Required input arguments validation in tools
-- [X] Build time method signature and return type validation
-- [X] Elicitation, Sampling, Progress support via exchange object
-- [X] MCP Inspector integration module
-
-Table of Contents:
-
-- [Quick Start](#quick-start)
-- [Tools & Prompts Example](#tools--prompts-example)
-- [Resource Example](#resource-example)
-- [Resource Template Example](#resource-template-example)
-- [Prompt Completion Example](#prompt-completion-example)
-- [Exchange Object](#exchange-object)
-- [Multiple Servers Support](#multiple-servers-support)
-- [Customizing Default Server Name and Package](#customizing-default-server-name-and-package)
-- [MCP Inspector Module](#mcp-inspector-module)
-- [Appendix: Supported Return Types](#appendix-supported-return-types)
-
-## Quick Start
-
-1. Add the dependency to your `pom.xml`:
- ```xml
-
- io.github.kliushnichenko
- jooby-mcp
- ${jooby.mcp.version}
-
- ```
-2. Add annotation processor
- ```xml
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- io.github.kliushnichenko
- jooby-mcp-apt
- ${jooby.mcp.version}
-
-
-
-
- ```
-
-3. Add configuration to `application.conf`
- ⚠️ Since version `1.4.0` default transport was changed from `SSE` to `Streamable HTTP`
- ```
- mcp.default { # `default` is the server key, can be customized over compiler arguments
- name: "my-awesome-mcp-server" # Required
- version: "0.1.0" # Required
- transport: "sse" # Optional (default: streamable-http)
- sseEndpoint: "/mcp/sse" # Optional (default: /mcp/sse), applicable only to SSE transport
- messageEndpoint: "/mcp/message" # Optional (default: /mcp/message), applicable only to SSE transport
- }
- ```
-
- Full config for `Streamable HTTP` transport:
- ```
- mcp.default {
- name: "my-awesome-mcp-server" # Required
- version: "0.1.0" # Required
- transport: "streamable-http" # Optional (default: streamable-http)
- mcpEndpoint: "/mcp/streamable" # Optional (default: /mcp), applicable only to Streamable HTTP transport
- disallowDelete: true # Optional (default: false)
- keepAliveInterval: 45 # Optional (default: N/A), in seconds
- instructions: "..." # Optional
- }
- ```
-
- Full config for `Stateless Streamable HTTP` transport:
- ```
- mcp.default {
- name: "my-awesome-mcp-server"
- version: "0.1.0"
- transport: "stateless-streamable-http"
- mcpEndpoint: "/mcp/stateless-streamable" # Optional (default: /mcp)
- }
- ```
-
- *`keepAliveInterval` - enables sending periodic keep-alive messages to the client.
- Disabled by default to avoid excessive network overhead. Set to a positive integer value (in seconds) to enable.
-
- *`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.
-
-4. Implement your features (tools, prompts, resources, etc.), see examples below or in
- the [example-project](https://github.com/kliushnichenko/jooby-mcp/blob/1.x/jooby-mcp-example/src/main/java/io/github/kliushnichenko/mcp/example)
-
-5. Install the module. After compilation, you can observe generated `DefaultMcpServer` class. Now register its instance in the module:
- ```java
- {
- install(new JacksonModule()); // a JSON encode/decoder is required for JSONRPC
- install(AvajeInjectModule.of()); // or other DI module of your choice
- install(new McpModule(new DefaultMcpServer()); // register MCP server
- }
- ```
-
-### Tools & Prompts Example
-
-```java
-import io.github.kliushnichenko.jooby.mcp.annotation.Tool;
-import io.github.kliushnichenko.jooby.mcp.annotation.ToolArg;
-import io.github.kliushnichenko.jooby.mcp.annotation.Prompt;
-
-@Singleton
-public class ToolsAndPromptsExample {
-
- @Tool(name = "add", description = "Adds two numbers together")
- public String add(
- @ToolArg(name = "first", description = "First number to add") int a,
- @ToolArg(name = "second", description = "Second number to add") int b
- ) {
- int result = a + b;
- return String.valueOf(result);
- }
-
- @Tool
- public String subtract(int a, int b) {
- int result = a - b;
- return String.valueOf(result);
- }
-
- @Prompt(name = "summarizeText", description = "Summarizes the provided text into a specified number of sentences")
- public String summarizeText(@PromptArg(name = "text") String text, String maxSentences) {
- return String.format("""
- Please provide a clear and concise summary of the following text in no more than %s sentences:
- %s
- """, maxSentences, text);
- }
-}
-```
-
-#### Tools Output Schema
-
-The output schema is automatically generated based on the return type of the method.
-For example, for the `find_pet` tool below
-
-```java
-@Tool(name = "find_pet", description = "Finds a pet by its ID")
-public Pet findPet(String petId) {
- ...
-}
-```
-the generated output schema will reflect the `Pet` class structure.
-
-If the return type is one of the reserved types (`String`, `McpSchema.CallToolResult`, `McpSchema.Content`)
-auto-generation step is omitted.
-Use `@OutputSchema` annotation to explicitly define the output schema in such cases:
-- `@OutputSchema.From(Pet.class)` - to use schema generated from a specific class
-- `@OutputSchema.ArrayOf(Pet.class)` - to use array of specific class as output schema
-- `@OutputSchema.MapOf(Pet.class)` - to use map of specific class as output schema
-
-Example:
-```java
-@Tool(name = "find_pet", description = "Finds a pet by its ID")
-@OutputSchema.From(Pet.class)
-public McpSchema.CallToolResult findPet(String petId) {
- ...
-
- return new McpSchema.CallToolResult(pet, false);
-}
-```
-
-**Tip**: You can use `@OutputSchema.Suppressed` to skip output schema generation from return type.
-
-### How to enrich json schema?
-
-Whether 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.
-
-```java
-import io.swagger.v3.oas.annotations.media.Schema;
-class User {
- @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The user's middle name")
- @JsonProperty("middle-name")
- private String middleName;
-}
-```
-
-### Resource Example
-
-```java
-import io.github.kliushnichenko.jooby.mcp.annotation.Resource;
-
-@Singleton
-public class ResourceExamples {
-
- @Resource(uri = "file:///project/README.md", name = "README.md", title = "README", mimeType = "text/markdown")
- public McpSchema.TextResourceContents textResource() {
- String content = """
- # Project Title
-
- This is an example README file for the project.
-
- ## Features
-
- - Feature 1
- - Feature 2
- - Feature 3
-
- """;
- return new McpSchema.TextResourceContents("file:///project/README.md", "text/markdown", content);
- }
-}
-```
-
-Find 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)
-
-### Resource Template Example
-
-```java
-import io.github.kliushnichenko.jooby.mcp.annotation.ResourceTemplate;
-import io.github.kliushnichenko.jooby.mcp.annotation.CompleteResourceTemplate;
-
-@Singleton
-public class ResourceTemplateExamples {
-
- private static final Map PROJECTS = Map.of(
- "project-alpha", "This is Project Alpha.",
- "project-beta", "This is Project Beta.",
- "project-gamma", "This is Project Gamma."
- );
-
- @ResourceTemplate(name = "get_project", uriTemplate = "file:///project/{name}")
- public McpSchema.TextResourceContents getProject(String name, ResourceUri resourceUri) {
- String content = PROJECTS.getOrDefault(name, "");
- return new McpSchema.TextResourceContents(resourceUri.uri(), "text/markdown", content);
- }
-
- @CompleteResourceTemplate("get_project")
- public List projectNameCompletion(@CompleteArg(name = "name") String partialInput) {
- return PROJECTS.keySet()
- .stream()
- .filter(name -> name.contains(partialInput))
- .toList();
- }
-}
-```
-
-### Prompt Completion Example
-
-```java
-import io.github.kliushnichenko.jooby.mcp.annotation.CompleteArg;
-import io.github.kliushnichenko.jooby.mcp.annotation.CompletePrompt;
-import io.github.kliushnichenko.jooby.mcp.annotation.Prompt;
-
-@Singleton
-public class PromptCompletionsExample {
-
- private static final List SUPPORTED_LANGUAGES = List.of("Java", "Python", "JavaScript", "Go", "TypeScript");
-
- @Prompt(name = "code_review", description = "Code Review Prompt")
- public String codeReviewPrompt(String codeSnippet, String language) {
- return """
- You are a senior software engineer tasked with reviewing the following %s code snippet:
-
- %s
-
- Please provide feedback on:
- 1. Code readability and maintainability.
- 2. Potential bugs or issues.
- 3. Suggestions for improvement.
- """.formatted(language, codeSnippet);
- }
-
- @CompletePrompt("code_review")
- public List completeCodeReviewLang(@CompleteArg(name = "language") String partialInput) {
- return SUPPORTED_LANGUAGES.stream()
- .filter(lang -> lang.toLowerCase().contains(partialInput.toLowerCase()))
- .toList();
- }
-}
-```
-### Exchange Object
-
-An exchange object can be used to access request metadata, and support `Elicitation`, `Sampling` or `Progress` features.
-Add `McpSyncServerExchange` argument to your tool method:
-
-```java
-public class ElicitationExample {
-
- @Tool(name = "elicitation_example")
- public String elicitationExample(McpSyncServerExchange exchange) {
- ...
- exchange.createElicitation(request);
- ...
- }
-}
-```
-See 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)
-
-In the similar manner you can support Sampling, Progress and other features by using `McpSyncServerExchange` object.
-Explore SDK documentation for more details: https://modelcontextprotocol.io/sdk/java/mcp-server#using-sampling-from-a-server
-
-### Multiple Servers Support
-
-Use `@McpServer` annotation to assign a tool or prompt to a specific server. Annotation can be applied at the class or
-method level.
-
- ```java
- import io.github.kliushnichenko.jooby.mcp.annotation.McpServer;
-
-@Singleton
-@McpServer("weather")
-public class WeatherService {
-
- public record Coordinates(double latitude, double longitude) {
- }
-
- @Tool(name = "get_weather")
- public String getWeather(Coordinates coordinates) {
- ...
- }
-}
- ```
-
-As a result, additional `WeatherMcpServer` class will be generated. Register it in the module:
-
- ```java
- {
- install(new McpModule(new DefaultMcpServer(),new WeatherMcpServer()));
- }
- ```
-
-The weather MCP server should have its own configuration section in `application.conf`:
-
- ```
- mcp.weather {
- name: "weather-mcp-server"
- version: "0.1.0"
- mcpEndpoint: "/mcp/weather"
- }
- ```
-
-### Customizing Default Server Name and Package
-
-You can customize the default server name and the package where the generated server classes will be placed by providing
-compiler arguments. For example, to set the default server name to `CalculatorMcpServer` and the package
-to `com.acme.corp.mcp`, you can add the following configuration to your `pom.xml`:
-
- ```xml
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- io.github.kliushnichenko
- jooby-mcp-apt
- ${version}
-
-
-
- -Amcp.default.server.key=calculator
- -Amcp.target.package=com.acme.corp.mcp
-
-
-
- ```
-
-Mind, that `mcp.default.server.key` should match the configuration section in `application.conf`:
-
- ```
- mcp.calculator {
- name: "calculator-mcp-server"
- version: "0.1.0"
- mcpEndpoint: "/mcp/calculator"
- }
- ```
-
-### MCP Inspector Module
-
-You can use the `McpInspectorModule` to spin up [mcp-inspector-ui](https://github.com/modelcontextprotocol/inspector) right on jooby server
-and speed up your development process. Mind, that it works in `Direct` connection mode only,
-aimed solely to test your local MCP server during development and requires `McpModule` to be installed.
-To enable it, just install the module alongside `McpModule`:
-
-```java
-{
- install(new McpInspectorModule());
-}
-```
-
-By default, inspector will be available at `/mcp-inspector` path. And it will try to auto-connect to MCP server upon loading.
-You can customize both options over corresponding mutators:
-
-```java
-{
- install(new McpInspectorModule()
- .path("/custom-inspector-path")
- .autoConnect(false);
- );
-}
-```
-
-If you have more than one MCP server registered, you can specify which one to connect to by default:
-
-```java
-{
- install(new McpInspectorModule()
- .defaultMcpServer("weather-mcp-server") // server name from application.conf settings
- );
-}
-```
-
-Dependency:
-```xml
-
- io.github.kliushnichenko
- jooby-mcp-inspector
- ${jooby.mcp.version}
-
-```
+**jooby-mcp** integrates the [Model Context Protocol (MCP) Java SDK](https://github.com/modelcontextprotocol/java-sdk)
+with the [Jooby](https://github.com/jooby-project/jooby) framework. It lets you expose **tools**, **prompts**, and **resources** to MCP clients using declarative, annotation-based APIs with discovery at **build time** via an annotation
+processor, so no runtime reflection is required.
-### Appendix: Supported Return Types
+⭐ **If you find this project useful, consider giving it a star. It helps others discover it**
-##### Supported return types in Tools
+🚀 Quickstart, guides, and API reference available in the docs
-- `String`
-- `McpSchema.CallToolResult`
-- `McpSchema.Content`
-- `McpSchema.TextContent`
-- POJO (will be serialized to JSON)
+### Features
-##### Supported return types in Prompts
+**Transport**
-- `McpSchema.GetPromptResult`
-- `McpSchema.PromptMessage`
-- `List`
-- `McpSchema.Content`
-- `String`
-- POJO (`toString()` method will be invoked to get the string representation)
+- SSE, Streamable HTTP, and Stateless Streamable HTTP
+- Multiple MCP servers in one application
-##### Supported return types in Resources and Resource Templates
+**MCP capabilities**
-- `McpSchema.ReadResourceResult`
-- `McpSchema.ResourceContents`
-- `List`
-- `McpSchema.TextResourceContents`
-- `McpSchema.BlobResourceContents`
-- POJO (will be serialized to JSON)
+- Tools, Prompts, Resources
+- Resource templates with URI patterns and completions
+- Prompt completions and resource-template completions
-##### Supported return types in Completions
+**Quality & tooling**
-- `McpSchema.CompleteResult`
-- `McpSchema.CompleteResult.CompleteCompletion`
-- `List`
-- `String`
+- Required argument validation and build-time checks for method signatures and return types
+- Elicitation, Sampling, and Progress via the exchange object
+- Optional `MCP Inspector` module for local testing
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..57d25c8
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,58 @@
+# jooby-mcp Documentation Site
+
+This directory contains the source for the [jooby-mcp](https://github.com/kliushnichenko/jooby-mcp) documentation site, built with [Hugo](https://gohugo.io/) and the [Hextra](https://themes.gohugo.io/themes/hextra/) theme.
+
+## Prerequisites
+
+- [Hugo Extended](https://gohugo.io/installation/) (v0.146.0 or later)
+- [Go](https://go.dev/dl/) (required for Hugo Modules)
+
+## Local development
+
+**First time** (or after adding/updating the theme): fetch the Hextra theme via Hugo Modules:
+
+```bash
+cd docs
+hugo mod get -u
+```
+
+Commit `go.mod` and `go.sum` so CI and others use the same theme version.
+
+Then run the site:
+
+```bash
+hugo server --baseURL=http://localhost:1313/
+```
+
+Open **http://localhost:1313/** in your browser. Using `--baseURL` overrides the config (which targets GitHub Pages at `.../jooby-mcp/`) so the site is served at the root locally.
+
+## Build for production
+
+```bash
+cd docs
+hugo --minify
+```
+
+Output is in `docs/public/`.
+
+## Syntax highlighting
+
+The code block colors follow Hextra’s default Chroma styles (light/dark). To use another style (e.g. `monokai`, `nord`):
+
+1. Generate a stylesheet (from the `docs` directory):
+ ```bash
+ hugo gen chromastyles --style=monokai > assets/css/chroma-monokai.css
+ ```
+2. In `assets/css/custom.css`, add:
+ ```css
+ @import "chroma-monokai.css";
+ ```
+3. Rebuild. For dark mode you can generate a dark style (e.g. `--style=nord`) and scope it under `.dark` if needed.
+
+[Chroma style gallery](https://xyproto.github.io/splash/docs/all.html) lists all style names.
+
+## Deployment
+
+The site is deployed to [GitHub Pages](https://pages.github.com/) via the [Deploy documentation to GitHub Pages](.github/workflows/hugo.yml) workflow. Pushing changes under `docs/` to the `main` branch triggers a build and deploy.
+
+To enable the site: in the repository **Settings → Pages**, set **Source** to **GitHub Actions**.
diff --git a/docs/assets/css/custom.css b/docs/assets/css/custom.css
new file mode 100644
index 0000000..ac7d275
--- /dev/null
+++ b/docs/assets/css/custom.css
@@ -0,0 +1,43 @@
+/* Custom CSS for the docs site. Hextra loads this file automatically. */
+
+/*
+ * Syntax highlight theme (Chroma)
+ * Hextra does not expose a config param for the highlight style; you override via CSS.
+ *
+ * To use a different style (e.g. monokai, nord, solarized-dark):
+ * 1. Run: hugo gen chromastyles --style=STYLE > chroma-STYLE.css
+ * 2. Save that file here (e.g. assets/css/chroma-monokai.css)
+ * 3. Uncomment and adapt the import below, or paste the generated rules here.
+ *
+ * Style names: github, monokai, nord, solarized-dark, solarized-light, dracula, etc.
+ * Gallery: https://xyproto.github.io/splash/docs/all.html
+ */
+/* Chroma (syntax highlight): light mode */
+@import "/css/chroma-github.css";
+/* Dark mode: Tokyo Night Moon */
+@import "/css/chroma-tokyonight-moon.css";
+
+/* Wider layout: page (sidebar+content), content column, and navbar. Defaults: 80rem. */
+:root {
+ --hextra-max-page-width: 96rem; /* whole row (sidebar + content) */
+ --hextra-max-content-width: 80rem; /* main text/code column */
+ --hextra-max-navbar-width: 96rem; /* align navbar with page width */
+}
+/* Force widths in case variables are overridden by load order */
+.hextra-max-page-width {
+ max-width: 96rem;
+}
+.hextra-max-navbar-width {
+ max-width: 96rem;
+}
+#content.hextra-max-content-width,
+main.hextra-max-content-width {
+ max-width: 80rem;
+}
+
+/* Code block font-size: override Hextra’s default (.9em). Target the same wrappers Hextra uses. */
+.highlight pre,
+.hextra-code-block,
+.hextra-code-block pre {
+ font-size: 0.875rem; /* 14px; change to e.g. 0.8125rem (13px) or 1rem (16px) as needed */
+}
diff --git a/docs/content/_index.md b/docs/content/_index.md
new file mode 100644
index 0000000..65f840c
--- /dev/null
+++ b/docs/content/_index.md
@@ -0,0 +1,65 @@
+---
+title: "Introduction"
+description: "jooby-mcp integrates the MCP Java SDK with Jooby. Expose tools, prompts, and resources via annotations with build-time discovery."
+type: docs
+weight: 1
+---
+
+
+
+
+
+
+
+
+## What is jooby-mcp?
+
+**jooby-mcp** integrates the [Model Context Protocol (MCP) Java SDK](https://github.com/modelcontextprotocol/java-sdk)
+with the [Jooby](https://github.com/jooby-project/jooby) framework. It lets you expose **tools**, **prompts**, and **resources** to MCP clients using declarative, annotation-based APIs with discovery at **build time** via an annotation
+processor, so no runtime reflection is required.
+
+## Prerequisites
+
+- A [Jooby](https://jooby.io) application (3.x or 4.x).
+- Basic familiarity with MCP
+ concepts ([tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools), [prompts](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts), [resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources))
+ is helpful but not required.
+
+## Version compatibility
+
+| Jooby | jooby-mcp |
+|-------|-----------|
+| 3.x | 1.x |
+| 4.x | 2.x |
+
+## Features
+
+**Transport**
+
+- SSE, Streamable HTTP, and Stateless Streamable HTTP
+- Multiple MCP servers in one application
+
+**MCP capabilities**
+
+- Tools, Prompts, Resources
+- Resource templates with URI patterns and completions
+- Prompt completions and resource-template completions
+
+**Quality & tooling**
+
+- Required argument validation and build-time checks for method signatures and return types
+- Elicitation, Sampling, and Progress via the exchange object
+- Optional [MCP Inspector]({{< ref "mcp-inspector" >}}) module for local testing
+
+## In this documentation
+
+| Section | Description |
+|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
+| [Quick Start]({{< ref "quick-start" >}}) | Add the dependency, configure the server, and run your first MCP endpoint. |
+| [Tools]({{< ref "tools" >}}), [Prompts]({{< ref "prompts" >}}), [Resources]({{< ref "resources" >}}) | Core MCP capabilities and annotations. |
+| [Exchange object]({{< ref "exchange-object" >}}) | Elicitation, Sampling, and Progress. |
+| [Multiple servers]({{< ref "multiple-servers" >}}), [Customizing server]({{< ref "customizing-server" >}}) | Run several servers and change the default key or package. |
+| [MCP Inspector]({{< ref "mcp-inspector" >}}) | Local testing in the browser. |
+| [Supported return types]({{< ref "appendix-return-types" >}}) | Reference for tools, prompts, and resources. |
+
+**Next:** [Quick Start]({{< ref "quick-start" >}}) — add the dependency and run your first MCP server.
diff --git a/docs/content/appendix-return-types.md b/docs/content/appendix-return-types.md
new file mode 100644
index 0000000..6770d28
--- /dev/null
+++ b/docs/content/appendix-return-types.md
@@ -0,0 +1,41 @@
+---
+title: "Supported Return Types"
+description: "Return types supported for tools, prompts, and resources. String, McpSchema types, and POJOs."
+type: docs
+weight: 10
+---
+
+This page lists the return types supported for each kind of handler. Use these so the annotation processor and runtime can serialize and expose results correctly.
+
+## Tools
+
+- `String`
+- `McpSchema.CallToolResult`
+- `McpSchema.Content`
+- `McpSchema.TextContent`
+- POJO (serialized to JSON)
+
+## Prompts
+
+- `McpSchema.GetPromptResult`
+- `McpSchema.PromptMessage`
+- `List`
+- `McpSchema.Content`
+- `String`
+- POJO (string representation via `toString()`)
+
+## Resources and Resource Templates
+
+- `McpSchema.ReadResourceResult`
+- `McpSchema.ResourceContents`
+- `List`
+- `McpSchema.TextResourceContents`
+- `McpSchema.BlobResourceContents`
+- POJO (serialized to JSON)
+
+## Completions
+
+- `McpSchema.CompleteResult`
+- `McpSchema.CompleteResult.CompleteCompletion`
+- `List`
+- `String`
\ No newline at end of file
diff --git a/docs/content/customizing-server.md b/docs/content/customizing-server.md
new file mode 100644
index 0000000..b9b4ef3
--- /dev/null
+++ b/docs/content/customizing-server.md
@@ -0,0 +1,47 @@
+---
+title: "Customizing Server Name and Package"
+description: "Change the default MCP server key and generated class package via compiler arguments."
+type: docs
+weight: 8
+---
+
+You can change the default MCP server key and the package of the generated server class via **compiler arguments**. That lets you align generated class names and config keys with your project (e.g. `CalculatorMcpServer` in `com.acme.corp.mcp`).
+
+## Maven configuration
+
+Add the arguments under the maven-compiler-plugin (next to the annotation processor path):
+
+```xml
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.github.kliushnichenko
+ jooby-mcp-apt
+ ${version}
+
+
+
+ -Amcp.default.server.key=calculator
+ -Amcp.target.package=com.acme.corp.mcp
+
+
+
+```
+
+- **mcp.default.server.key** — Config key for the default server (e.g. `calculator` → `mcp.calculator` in config and a generated class name like `CalculatorMcpServer`).
+- **mcp.target.package** — Package for generated server classes.
+
+## application.conf
+
+The server key in config must match the compiler argument:
+
+```hocon
+mcp.calculator {
+ name: "calculator-mcp-server"
+ version: "0.1.0"
+ mcpEndpoint: "/mcp/calculator"
+}
+```
\ No newline at end of file
diff --git a/docs/content/exchange-object.md b/docs/content/exchange-object.md
new file mode 100644
index 0000000..3a19503
--- /dev/null
+++ b/docs/content/exchange-object.md
@@ -0,0 +1,26 @@
+---
+title: "Exchange Object"
+description: "Use McpSyncServerExchange for Elicitation, Sampling, and Progress. Inject the exchange as a method argument."
+type: docs
+weight: 6
+---
+
+The **McpSyncServerExchange** gives tool (and related) handlers access to the current request and to MCP features such as **Elicitation**, **Sampling**, and **Progress**. Add it as an argument to your method. The framework injects the current exchange.
+
+## Example
+
+```java
+public class ElicitationExample {
+
+ @Tool(name = "elicitation_example")
+ public String elicitationExample(McpSyncServerExchange exchange) {
+ // ...
+ exchange.createElicitation(request);
+ // ...
+ }
+}
+```
+
+Use the same pattern for Sampling, Progress, and other exchange-based features. For a full example, see the [example project](https://github.com/kliushnichenko/jooby-mcp/blob/1.x/jooby-mcp-example/src/main/java/io/github/kliushnichenko/mcp/example/ElicitationExample.java).
+
+For details on the SDK APIs, see the [MCP Java SDK documentation](https://modelcontextprotocol.io/sdk/java/mcp-server#using-sampling-from-a-server).
diff --git a/docs/content/mcp-inspector.md b/docs/content/mcp-inspector.md
new file mode 100644
index 0000000..eb99831
--- /dev/null
+++ b/docs/content/mcp-inspector.md
@@ -0,0 +1,49 @@
+---
+title: "MCP Inspector"
+description: "Embed the MCP Inspector UI in your Jooby app for local testing. Direct connection mode."
+type: docs
+weight: 9
+---
+
+The **McpInspectorModule** embeds the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) UI in your Jooby app so you can test your MCP server in the browser. It uses **Direct** connection mode only and is intended for local development. **McpModule** must be installed first.
+
+## Setup
+
+Add the dependency:
+
+```xml
+
+ io.github.kliushnichenko
+ jooby-mcp-inspector
+ ${jooby.mcp.version}
+
+```
+
+Install the module next to **McpModule**:
+
+```java
+{
+ install(new McpInspectorModule());
+}
+```
+
+By default the inspector is served at **/mcp-inspector** and tries to auto-connect to the MCP server when the page loads.
+
+## Configuration
+
+You can change the path and disable auto-connect:
+
+```java
+install(new McpInspectorModule()
+ .path("/custom-inspector-path")
+ .autoConnect(false));
+```
+
+## Multiple servers
+
+When more than one MCP server is registered, choose which one the inspector connects to by default:
+
+```java
+install(new McpInspectorModule()
+ .defaultMcpServer("weather-mcp-server")); // name from application.conf
+```
diff --git a/docs/content/multiple-servers.md b/docs/content/multiple-servers.md
new file mode 100644
index 0000000..bcafaee
--- /dev/null
+++ b/docs/content/multiple-servers.md
@@ -0,0 +1,55 @@
+---
+title: "Multiple Servers"
+description: "Run several MCP servers in one app with @McpServer. Assign tools and prompts to a server by key."
+type: docs
+weight: 7
+---
+
+You can run several MCP servers in one application. Use **@McpServer** to assign tools or prompts to a specific server. The annotation processor generates a dedicated server class per key (e.g. `WeatherMcpServer` for `"weather"`).
+
+## 1. Annotate classes or methods
+
+Apply **@McpServer** at class or method level:
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.McpServer;
+import io.github.kliushnichenko.jooby.mcp.annotation.Tool;
+
+@Singleton
+@McpServer("weather")
+public class WeatherService {
+
+ public record Coordinates(double latitude, double longitude) {}
+
+ @Tool(name = "get_weather")
+ public String getWeather(Coordinates coordinates) {
+ // ...
+ }
+}
+```
+
+The processor generates **WeatherMcpServer** (and keeps **DefaultMcpServer** for the default key).
+
+## 2. Register all server instances
+
+Pass every generated server to **McpModule**:
+
+```java
+{
+ install(new McpModule(new DefaultMcpServer(), new WeatherMcpServer()));
+}
+```
+
+## 3. Configure each server in application.conf
+
+Each server key has its own config block:
+
+```hocon
+mcp.weather {
+ name: "weather-mcp-server"
+ version: "0.1.0"
+ mcpEndpoint: "/mcp/weather"
+}
+```
+
+Use the same options as for the default server (transport, endpoints, etc.) as needed.
diff --git a/docs/content/prompts.md b/docs/content/prompts.md
new file mode 100644
index 0000000..1dc98c6
--- /dev/null
+++ b/docs/content/prompts.md
@@ -0,0 +1,72 @@
+---
+title: "Prompts"
+description: "Expose prompt templates with @Prompt and @PromptArg. Optional prompt completions for suggested argument values."
+type: docs
+weight: 4
+---
+
+Prompts expose template-style inputs to MCP clients (e.g. for LLM prompts). Annotate methods with **@Prompt**. You can optionally add **prompt completions** so clients get suggested values for arguments as the user types.
+
+## Defining a prompt
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.Prompt;
+import io.github.kliushnichenko.jooby.mcp.annotation.PromptArg;
+
+@Singleton
+public class PromptsExample {
+
+ @Prompt(name = "summarizeText", description = "Summarizes the provided text into a specified number of sentences")
+ public String summarizeText(
+ @PromptArg(name = "text") String text,
+ String maxSentences
+ ) {
+ return String.format("""
+ Please provide a clear and concise summary of the following text in no more than %s sentences:
+ %s
+ """, maxSentences, text);
+ }
+}
+```
+
+- **@Prompt** — Registers the method as a prompt. Name and description can be inferred or set explicitly.
+- **@PromptArg** — Describes parameters for the generated schema and client UX.
+
+## Prompt completions
+
+Prompt completions let clients get suggested values for a prompt’s arguments while the user is filling the form. Add a separate method annotated with **@CompletePrompt** that returns suggestions for a given argument and partial input.
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.CompleteArg;
+import io.github.kliushnichenko.jooby.mcp.annotation.CompletePrompt;
+import io.github.kliushnichenko.jooby.mcp.annotation.Prompt;
+
+@Singleton
+public class PromptCompletionsExample {
+
+ private static final List SUPPORTED_LANGUAGES =
+ List.of("Java", "Python", "JavaScript", "Go", "TypeScript");
+
+ @Prompt(name = "code_review", description = "Code Review Prompt")
+ public String codeReviewPrompt(String codeSnippet, String language) {
+ return """
+ You are a senior software engineer tasked with reviewing the following %s code snippet:
+ %s
+ Please provide feedback on:
+ 1. Code readability and maintainability.
+ 2. Potential bugs or issues.
+ 3. Suggestions for improvement.
+ """.formatted(language, codeSnippet);
+ }
+
+ @CompletePrompt("code_review")
+ public List completeCodeReviewLang(@CompleteArg(name = "language") String partialInput) {
+ return SUPPORTED_LANGUAGES.stream()
+ .filter(lang -> lang.toLowerCase().contains(partialInput.toLowerCase()))
+ .toList();
+ }
+}
+```
+
+- **@CompletePrompt("code_review")** — Ties this method to the prompt named `code_review`.
+- **@CompleteArg(name = "language")** — This method completes the `language` argument. It receives the current partial input and returns a list of suggestions.
diff --git a/docs/content/quick-start.md b/docs/content/quick-start.md
new file mode 100644
index 0000000..c44b37f
--- /dev/null
+++ b/docs/content/quick-start.md
@@ -0,0 +1,104 @@
+---
+title: "Quick Start"
+description: "Add the jooby-mcp dependency, configure the annotation processor, and run your first MCP server with Jooby."
+type: docs
+weight: 2
+---
+
+This guide gets you from zero to a running MCP server with jooby-mcp in a few steps.
+
+## 1. Add the dependency
+
+In your `pom.xml`:
+
+```xml
+
+ io.github.kliushnichenko
+ jooby-mcp
+ ${jooby.mcp.version}
+
+```
+
+## 2. Add the annotation processor
+
+Configure the Maven Compiler Plugin so the APT processor generates the MCP server class:
+
+```xml
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.github.kliushnichenko
+ jooby-mcp-apt
+ ${jooby.mcp.version}
+
+
+
+
+```
+
+## 3. Configure the MCP server
+
+In `application.conf`, define at least one MCP server. The key (e.g. `default`) must match what the annotation processor expects (see [Customizing default server]({{< ref "customizing-server" >}}) to change it).
+
+> **Note:** Since **1.4.0** the default transport is **Streamable HTTP**, not SSE. Set `transport: "sse"` explicitly if you need SSE.
+
+**SSE transport**
+
+```hocon
+mcp.default {
+ name: "my-awesome-mcp-server" # Required
+ version: "0.1.0" # Required
+ transport: "sse" # Optional (default: streamable-http)
+ sseEndpoint: "/mcp/sse" # Optional (default: /mcp/sse)
+ messageEndpoint: "/mcp/message" # Optional (default: /mcp/message)
+}
+```
+
+**Streamable HTTP transport** (default)
+
+```hocon
+mcp.default {
+ name: "my-awesome-mcp-server"
+ version: "0.1.0"
+ transport: "streamable-http"
+ mcpEndpoint: "/mcp/streamable" # Optional (default: /mcp)
+ disallowDelete: true # Optional (default: false)
+ keepAliveInterval: 45 # Optional, in seconds
+ instructions: "..." # Optional: server instructions for clients
+}
+```
+
+**Stateless Streamable HTTP**
+
+```hocon
+mcp.default {
+ name: "my-awesome-mcp-server"
+ version: "0.1.0"
+ transport: "stateless-streamable-http"
+ mcpEndpoint: "/mcp/stateless-streamable" # Optional (default: /mcp)
+}
+```
+
+- **keepAliveInterval** — Sends periodic keep-alive messages when set to a positive number (seconds). Off by default.
+- **instructions** — Shown to clients during initialization. Use it to describe how to use the server.
+
+## 4. Implement tools, prompts, or resources
+
+Add one or more classes with `@Tool`, `@Prompt`, or `@Resource` (and related) annotations. See [Tools]({{< ref "tools" >}}), [Prompts]({{< ref "prompts" >}}), and [Resources]({{< ref "resources" >}}) for examples, or browse the [example project](https://github.com/kliushnichenko/jooby-mcp/tree/main/jooby-mcp-example/src/main/java/io/github/kliushnichenko/jooby/mcp/example).
+
+## 5. Register the MCP module
+
+After building, the processor generates a `DefaultMcpServer` class (or a custom name if configured). Install the module in your Jooby app:
+
+```java
+{
+ install(new JacksonModule()); // Required for JSON-RPC
+ install(AvajeInjectModule.of()); // Or your preferred DI module
+ install(new McpModule(new DefaultMcpServer()));
+}
+```
+
+Your MCP server is now available at the configured endpoint. Next, define [Tools]({{< ref "tools" >}}), [Prompts]({{< ref "prompts" >}}), or [Resources]({{< ref "resources" >}}).
diff --git a/docs/content/resources.md b/docs/content/resources.md
new file mode 100644
index 0000000..1ae5242
--- /dev/null
+++ b/docs/content/resources.md
@@ -0,0 +1,78 @@
+---
+title: "Resources"
+description: "Expose content by URI with @Resource. Static resources and resource templates with URI patterns and completions."
+type: docs
+weight: 5
+---
+
+Resources expose content to MCP clients by URI. You can expose **static resources** (one URI per method) or **resource templates** (a URI pattern with variables and optional completions).
+
+## Static resources
+
+Each method annotated with **@Resource** is registered as a resource the client can read by that URI.
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.Resource;
+
+@Singleton
+public class ResourceExamples {
+
+ @Resource(uri = "file:///project/README.md", name = "README.md", title = "README", mimeType = "text/markdown")
+ public McpSchema.TextResourceContents textResource() {
+ String content = """
+ # Project Title
+
+ This is an example README file for the project.
+
+ ## Features
+
+ - Feature 1
+ - Feature 2
+ - Feature 3
+ """;
+ return new McpSchema.TextResourceContents("file:///project/README.md", "text/markdown", content);
+ }
+}
+```
+
+- **uri** — Identifier clients use to request this resource.
+- **name** / **title** — Metadata for listing and display.
+- **mimeType** — Content type of the returned body.
+
+## Resource templates
+
+Resource templates expose a family of resources under a URI pattern (e.g. `file:///project/{name}`). You implement a handler that receives the template variables and returns the content, and optionally a **completion** method so clients can discover or suggest valid values for a variable (e.g. project names).
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.ResourceTemplate;
+import io.github.kliushnichenko.jooby.mcp.annotation.CompleteResourceTemplate;
+import io.github.kliushnichenko.jooby.mcp.annotation.CompleteArg;
+
+@Singleton
+public class ResourceTemplateExamples {
+
+ private static final Map PROJECTS = Map.of(
+ "project-alpha", "This is Project Alpha.",
+ "project-beta", "This is Project Beta.",
+ "project-gamma", "This is Project Gamma."
+ );
+
+ @ResourceTemplate(name = "get_project", uriTemplate = "file:///project/{name}")
+ public McpSchema.TextResourceContents getProject(String name, ResourceUri resourceUri) {
+ String content = PROJECTS.getOrDefault(name, "");
+ return new McpSchema.TextResourceContents(resourceUri.uri(), "text/markdown", content);
+ }
+
+ @CompleteResourceTemplate("get_project")
+ public List projectNameCompletion(@CompleteArg(name = "name") String partialInput) {
+ return PROJECTS.keySet().stream()
+ .filter(n -> n.contains(partialInput))
+ .toList();
+ }
+}
+```
+
+- **@ResourceTemplate** — Binds a handler to a URI template. Method parameters map to template variables (and `ResourceUri` gives the resolved URI).
+- **@CompleteResourceTemplate** — Binds a completion handler to that template. **@CompleteArg** maps parameters to template variables so the client can get suggestions as the user types.
+
+Return types for resources and templates must be one of the [supported resource return types]({{< ref "appendix-return-types" >}}#resources-and-resource-templates). For more examples, see 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).
diff --git a/docs/content/tools.md b/docs/content/tools.md
new file mode 100644
index 0000000..be8f8cf
--- /dev/null
+++ b/docs/content/tools.md
@@ -0,0 +1,76 @@
+---
+title: "Tools"
+description: "Expose callable operations to MCP clients with @Tool and @ToolArg. Build-time discovery, validation, and schema generation."
+type: docs
+weight: 3
+---
+
+Tools expose callable operations to MCP clients. Annotate methods with **@Tool**. The annotation processor discovers them at build time and registers them with the generated MCP server.
+
+## Example
+
+```java
+import io.github.kliushnichenko.jooby.mcp.annotation.Tool;
+import io.github.kliushnichenko.jooby.mcp.annotation.ToolArg;
+
+@Singleton
+public class ToolsExample {
+
+ @Tool(name = "add", description = "Adds two numbers together")
+ public String add(
+ @ToolArg(name = "first", description = "First number to add") int a,
+ @ToolArg(name = "second", description = "Second number to add") int b
+ ) {
+ return String.valueOf(a + b);
+ }
+
+ @Tool
+ public String subtract(int a, int b) {
+ return String.valueOf(a - b);
+ }
+}
+```
+
+- **@Tool** — Exposes the method as a tool. Name and description can be inferred or set explicitly.
+- **@ToolArg** — Describes parameters for the generated JSON schema and client UX.
+
+## Output schema
+
+The output schema is derived from the method’s return type. For example, a tool that returns a `Pet` produces a schema that matches that class.
+
+If the return type is a reserved type (`String`, `McpSchema.CallToolResult`, or `McpSchema.Content`), no schema is generated automatically. Use **@OutputSchema** in that case:
+
+- **@OutputSchema.From(MyClass.class)** — Use the schema for that class.
+- **@OutputSchema.ArrayOf(MyClass.class)** — Array of that class.
+- **@OutputSchema.MapOf(MyClass.class)** — Map with that class as value type.
+
+Example for a tool that returns `CallToolResult` but should advertise a `Pet` schema:
+
+```java
+@Tool(name = "find_pet", description = "Finds a pet by its ID")
+@OutputSchema.From(Pet.class)
+public McpSchema.CallToolResult findPet(String petId) {
+ // ...
+ return new McpSchema.CallToolResult(pet, false);
+}
+```
+
+Use **@OutputSchema.Suppressed** to skip output schema generation when you don’t want to expose a structured result.
+
+## Enriching the JSON schema
+
+You can refine the generated schema (for both arguments and return types) with OpenAPI annotations. The processor respects:
+
+- **@Schema** — `description`, `requiredMode` (and related).
+- **@JsonProperty** — The annotation’s `value` is used as the property name in the schema.
+
+```java
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+class User {
+ @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "The user's middle name")
+ @JsonProperty("middle-name")
+ private String middleName;
+}
+```
diff --git a/docs/go.mod b/docs/go.mod
new file mode 100644
index 0000000..3cc0fdb
--- /dev/null
+++ b/docs/go.mod
@@ -0,0 +1,5 @@
+module github.com/kliushnichenko/jooby-mcp/docs
+
+go 1.21
+
+require github.com/imfing/hextra v0.12.1 // indirect
diff --git a/docs/go.sum b/docs/go.sum
new file mode 100644
index 0000000..a889b91
--- /dev/null
+++ b/docs/go.sum
@@ -0,0 +1,2 @@
+github.com/imfing/hextra v0.12.1 h1:3t1n0bmJbDzSTVfht93UDcfF1BXMRjeFojA071ri2l8=
+github.com/imfing/hextra v0.12.1/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo=
diff --git a/docs/hugo.yaml b/docs/hugo.yaml
new file mode 100644
index 0000000..3f8853c
--- /dev/null
+++ b/docs/hugo.yaml
@@ -0,0 +1,62 @@
+baseURL: "https://kliushnichenko.github.io/jooby-mcp"
+languageCode: "en-us"
+title: "∞ jooby-mcp"
+
+module:
+ hugoVersion:
+ min: "0.146.0"
+ imports:
+ - path: "github.com/imfing/hextra"
+
+markup:
+ highlight:
+ noClasses: false
+ goldmark:
+ renderer:
+ unsafe: true
+
+menu:
+ main:
+ - name: "Documentation"
+ pageRef: "/"
+ weight: 10
+ - name: "GitHub"
+ url: "https://github.com/kliushnichenko/jooby-mcp"
+ weight: 15
+ - name: "Search"
+ weight: 20
+ params:
+ type: "search"
+
+# Analytics: ID is set via env HUGO_SERVICES_GOOGLEANALYTICS_ID in CI (use secret GA_MEASUREMENT_ID)
+services:
+ googleAnalytics:
+ ID: ""
+
+params:
+ description: "Jooby MCP - Integrate Java MCP SDK with Jooby framework. Annotation-based MCP tools, prompts, and resources for Jooby apps."
+ # Default image for Open Graph / social sharing (optional: add a 1200×630 image for better previews)
+ images:
+ - "images/mcp_logo.svg"
+ defaultTheme: "auto"
+ navbar:
+ displayTitle: true
+ displayLogo: true
+ logo:
+ path: images/mcp_logo.svg
+ dark: images/dark_mcp_logo.svg
+ link: /jooby-mcp/
+ width: 40
+ height: 20
+ footer:
+ enable: false
+ search:
+ enable: true
+ editURL:
+ enable: true
+ base: "https://github.com/kliushnichenko/jooby-mcp/edit/main/docs"
+ # Code block copy button (Hextra)
+ highlight:
+ copy:
+ enable: true
+ display: "hover" # "hover" or "always"
diff --git a/docs/layouts/index.html b/docs/layouts/index.html
new file mode 100644
index 0000000..c921e6c
--- /dev/null
+++ b/docs/layouts/index.html
@@ -0,0 +1,25 @@
+{{ define "main" }}
+