@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/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
+ }