@isolo/trinity 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -0
- package/docs/TOOLS.md +604 -0
- package/package.json +49 -0
- package/src/Config.ts +89 -0
- package/src/Logger.ts +52 -0
- package/src/Server.ts +274 -0
- package/src/Tools.ts +165 -0
- package/src/index.ts +36 -0
package/docs/TOOLS.md
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# Trinity MCP Tools Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to create, structure, and manage tools in the Trinity MCP server.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Tools are dynamically loaded TypeScript modules that perform actions and are exposed via the Model Context Protocol (MCP). Trinity automatically detects, loads, and reloads tools without requiring a server restart.
|
|
8
|
+
|
|
9
|
+
## Tool Location & Discovery
|
|
10
|
+
|
|
11
|
+
**Default Location**: `example/` directory (configured via `--target` or `MCP_TARGET` environment variable)
|
|
12
|
+
|
|
13
|
+
Tools are discovered using a glob pattern:
|
|
14
|
+
|
|
15
|
+
- **Default Pattern**: `**/*.ts` (all TypeScript files in target folder)
|
|
16
|
+
- **Custom Pattern**: Set via `--glob` or `MCP_GLOB` environment variable
|
|
17
|
+
|
|
18
|
+
**File Naming**: Tool names are derived from the filename (without extension)
|
|
19
|
+
|
|
20
|
+
- File: `hello.ts` → Tool name: `mcp_trinity_hello`
|
|
21
|
+
- File: `timestamp.ts` → Tool name: `mcp_trinity_timestamp`
|
|
22
|
+
|
|
23
|
+
## Tool File Structure
|
|
24
|
+
|
|
25
|
+
Every tool file must export three things:
|
|
26
|
+
|
|
27
|
+
### 1. `description` (required)
|
|
28
|
+
|
|
29
|
+
A string describing what the tool does.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const description = "Says hello to a person by name";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Trinity automatically appends source information to the description for the MCP client:
|
|
36
|
+
|
|
37
|
+
- Shows the source file path
|
|
38
|
+
- Notes that the tool is dynamically loaded
|
|
39
|
+
- Indicates that changes to the file immediately affect behavior
|
|
40
|
+
|
|
41
|
+
Example expanded description shown to clients:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Says hello to a person by name
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
Source: /path/to/example/hello.ts
|
|
48
|
+
Note: This tool is dynamically loaded. Changes to this file will immediately affect this tool's behavior. You can read and modify this file to change the tool.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. `inputSchema` (required)
|
|
52
|
+
|
|
53
|
+
A Zod schema defining the tool's input parameters. Must be a `z.object()`.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { z } from "zod";
|
|
57
|
+
|
|
58
|
+
export const inputSchema = z.object({
|
|
59
|
+
name: z.string().describe("The person's name to greet"),
|
|
60
|
+
greeting: z
|
|
61
|
+
.string()
|
|
62
|
+
.default("Hello")
|
|
63
|
+
.describe("The greeting prefix (default: Hello)"),
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Schema Features**:
|
|
68
|
+
|
|
69
|
+
- **Type Support**: All Zod types (string, number, boolean, enum, array, etc.)
|
|
70
|
+
- **Descriptions**: Use `.describe()` to document each parameter (shown in MCP client)
|
|
71
|
+
- **Defaults**: Use `.default()` to provide default values
|
|
72
|
+
- **Validation**: Zod handles validation automatically (min, max, regex patterns, etc.)
|
|
73
|
+
|
|
74
|
+
Example with more complex validation:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
export const inputSchema = z.object({
|
|
78
|
+
count: z.number().int().min(1).max(100).describe("Number between 1-100"),
|
|
79
|
+
format: z.enum(["json", "csv", "xml"]).describe("Output format"),
|
|
80
|
+
options: z
|
|
81
|
+
.object({
|
|
82
|
+
verbose: z.boolean().default(false),
|
|
83
|
+
depth: z.number().int().min(0).max(5),
|
|
84
|
+
})
|
|
85
|
+
.optional(),
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Default Export Function (required)
|
|
90
|
+
|
|
91
|
+
An async function that executes the tool. Must accept the validated arguments and return the result.
|
|
92
|
+
|
|
93
|
+
**Typing**: Use `z.infer<typeof inputSchema>` for TypeScript type safety
|
|
94
|
+
|
|
95
|
+
- Automatically infers types from your Zod schema
|
|
96
|
+
- Provides IDE autocomplete for parameters
|
|
97
|
+
|
|
98
|
+
**Return Format**: Tools can return simple values—Trinity automatically converts them to MCP-compatible format:
|
|
99
|
+
|
|
100
|
+
#### Return Primitives (Recommended)
|
|
101
|
+
|
|
102
|
+
Return strings, numbers, or booleans directly:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// String result
|
|
106
|
+
export default async ({ name }: z.infer<typeof inputSchema>) => {
|
|
107
|
+
return `Hello, ${name}!`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Number result
|
|
111
|
+
export default async ({ a, b }: z.infer<typeof inputSchema>) => {
|
|
112
|
+
return a + b;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Boolean result
|
|
116
|
+
export default async ({ value }: z.infer<typeof inputSchema>) => {
|
|
117
|
+
return value > 0;
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Return Objects (Structured Content)
|
|
122
|
+
|
|
123
|
+
Return objects for structured data—Trinity creates both text and structured content:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
export default async ({ location }: z.infer<typeof inputSchema>) => {
|
|
127
|
+
return {
|
|
128
|
+
temperature: 22.5,
|
|
129
|
+
conditions: "Partly cloudy",
|
|
130
|
+
humidity: 65,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Server automatically provides:
|
|
136
|
+
|
|
137
|
+
- `content: [{ type: "text", text: "{...JSON...}" }]` for backward compatibility
|
|
138
|
+
- `structuredContent: { temperature: 22.5, ... }` for clients that support it
|
|
139
|
+
|
|
140
|
+
#### Return Arrays
|
|
141
|
+
|
|
142
|
+
Return arrays to create multiple content items:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
export default async ({ items }: z.infer<typeof inputSchema>) => {
|
|
146
|
+
return ["First item", "Second item", "Third item"];
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Error Handling
|
|
151
|
+
|
|
152
|
+
**Option 1: Return error object** (recommended for validation errors):
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
export default async ({ location }: z.infer<typeof inputSchema>) => {
|
|
156
|
+
if (!isValidLocation(location)) {
|
|
157
|
+
return {
|
|
158
|
+
isError: true,
|
|
159
|
+
error: `Location '${location}' not found. Please provide a valid city name.`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return getWeatherData(location);
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Option 2: Throw exception** (for unexpected errors):
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
export default async ({ apiKey }: z.infer<typeof inputSchema>) => {
|
|
170
|
+
if (!apiKey) {
|
|
171
|
+
throw new Error("API key is required");
|
|
172
|
+
}
|
|
173
|
+
return await callExternalAPI(apiKey);
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Both approaches result in MCP-compatible error responses with `isError: true`.
|
|
178
|
+
|
|
179
|
+
#### Advanced: Return MCP Content Types
|
|
180
|
+
|
|
181
|
+
For advanced use cases, return MCP content objects directly:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// Image content
|
|
185
|
+
return {
|
|
186
|
+
type: "image",
|
|
187
|
+
data: "base64-encoded-data",
|
|
188
|
+
mimeType: "image/png",
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Resource link
|
|
192
|
+
return {
|
|
193
|
+
type: "resource_link",
|
|
194
|
+
uri: "file:///path/to/file.txt",
|
|
195
|
+
name: "file.txt",
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Complete Tool Example
|
|
200
|
+
|
|
201
|
+
Here's a complete, self-contained tool example:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { z } from "zod";
|
|
205
|
+
|
|
206
|
+
export const description = "Transforms text to different cases";
|
|
207
|
+
|
|
208
|
+
export const inputSchema = z.object({
|
|
209
|
+
text: z.string().describe("The text to transform"),
|
|
210
|
+
case: z
|
|
211
|
+
.enum(["upper", "lower", "title", "reverse"])
|
|
212
|
+
.describe("The transformation type"),
|
|
213
|
+
repeat: z
|
|
214
|
+
.number()
|
|
215
|
+
.int()
|
|
216
|
+
.min(1)
|
|
217
|
+
.default(1)
|
|
218
|
+
.describe("Number of times to apply (default: 1)"),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export default async ({
|
|
222
|
+
text,
|
|
223
|
+
case: caseType,
|
|
224
|
+
repeat,
|
|
225
|
+
}: z.infer<typeof inputSchema>) => {
|
|
226
|
+
const transformers: Record<string, (s: string) => string> = {
|
|
227
|
+
upper: (s) => s.toUpperCase(),
|
|
228
|
+
lower: (s) => s.toLowerCase(),
|
|
229
|
+
title: (s) =>
|
|
230
|
+
s
|
|
231
|
+
.split(" ")
|
|
232
|
+
.map(
|
|
233
|
+
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
|
|
234
|
+
)
|
|
235
|
+
.join(" "),
|
|
236
|
+
reverse: (s) => s.split("").reverse().join(""),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
let result = text;
|
|
240
|
+
for (let i = 0; i < repeat; i++) {
|
|
241
|
+
result = transformers[caseType](result);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Return the string directly - Trinity handles MCP formatting
|
|
245
|
+
return result;
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Automatic MCP Formatting
|
|
250
|
+
|
|
251
|
+
Trinity automatically converts tool return values into MCP-compatible responses. You don't need to worry about the MCP protocol format—just return your data naturally.
|
|
252
|
+
|
|
253
|
+
### How It Works
|
|
254
|
+
|
|
255
|
+
| Tool Returns | Trinity Creates |
|
|
256
|
+
| --------------------------------- | -------------------------------------------------------------------------- |
|
|
257
|
+
| String, number, boolean | `{ content: [{ type: "text", text: "value" }] }` |
|
|
258
|
+
| Object | `{ content: [{ type: "text", text: "{...}" }], structuredContent: {...} }` |
|
|
259
|
+
| Array | `{ content: [item1, item2, ...] }` (each item becomes content) |
|
|
260
|
+
| `{ isError: true, error: "..." }` | `{ content: [{ type: "text", text: "..." }], isError: true }` |
|
|
261
|
+
| Thrown exception | `{ content: [{ type: "text", text: "Error: ..." }], isError: true }` |
|
|
262
|
+
| MCP content object | Used directly (advanced) |
|
|
263
|
+
|
|
264
|
+
### Examples
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Tool returns: "Hello, World!"
|
|
268
|
+
// MCP response: { content: [{ type: "text", text: "Hello, World!" }] }
|
|
269
|
+
|
|
270
|
+
// Tool returns: 42
|
|
271
|
+
// MCP response: { content: [{ type: "text", text: "42" }] }
|
|
272
|
+
|
|
273
|
+
// Tool returns: { temp: 22.5, conditions: "Sunny" }
|
|
274
|
+
// MCP response: {
|
|
275
|
+
// content: [{ type: "text", text: '{"temp": 22.5, "conditions": "Sunny"}' }],
|
|
276
|
+
// structuredContent: { temp: 22.5, conditions: "Sunny" }
|
|
277
|
+
// }
|
|
278
|
+
|
|
279
|
+
// Tool returns: { isError: true, error: "Not found" }
|
|
280
|
+
// MCP response: {
|
|
281
|
+
// content: [{ type: "text", text: "Not found" }],
|
|
282
|
+
// isError: true
|
|
283
|
+
// }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
This automatic formatting follows the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result) for tool results.
|
|
287
|
+
|
|
288
|
+
## Hot Reload
|
|
289
|
+
|
|
290
|
+
Trinity includes a file watcher that automatically detects changes to tool files.
|
|
291
|
+
|
|
292
|
+
### How It Works
|
|
293
|
+
|
|
294
|
+
1. **File Monitoring**: Trinity watches the target folder for file changes
|
|
295
|
+
2. **Detection**: When a tool file is modified and saved, the file watcher detects the change
|
|
296
|
+
3. **Notification**: Trinity sends a `tools/listChanged` notification to all connected MCP clients
|
|
297
|
+
4. **Cache Busting**: Dynamic imports use a timestamp (`?t=Date.now()`) to bypass module caching
|
|
298
|
+
5. **Immediate Availability**: The updated tool is immediately available with new schema and implementation
|
|
299
|
+
|
|
300
|
+
### No Server Restart Required
|
|
301
|
+
|
|
302
|
+
You can:
|
|
303
|
+
|
|
304
|
+
- Add new tool files (automatically detected)
|
|
305
|
+
- Modify existing tool implementations (hot reloaded)
|
|
306
|
+
- Change input schemas and parameters (clients are notified)
|
|
307
|
+
- Fix bugs and see changes instantly
|
|
308
|
+
|
|
309
|
+
Example workflow:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# Terminal 1: Start the server (stays running)
|
|
313
|
+
bun run src/index.ts --target example
|
|
314
|
+
|
|
315
|
+
# Terminal 2: Make changes to a tool file
|
|
316
|
+
# Edit example/hello.ts - save the file
|
|
317
|
+
# The change is immediately available with no restart needed
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Caveats
|
|
321
|
+
|
|
322
|
+
- **VS Code MCP Client Caching**: VS Code's MCP client may cache the schema locally. To see updated enum values or new parameters, you may need to restart the MCP client or reload VS Code
|
|
323
|
+
- **Server-side works immediately**: The server-side tool execution updates instantly
|
|
324
|
+
- **Client-side UI delay**: The MCP client UI may show the old schema temporarily
|
|
325
|
+
|
|
326
|
+
## Built-in Examples
|
|
327
|
+
|
|
328
|
+
Trinity includes several example tools demonstrating different patterns:
|
|
329
|
+
|
|
330
|
+
### `hello.ts`
|
|
331
|
+
|
|
332
|
+
Greeting tool with customizable greeting prefix.
|
|
333
|
+
|
|
334
|
+
- **Params**: `name` (string), `greeting` (optional string)
|
|
335
|
+
- **Shows**: Basic tool structure, optional parameters
|
|
336
|
+
|
|
337
|
+
### `calculator.ts`
|
|
338
|
+
|
|
339
|
+
Mathematical operations with parameter validation.
|
|
340
|
+
|
|
341
|
+
- **Params**: `operation` (enum), `a` (number), `b` (number)
|
|
342
|
+
- **Shows**: Enum validation, numeric operations
|
|
343
|
+
|
|
344
|
+
### `timestamp.ts`
|
|
345
|
+
|
|
346
|
+
Date/time formatting with timezone support.
|
|
347
|
+
|
|
348
|
+
- **Params**: `format` (enum: iso/unix/human/relative), `timezone` (string)
|
|
349
|
+
- **Shows**: Multiple output formats, optional parameters with defaults
|
|
350
|
+
|
|
351
|
+
### `textutil.ts`
|
|
352
|
+
|
|
353
|
+
Text transformation with repetition.
|
|
354
|
+
|
|
355
|
+
- **Params**: `text` (string), `case` (enum), `repeat` (integer with min/max)
|
|
356
|
+
- **Shows**: String transformations, iteration, validation ranges
|
|
357
|
+
|
|
358
|
+
### `random.ts`
|
|
359
|
+
|
|
360
|
+
Random value generation (number, string, UUID, boolean).
|
|
361
|
+
|
|
362
|
+
- **Params**: `type` (enum), `min` (optional), `max` (optional), `length` (optional)
|
|
363
|
+
- **Shows**: Different value types, conditional parameters, crypto usage
|
|
364
|
+
|
|
365
|
+
## Best Practices
|
|
366
|
+
|
|
367
|
+
### 1. Clear Descriptions
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Good
|
|
371
|
+
export const description = "Generates a random UUID v4";
|
|
372
|
+
|
|
373
|
+
// Avoid
|
|
374
|
+
export const description = "Random tool";
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 2. Descriptive Parameter Labels
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Good
|
|
381
|
+
z.string().describe("The email address to validate"),
|
|
382
|
+
|
|
383
|
+
// Avoid
|
|
384
|
+
z.string().describe("email"),
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 3. Use Enums for Choices
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// Good
|
|
391
|
+
z.enum(["json", "csv", "xml"]).describe("Output format"),
|
|
392
|
+
|
|
393
|
+
// Avoid
|
|
394
|
+
z.string().describe("Format: json, csv, or xml"),
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 4. Provide Defaults
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Good
|
|
401
|
+
z.number().default(10).describe("Limit (default: 10)"),
|
|
402
|
+
|
|
403
|
+
// Avoid
|
|
404
|
+
z.number().describe("Limit (required)"),
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 5. Validate Inputs Early
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Good
|
|
411
|
+
z.number().int().min(0).max(100),
|
|
412
|
+
|
|
413
|
+
// Avoid
|
|
414
|
+
z.number(), // then validate in execution
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 6. Handle Errors Gracefully
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Option 1: Return error object for validation errors
|
|
421
|
+
export default async ({ date }: z.infer<typeof inputSchema>) => {
|
|
422
|
+
if (new Date(date) < new Date()) {
|
|
423
|
+
return {
|
|
424
|
+
isError: true,
|
|
425
|
+
error: `Invalid date: must be in the future. Current date is ${new Date().toLocaleDateString()}.`,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return processDate(date);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Option 2: Let exceptions bubble up for unexpected errors
|
|
432
|
+
export default async ({ apiKey }: z.infer<typeof inputSchema>) => {
|
|
433
|
+
// Trinity will catch and format as MCP error
|
|
434
|
+
const data = await externalAPI.call(apiKey); // may throw
|
|
435
|
+
return data;
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### 7. Use Type Inference
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// Good
|
|
443
|
+
export default async ({ param1, param2 }: z.infer<typeof inputSchema>) => {
|
|
444
|
+
// param1 and param2 are properly typed
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
// Avoid
|
|
448
|
+
export default async (args: any) => {
|
|
449
|
+
// No type safety
|
|
450
|
+
},
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Configuration
|
|
454
|
+
|
|
455
|
+
Tools behavior is controlled via CLI arguments or environment variables:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Specify tool directory
|
|
459
|
+
bun run src/index.ts --target ./my-tools
|
|
460
|
+
|
|
461
|
+
# Specify glob pattern
|
|
462
|
+
bun run src/index.ts --glob "tools/**/*.ts"
|
|
463
|
+
|
|
464
|
+
# Combined
|
|
465
|
+
bun run src/index.ts --target ./my-tools --glob "*.ts"
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Environment variables:
|
|
469
|
+
|
|
470
|
+
- `MCP_TARGET`: Tool directory (default: `example`)
|
|
471
|
+
- `MCP_GLOB`: Glob pattern (default: `**/*.ts`)
|
|
472
|
+
- `MCP_MODE`: `stdio` or `http` (default: `stdio`)
|
|
473
|
+
- `MCP_PORT`: HTTP port (default: `3001`)
|
|
474
|
+
- `LOG_FILE`: Log file path (optional, defaults to stderr)
|
|
475
|
+
|
|
476
|
+
## Troubleshooting
|
|
477
|
+
|
|
478
|
+
### Tool Not Found
|
|
479
|
+
|
|
480
|
+
- Check file is in the target directory
|
|
481
|
+
- Verify filename matches the tool name (exclude `.ts` extension)
|
|
482
|
+
- Check glob pattern matches the file
|
|
483
|
+
|
|
484
|
+
### Schema Not Updating
|
|
485
|
+
|
|
486
|
+
- Trinity sends `tools/listChanged` notification
|
|
487
|
+
- VS Code MCP client may cache the schema locally
|
|
488
|
+
- Try restarting the MCP client or reloading VS Code
|
|
489
|
+
|
|
490
|
+
### Type Errors
|
|
491
|
+
|
|
492
|
+
- Ensure `inputSchema` is a `z.object()`
|
|
493
|
+
- Use `z.infer<typeof inputSchema>` for typing
|
|
494
|
+
- Check for typos in parameter names
|
|
495
|
+
|
|
496
|
+
### Parameters Not Passed
|
|
497
|
+
|
|
498
|
+
- VS Code MCP client doesn't always pass parameters to the LLM initially
|
|
499
|
+
- The schema is correct on the server side
|
|
500
|
+
- This is a known VS Code MCP client limitation
|
|
501
|
+
|
|
502
|
+
## Advanced Examples
|
|
503
|
+
|
|
504
|
+
### File Processing Tool
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { z } from "zod";
|
|
508
|
+
import fs from "node:fs";
|
|
509
|
+
|
|
510
|
+
export const description = "Reads and processes a file";
|
|
511
|
+
|
|
512
|
+
export const inputSchema = z.object({
|
|
513
|
+
filepath: z.string().describe("Path to the file to read"),
|
|
514
|
+
encoding: z
|
|
515
|
+
.enum(["utf8", "ascii", "base64"])
|
|
516
|
+
.default("utf8")
|
|
517
|
+
.describe("File encoding (default: utf8)"),
|
|
518
|
+
maxLines: z
|
|
519
|
+
.number()
|
|
520
|
+
.int()
|
|
521
|
+
.min(1)
|
|
522
|
+
.optional()
|
|
523
|
+
.describe("Maximum lines to read (optional)"),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
export default async ({
|
|
527
|
+
filepath,
|
|
528
|
+
encoding,
|
|
529
|
+
maxLines,
|
|
530
|
+
}: z.infer<typeof inputSchema>) => {
|
|
531
|
+
try {
|
|
532
|
+
const content = fs
|
|
533
|
+
.readFileSync(filepath, encoding as BufferEncoding)
|
|
534
|
+
.toString();
|
|
535
|
+
const lines = content.split("\n");
|
|
536
|
+
const limitedLines = maxLines ? lines.slice(0, maxLines) : lines;
|
|
537
|
+
|
|
538
|
+
// Return the string directly
|
|
539
|
+
return limitedLines.join("\n");
|
|
540
|
+
} catch (error) {
|
|
541
|
+
// Return error object
|
|
542
|
+
return {
|
|
543
|
+
isError: true,
|
|
544
|
+
error: `Failed to read file: ${(error as Error).message}`,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Weather API Tool (Structured Data)
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { z } from "zod";
|
|
554
|
+
|
|
555
|
+
export const description = "Returns structured weather data for a location";
|
|
556
|
+
|
|
557
|
+
export const inputSchema = z.object({
|
|
558
|
+
location: z.string().describe("City name"),
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
export default async ({ location }: z.infer<typeof inputSchema>) => {
|
|
562
|
+
// Validate input
|
|
563
|
+
if (location.toLowerCase() === "unknown") {
|
|
564
|
+
return {
|
|
565
|
+
isError: true,
|
|
566
|
+
error: `Location '${location}' not found. Please provide a valid city name.`,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Return structured data - Trinity provides both text and structuredContent
|
|
571
|
+
return {
|
|
572
|
+
location,
|
|
573
|
+
temperature: 22.5,
|
|
574
|
+
conditions: "Partly cloudy",
|
|
575
|
+
humidity: 65,
|
|
576
|
+
wind: {
|
|
577
|
+
speed: 12,
|
|
578
|
+
direction: "NW",
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Summary
|
|
585
|
+
|
|
586
|
+
Trinity tools are simple, self-contained TypeScript modules that:
|
|
587
|
+
|
|
588
|
+
- Are automatically discovered from the target directory
|
|
589
|
+
- Define their interface via Zod schemas
|
|
590
|
+
- Return simple values (strings, numbers, objects)—Trinity handles MCP formatting
|
|
591
|
+
- Execute with validated, type-safe parameters
|
|
592
|
+
- Support hot reload without server restart
|
|
593
|
+
- Are exposed via the Model Context Protocol to AI clients
|
|
594
|
+
|
|
595
|
+
### Key Benefits
|
|
596
|
+
|
|
597
|
+
- **Simple API**: Return strings, numbers, or objects—no need to understand MCP protocol
|
|
598
|
+
- **Automatic Formatting**: Trinity converts your returns to MCP-compatible responses
|
|
599
|
+
- **Structured Data**: Objects automatically get both text and structured content
|
|
600
|
+
- **Error Handling**: Both exceptions and error objects are properly formatted
|
|
601
|
+
- **Type Safety**: Full TypeScript support with Zod schema inference
|
|
602
|
+
- **Hot Reload**: Changes are immediately available without server restart
|
|
603
|
+
|
|
604
|
+
Create new tools by adding `.ts` files to your target directory. Changes are immediately detected and available—no server restart needed.
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isolo/trinity",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A Bun-based MCP server framework with dynamic tool loading, hot reload, and stdio/HTTP modes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Igor Solomakha",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Sujimoshi/trinity.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"bun",
|
|
16
|
+
"tools",
|
|
17
|
+
"hot-reload",
|
|
18
|
+
"ai",
|
|
19
|
+
"llm"
|
|
20
|
+
],
|
|
21
|
+
"bin": {
|
|
22
|
+
"trinity": "./src/index.ts"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"docs",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "bun --watch src/index.ts --target ./example",
|
|
32
|
+
"start:http": "MCP_MODE=http bun --hot src/index.ts --target ./example"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
36
|
+
"json-stringify-safe": "^5.0.1",
|
|
37
|
+
"zod": "^4.3.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.8",
|
|
41
|
+
"@types/json-stringify-safe": "^5.0.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"bun": ">=1.1.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|