@sparkleideas/shared 3.0.0-alpha.7
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 +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 MCP Tool Registry
|
|
3
|
+
*
|
|
4
|
+
* High-performance tool management with:
|
|
5
|
+
* - Fast O(1) lookup using Map
|
|
6
|
+
* - Category-based organization
|
|
7
|
+
* - Tool validation on registration
|
|
8
|
+
* - Dynamic registration/unregistration
|
|
9
|
+
* - Caching for frequently used tools
|
|
10
|
+
*
|
|
11
|
+
* Performance Targets:
|
|
12
|
+
* - Tool registration: <10ms
|
|
13
|
+
* - Tool lookup: <1ms
|
|
14
|
+
* - Tool validation: <5ms
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
import {
|
|
19
|
+
MCPTool,
|
|
20
|
+
JSONSchema,
|
|
21
|
+
ToolHandler,
|
|
22
|
+
ToolContext,
|
|
23
|
+
ToolCallResult,
|
|
24
|
+
ToolRegistrationOptions,
|
|
25
|
+
ILogger,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tool metadata for enhanced lookup
|
|
30
|
+
*/
|
|
31
|
+
interface ToolMetadata {
|
|
32
|
+
tool: MCPTool;
|
|
33
|
+
registeredAt: Date;
|
|
34
|
+
callCount: number;
|
|
35
|
+
lastCalled?: Date;
|
|
36
|
+
avgExecutionTime: number;
|
|
37
|
+
errorCount: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tool search options
|
|
42
|
+
*/
|
|
43
|
+
interface ToolSearchOptions {
|
|
44
|
+
category?: string;
|
|
45
|
+
tags?: string[];
|
|
46
|
+
deprecated?: boolean;
|
|
47
|
+
cacheable?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Tool Registry
|
|
52
|
+
*
|
|
53
|
+
* Manages registration, lookup, and execution of MCP tools
|
|
54
|
+
*/
|
|
55
|
+
export class ToolRegistry extends EventEmitter {
|
|
56
|
+
private readonly tools: Map<string, ToolMetadata> = new Map();
|
|
57
|
+
private readonly categoryIndex: Map<string, Set<string>> = new Map();
|
|
58
|
+
private readonly tagIndex: Map<string, Set<string>> = new Map();
|
|
59
|
+
private defaultContext?: ToolContext;
|
|
60
|
+
|
|
61
|
+
// Performance tracking
|
|
62
|
+
private totalRegistrations = 0;
|
|
63
|
+
private totalLookups = 0;
|
|
64
|
+
private totalExecutions = 0;
|
|
65
|
+
|
|
66
|
+
constructor(private readonly logger: ILogger) {
|
|
67
|
+
super();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register a tool
|
|
72
|
+
*/
|
|
73
|
+
register(tool: MCPTool, options: ToolRegistrationOptions = {}): boolean {
|
|
74
|
+
const startTime = performance.now();
|
|
75
|
+
|
|
76
|
+
// Check for existing tool
|
|
77
|
+
if (this.tools.has(tool.name) && !options.override) {
|
|
78
|
+
this.logger.warn('Tool already registered', { name: tool.name });
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate tool if requested
|
|
83
|
+
if (options.validate !== false) {
|
|
84
|
+
const validation = this.validateTool(tool);
|
|
85
|
+
if (!validation.valid) {
|
|
86
|
+
this.logger.error('Tool validation failed', {
|
|
87
|
+
name: tool.name,
|
|
88
|
+
errors: validation.errors,
|
|
89
|
+
});
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Create metadata
|
|
95
|
+
const metadata: ToolMetadata = {
|
|
96
|
+
tool,
|
|
97
|
+
registeredAt: new Date(),
|
|
98
|
+
callCount: 0,
|
|
99
|
+
avgExecutionTime: 0,
|
|
100
|
+
errorCount: 0,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Register tool
|
|
104
|
+
this.tools.set(tool.name, metadata);
|
|
105
|
+
this.totalRegistrations++;
|
|
106
|
+
|
|
107
|
+
// Update category index
|
|
108
|
+
if (tool.category) {
|
|
109
|
+
if (!this.categoryIndex.has(tool.category)) {
|
|
110
|
+
this.categoryIndex.set(tool.category, new Set());
|
|
111
|
+
}
|
|
112
|
+
this.categoryIndex.get(tool.category)!.add(tool.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update tag index
|
|
116
|
+
if (tool.tags) {
|
|
117
|
+
for (const tag of tool.tags) {
|
|
118
|
+
if (!this.tagIndex.has(tag)) {
|
|
119
|
+
this.tagIndex.set(tag, new Set());
|
|
120
|
+
}
|
|
121
|
+
this.tagIndex.get(tag)!.add(tool.name);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const duration = performance.now() - startTime;
|
|
126
|
+
this.logger.debug('Tool registered', {
|
|
127
|
+
name: tool.name,
|
|
128
|
+
category: tool.category,
|
|
129
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this.emit('tool:registered', tool.name);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register multiple tools at once
|
|
138
|
+
*/
|
|
139
|
+
registerBatch(tools: MCPTool[], options: ToolRegistrationOptions = {}): {
|
|
140
|
+
registered: number;
|
|
141
|
+
failed: string[];
|
|
142
|
+
} {
|
|
143
|
+
const startTime = performance.now();
|
|
144
|
+
const failed: string[] = [];
|
|
145
|
+
let registered = 0;
|
|
146
|
+
|
|
147
|
+
for (const tool of tools) {
|
|
148
|
+
if (this.register(tool, options)) {
|
|
149
|
+
registered++;
|
|
150
|
+
} else {
|
|
151
|
+
failed.push(tool.name);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const duration = performance.now() - startTime;
|
|
156
|
+
this.logger.info('Batch registration complete', {
|
|
157
|
+
total: tools.length,
|
|
158
|
+
registered,
|
|
159
|
+
failed: failed.length,
|
|
160
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return { registered, failed };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unregister a tool
|
|
168
|
+
*/
|
|
169
|
+
unregister(name: string): boolean {
|
|
170
|
+
const metadata = this.tools.get(name);
|
|
171
|
+
if (!metadata) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Remove from category index
|
|
176
|
+
if (metadata.tool.category) {
|
|
177
|
+
const categoryTools = this.categoryIndex.get(metadata.tool.category);
|
|
178
|
+
categoryTools?.delete(name);
|
|
179
|
+
if (categoryTools?.size === 0) {
|
|
180
|
+
this.categoryIndex.delete(metadata.tool.category);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Remove from tag index
|
|
185
|
+
if (metadata.tool.tags) {
|
|
186
|
+
for (const tag of metadata.tool.tags) {
|
|
187
|
+
const tagTools = this.tagIndex.get(tag);
|
|
188
|
+
tagTools?.delete(name);
|
|
189
|
+
if (tagTools?.size === 0) {
|
|
190
|
+
this.tagIndex.delete(tag);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.tools.delete(name);
|
|
196
|
+
this.logger.debug('Tool unregistered', { name });
|
|
197
|
+
this.emit('tool:unregistered', name);
|
|
198
|
+
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get a tool by name
|
|
204
|
+
*/
|
|
205
|
+
getTool(name: string): MCPTool | undefined {
|
|
206
|
+
this.totalLookups++;
|
|
207
|
+
return this.tools.get(name)?.tool;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if a tool exists
|
|
212
|
+
*/
|
|
213
|
+
hasTool(name: string): boolean {
|
|
214
|
+
return this.tools.has(name);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get tool count
|
|
219
|
+
*/
|
|
220
|
+
getToolCount(): number {
|
|
221
|
+
return this.tools.size;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get all tool names
|
|
226
|
+
*/
|
|
227
|
+
getToolNames(): string[] {
|
|
228
|
+
return Array.from(this.tools.keys());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* List all tools with metadata
|
|
233
|
+
*/
|
|
234
|
+
listTools(): Array<{
|
|
235
|
+
name: string;
|
|
236
|
+
description: string;
|
|
237
|
+
category?: string;
|
|
238
|
+
tags?: string[];
|
|
239
|
+
deprecated?: boolean;
|
|
240
|
+
}> {
|
|
241
|
+
return Array.from(this.tools.values()).map(({ tool }) => ({
|
|
242
|
+
name: tool.name,
|
|
243
|
+
description: tool.description,
|
|
244
|
+
category: tool.category,
|
|
245
|
+
tags: tool.tags,
|
|
246
|
+
deprecated: tool.deprecated,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Search tools by criteria
|
|
252
|
+
*/
|
|
253
|
+
search(options: ToolSearchOptions): MCPTool[] {
|
|
254
|
+
let results: Set<string> | undefined;
|
|
255
|
+
|
|
256
|
+
// Filter by category
|
|
257
|
+
if (options.category) {
|
|
258
|
+
const categoryTools = this.categoryIndex.get(options.category);
|
|
259
|
+
if (!categoryTools) return [];
|
|
260
|
+
results = new Set(categoryTools);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Filter by tags (intersection)
|
|
264
|
+
if (options.tags && options.tags.length > 0) {
|
|
265
|
+
for (const tag of options.tags) {
|
|
266
|
+
const tagTools = this.tagIndex.get(tag);
|
|
267
|
+
if (!tagTools) return [];
|
|
268
|
+
|
|
269
|
+
if (results) {
|
|
270
|
+
results = new Set([...results].filter((name) => tagTools.has(name)));
|
|
271
|
+
} else {
|
|
272
|
+
results = new Set(tagTools);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get all tools if no filters applied
|
|
278
|
+
if (!results) {
|
|
279
|
+
results = new Set(this.tools.keys());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Convert to tools and apply additional filters
|
|
283
|
+
const tools: MCPTool[] = [];
|
|
284
|
+
for (const name of results) {
|
|
285
|
+
const metadata = this.tools.get(name);
|
|
286
|
+
if (!metadata) continue;
|
|
287
|
+
|
|
288
|
+
const tool = metadata.tool;
|
|
289
|
+
|
|
290
|
+
// Filter by deprecated status
|
|
291
|
+
if (options.deprecated !== undefined && tool.deprecated !== options.deprecated) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Filter by cacheable status
|
|
296
|
+
if (options.cacheable !== undefined && tool.cacheable !== options.cacheable) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
tools.push(tool);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return tools;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get tools by category
|
|
308
|
+
*/
|
|
309
|
+
getByCategory(category: string): MCPTool[] {
|
|
310
|
+
const toolNames = this.categoryIndex.get(category);
|
|
311
|
+
if (!toolNames) return [];
|
|
312
|
+
|
|
313
|
+
return Array.from(toolNames)
|
|
314
|
+
.map((name) => this.tools.get(name)?.tool)
|
|
315
|
+
.filter((tool): tool is MCPTool => tool !== undefined);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get tools by tag
|
|
320
|
+
*/
|
|
321
|
+
getByTag(tag: string): MCPTool[] {
|
|
322
|
+
const toolNames = this.tagIndex.get(tag);
|
|
323
|
+
if (!toolNames) return [];
|
|
324
|
+
|
|
325
|
+
return Array.from(toolNames)
|
|
326
|
+
.map((name) => this.tools.get(name)?.tool)
|
|
327
|
+
.filter((tool): tool is MCPTool => tool !== undefined);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get all categories
|
|
332
|
+
*/
|
|
333
|
+
getCategories(): string[] {
|
|
334
|
+
return Array.from(this.categoryIndex.keys());
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get all tags
|
|
339
|
+
*/
|
|
340
|
+
getTags(): string[] {
|
|
341
|
+
return Array.from(this.tagIndex.keys());
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Execute a tool
|
|
346
|
+
*/
|
|
347
|
+
async execute(
|
|
348
|
+
name: string,
|
|
349
|
+
input: Record<string, unknown>,
|
|
350
|
+
context?: ToolContext
|
|
351
|
+
): Promise<ToolCallResult> {
|
|
352
|
+
const startTime = performance.now();
|
|
353
|
+
const metadata = this.tools.get(name);
|
|
354
|
+
|
|
355
|
+
if (!metadata) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: 'text', text: `Tool not found: ${name}` }],
|
|
358
|
+
isError: true,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Build execution context with required sessionId
|
|
363
|
+
const execContext: ToolContext = {
|
|
364
|
+
sessionId: context?.sessionId || this.defaultContext?.sessionId || 'default-session',
|
|
365
|
+
...this.defaultContext,
|
|
366
|
+
...context,
|
|
367
|
+
};
|
|
368
|
+
this.totalExecutions++;
|
|
369
|
+
metadata.callCount++;
|
|
370
|
+
metadata.lastCalled = new Date();
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
this.emit('tool:called', { name, input });
|
|
374
|
+
|
|
375
|
+
const result = await metadata.tool.handler(input, execContext);
|
|
376
|
+
|
|
377
|
+
const duration = performance.now() - startTime;
|
|
378
|
+
this.updateAverageExecutionTime(metadata, duration);
|
|
379
|
+
|
|
380
|
+
this.logger.debug('Tool executed', {
|
|
381
|
+
name,
|
|
382
|
+
duration: `${duration.toFixed(2)}ms`,
|
|
383
|
+
success: true,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
this.emit('tool:completed', { name, duration, success: true });
|
|
387
|
+
|
|
388
|
+
// Format result
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
393
|
+
}],
|
|
394
|
+
isError: false,
|
|
395
|
+
};
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const duration = performance.now() - startTime;
|
|
398
|
+
metadata.errorCount++;
|
|
399
|
+
|
|
400
|
+
this.logger.error('Tool execution failed', { name, error });
|
|
401
|
+
this.emit('tool:error', { name, error, duration });
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
content: [{
|
|
405
|
+
type: 'text',
|
|
406
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
407
|
+
}],
|
|
408
|
+
isError: true,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Set default execution context
|
|
415
|
+
*/
|
|
416
|
+
setDefaultContext(context: ToolContext): void {
|
|
417
|
+
this.defaultContext = context;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get tool metadata
|
|
422
|
+
*/
|
|
423
|
+
getMetadata(name: string): ToolMetadata | undefined {
|
|
424
|
+
return this.tools.get(name);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get registry statistics
|
|
429
|
+
*/
|
|
430
|
+
getStats(): {
|
|
431
|
+
totalTools: number;
|
|
432
|
+
totalCategories: number;
|
|
433
|
+
totalTags: number;
|
|
434
|
+
totalRegistrations: number;
|
|
435
|
+
totalLookups: number;
|
|
436
|
+
totalExecutions: number;
|
|
437
|
+
topTools: Array<{ name: string; calls: number }>;
|
|
438
|
+
} {
|
|
439
|
+
// Get top 10 most used tools
|
|
440
|
+
const topTools = Array.from(this.tools.entries())
|
|
441
|
+
.map(([name, metadata]) => ({ name, calls: metadata.callCount }))
|
|
442
|
+
.sort((a, b) => b.calls - a.calls)
|
|
443
|
+
.slice(0, 10);
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
totalTools: this.tools.size,
|
|
447
|
+
totalCategories: this.categoryIndex.size,
|
|
448
|
+
totalTags: this.tagIndex.size,
|
|
449
|
+
totalRegistrations: this.totalRegistrations,
|
|
450
|
+
totalLookups: this.totalLookups,
|
|
451
|
+
totalExecutions: this.totalExecutions,
|
|
452
|
+
topTools,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Validate a tool definition
|
|
458
|
+
*/
|
|
459
|
+
validateTool(tool: MCPTool): { valid: boolean; errors: string[] } {
|
|
460
|
+
const errors: string[] = [];
|
|
461
|
+
|
|
462
|
+
if (!tool.name || typeof tool.name !== 'string') {
|
|
463
|
+
errors.push('Tool name is required and must be a string');
|
|
464
|
+
} else if (!/^[a-zA-Z][a-zA-Z0-9_/:-]*$/.test(tool.name)) {
|
|
465
|
+
errors.push('Tool name must start with a letter and contain only alphanumeric characters, underscores, slashes, colons, and hyphens');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!tool.description || typeof tool.description !== 'string') {
|
|
469
|
+
errors.push('Tool description is required and must be a string');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (!tool.inputSchema || typeof tool.inputSchema !== 'object') {
|
|
473
|
+
errors.push('Tool inputSchema is required and must be an object');
|
|
474
|
+
} else {
|
|
475
|
+
const schemaErrors = this.validateSchema(tool.inputSchema);
|
|
476
|
+
errors.push(...schemaErrors);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (typeof tool.handler !== 'function') {
|
|
480
|
+
errors.push('Tool handler is required and must be a function');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
valid: errors.length === 0,
|
|
485
|
+
errors,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Validate JSON Schema
|
|
491
|
+
*/
|
|
492
|
+
private validateSchema(schema: JSONSchema, path = ''): string[] {
|
|
493
|
+
const errors: string[] = [];
|
|
494
|
+
|
|
495
|
+
if (!schema.type) {
|
|
496
|
+
errors.push(`${path || 'schema'}: type is required`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (schema.type === 'object' && schema.properties) {
|
|
500
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
501
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
502
|
+
errors.push(...this.validateSchema(propSchema, propPath));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (schema.type === 'array' && schema.items) {
|
|
507
|
+
errors.push(...this.validateSchema(schema.items, `${path}[]`));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return errors;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Update average execution time
|
|
515
|
+
*/
|
|
516
|
+
private updateAverageExecutionTime(metadata: ToolMetadata, duration: number): void {
|
|
517
|
+
const n = metadata.callCount;
|
|
518
|
+
metadata.avgExecutionTime =
|
|
519
|
+
((metadata.avgExecutionTime * (n - 1)) + duration) / n;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Clear all tools
|
|
524
|
+
*/
|
|
525
|
+
clear(): void {
|
|
526
|
+
this.tools.clear();
|
|
527
|
+
this.categoryIndex.clear();
|
|
528
|
+
this.tagIndex.clear();
|
|
529
|
+
this.logger.info('Tool registry cleared');
|
|
530
|
+
this.emit('registry:cleared');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Create a tool registry
|
|
536
|
+
*/
|
|
537
|
+
export function createToolRegistry(logger: ILogger): ToolRegistry {
|
|
538
|
+
return new ToolRegistry(logger);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Helper to create a tool definition
|
|
543
|
+
*/
|
|
544
|
+
export function defineTool<TInput = Record<string, unknown>, TOutput = unknown>(
|
|
545
|
+
name: string,
|
|
546
|
+
description: string,
|
|
547
|
+
inputSchema: JSONSchema,
|
|
548
|
+
handler: ToolHandler<TInput, TOutput>,
|
|
549
|
+
options?: {
|
|
550
|
+
category?: string;
|
|
551
|
+
tags?: string[];
|
|
552
|
+
version?: string;
|
|
553
|
+
deprecated?: boolean;
|
|
554
|
+
cacheable?: boolean;
|
|
555
|
+
cacheTTL?: number;
|
|
556
|
+
timeout?: number;
|
|
557
|
+
}
|
|
558
|
+
): MCPTool<TInput, TOutput> {
|
|
559
|
+
return {
|
|
560
|
+
name,
|
|
561
|
+
description,
|
|
562
|
+
inputSchema,
|
|
563
|
+
handler,
|
|
564
|
+
...options,
|
|
565
|
+
};
|
|
566
|
+
}
|