diff --git a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/MCPServer.java.tmpl b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/MCPServer.java.tmpl index 0a8cfa0..960186e 100644 --- a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/MCPServer.java.tmpl +++ b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/MCPServer.java.tmpl @@ -393,6 +393,29 @@ public class MCPServer { toolNode.put("name", tool.getName()); toolNode.put("description", tool.getDescription()); toolNode.set("inputSchema", convertToJsonSchema(tool.getInputSchema())); + + // Add tool annotations if available + McpSchema.ToolAnnotations annotations = tool.getAnnotations(); + if (annotations != null) { + ObjectNode annotationsNode = objectMapper.createObjectNode(); + if (annotations.title() != null) { + annotationsNode.put("title", annotations.title()); + } + if (annotations.readOnlyHint() != null) { + annotationsNode.put("readOnlyHint", annotations.readOnlyHint()); + } + if (annotations.destructiveHint() != null) { + annotationsNode.put("destructiveHint", annotations.destructiveHint()); + } + if (annotations.idempotentHint() != null) { + annotationsNode.put("idempotentHint", annotations.idempotentHint()); + } + if (annotations.openWorldHint() != null) { + annotationsNode.put("openWorldHint", annotations.openWorldHint()); + } + toolNode.set("annotations", annotationsNode); + } + toolsArray.add(toolNode); } @@ -576,12 +599,18 @@ Content-Type: application/json .serverInfo(serverName, "1.0.0"); for (Tool tool : tools.values()) { + McpSchema.Tool.Builder toolBuilder = McpSchema.Tool.builder() + .name(tool.getName()) + .description(tool.getDescription()) + .inputSchema(tool.getInputSchema()); + + // Add tool annotations if available + if (tool.getAnnotations() != null) { + toolBuilder.annotations(tool.getAnnotations()); + } + serverBuilder = serverBuilder.tool( - McpSchema.Tool.builder() - .name(tool.getName()) - .description(tool.getDescription()) - .inputSchema(tool.getInputSchema()) - .build(), + toolBuilder.build(), (exchange, args) -> { try { Object result = tool.execute(args); diff --git a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Echo.java.tmpl b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Echo.java.tmpl index c337d94..66cff71 100644 --- a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Echo.java.tmpl +++ b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Echo.java.tmpl @@ -1,29 +1,35 @@ package com.example.tools; +import io.modelcontextprotocol.spec.McpSchema; import java.util.Map; import java.util.List; import com.example.tools.ToolConfig; /** * Echo tool for {{.ProjectName}} MCP server. - * + * * This is an example tool showing the basic structure for MCP tools. * Each tool should implement the Tool interface and be registered in Tools.java. */ public class Echo implements Tool { - + @Override public String getName() { return "echo"; } - + @Override public String getDescription() { return "Echo a message back to the client"; } - + + @Override + public McpSchema.ToolAnnotations getAnnotations() { + return new McpSchema.ToolAnnotations("Echo Message", true, null, null, null); + } + @Override - public io.modelcontextprotocol.spec.McpSchema.JsonSchema getInputSchema() { + public McpSchema.JsonSchema getInputSchema() { return new io.modelcontextprotocol.spec.McpSchema.JsonSchema( "object", java.util.Map.of( diff --git a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Tool.java.tmpl b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Tool.java.tmpl index e43dc5a..41f0165 100644 --- a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Tool.java.tmpl +++ b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/Tool.java.tmpl @@ -5,36 +5,46 @@ import java.util.Map; /** * Interface for MCP tools. - * + * * All tools should implement this interface and be registered in the Tools class. * This interface provides the basic structure for MCP tools with input schema support. */ public interface Tool { /** * Get the name of the tool. - * + * * @return the tool name */ String getName(); - + /** * Get the description of the tool. - * + * * @return the tool description */ String getDescription(); - + /** * Get the input schema for the tool. * This defines the expected parameters and their types. - * + * * @return the input schema as a JsonSchema */ McpSchema.JsonSchema getInputSchema(); - + + /** + * Get the tool annotations for MCP clients. + * Annotations provide hints about tool behavior (read-only, destructive, etc.). + * + * @return the tool annotations, or null if not specified + */ + default McpSchema.ToolAnnotations getAnnotations() { + return null; + } + /** * Execute the tool with the given parameters. - * + * * @param parameters the input parameters for the tool * @return the result of the tool execution * @throws Exception if the tool execution fails diff --git a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/ToolTemplate.java.tmpl b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/ToolTemplate.java.tmpl index 605b8dd..108ace4 100644 --- a/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/ToolTemplate.java.tmpl +++ b/pkg/cli/internal/frameworks/java/templates/src/main/java/com/example/tools/ToolTemplate.java.tmpl @@ -1,5 +1,6 @@ package com.example.tools; +import io.modelcontextprotocol.spec.McpSchema; import java.util.Map; import java.util.List; import com.example.tools.ToolConfig; @@ -8,23 +9,29 @@ import com.example.tools.ToolConfig; * {{if .ToolNameTitle}}{{.ToolNameTitle}} tool{{else}}Example tool{{end}} for MCP server.{{if .Description}} {{.Description}}{{end}} - * + * * This is a template tool. Replace this implementation with your tool logic. */ public class {{if .ToolNamePascalCase}}{{.ToolNamePascalCase}}{{else}}Tool{{end}} implements Tool { - + @Override public String getName() { return "{{if .ToolName}}{{.ToolName}}{{else}}tool{{end}}"; } - + @Override public String getDescription() { return "{{if .ToolNameTitle}}{{.ToolNameTitle}}{{else}}Example{{end}} tool implementation"; } - + + @Override + public McpSchema.ToolAnnotations getAnnotations() { + // Set readOnlyHint to false if this tool modifies state + return new McpSchema.ToolAnnotations("{{if .ToolNameTitle}}{{.ToolNameTitle}}{{else}}Tool{{end}}", true, null, null, null); + } + @Override - public io.modelcontextprotocol.spec.McpSchema.JsonSchema getInputSchema() { + public McpSchema.JsonSchema getInputSchema() { return new io.modelcontextprotocol.spec.McpSchema.JsonSchema( "object", java.util.Map.of( diff --git a/pkg/cli/internal/frameworks/python/templates/src/tools/echo.py.tmpl b/pkg/cli/internal/frameworks/python/templates/src/tools/echo.py.tmpl index 6c09d6d..8f4c249 100644 --- a/pkg/cli/internal/frameworks/python/templates/src/tools/echo.py.tmpl +++ b/pkg/cli/internal/frameworks/python/templates/src/tools/echo.py.tmpl @@ -4,11 +4,18 @@ This is an example tool showing the basic structure for FastMCP tools. Each tool file should contain a function decorated with @mcp.tool(). """ +from mcp.types import ToolAnnotations + from core.server import mcp from core.utils import get_tool_config -@mcp.tool() +@mcp.tool( + annotations=ToolAnnotations( + title="Echo Message", + readOnlyHint=True, + ), +) def echo(message: str) -> str: """Echo a message back to the client. diff --git a/pkg/cli/internal/frameworks/python/templates/src/tools/tool.py.tmpl b/pkg/cli/internal/frameworks/python/templates/src/tools/tool.py.tmpl index 85134d0..b554e93 100644 --- a/pkg/cli/internal/frameworks/python/templates/src/tools/tool.py.tmpl +++ b/pkg/cli/internal/frameworks/python/templates/src/tools/tool.py.tmpl @@ -3,11 +3,18 @@ {{.description}}{{end}} """ +from mcp.types import ToolAnnotations + from core.server import mcp from core.utils import get_tool_config -@mcp.tool() +@mcp.tool( + annotations=ToolAnnotations( + title="{{if .ToolNameTitle}}{{.ToolNameTitle}}{{else}}Tool{{end}}", + readOnlyHint=True, # Set to False if tool modifies state + ), +) def {{if .ToolName}}{{.ToolName}}{{else}}tool{{end}}(message: str) -> str: """{{if .ToolNameTitle}}{{.ToolNameTitle}}{{else}}Example{{end}} tool implementation. diff --git a/pkg/cli/internal/frameworks/typescript/templates/src/server.ts.tmpl b/pkg/cli/internal/frameworks/typescript/templates/src/server.ts.tmpl index 3011a92..b497fde 100644 --- a/pkg/cli/internal/frameworks/typescript/templates/src/server.ts.tmpl +++ b/pkg/cli/internal/frameworks/typescript/templates/src/server.ts.tmpl @@ -24,6 +24,7 @@ export async function createServer(): Promise { name: tool.name, description: tool.description, inputSchema: zodToJsonSchema(tool.inputSchema, { strictUnions: true }) as any, + annotations: tool.annotations, })), }; }); @@ -77,11 +78,16 @@ export async function createHttpServer(): Promise { name: 'health', description: 'Health check', inputSchema: { type: 'object', properties: {}, additionalProperties: false }, + annotations: { + title: 'Health Check', + readOnlyHint: true, + }, }, ...allTools.map(tool => ({ name: tool.name, description: tool.description, inputSchema: zodToJsonSchema(tool.inputSchema, { strictUnions: true }) as any, + annotations: tool.annotations, })), ], }; diff --git a/pkg/cli/internal/frameworks/typescript/templates/src/tools/echo.ts.tmpl b/pkg/cli/internal/frameworks/typescript/templates/src/tools/echo.ts.tmpl index e53df17..a5d0b80 100644 --- a/pkg/cli/internal/frameworks/typescript/templates/src/tools/echo.ts.tmpl +++ b/pkg/cli/internal/frameworks/typescript/templates/src/tools/echo.ts.tmpl @@ -8,6 +8,10 @@ const echo = { name: 'echo', description: 'Echo back the provided message', inputSchema: echoSchema, + annotations: { + title: 'Echo Message', + readOnlyHint: true, + }, handler: async (params: z.infer) => { return { message: `Echo: ${params.message}`, diff --git a/pkg/cli/internal/frameworks/typescript/templates/src/tools/tool.ts.tmpl b/pkg/cli/internal/frameworks/typescript/templates/src/tools/tool.ts.tmpl index 0f7bd1a..3c4d7fb 100644 --- a/pkg/cli/internal/frameworks/typescript/templates/src/tools/tool.ts.tmpl +++ b/pkg/cli/internal/frameworks/typescript/templates/src/tools/tool.ts.tmpl @@ -10,6 +10,10 @@ const {{.ToolNameCamelCase}} = { name: '{{.ToolName}}', description: '{{.Description}}', inputSchema: {{.ToolNameCamelCase}}Schema, + annotations: { + title: '{{.ToolNameTitle}}', + readOnlyHint: true, // Set to false if tool modifies state + }, handler: async (params: z.infer) => { // Implement your tool logic here // Example: diff --git a/pkg/cli/internal/frameworks/typescript/templates/src/types/index.ts.tmpl b/pkg/cli/internal/frameworks/typescript/templates/src/types/index.ts.tmpl index 54caf9b..55fe1f0 100644 --- a/pkg/cli/internal/frameworks/typescript/templates/src/types/index.ts.tmpl +++ b/pkg/cli/internal/frameworks/typescript/templates/src/types/index.ts.tmpl @@ -1,4 +1,5 @@ import { z } from 'zod'; +import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'; /** * Common type definitions for the MCP server. @@ -8,6 +9,7 @@ export interface Tool { name: string; description: string; inputSchema: z.ZodSchema; + annotations?: ToolAnnotations; handler: (params: any) => Promise; }