@nahisaho/katashiro-mcp-server 0.1.0
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/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/prompts/prompt-registry.d.ts.map +1 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/protocol-handler.d.ts.map +1 -0
- package/dist/resources/resource-manager.d.ts.map +1 -0
- package/dist/server/mcp-server.d.ts.map +1 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/stdio-transport.d.ts.map +1 -0
- package/package.json +40 -0
- package/src/cli.ts +64 -0
- package/src/index.ts +77 -0
- package/src/prompts/prompt-registry.ts +195 -0
- package/src/protocol/index.ts +5 -0
- package/src/protocol/protocol-handler.ts +438 -0
- package/src/resources/resource-manager.ts +265 -0
- package/src/server/mcp-server.ts +474 -0
- package/src/tools/tool-registry.ts +158 -0
- package/src/transport/index.ts +5 -0
- package/src/transport/stdio-transport.ts +272 -0
- package/tests/unit/mcp-server.test.ts +164 -0
- package/tests/unit/prompt-registry.test.ts +193 -0
- package/tests/unit/protocol-handler.test.ts +331 -0
- package/tests/unit/resource-manager.test.ts +162 -0
- package/tests/unit/stdio-transport.test.ts +180 -0
- package/tests/unit/tool-registry.test.ts +140 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KatashiroMCPServer - MCP準拠サーバー実装
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol (https://modelcontextprotocol.io) に準拠
|
|
5
|
+
* JSON-RPC 2.0ベースのプロトコルでTools, Resources, Promptsを提供
|
|
6
|
+
*
|
|
7
|
+
* @module @nahisaho/katashiro-mcp-server
|
|
8
|
+
* @task TSK-060
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ok, err, type Result } from '@nahisaho/katashiro-core';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MCP Tool definition (JSON Schema based)
|
|
15
|
+
*/
|
|
16
|
+
export interface MCPTool {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object';
|
|
21
|
+
properties: Record<string, unknown>;
|
|
22
|
+
required?: string[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* MCP Prompt definition
|
|
28
|
+
*/
|
|
29
|
+
export interface MCPPrompt {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
arguments?: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* MCP Resource definition
|
|
41
|
+
*/
|
|
42
|
+
export interface MCPResource {
|
|
43
|
+
uri: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
mimeType?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tool execution result (MCP content array format)
|
|
51
|
+
*/
|
|
52
|
+
export interface ToolResult {
|
|
53
|
+
content: Array<{
|
|
54
|
+
type: 'text' | 'image' | 'resource';
|
|
55
|
+
text?: string;
|
|
56
|
+
data?: string;
|
|
57
|
+
mimeType?: string;
|
|
58
|
+
}>;
|
|
59
|
+
isError?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Prompt result (MCP messages format)
|
|
64
|
+
*/
|
|
65
|
+
export interface PromptResult {
|
|
66
|
+
messages: Array<{
|
|
67
|
+
role: 'user' | 'assistant';
|
|
68
|
+
content: {
|
|
69
|
+
type: 'text';
|
|
70
|
+
text: string;
|
|
71
|
+
};
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Server capabilities (MCP capability negotiation)
|
|
77
|
+
*/
|
|
78
|
+
export interface ServerCapabilities {
|
|
79
|
+
tools?: { listChanged?: boolean };
|
|
80
|
+
resources?: { subscribe?: boolean; listChanged?: boolean };
|
|
81
|
+
prompts?: { listChanged?: boolean };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Server info
|
|
86
|
+
*/
|
|
87
|
+
export interface ServerInfo {
|
|
88
|
+
name: string;
|
|
89
|
+
version: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* KatashiroMCPServer
|
|
94
|
+
*
|
|
95
|
+
* MCP server implementation for KATASHIRO
|
|
96
|
+
* Provides tools, resources, and prompts for AI-powered research
|
|
97
|
+
*/
|
|
98
|
+
export class KatashiroMCPServer {
|
|
99
|
+
private tools: Map<string, MCPTool> = new Map();
|
|
100
|
+
private prompts: Map<string, MCPPrompt> = new Map();
|
|
101
|
+
private resources: Map<string, MCPResource> = new Map();
|
|
102
|
+
|
|
103
|
+
private readonly serverInfo: ServerInfo = {
|
|
104
|
+
name: 'katashiro',
|
|
105
|
+
version: '0.1.0',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
private readonly capabilities: ServerCapabilities = {
|
|
109
|
+
tools: { listChanged: true },
|
|
110
|
+
resources: { subscribe: false, listChanged: true },
|
|
111
|
+
prompts: { listChanged: true },
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
constructor() {
|
|
115
|
+
this.registerTools();
|
|
116
|
+
this.registerPrompts();
|
|
117
|
+
this.registerResources();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get server name
|
|
122
|
+
*/
|
|
123
|
+
getName(): string {
|
|
124
|
+
return this.serverInfo.name;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get server info
|
|
129
|
+
*/
|
|
130
|
+
getServerInfo(): ServerInfo {
|
|
131
|
+
return this.serverInfo;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get server capabilities
|
|
136
|
+
*/
|
|
137
|
+
getCapabilities(): ServerCapabilities {
|
|
138
|
+
return this.capabilities;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all registered tools (tools/list)
|
|
143
|
+
*/
|
|
144
|
+
getTools(): MCPTool[] {
|
|
145
|
+
return Array.from(this.tools.values());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get all registered prompts (prompts/list)
|
|
150
|
+
*/
|
|
151
|
+
getPrompts(): MCPPrompt[] {
|
|
152
|
+
return Array.from(this.prompts.values());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Execute a tool (tools/call)
|
|
157
|
+
*/
|
|
158
|
+
async executeTool(
|
|
159
|
+
name: string,
|
|
160
|
+
args: Record<string, unknown>
|
|
161
|
+
): Promise<Result<ToolResult, Error>> {
|
|
162
|
+
try {
|
|
163
|
+
const tool = this.tools.get(name);
|
|
164
|
+
if (!tool) {
|
|
165
|
+
return err(new Error(`Unknown tool: ${name}`));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const result = await this.handleToolExecution(name, args);
|
|
169
|
+
return result;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Execute a prompt (prompts/get)
|
|
177
|
+
*/
|
|
178
|
+
async executePrompt(
|
|
179
|
+
name: string,
|
|
180
|
+
args: Record<string, unknown>
|
|
181
|
+
): Promise<Result<PromptResult, Error>> {
|
|
182
|
+
try {
|
|
183
|
+
const prompt = this.prompts.get(name);
|
|
184
|
+
if (!prompt) {
|
|
185
|
+
return err(new Error(`Unknown prompt: ${name}`));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = await this.handlePromptExecution(name, args);
|
|
189
|
+
return result;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* List all resources (resources/list)
|
|
197
|
+
*/
|
|
198
|
+
async listResources(): Promise<Result<MCPResource[], Error>> {
|
|
199
|
+
try {
|
|
200
|
+
return ok(Array.from(this.resources.values()));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Read a resource (resources/read)
|
|
208
|
+
*/
|
|
209
|
+
async readResource(uri: string): Promise<Result<string, Error>> {
|
|
210
|
+
try {
|
|
211
|
+
const resource = this.resources.get(uri);
|
|
212
|
+
if (!resource) {
|
|
213
|
+
return err(new Error(`Resource not found: ${uri}`));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return ok(`Resource content for: ${uri}`);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Register KATASHIRO tools
|
|
224
|
+
*/
|
|
225
|
+
private registerTools(): void {
|
|
226
|
+
// Web Search Tool
|
|
227
|
+
this.tools.set('web_search', {
|
|
228
|
+
name: 'web_search',
|
|
229
|
+
description: 'Search the web for information on any topic',
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
query: { type: 'string', description: 'Search query' },
|
|
234
|
+
limit: { type: 'number', description: 'Maximum number of results' },
|
|
235
|
+
},
|
|
236
|
+
required: ['query'],
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Analyze Content Tool
|
|
241
|
+
this.tools.set('analyze_content', {
|
|
242
|
+
name: 'analyze_content',
|
|
243
|
+
description: 'Analyze content for entities, topics, and sentiment',
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
content: { type: 'string', description: 'Content to analyze' },
|
|
248
|
+
type: { type: 'string', description: 'Content type (text, code, html)' },
|
|
249
|
+
},
|
|
250
|
+
required: ['content'],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Generate Summary Tool
|
|
255
|
+
this.tools.set('generate_summary', {
|
|
256
|
+
name: 'generate_summary',
|
|
257
|
+
description: 'Generate a summary of the provided content',
|
|
258
|
+
inputSchema: {
|
|
259
|
+
type: 'object',
|
|
260
|
+
properties: {
|
|
261
|
+
content: { type: 'string', description: 'Content to summarize' },
|
|
262
|
+
style: { type: 'string', description: 'Summary style (brief, detailed, bullet)' },
|
|
263
|
+
},
|
|
264
|
+
required: ['content'],
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Knowledge Query Tool
|
|
269
|
+
this.tools.set('query_knowledge', {
|
|
270
|
+
name: 'query_knowledge',
|
|
271
|
+
description: 'Query the knowledge graph for related information',
|
|
272
|
+
inputSchema: {
|
|
273
|
+
type: 'object',
|
|
274
|
+
properties: {
|
|
275
|
+
query: { type: 'string', description: 'Query string' },
|
|
276
|
+
type: { type: 'string', description: 'Node type filter' },
|
|
277
|
+
},
|
|
278
|
+
required: ['query'],
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Generate Report Tool
|
|
283
|
+
this.tools.set('generate_report', {
|
|
284
|
+
name: 'generate_report',
|
|
285
|
+
description: 'Generate a comprehensive research report',
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
properties: {
|
|
289
|
+
topic: { type: 'string', description: 'Report topic' },
|
|
290
|
+
format: { type: 'string', description: 'Output format (markdown, html, pdf)' },
|
|
291
|
+
sections: { type: 'array', description: 'Report sections to include' },
|
|
292
|
+
},
|
|
293
|
+
required: ['topic'],
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Register KATASHIRO prompts
|
|
300
|
+
*/
|
|
301
|
+
private registerPrompts(): void {
|
|
302
|
+
this.prompts.set('research_topic', {
|
|
303
|
+
name: 'research_topic',
|
|
304
|
+
description: 'Research a topic comprehensively using web search and analysis',
|
|
305
|
+
arguments: [
|
|
306
|
+
{ name: 'topic', description: 'Topic to research', required: true },
|
|
307
|
+
{ name: 'depth', description: 'Research depth (shallow, moderate, deep)', required: false },
|
|
308
|
+
],
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
this.prompts.set('analyze_document', {
|
|
312
|
+
name: 'analyze_document',
|
|
313
|
+
description: 'Analyze a document for key insights and structure',
|
|
314
|
+
arguments: [
|
|
315
|
+
{ name: 'document', description: 'Document content to analyze', required: true },
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
this.prompts.set('create_presentation', {
|
|
320
|
+
name: 'create_presentation',
|
|
321
|
+
description: 'Create a presentation outline from research',
|
|
322
|
+
arguments: [
|
|
323
|
+
{ name: 'topic', description: 'Presentation topic', required: true },
|
|
324
|
+
{ name: 'slides', description: 'Number of slides', required: false },
|
|
325
|
+
],
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Register KATASHIRO resources
|
|
331
|
+
*/
|
|
332
|
+
private registerResources(): void {
|
|
333
|
+
this.resources.set('katashiro://knowledge/graph', {
|
|
334
|
+
uri: 'katashiro://knowledge/graph',
|
|
335
|
+
name: 'Knowledge Graph',
|
|
336
|
+
description: 'The knowledge graph containing research data',
|
|
337
|
+
mimeType: 'application/json',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
this.resources.set('katashiro://patterns/library', {
|
|
341
|
+
uri: 'katashiro://patterns/library',
|
|
342
|
+
name: 'Pattern Library',
|
|
343
|
+
description: 'Learned patterns from user feedback',
|
|
344
|
+
mimeType: 'application/json',
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
this.resources.set('katashiro://feedback/stats', {
|
|
348
|
+
uri: 'katashiro://feedback/stats',
|
|
349
|
+
name: 'Feedback Statistics',
|
|
350
|
+
description: 'Statistics about user feedback and learning',
|
|
351
|
+
mimeType: 'application/json',
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Handle tool execution
|
|
357
|
+
*/
|
|
358
|
+
private async handleToolExecution(
|
|
359
|
+
name: string,
|
|
360
|
+
args: Record<string, unknown>
|
|
361
|
+
): Promise<Result<ToolResult, Error>> {
|
|
362
|
+
switch (name) {
|
|
363
|
+
case 'web_search':
|
|
364
|
+
return this.executeWebSearch(args);
|
|
365
|
+
case 'analyze_content':
|
|
366
|
+
return this.executeAnalyzeContent(args);
|
|
367
|
+
case 'generate_summary':
|
|
368
|
+
return this.executeGenerateSummary(args);
|
|
369
|
+
case 'query_knowledge':
|
|
370
|
+
return this.executeQueryKnowledge(args);
|
|
371
|
+
case 'generate_report':
|
|
372
|
+
return this.executeGenerateReport(args);
|
|
373
|
+
default:
|
|
374
|
+
return err(new Error(`Unhandled tool: ${name}`));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Handle prompt execution
|
|
380
|
+
*/
|
|
381
|
+
private async handlePromptExecution(
|
|
382
|
+
name: string,
|
|
383
|
+
args: Record<string, unknown>
|
|
384
|
+
): Promise<Result<PromptResult, Error>> {
|
|
385
|
+
const topic = String(args['topic'] || args['document'] || 'Unknown');
|
|
386
|
+
|
|
387
|
+
return ok({
|
|
388
|
+
messages: [
|
|
389
|
+
{
|
|
390
|
+
role: 'user',
|
|
391
|
+
content: {
|
|
392
|
+
type: 'text',
|
|
393
|
+
text: `Execute prompt "${name}" with topic: ${topic}`,
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Tool implementations
|
|
401
|
+
|
|
402
|
+
private async executeWebSearch(
|
|
403
|
+
args: Record<string, unknown>
|
|
404
|
+
): Promise<Result<ToolResult, Error>> {
|
|
405
|
+
const query = String(args['query']);
|
|
406
|
+
return ok({
|
|
407
|
+
content: [
|
|
408
|
+
{
|
|
409
|
+
type: 'text',
|
|
410
|
+
text: `Search results for: ${query}\n\n[Simulated results]`,
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private async executeAnalyzeContent(
|
|
417
|
+
args: Record<string, unknown>
|
|
418
|
+
): Promise<Result<ToolResult, Error>> {
|
|
419
|
+
const content = String(args['content']);
|
|
420
|
+
const type = String(args['type'] || 'text');
|
|
421
|
+
return ok({
|
|
422
|
+
content: [
|
|
423
|
+
{
|
|
424
|
+
type: 'text',
|
|
425
|
+
text: `Analysis of ${type} content:\n\nLength: ${content.length}\nType: ${type}`,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private async executeGenerateSummary(
|
|
432
|
+
args: Record<string, unknown>
|
|
433
|
+
): Promise<Result<ToolResult, Error>> {
|
|
434
|
+
const content = String(args['content']);
|
|
435
|
+
const style = String(args['style'] || 'brief');
|
|
436
|
+
return ok({
|
|
437
|
+
content: [
|
|
438
|
+
{
|
|
439
|
+
type: 'text',
|
|
440
|
+
text: `Summary (${style}):\n\n${content.substring(0, 100)}...`,
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private async executeQueryKnowledge(
|
|
447
|
+
args: Record<string, unknown>
|
|
448
|
+
): Promise<Result<ToolResult, Error>> {
|
|
449
|
+
const query = String(args['query']);
|
|
450
|
+
return ok({
|
|
451
|
+
content: [
|
|
452
|
+
{
|
|
453
|
+
type: 'text',
|
|
454
|
+
text: `Knowledge query: ${query}\n\n[Query results]`,
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async executeGenerateReport(
|
|
461
|
+
args: Record<string, unknown>
|
|
462
|
+
): Promise<Result<ToolResult, Error>> {
|
|
463
|
+
const topic = String(args['topic']);
|
|
464
|
+
const format = String(args['format'] || 'markdown');
|
|
465
|
+
return ok({
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: `# Report: ${topic}\n\nFormat: ${format}\n\n[Report content]`,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolRegistry - ツール登録・管理
|
|
3
|
+
*
|
|
4
|
+
* MCPツールの登録と実行を管理
|
|
5
|
+
*
|
|
6
|
+
* @module @nahisaho/katashiro-mcp-server
|
|
7
|
+
* @task TSK-061
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ok, err, type Result } from '@nahisaho/katashiro-core';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tool content item
|
|
14
|
+
*/
|
|
15
|
+
export interface ToolContent {
|
|
16
|
+
type: 'text' | 'image' | 'resource';
|
|
17
|
+
text?: string;
|
|
18
|
+
data?: string;
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tool result
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolExecutionResult {
|
|
26
|
+
content: ToolContent[];
|
|
27
|
+
isError?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tool handler function
|
|
32
|
+
*/
|
|
33
|
+
export type ToolHandler = (
|
|
34
|
+
args: Record<string, unknown>
|
|
35
|
+
) => Promise<ToolExecutionResult>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Registered tool
|
|
39
|
+
*/
|
|
40
|
+
export interface RegisteredTool {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object';
|
|
45
|
+
properties: Record<string, unknown>;
|
|
46
|
+
required?: string[];
|
|
47
|
+
};
|
|
48
|
+
handler: ToolHandler;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* ToolRegistry
|
|
53
|
+
*
|
|
54
|
+
* Manages MCP tool registration and execution
|
|
55
|
+
*/
|
|
56
|
+
export class ToolRegistry {
|
|
57
|
+
private tools: Map<string, RegisteredTool> = new Map();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a tool
|
|
61
|
+
*
|
|
62
|
+
* @param tool - Tool to register
|
|
63
|
+
* @returns Result
|
|
64
|
+
*/
|
|
65
|
+
register(tool: RegisteredTool): Result<void, Error> {
|
|
66
|
+
try {
|
|
67
|
+
if (this.tools.has(tool.name)) {
|
|
68
|
+
return err(new Error(`Tool already registered: ${tool.name}`));
|
|
69
|
+
}
|
|
70
|
+
this.tools.set(tool.name, tool);
|
|
71
|
+
return ok(undefined);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get a registered tool
|
|
79
|
+
*
|
|
80
|
+
* @param name - Tool name
|
|
81
|
+
* @returns Tool or null
|
|
82
|
+
*/
|
|
83
|
+
get(name: string): Result<RegisteredTool | null, Error> {
|
|
84
|
+
try {
|
|
85
|
+
return ok(this.tools.get(name) ?? null);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* List all registered tools
|
|
93
|
+
*
|
|
94
|
+
* @returns Array of tools
|
|
95
|
+
*/
|
|
96
|
+
list(): Result<RegisteredTool[], Error> {
|
|
97
|
+
try {
|
|
98
|
+
return ok(Array.from(this.tools.values()));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Execute a tool
|
|
106
|
+
*
|
|
107
|
+
* @param name - Tool name
|
|
108
|
+
* @param args - Tool arguments
|
|
109
|
+
* @returns Execution result
|
|
110
|
+
*/
|
|
111
|
+
async execute(
|
|
112
|
+
name: string,
|
|
113
|
+
args: Record<string, unknown>
|
|
114
|
+
): Promise<Result<ToolExecutionResult, Error>> {
|
|
115
|
+
try {
|
|
116
|
+
const tool = this.tools.get(name);
|
|
117
|
+
if (!tool) {
|
|
118
|
+
return err(new Error(`Unknown tool: ${name}`));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const result = await tool.handler(args);
|
|
122
|
+
return ok(result);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Unregister a tool
|
|
130
|
+
*
|
|
131
|
+
* @param name - Tool name
|
|
132
|
+
* @returns Whether unregistered
|
|
133
|
+
*/
|
|
134
|
+
unregister(name: string): Result<boolean, Error> {
|
|
135
|
+
try {
|
|
136
|
+
return ok(this.tools.delete(name));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return err(error instanceof Error ? error : new Error(String(error)));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if tool exists
|
|
144
|
+
*
|
|
145
|
+
* @param name - Tool name
|
|
146
|
+
* @returns Whether exists
|
|
147
|
+
*/
|
|
148
|
+
has(name: string): boolean {
|
|
149
|
+
return this.tools.has(name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Clear all tools
|
|
154
|
+
*/
|
|
155
|
+
clear(): void {
|
|
156
|
+
this.tools.clear();
|
|
157
|
+
}
|
|
158
|
+
}
|