@claude-flow/mcp 3.0.0-alpha.1
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/.agentic-flow/intelligence.json +16 -0
- package/README.md +428 -0
- package/__tests__/integration.test.ts +449 -0
- package/__tests__/mcp.test.ts +641 -0
- package/dist/connection-pool.d.ts +36 -0
- package/dist/connection-pool.d.ts.map +1 -0
- package/dist/connection-pool.js +273 -0
- package/dist/connection-pool.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +146 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +318 -0
- package/dist/oauth.js.map +1 -0
- package/dist/prompt-registry.d.ts +90 -0
- package/dist/prompt-registry.d.ts.map +1 -0
- package/dist/prompt-registry.js +209 -0
- package/dist/prompt-registry.js.map +1 -0
- package/dist/rate-limiter.d.ts +86 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +197 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resource-registry.d.ts +144 -0
- package/dist/resource-registry.d.ts.map +1 -0
- package/dist/resource-registry.js +405 -0
- package/dist/resource-registry.js.map +1 -0
- package/dist/sampling.d.ts +102 -0
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +268 -0
- package/dist/sampling.js.map +1 -0
- package/dist/schema-validator.d.ts +30 -0
- package/dist/schema-validator.d.ts.map +1 -0
- package/dist/schema-validator.js +182 -0
- package/dist/schema-validator.js.map +1 -0
- package/dist/server.d.ts +122 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +829 -0
- package/dist/server.js.map +1 -0
- package/dist/session-manager.d.ts +55 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +252 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/task-manager.d.ts +81 -0
- package/dist/task-manager.d.ts.map +1 -0
- package/dist/task-manager.js +337 -0
- package/dist/task-manager.js.map +1 -0
- package/dist/tool-registry.d.ts +88 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +353 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/transport/http.d.ts +55 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +446 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/index.d.ts +50 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +181 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/stdio.d.ts +43 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +194 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport/websocket.d.ts +65 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +314 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/types.d.ts +473 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
- package/src/connection-pool.ts +344 -0
- package/src/index.ts +253 -0
- package/src/oauth.ts +447 -0
- package/src/prompt-registry.ts +296 -0
- package/src/rate-limiter.ts +266 -0
- package/src/resource-registry.ts +530 -0
- package/src/sampling.ts +363 -0
- package/src/schema-validator.ts +213 -0
- package/src/server.ts +1134 -0
- package/src/session-manager.ts +339 -0
- package/src/task-manager.ts +427 -0
- package/src/tool-registry.ts +475 -0
- package/src/transport/http.ts +532 -0
- package/src/transport/index.ts +233 -0
- package/src/transport/stdio.ts +252 -0
- package/src/transport/websocket.ts +396 -0
- package/src/types.ts +664 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @claude-flow/mcp - Tool Registry
|
|
3
|
+
*
|
|
4
|
+
* High-performance tool management with O(1) lookup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import type {
|
|
9
|
+
MCPTool,
|
|
10
|
+
JSONSchema,
|
|
11
|
+
ToolHandler,
|
|
12
|
+
ToolContext,
|
|
13
|
+
ToolCallResult,
|
|
14
|
+
ToolRegistrationOptions,
|
|
15
|
+
ILogger,
|
|
16
|
+
} from './types.js';
|
|
17
|
+
import { validateSchema, formatValidationErrors } from './schema-validator.js';
|
|
18
|
+
|
|
19
|
+
interface ToolMetadata {
|
|
20
|
+
tool: MCPTool;
|
|
21
|
+
registeredAt: Date;
|
|
22
|
+
callCount: number;
|
|
23
|
+
lastCalled?: Date;
|
|
24
|
+
avgExecutionTime: number;
|
|
25
|
+
errorCount: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ToolSearchOptions {
|
|
29
|
+
category?: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
deprecated?: boolean;
|
|
32
|
+
cacheable?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class ToolRegistry extends EventEmitter {
|
|
36
|
+
private readonly tools: Map<string, ToolMetadata> = new Map();
|
|
37
|
+
private readonly categoryIndex: Map<string, Set<string>> = new Map();
|
|
38
|
+
private readonly tagIndex: Map<string, Set<string>> = new Map();
|
|
39
|
+
private defaultContext?: ToolContext;
|
|
40
|
+
|
|
41
|
+
private totalRegistrations = 0;
|
|
42
|
+
private totalLookups = 0;
|
|
43
|
+
private totalExecutions = 0;
|
|
44
|
+
|
|
45
|
+
constructor(private readonly logger: ILogger) {
|
|
46
|
+
super();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
register(tool: MCPTool, options: ToolRegistrationOptions = {}): boolean {
|
|
50
|
+
const startTime = performance.now();
|
|
51
|
+
|
|
52
|
+
if (this.tools.has(tool.name) && !options.override) {
|
|
53
|
+
this.logger.warn('Tool already registered', { name: tool.name });
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (options.validate !== false) {
|
|
58
|
+
const validation = this.validateTool(tool);
|
|
59
|
+
if (!validation.valid) {
|
|
60
|
+
this.logger.error('Tool validation failed', {
|
|
61
|
+
name: tool.name,
|
|
62
|
+
errors: validation.errors,
|
|
63
|
+
});
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const metadata: ToolMetadata = {
|
|
69
|
+
tool,
|
|
70
|
+
registeredAt: new Date(),
|
|
71
|
+
callCount: 0,
|
|
72
|
+
avgExecutionTime: 0,
|
|
73
|
+
errorCount: 0,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
this.tools.set(tool.name, metadata);
|
|
77
|
+
this.totalRegistrations++;
|
|
78
|
+
|
|
79
|
+
if (tool.category) {
|
|
80
|
+
if (!this.categoryIndex.has(tool.category)) {
|
|
81
|
+
this.categoryIndex.set(tool.category, new Set());
|
|
82
|
+
}
|
|
83
|
+
this.categoryIndex.get(tool.category)!.add(tool.name);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (tool.tags) {
|
|
87
|
+
for (const tag of tool.tags) {
|
|
88
|
+
if (!this.tagIndex.has(tag)) {
|
|
89
|
+
this.tagIndex.set(tag, new Set());
|
|
90
|
+
}
|
|
91
|
+
this.tagIndex.get(tag)!.add(tool.name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const duration = performance.now() - startTime;
|
|
96
|
+
this.logger.debug('Tool registered', {
|
|
97
|
+
name: tool.name,
|
|
98
|
+
category: tool.category,
|
|
99
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.emit('tool:registered', tool.name);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
registerBatch(tools: MCPTool[], options: ToolRegistrationOptions = {}): {
|
|
107
|
+
registered: number;
|
|
108
|
+
failed: string[];
|
|
109
|
+
} {
|
|
110
|
+
const startTime = performance.now();
|
|
111
|
+
const failed: string[] = [];
|
|
112
|
+
let registered = 0;
|
|
113
|
+
|
|
114
|
+
for (const tool of tools) {
|
|
115
|
+
if (this.register(tool, options)) {
|
|
116
|
+
registered++;
|
|
117
|
+
} else {
|
|
118
|
+
failed.push(tool.name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const duration = performance.now() - startTime;
|
|
123
|
+
this.logger.info('Batch registration complete', {
|
|
124
|
+
total: tools.length,
|
|
125
|
+
registered,
|
|
126
|
+
failed: failed.length,
|
|
127
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return { registered, failed };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
unregister(name: string): boolean {
|
|
134
|
+
const metadata = this.tools.get(name);
|
|
135
|
+
if (!metadata) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (metadata.tool.category) {
|
|
140
|
+
const categoryTools = this.categoryIndex.get(metadata.tool.category);
|
|
141
|
+
categoryTools?.delete(name);
|
|
142
|
+
if (categoryTools?.size === 0) {
|
|
143
|
+
this.categoryIndex.delete(metadata.tool.category);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (metadata.tool.tags) {
|
|
148
|
+
for (const tag of metadata.tool.tags) {
|
|
149
|
+
const tagTools = this.tagIndex.get(tag);
|
|
150
|
+
tagTools?.delete(name);
|
|
151
|
+
if (tagTools?.size === 0) {
|
|
152
|
+
this.tagIndex.delete(tag);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.tools.delete(name);
|
|
158
|
+
this.logger.debug('Tool unregistered', { name });
|
|
159
|
+
this.emit('tool:unregistered', name);
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
getTool(name: string): MCPTool | undefined {
|
|
165
|
+
this.totalLookups++;
|
|
166
|
+
return this.tools.get(name)?.tool;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
hasTool(name: string): boolean {
|
|
170
|
+
return this.tools.has(name);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getToolCount(): number {
|
|
174
|
+
return this.tools.size;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getToolNames(): string[] {
|
|
178
|
+
return Array.from(this.tools.keys());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
listTools(): Array<{
|
|
182
|
+
name: string;
|
|
183
|
+
description: string;
|
|
184
|
+
category?: string;
|
|
185
|
+
tags?: string[];
|
|
186
|
+
deprecated?: boolean;
|
|
187
|
+
}> {
|
|
188
|
+
return Array.from(this.tools.values()).map(({ tool }) => ({
|
|
189
|
+
name: tool.name,
|
|
190
|
+
description: tool.description,
|
|
191
|
+
category: tool.category,
|
|
192
|
+
tags: tool.tags,
|
|
193
|
+
deprecated: tool.deprecated,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
search(options: ToolSearchOptions): MCPTool[] {
|
|
198
|
+
let results: Set<string> | undefined;
|
|
199
|
+
|
|
200
|
+
if (options.category) {
|
|
201
|
+
const categoryTools = this.categoryIndex.get(options.category);
|
|
202
|
+
if (!categoryTools) return [];
|
|
203
|
+
results = new Set(categoryTools);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (options.tags && options.tags.length > 0) {
|
|
207
|
+
for (const tag of options.tags) {
|
|
208
|
+
const tagTools = this.tagIndex.get(tag);
|
|
209
|
+
if (!tagTools) return [];
|
|
210
|
+
|
|
211
|
+
if (results) {
|
|
212
|
+
results = new Set([...results].filter((name) => tagTools.has(name)));
|
|
213
|
+
} else {
|
|
214
|
+
results = new Set(tagTools);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!results) {
|
|
220
|
+
results = new Set(this.tools.keys());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const tools: MCPTool[] = [];
|
|
224
|
+
for (const name of results) {
|
|
225
|
+
const metadata = this.tools.get(name);
|
|
226
|
+
if (!metadata) continue;
|
|
227
|
+
|
|
228
|
+
const tool = metadata.tool;
|
|
229
|
+
|
|
230
|
+
if (options.deprecated !== undefined && tool.deprecated !== options.deprecated) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.cacheable !== undefined && tool.cacheable !== options.cacheable) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
tools.push(tool);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return tools;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getByCategory(category: string): MCPTool[] {
|
|
245
|
+
const toolNames = this.categoryIndex.get(category);
|
|
246
|
+
if (!toolNames) return [];
|
|
247
|
+
|
|
248
|
+
return Array.from(toolNames)
|
|
249
|
+
.map((name) => this.tools.get(name)?.tool)
|
|
250
|
+
.filter((tool): tool is MCPTool => tool !== undefined);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getByTag(tag: string): MCPTool[] {
|
|
254
|
+
const toolNames = this.tagIndex.get(tag);
|
|
255
|
+
if (!toolNames) return [];
|
|
256
|
+
|
|
257
|
+
return Array.from(toolNames)
|
|
258
|
+
.map((name) => this.tools.get(name)?.tool)
|
|
259
|
+
.filter((tool): tool is MCPTool => tool !== undefined);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getCategories(): string[] {
|
|
263
|
+
return Array.from(this.categoryIndex.keys());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getTags(): string[] {
|
|
267
|
+
return Array.from(this.tagIndex.keys());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async execute(
|
|
271
|
+
name: string,
|
|
272
|
+
input: Record<string, unknown>,
|
|
273
|
+
context?: ToolContext
|
|
274
|
+
): Promise<ToolCallResult> {
|
|
275
|
+
const startTime = performance.now();
|
|
276
|
+
const metadata = this.tools.get(name);
|
|
277
|
+
|
|
278
|
+
if (!metadata) {
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: 'text', text: `Tool not found: ${name}` }],
|
|
281
|
+
isError: true,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Validate input against schema (security feature)
|
|
286
|
+
if (metadata.tool.inputSchema) {
|
|
287
|
+
const validation = validateSchema(input, metadata.tool.inputSchema);
|
|
288
|
+
if (!validation.valid) {
|
|
289
|
+
const errorMsg = formatValidationErrors(validation.errors);
|
|
290
|
+
this.logger.warn('Tool input validation failed', {
|
|
291
|
+
name,
|
|
292
|
+
errors: validation.errors,
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: 'text', text: `Invalid input: ${errorMsg}` }],
|
|
296
|
+
isError: true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const execContext: ToolContext = {
|
|
302
|
+
sessionId: context?.sessionId || this.defaultContext?.sessionId || 'default-session',
|
|
303
|
+
...this.defaultContext,
|
|
304
|
+
...context,
|
|
305
|
+
};
|
|
306
|
+
this.totalExecutions++;
|
|
307
|
+
metadata.callCount++;
|
|
308
|
+
metadata.lastCalled = new Date();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
this.emit('tool:called', { name, input });
|
|
312
|
+
|
|
313
|
+
const result = await metadata.tool.handler(input, execContext);
|
|
314
|
+
|
|
315
|
+
const duration = performance.now() - startTime;
|
|
316
|
+
this.updateAverageExecutionTime(metadata, duration);
|
|
317
|
+
|
|
318
|
+
this.logger.debug('Tool executed', {
|
|
319
|
+
name,
|
|
320
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
321
|
+
success: true,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.emit('tool:completed', { name, duration, success: true });
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
content: [{
|
|
328
|
+
type: 'text',
|
|
329
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
330
|
+
}],
|
|
331
|
+
isError: false,
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
const duration = performance.now() - startTime;
|
|
335
|
+
metadata.errorCount++;
|
|
336
|
+
|
|
337
|
+
this.logger.error('Tool execution failed', { name, error });
|
|
338
|
+
this.emit('tool:error', { name, error, duration });
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
content: [{
|
|
342
|
+
type: 'text',
|
|
343
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
344
|
+
}],
|
|
345
|
+
isError: true,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
setDefaultContext(context: ToolContext): void {
|
|
351
|
+
this.defaultContext = context;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
getMetadata(name: string): ToolMetadata | undefined {
|
|
355
|
+
return this.tools.get(name);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getStats(): {
|
|
359
|
+
totalTools: number;
|
|
360
|
+
totalCategories: number;
|
|
361
|
+
totalTags: number;
|
|
362
|
+
totalRegistrations: number;
|
|
363
|
+
totalLookups: number;
|
|
364
|
+
totalExecutions: number;
|
|
365
|
+
topTools: Array<{ name: string; calls: number }>;
|
|
366
|
+
} {
|
|
367
|
+
const topTools = Array.from(this.tools.entries())
|
|
368
|
+
.map(([name, metadata]) => ({ name, calls: metadata.callCount }))
|
|
369
|
+
.sort((a, b) => b.calls - a.calls)
|
|
370
|
+
.slice(0, 10);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
totalTools: this.tools.size,
|
|
374
|
+
totalCategories: this.categoryIndex.size,
|
|
375
|
+
totalTags: this.tagIndex.size,
|
|
376
|
+
totalRegistrations: this.totalRegistrations,
|
|
377
|
+
totalLookups: this.totalLookups,
|
|
378
|
+
totalExecutions: this.totalExecutions,
|
|
379
|
+
topTools,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
validateTool(tool: MCPTool): { valid: boolean; errors: string[] } {
|
|
384
|
+
const errors: string[] = [];
|
|
385
|
+
|
|
386
|
+
if (!tool.name || typeof tool.name !== 'string') {
|
|
387
|
+
errors.push('Tool name is required and must be a string');
|
|
388
|
+
} else if (!/^[a-zA-Z][a-zA-Z0-9_/:-]*$/.test(tool.name)) {
|
|
389
|
+
errors.push('Tool name must start with a letter and contain only alphanumeric characters, underscores, slashes, colons, and hyphens');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!tool.description || typeof tool.description !== 'string') {
|
|
393
|
+
errors.push('Tool description is required and must be a string');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (!tool.inputSchema || typeof tool.inputSchema !== 'object') {
|
|
397
|
+
errors.push('Tool inputSchema is required and must be an object');
|
|
398
|
+
} else {
|
|
399
|
+
const schemaErrors = this.validateSchema(tool.inputSchema);
|
|
400
|
+
errors.push(...schemaErrors);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (typeof tool.handler !== 'function') {
|
|
404
|
+
errors.push('Tool handler is required and must be a function');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
valid: errors.length === 0,
|
|
409
|
+
errors,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private validateSchema(schema: JSONSchema, path = ''): string[] {
|
|
414
|
+
const errors: string[] = [];
|
|
415
|
+
|
|
416
|
+
if (!schema.type) {
|
|
417
|
+
errors.push(`${path || 'schema'}: type is required`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (schema.type === 'object' && schema.properties) {
|
|
421
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
422
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
423
|
+
errors.push(...this.validateSchema(propSchema, propPath));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (schema.type === 'array' && schema.items) {
|
|
428
|
+
errors.push(...this.validateSchema(schema.items, `${path}[]`));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private updateAverageExecutionTime(metadata: ToolMetadata, duration: number): void {
|
|
435
|
+
const n = metadata.callCount;
|
|
436
|
+
metadata.avgExecutionTime =
|
|
437
|
+
((metadata.avgExecutionTime * (n - 1)) + duration) / n;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
clear(): void {
|
|
441
|
+
this.tools.clear();
|
|
442
|
+
this.categoryIndex.clear();
|
|
443
|
+
this.tagIndex.clear();
|
|
444
|
+
this.logger.info('Tool registry cleared');
|
|
445
|
+
this.emit('registry:cleared');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function createToolRegistry(logger: ILogger): ToolRegistry {
|
|
450
|
+
return new ToolRegistry(logger);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function defineTool<TInput = Record<string, unknown>, TOutput = unknown>(
|
|
454
|
+
name: string,
|
|
455
|
+
description: string,
|
|
456
|
+
inputSchema: JSONSchema,
|
|
457
|
+
handler: ToolHandler<TInput, TOutput>,
|
|
458
|
+
options?: {
|
|
459
|
+
category?: string;
|
|
460
|
+
tags?: string[];
|
|
461
|
+
version?: string;
|
|
462
|
+
deprecated?: boolean;
|
|
463
|
+
cacheable?: boolean;
|
|
464
|
+
cacheTTL?: number;
|
|
465
|
+
timeout?: number;
|
|
466
|
+
}
|
|
467
|
+
): MCPTool<TInput, TOutput> {
|
|
468
|
+
return {
|
|
469
|
+
name,
|
|
470
|
+
description,
|
|
471
|
+
inputSchema,
|
|
472
|
+
handler,
|
|
473
|
+
...options,
|
|
474
|
+
};
|
|
475
|
+
}
|