@isolo/trinity 1.0.4 → 1.0.5

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/Server.ts +43 -18
  3. package/src/Tools.ts +17 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isolo/trinity",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A Bun-based MCP server framework with dynamic tool loading, hot reload, and stdio/HTTP modes",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/Server.ts CHANGED
@@ -13,6 +13,7 @@ import { Config } from "./Config.js";
13
13
  import { Logger } from "./Logger.js";
14
14
  import { Tools } from "./Tools.js";
15
15
  import safeJsonStringify from "json-stringify-safe";
16
+ import { ZodType } from "zod";
16
17
 
17
18
  export class Server {
18
19
  private static instance: Server | null = null;
@@ -90,32 +91,53 @@ export class Server {
90
91
  );
91
92
  }
92
93
 
94
+ safeZodToJson(schema: ZodType) {
95
+ try {
96
+ // Validate it's a proper Zod schema
97
+ if (!schema || typeof schema !== "object" || !("_zod" in schema)) {
98
+ this.logger.error("Invalid Zod schema: missing _zod property", {
99
+ schemaType: typeof schema,
100
+ hasZodProp: schema ? "_zod" in schema : false,
101
+ });
102
+ return {
103
+ type: "object",
104
+ properties: {},
105
+ description: "Invalid Zod schema: not a valid Zod schema object",
106
+ };
107
+ }
108
+ return schema.toJSONSchema();
109
+ } catch (err) {
110
+ this.logger.error("Failed to convert Zod schema to JSON Schema", {
111
+ error: (err as Error).message,
112
+ stack: (err as Error).stack,
113
+ });
114
+ return {
115
+ type: "object",
116
+ properties: {},
117
+ description:
118
+ "Failed to convert zod schema to JSON Schema, pls check your zod schema maybe it have some problems",
119
+ };
120
+ }
121
+ }
122
+
93
123
  private async handleListTools() {
94
124
  this.logger.debug("Handling list_tools request");
95
125
 
96
126
  const toolDefs = await this.tools.loadTools();
97
127
 
98
128
  const tools = toolDefs.map((tool) => {
99
- // Convert Zod schema to JSON Schema for the MCP protocol
100
- const jsonSchema = tool.inputSchema.toJSONSchema();
101
-
102
129
  this.logger.debug(`Generated JSON schema for tool`, {
103
130
  name: tool.name,
104
- jsonSchema,
105
131
  });
106
132
 
107
133
  return {
108
134
  name: tool.name,
109
135
  description: tool.description,
110
- inputSchema: jsonSchema as {
111
- type: "object";
112
- properties?: Record<string, unknown>;
113
- required?: string[];
114
- },
136
+ inputSchema: this.safeZodToJson(tool.inputSchema),
115
137
  };
116
138
  });
117
139
 
118
- this.logger.debug(`Returning ${tools.length} tools`);
140
+ this.logger.debug("Returning tools", { count: tools.length });
119
141
  return { tools };
120
142
  }
121
143
 
@@ -123,16 +145,16 @@ export class Server {
123
145
  params: { name: string; arguments?: Record<string, unknown> };
124
146
  }) {
125
147
  const { name, arguments: args } = request.params;
126
- this.logger.info(`Calling tool: ${name}`);
148
+ this.logger.info("Calling tool", { name });
127
149
 
128
150
  try {
129
151
  const tool = await this.tools.loadTool(name);
130
152
  const raw = await tool.execute(args || {});
131
- this.logger.debug(`Tool ${name} executed successfully`, { raw });
153
+ this.logger.debug("Tool executed successfully", { name, raw });
132
154
 
133
155
  return this.formatToolResult(raw);
134
156
  } catch (err) {
135
- this.logger.error(`Tool ${name} execution failed`, err as Error);
157
+ this.logger.error("Tool execution failed", { name, error: err as Error });
136
158
  return {
137
159
  content: [
138
160
  {
@@ -187,7 +209,7 @@ export class Server {
187
209
  return new StdioServerTransport();
188
210
  }
189
211
 
190
- this.logger.info(`Starting HTTP server on port ${this.config.port}`);
212
+ this.logger.info("Starting HTTP server", { port: this.config.port });
191
213
  return this.createHttpTransport();
192
214
  }
193
215
 
@@ -219,7 +241,7 @@ export class Server {
219
241
  },
220
242
  });
221
243
 
222
- this.logger.info(`HTTP server listening on port ${this.config.port}`);
244
+ this.logger.info("HTTP server listening", { port: this.config.port });
223
245
  return transport;
224
246
  }
225
247
 
@@ -236,13 +258,13 @@ export class Server {
236
258
  },
237
259
  ];
