|
1 | | -# Developing MCP Tools for tinystruct-mcp |
| 1 | +# Developing MCP Tools with tinystruct |
2 | 2 |
|
3 | | -This guide explains how to extend the tinystruct-mcp server by developing your own MCP tools using the modern `@Action` annotation pattern. |
| 3 | +This guide providing step-by-step instructions on how to extend the **tinystruct-mcp** server by developing your own custom MCP tools using the annotation-driven pattern. |
4 | 4 |
|
5 | 5 | --- |
6 | 6 |
|
7 | | -## 1. Create a Custom Tool |
| 7 | +## 🏗 Understanding the Architecture |
8 | 8 |
|
9 | | -To add new functionality, create a class that extends `MCPTool` and use `@Action` annotations for each operation: |
| 9 | +A tinystruct MCP implementation typically consists of two main components: |
| 10 | +1. **`MCPTool`**: A class (often an inner class) that defines the actual operations (actions) using `@Action` and `@Argument` annotations. |
| 11 | +2. **`MCPServer`**: The main application class that handles initialization and registration of tools and prompts. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## 🛠 1. Create a Custom Tool |
| 16 | + |
| 17 | +Extend the `MCPTool` class and define your operations. Each method annotated with `@Action` becomes a tool available to the AI. |
10 | 18 |
|
11 | 19 | ```java |
12 | 20 | import org.tinystruct.mcp.MCPTool; |
13 | | -import org.tinystruct.data.component.Builder; |
14 | 21 | import org.tinystruct.system.annotation.Action; |
15 | 22 | import org.tinystruct.system.annotation.Argument; |
| 23 | +import java.util.logging.Logger; |
16 | 24 |
|
17 | | -public class EchoTool extends MCPTool { |
18 | | - |
19 | | - /** |
20 | | - * Constructs a new EchoTool with local execution support. |
21 | | - */ |
22 | | - public EchoTool() { |
23 | | - // Note the true parameter at the end to enable local execution |
24 | | - super("echo", "A tool that echoes back input"); |
25 | | - } |
26 | | - |
27 | | - /** |
28 | | - * Constructs a new EchoTool with a client. |
29 | | - * |
30 | | - * @param client The MCP client |
31 | | - */ |
32 | | - public EchoTool(MCPClient client) { |
33 | | - // Note the true parameter at the end to enable local execution |
34 | | - super("echo", "A tool that echoes back input", null, client, true); |
35 | | - } |
| 25 | +public class WeatherTool extends MCPTool { |
| 26 | + private static final Logger LOGGER = Logger.getLogger(WeatherTool.class.getName()); |
36 | 27 |
|
37 | | - /** |
38 | | - * Echoes back the input message. |
39 | | - * @param message The message to echo |
40 | | - * @return The echoed message |
41 | | - */ |
42 | | - @Action(value = "echo/message", description = "Echo back the input message", arguments = { |
43 | | - @Argument(key = "message", description = "The message to echo", type = "string") |
44 | | - }) |
45 | | - public String echoMessage(String message) { |
46 | | - return message; |
| 28 | + public WeatherTool() { |
| 29 | + // Initialize with tool name and description |
| 30 | + super("weather", "A tool for retrieving weather information"); |
47 | 31 | } |
48 | 32 |
|
49 | | - /** |
50 | | - * Echoes back the input with a prefix. |
51 | | - * @param message The message to echo |
52 | | - * @param prefix The prefix to add |
53 | | - * @return The prefixed message |
54 | | - */ |
55 | | - @Action(value = "echo/with-prefix", description = "Echo back the input with a prefix", arguments = { |
56 | | - @Argument(key = "message", description = "The message to echo", type = "string"), |
57 | | - @Argument(key = "prefix", description = "The prefix to add", type = "string") |
| 33 | + @Action(value = "weather/get", description = "Get current weather for a city", arguments = { |
| 34 | + @Argument(key = "city", description = "The name of the city", type = "string"), |
| 35 | + @Argument(key = "unit", description = "Temperature unit (celsius/fahrenheit)", type = "string") |
58 | 36 | }) |
59 | | - public String echoWithPrefix(String message, String prefix) { |
60 | | - return prefix + ": " + message; |
| 37 | + public String getWeather(String city, String unit) { |
| 38 | + LOGGER.info("Fetching weather for " + city + " in " + unit); |
| 39 | + // Implement your logic here |
| 40 | + return "The weather in " + city + " is sunny, 25° " + (unit.equals("fahrenheit") ? "F" : "C"); |
61 | 41 | } |
62 | 42 | } |
63 | 43 | ``` |
64 | 44 |
|
65 | 45 | --- |
66 | 46 |
|
67 | | -## 2. Register Your Tool in the Server |
| 47 | +## 🔌 2. Register the Tool in the Server |
68 | 48 |
|
69 | | -In your server class (extending `MCPServerApplication`), register your tool in the `init()` method: |
| 49 | +To make your tool active, you must register it within an `MCPServer` implementation's `init()` method. |
70 | 50 |
|
71 | 51 | ```java |
72 | | -public class MyMCPServer extends MCPServerApplication { |
| 52 | +import org.tinystruct.mcp.MCPServer; |
| 53 | + |
| 54 | +public class MyMCPServer extends MCPServer { |
73 | 55 | @Override |
74 | 56 | public void init() { |
75 | 57 | super.init(); |
76 | | - this.registerToolMethods(new EchoTool()); |
77 | | - // Register other tools or prompts as needed |
| 58 | + |
| 59 | + // Register your custom tool |
| 60 | + this.registerTool(new WeatherTool()); |
| 61 | + |
| 62 | + // Optionally register other tools or prompts |
78 | 63 | } |
79 | 64 | } |
80 | 65 | ``` |
81 | 66 |
|
82 | 67 | --- |
83 | 68 |
|
84 | | -## 3. Key Features of Modern MCP Tools |
| 69 | +## 🚀 3. Start Your Server |
85 | 70 |
|
86 | | -### Constructor Pattern |
87 | | -- **Default constructor**: `super("tool-name", "Tool description")` enables local execution |
88 | | -- **Client constructor**: `super("tool-name", "Tool description", null, client, true)` for client-based execution |
| 71 | +Once your tool is registered, you can start your server using the dispatcher. |
89 | 72 |
|
90 | | -### @Action Annotations |
91 | | -- **Automatic schema generation**: No need to manually build schemas |
92 | | -- **Method-based operations**: Each `@Action` method becomes a separate tool operation |
93 | | -- **Parameter validation**: `@Argument` annotations define parameter types and descriptions |
| 73 | +### Using the Dispatcher (Recommended) |
94 | 74 |
|
95 | | -### Automatic Features |
96 | | -- **Name and description**: Set in constructor, no need to override `getName()` or `getDescription()` |
97 | | -- **Schema generation**: Built automatically from `@Action` and `@Argument` annotations |
98 | | -- **Local execution**: Enabled by default for better performance |
99 | | - |
100 | | ---- |
| 75 | +After compiling your project, use the `bin/dispatcher` script to load your server class: |
101 | 76 |
|
102 | | -## 4. Add Custom Prompts (Optional) |
| 77 | +```sh |
| 78 | +# On Linux/macOS |
| 79 | +bin/dispatcher start --import org.tinystruct.system.HttpServer --import com.yourpackage.MyMCPServer --server-port 777 |
103 | 80 |
|
104 | | -You can also register prompts for user interaction or automation: |
105 | | - |
106 | | -```java |
107 | | -Builder promptSchema = new Builder(); |
108 | | -Builder properties = new Builder(); |
109 | | -Builder nameParam = new Builder(); |
110 | | -nameParam.put("type", "string"); |
111 | | -nameParam.put("description", "The name to greet"); |
112 | | -properties.put("name", nameParam); |
113 | | -promptSchema.put("type", "object"); |
114 | | -promptSchema.put("properties", properties); |
115 | | -promptSchema.put("required", new String[]{"name"}); |
116 | | - |
117 | | -MCPPrompt greetingPrompt = new MCPPrompt( |
118 | | - "greeting", |
119 | | - "A greeting prompt", |
120 | | - "Hello, {{name}}!", |
121 | | - promptSchema, |
122 | | - null |
123 | | -) { |
124 | | - @Override |
125 | | - protected boolean supportsLocalExecution() { return true; } |
126 | | -}; |
127 | | -this.registerPrompt(greetingPrompt); |
| 81 | +# On Windows |
| 82 | +bin\dispatcher.cmd start --import org.tinystruct.system.HttpServer --import com.yourpackage.MyMCPServer --server-port 777 |
128 | 83 | ``` |
129 | 84 |
|
130 | 85 | --- |
131 | 86 |
|
132 | | -## 5. Best Practices |
| 87 | +## 📝 4. Defining Arguments and Schemas |
133 | 88 |
|
134 | | -### Tool Design |
135 | | -- **Single responsibility**: Each tool should focus on one domain (e.g., file operations, Git operations) |
136 | | -- **Consistent naming**: Use `tool-name/operation` format for action values |
137 | | -- **Clear descriptions**: Provide helpful descriptions for tools and arguments |
138 | | -- **Error handling**: Wrap internal operations in try-catch blocks |
| 89 | +The `@Argument` annotation is crucial as it automatically generates the JSON schema required by the Model Context Protocol. |
139 | 90 |
|
140 | | -### Method Structure |
| 91 | +### Supported Types: |
| 92 | +* **`string`**: For text values. |
| 93 | +* **`number`**: For integers, doubles, and floats. |
| 94 | +* **`boolean`**: For true/false values. |
| 95 | +* **`object`**: For complex data structures (use `org.tinystruct.data.component.Builder`). |
| 96 | + |
| 97 | +### Example with Complex Objects: |
141 | 98 | ```java |
142 | | -@Action(value = "tool/operation", description = "What this operation does", arguments = { |
143 | | - @Argument(key = "param1", description = "Description of param1", type = "string"), |
144 | | - @Argument(key = "param2", description = "Description of param2", type = "number") |
| 99 | +@Action(value = "data/process", description = "Process complex data", arguments = { |
| 100 | + @Argument(key = "payload", description = "The JSON payload", type = "object") |
145 | 101 | }) |
146 | | -public ReturnType operationName(String param1, int param2) throws MCPException { |
147 | | - try { |
148 | | - // Implementation logic here |
149 | | - return result; |
150 | | - } catch (Exception e) { |
151 | | - LOGGER.log(Level.SEVERE, "Error in operation: " + e.getMessage(), e); |
152 | | - throw new MCPException("Error in operation: " + e.getMessage()); |
153 | | - } |
| 102 | +public Builder processData(Builder payload) { |
| 103 | + // Logic to process the Builder object |
| 104 | + return payload; |
154 | 105 | } |
155 | 106 | ``` |
156 | 107 |
|
157 | | -### Parameter Types |
158 | | -- **string**: Text values |
159 | | -- **number**: Numeric values (int, double, etc.) |
160 | | -- **boolean**: True/false values |
161 | | -- **object**: Complex objects (use Builder) |
162 | | - |
163 | 108 | --- |
164 | 109 |
|
165 | | -## 6. Recommended Workflow |
| 110 | +## 💡 Best Practices |
166 | 111 |
|
167 | | -1. **Extend `MCPServerApplication`** and implement the `init()` method |
168 | | -2. **Create tool class** extending `MCPTool` with proper constructors |
169 | | -3. **Add `@Action` methods** for each operation with `@Argument` annotations |
170 | | -4. **Register tools** using `registerToolMethods()` in `init()` |
171 | | -5. **Optionally register prompts** for user interaction |
172 | | -6. **Start the server** via Java or CLI |
173 | | -7. **Configure** via properties or `Settings` |
| 112 | +1. **Namespacing**: Use the `toolname/action` format for `value` in `@Action` (e.g., `github/clone`). |
| 113 | +2. **Clear Descriptions**: Provide detailed descriptions for both tools and arguments; these are used by the LLM to understand when and how to call your tool. |
| 114 | +3. **Local Execution**: The tinystruct MCP implementation supports local execution by default. Ensure your tool methods are thread-safe. |
| 115 | +4. **Error Handling**: Wrap your logic in try-catch blocks and throw `MCPException` for errors that should be reported back to the LLM. |
| 116 | +5. **Logging**: Use `java.util.logging` to provide visibility into tool execution on the server side. |
174 | 117 |
|
175 | 118 | --- |
176 | 119 |
|
177 | | -## 7. Example: Complete Calculator Tool |
| 120 | +## 🎯 Complete Example: Calculator |
178 | 121 |
|
179 | 122 | ```java |
180 | | -public class CalculatorTool extends MCPTool { |
181 | | - |
182 | | - public CalculatorTool() { |
183 | | - super("calculator", "A calculator that performs arithmetic operations"); |
184 | | - } |
185 | | - |
186 | | - public CalculatorTool(MCPClient client) { |
187 | | - super("calculator", "A calculator that performs arithmetic operations", null, client, true); |
188 | | - } |
189 | | - |
190 | | - @Action(value = "calculator/add", description = "Add two numbers", arguments = { |
191 | | - @Argument(key = "a", description = "The first operand", type = "number"), |
192 | | - @Argument(key = "b", description = "The second operand", type = "number") |
193 | | - }) |
194 | | - public double add(double a, double b) { |
195 | | - return a + b; |
| 123 | +public class Calculator extends MCPServer { |
| 124 | + @Override |
| 125 | + public void init() { |
| 126 | + super.init(); |
| 127 | + this.registerTool(new CalcTool()); |
196 | 128 | } |
197 | 129 |
|
198 | | - @Action(value = "calculator/multiply", description = "Multiply two numbers", arguments = { |
199 | | - @Argument(key = "a", description = "The first operand", type = "number"), |
200 | | - @Argument(key = "b", description = "The second operand", type = "number") |
201 | | - }) |
202 | | - public double multiply(double a, double b) { |
203 | | - return a * b; |
| 130 | + public static class CalcTool extends MCPTool { |
| 131 | + public CalcTool() { |
| 132 | + super("calc", "Basic arithmetic operations"); |
| 133 | + } |
| 134 | + |
| 135 | + @Action(value = "calc/add", description = "Add two numbers", arguments = { |
| 136 | + @Argument(key = "a", description = "First number", type = "number"), |
| 137 | + @Argument(key = "b", description = "Second number", type = "number") |
| 138 | + }) |
| 139 | + public double add(double a, double b) { |
| 140 | + return a + b; |
| 141 | + } |
204 | 142 | } |
205 | 143 | } |
206 | 144 | ``` |
207 | 145 |
|
208 | | -**MCP stands for Model Context Protocol.** |
| 146 | +--- |
| 147 | + |
| 148 | +**Note**: *MCP stands for Model Context Protocol.* |
0 commit comments