238
260
 
239
- this.logger.debug(`Returning ${resources.length} resources`);
261
+ this.logger.debug("Returning resources", { count: resources.length });
240
262
  return { resources };
241
263
  }
242
264
 
243
265
  private async handleReadResource(request: { params: { uri: string } }) {
244
266
  const { uri } = request.params;
245
- this.logger.info(`Reading resource: ${uri}`);
267
+ this.logger.info("Reading resource", { uri });
246
268
 
247
269
  if (uri === "trinity://docs/tools") {
248
270
  try {
@@ -259,7 +281,10 @@ export class Server {
259
281
  ],
260
282
  };
261
283
  } catch (err) {
262
- this.logger.error(`Failed to read resource ${uri}`, err as Error);
284
+ this.logger.error("Failed to read resource", {
285
+ uri,
286
+ error: err as Error,
287
+ });
263
288
  throw new Error(`Failed to read resource: ${(err as Error).message}`);
264
289
  }
265
290
  }
package/src/Tools.ts CHANGED
@@ -32,22 +32,26 @@ export class Tools {
32
32
 
33
33
  async loadTools(): Promise<ToolDefinition[]> {
34
34
  const files = await this.resolveToolFiles();
35
- this.logger.debug(`Resolved ${files.length} tool files`);
35
+ this.logger.debug("Resolved tool files", { count: files.length });
36
+
37
+ // Load all tools in parallel
38
+ const toolPromises = files.map((filePath) =>
39
+ this.loadToolFromFile(filePath),
40
+ );
41
+ const results = await Promise.allSettled(toolPromises);
36
42
 
37
43
  const tools: ToolDefinition[] = [];
38
44
 
39
- for (const filePath of files) {
40
- try {
41
- const tool = await this.loadToolFromFile(filePath);
42
- tools.push(tool);
43
- } catch (err: any) {
44
- this.logger.error(`Failed to load tool from ${filePath}`, {
45
- err: err.message,
45
+ results.forEach((result, index) => {
46
+ if (result.status === "fulfilled") {
47
+ tools.push(result.value);
48
+ } else {
49
+ this.logger.error(`Failed to load tool`, {
50
+ file: files[index],
51
+ err: result.reason?.message || result.reason,
46
52
  });
47
53
  }
48
- }
49
-
50
- this.logger.debug(`Loaded ${tools.length} tools successfully`);
54
+ });
51
55
 
52
56
  return tools;
53
57
  }
@@ -71,7 +75,7 @@ export class Tools {
71
75
 
72
76
  startWatching(): void {
73
77
  const targetFolder = this.config.targetFolder;
74
- this.logger.info(`Watching for changes in ${targetFolder}`);
78
+ this.logger.info("Watching for changes", { targetFolder });
75
79
 
76
80
  this.watcher = fs.watch(
77
81
  targetFolder,
@@ -171,7 +175,7 @@ export class Tools {
171
175
  return;
172
176
  }
173
177
 
174
- this.logger.info(`File ${event}: ${filename}`);
178
+ this.logger.info("File changed", { event, filename });
175
179
 
176
180
  if (this.onChangeCallback) {
177
181
  this.onChangeCallback();