@l10nmonster/mcp 3.0.0-alpha.17 → 3.1.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/CHANGELOG.md +28 -0
- package/interfaces.d.ts +77 -0
- package/package.json +30 -29
- package/server.js +6 -19
- package/tools/mcpTool.js +1 -3
- package/tools/sourceQuery.js +23 -17
- package/tsconfig.json +18 -0
- package/tests/integration.test.js +0 -215
- package/tests/mcpToolValidation.test.js +0 -947
- package/tests/registry.test.js +0 -169
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## @l10nmonster/mcp [3.1.1](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/mcp@3.1.0...@l10nmonster/mcp@3.1.1) (2025-12-23)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Improve type definitions and checks ([826b412](https://public-github/l10nmonster/l10nmonster/commit/826b412f0f7e761d404165a243b0c2b26c416ac1))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Dependencies
|
|
13
|
+
|
|
14
|
+
* **@l10nmonster/core:** upgraded to 3.1.1
|
|
15
|
+
|
|
16
|
+
# @l10nmonster/mcp [3.1.0](https://public-github/l10nmonster/l10nmonster/compare/@l10nmonster/mcp@3.0.0...@l10nmonster/mcp@3.1.0) (2025-12-20)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* Make source query channel optional ([1addc68](https://public-github/l10nmonster/l10nmonster/commit/1addc68d009794a92588a28b816d7e9edbab6b47))
|
|
22
|
+
* Pluralization improvements ([5964250](https://public-github/l10nmonster/l10nmonster/commit/596425092c425cc8d6c312ef58509c4c3c537431))
|
|
23
|
+
* Version bumps ([d3030bd](https://public-github/l10nmonster/l10nmonster/commit/d3030bdd0af6ddbc79b3076af7427111ca9b04d0))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* implement an approach for mcp extensibility ([#50](https://public-github/l10nmonster/l10nmonster/issues/50)) ([5fcc89b](https://public-github/l10nmonster/l10nmonster/commit/5fcc89bef8c8af01d88f35dece8290989d04e4d5))
|
package/interfaces.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central type definitions for @l10nmonster/mcp
|
|
3
|
+
* This is the single source of truth for MCP tool interfaces.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MCP tool metadata definition.
|
|
8
|
+
*/
|
|
9
|
+
export interface McpToolMetadata {
|
|
10
|
+
/** Tool name identifier. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Tool description for discovery. */
|
|
13
|
+
description: string;
|
|
14
|
+
/** Zod input schema for validation. */
|
|
15
|
+
inputSchema: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* MCP content item in a response.
|
|
20
|
+
*/
|
|
21
|
+
export interface McpContentItem {
|
|
22
|
+
/** Content type (text, image, resource). */
|
|
23
|
+
type: 'text' | 'image' | 'resource';
|
|
24
|
+
/** Text content (for type='text'). */
|
|
25
|
+
text?: string;
|
|
26
|
+
/** Base64 data (for type='image'). */
|
|
27
|
+
data?: string;
|
|
28
|
+
/** MIME type (for type='image' or 'resource'). */
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* MCP tool response format.
|
|
34
|
+
*/
|
|
35
|
+
export interface McpToolResponse {
|
|
36
|
+
/** Array of content items. */
|
|
37
|
+
content: McpContentItem[];
|
|
38
|
+
/** True if this response represents an error. */
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* MCP tool interface - defines contract for MCP tools.
|
|
44
|
+
*/
|
|
45
|
+
export interface McpToolInterface {
|
|
46
|
+
/** Static metadata for the tool. */
|
|
47
|
+
metadata: McpToolMetadata;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute the tool with given arguments.
|
|
51
|
+
* @param mm - MonsterManager instance.
|
|
52
|
+
* @param args - Tool arguments.
|
|
53
|
+
* @returns Tool result.
|
|
54
|
+
*/
|
|
55
|
+
execute(mm: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get handler function for MCP registration.
|
|
59
|
+
* @param mm - MonsterManager instance.
|
|
60
|
+
* @returns Handler function.
|
|
61
|
+
*/
|
|
62
|
+
handler(mm: unknown): (args: Record<string, unknown>) => Promise<McpToolResponse>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format a result for MCP response.
|
|
66
|
+
* @param result - Tool execution result.
|
|
67
|
+
* @returns Formatted MCP response.
|
|
68
|
+
*/
|
|
69
|
+
formatResult(result: unknown): McpToolResponse;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format an error for MCP response.
|
|
73
|
+
* @param error - Error to format.
|
|
74
|
+
* @returns Formatted error response.
|
|
75
|
+
*/
|
|
76
|
+
formatError(error: Error): McpToolResponse;
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
2
|
+
"name": "@l10nmonster/mcp",
|
|
3
|
+
"version": "3.1.1",
|
|
4
|
+
"description": "L10n Monster Model Context Protocol (MCP) Server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test tests/*.test.js",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
13
|
+
"zod": "^3"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@l10nmonster/core": "3.1.1"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.11.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"localization",
|
|
27
|
+
"translation",
|
|
28
|
+
"l10n"
|
|
29
|
+
],
|
|
30
|
+
"author": "L10n Monster",
|
|
31
|
+
"license": "MIT"
|
|
31
32
|
}
|
package/server.js
CHANGED
|
@@ -4,8 +4,7 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
4
4
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import * as mcpTools from './tools/index.js';
|
|
6
6
|
import { registry } from './tools/registry.js';
|
|
7
|
-
import {
|
|
8
|
-
import path from 'node:path';
|
|
7
|
+
import { l10nMonsterVersion } from '@l10nmonster/core';
|
|
9
8
|
|
|
10
9
|
// Session management for HTTP transport
|
|
11
10
|
const sessions = new Map(); // sessionId -> { transport, lastActivity }
|
|
@@ -15,20 +14,8 @@ const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
|
15
14
|
// Use a WeakMap to avoid memory leaks. Once all sessions to a server are closed the server will be garbage collected.
|
|
16
15
|
const serverInstances = new WeakMap(); // monsterManager -> McpServer
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const packageJsonContent = await readFile(path.join(import.meta.dirname, 'package.json'), 'utf-8');
|
|
22
|
-
const packageJson = JSON.parse(packageJsonContent.toString());
|
|
23
|
-
return packageJson.version;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('Error parsing MCP package version:', error);
|
|
26
|
-
return '0.0.1-unknown';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Set server version to be the package version
|
|
31
|
-
const serverVersion = await getMcpPackageVersion();
|
|
17
|
+
// Server version from core package
|
|
18
|
+
const serverVersion = l10nMonsterVersion;
|
|
32
19
|
|
|
33
20
|
/**
|
|
34
21
|
* Setup tools on an MCP server instance
|
|
@@ -116,9 +103,9 @@ async function cleanupExpiredSessions() {
|
|
|
116
103
|
/**
|
|
117
104
|
* Creates MCP route handlers for use with the serve action extension mechanism.
|
|
118
105
|
* Returns route definitions that can be registered via ServeAction.registerExtension.
|
|
119
|
-
*
|
|
120
|
-
* @param {
|
|
121
|
-
* @returns {Array<[string, string, Function]>}
|
|
106
|
+
*
|
|
107
|
+
* @param {unknown} mm - MonsterManager instance.
|
|
108
|
+
* @returns {Array<[string, string, Function]>} Route definitions.
|
|
122
109
|
*/
|
|
123
110
|
export function createMcpRoutes(mm) {
|
|
124
111
|
// Handle POST requests for client-to-server communication
|
package/tools/mcpTool.js
CHANGED
|
@@ -57,14 +57,12 @@ export class McpProviderError extends McpToolError {
|
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Base class for MCP tools that call underlying MonsterManager functions directly.
|
|
60
|
-
*
|
|
60
|
+
*
|
|
61
61
|
* MCP tools are separate from CLI actions and:
|
|
62
62
|
* - Return structured data (not console output)
|
|
63
63
|
* - Have MCP-optimized schemas (not CLI-optimized)
|
|
64
64
|
* - Call underlying MonsterManager methods directly
|
|
65
|
-
*
|
|
66
65
|
*/
|
|
67
|
-
|
|
68
66
|
export class McpTool {
|
|
69
67
|
|
|
70
68
|
/**
|
package/tools/sourceQuery.js
CHANGED
|
@@ -32,7 +32,8 @@ You can write your own where conditions using SQL syntaxt against the following
|
|
|
32
32
|
targetLang: z.string()
|
|
33
33
|
.describe('Target language code (e.g., "es-419")'),
|
|
34
34
|
channel: z.string()
|
|
35
|
-
.
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Channel ID to query sources from. If omitted, queries all channels.'),
|
|
36
37
|
whereCondition: z.string()
|
|
37
38
|
.optional()
|
|
38
39
|
.describe('SQL WHERE condition against sources (default: "true" to match all)'),
|
|
@@ -40,12 +41,14 @@ You can write your own where conditions using SQL syntaxt against the following
|
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
static async execute(mm, args) {
|
|
43
|
-
const { sourceLang, targetLang, channel
|
|
44
|
+
const { sourceLang, targetLang, channel } = args;
|
|
44
45
|
const whereCondition = args.whereCondition ?? 'true';
|
|
45
46
|
|
|
46
47
|
const availableChannels = mm.rm.channelIds ?? [];
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const channels = channel ? [channel] : availableChannels;
|
|
49
|
+
|
|
50
|
+
if (channel && availableChannels.length > 0 && !availableChannels.includes(channel)) {
|
|
51
|
+
throw new McpNotFoundError(`Channel "${channel}" not found`, {
|
|
49
52
|
hints: [`Available channels: ${availableChannels.join(', ')}`]
|
|
50
53
|
});
|
|
51
54
|
}
|
|
@@ -61,19 +64,22 @@ You can write your own where conditions using SQL syntaxt against the following
|
|
|
61
64
|
});
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
const tus = [];
|
|
68
|
+
for (const channelId of channels) {
|
|
69
|
+
try {
|
|
70
|
+
const channelTus = await tm.querySource(channelId, whereCondition);
|
|
71
|
+
tus.push(...channelTus);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new McpToolError('Failed to execute query against source snapshot', {
|
|
74
|
+
code: 'QUERY_FAILED',
|
|
75
|
+
hints: [
|
|
76
|
+
'Verify that your SQL WHERE clause only references supported columns.',
|
|
77
|
+
'Escaping: wrap string literals in single quotes.'
|
|
78
|
+
],
|
|
79
|
+
details: { channelId, whereCondition },
|
|
80
|
+
cause: error
|
|
81
|
+
});
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
return {
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.base.json",
|
|
3
|
+
"include": [
|
|
4
|
+
"*.js",
|
|
5
|
+
"**/*.js"
|
|
6
|
+
],
|
|
7
|
+
"exclude": [
|
|
8
|
+
"node_modules",
|
|
9
|
+
"**/node_modules",
|
|
10
|
+
"test/**",
|
|
11
|
+
"tests/**",
|
|
12
|
+
"**/*.test.js",
|
|
13
|
+
"**/*.spec.js",
|
|
14
|
+
"dist/**",
|
|
15
|
+
"ui/**",
|
|
16
|
+
"types/**"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import { registerTool, McpTool, McpInputError } from '../index.js';
|
|
4
|
-
import { registry } from '../tools/registry.js';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
// Create a custom tool for testing
|
|
8
|
-
class CustomTestTool extends McpTool {
|
|
9
|
-
static metadata = {
|
|
10
|
-
name: 'custom_test_tool',
|
|
11
|
-
description: 'A custom tool for integration testing',
|
|
12
|
-
inputSchema: z.object({
|
|
13
|
-
message: z.string().describe('A test message')
|
|
14
|
-
})
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
static async execute(mm, args) {
|
|
18
|
-
return {
|
|
19
|
-
tool: 'custom_test_tool',
|
|
20
|
-
message: args.message,
|
|
21
|
-
timestamp: new Date().toISOString()
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Create a tool that overrides a built-in tool name
|
|
27
|
-
class OverrideStatusTool extends McpTool {
|
|
28
|
-
static metadata = {
|
|
29
|
-
name: 'status',
|
|
30
|
-
description: 'Custom status tool override',
|
|
31
|
-
inputSchema: z.object({})
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
static async execute() {
|
|
35
|
-
return { custom: true, overridden: true };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe('MCP Integration Tests', () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
registry.clear();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should allow registering and using custom tools', async () => {
|
|
45
|
-
// Register the custom tool
|
|
46
|
-
registerTool(CustomTestTool);
|
|
47
|
-
|
|
48
|
-
// Verify it's registered
|
|
49
|
-
assert.strictEqual(registry.hasTool('custom_test_tool'), true);
|
|
50
|
-
|
|
51
|
-
// Get the tool and create a handler
|
|
52
|
-
const ToolClass = registry.getTool('custom_test_tool');
|
|
53
|
-
const handler = ToolClass.handler({});
|
|
54
|
-
|
|
55
|
-
// Execute the handler
|
|
56
|
-
const result = await handler({ message: 'Hello from custom tool' });
|
|
57
|
-
|
|
58
|
-
// Verify the result
|
|
59
|
-
assert.ok(result.content);
|
|
60
|
-
assert.strictEqual(result.content[0].type, 'text');
|
|
61
|
-
const data = JSON.parse(result.content[0].text);
|
|
62
|
-
assert.strictEqual(data.tool, 'custom_test_tool');
|
|
63
|
-
assert.strictEqual(data.message, 'Hello from custom tool');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should allow overriding built-in tools', () => {
|
|
67
|
-
// Register an override tool
|
|
68
|
-
registerTool(OverrideStatusTool);
|
|
69
|
-
|
|
70
|
-
// Verify it's registered with the same name
|
|
71
|
-
assert.strictEqual(registry.hasTool('status'), true);
|
|
72
|
-
|
|
73
|
-
// Get the tool and verify it's the override
|
|
74
|
-
const ToolClass = registry.getTool('status');
|
|
75
|
-
assert.strictEqual(ToolClass.metadata.description, 'Custom status tool override');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should handle tool execution errors gracefully', async () => {
|
|
79
|
-
class ErrorTool extends McpTool {
|
|
80
|
-
static metadata = {
|
|
81
|
-
name: 'error_tool',
|
|
82
|
-
description: 'Tool that throws errors',
|
|
83
|
-
inputSchema: z.object({
|
|
84
|
-
shouldError: z.boolean()
|
|
85
|
-
})
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
static async execute(mm, args) {
|
|
89
|
-
if (args.shouldError) {
|
|
90
|
-
throw new McpInputError('Test error', {
|
|
91
|
-
hints: ['Try setting shouldError to false']
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return { success: true };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
registerTool(ErrorTool);
|
|
99
|
-
|
|
100
|
-
const ToolClass = registry.getTool('error_tool');
|
|
101
|
-
const handler = ToolClass.handler({});
|
|
102
|
-
|
|
103
|
-
// Execute with error
|
|
104
|
-
const errorResult = await handler({ shouldError: true });
|
|
105
|
-
|
|
106
|
-
// Verify error response
|
|
107
|
-
assert.strictEqual(errorResult.isError, true);
|
|
108
|
-
assert.ok(errorResult.content);
|
|
109
|
-
assert.ok(errorResult.content[0].text.includes('Test error'));
|
|
110
|
-
|
|
111
|
-
// Execute without error
|
|
112
|
-
const successResult = await handler({ shouldError: false });
|
|
113
|
-
|
|
114
|
-
// Verify success response
|
|
115
|
-
assert.strictEqual(successResult.isError, undefined);
|
|
116
|
-
const data = JSON.parse(successResult.content[0].text);
|
|
117
|
-
assert.strictEqual(data.success, true);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should validate input schemas', async () => {
|
|
121
|
-
class StrictTool extends McpTool {
|
|
122
|
-
static metadata = {
|
|
123
|
-
name: 'strict_tool',
|
|
124
|
-
description: 'Tool with strict validation',
|
|
125
|
-
inputSchema: z.object({
|
|
126
|
-
required: z.string().min(1),
|
|
127
|
-
optional: z.number().optional()
|
|
128
|
-
})
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
static async execute(mm, args) {
|
|
132
|
-
return args;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
registerTool(StrictTool);
|
|
137
|
-
|
|
138
|
-
const ToolClass = registry.getTool('strict_tool');
|
|
139
|
-
const handler = ToolClass.handler({});
|
|
140
|
-
|
|
141
|
-
// Test with missing required field
|
|
142
|
-
const invalidResult = await handler({});
|
|
143
|
-
assert.strictEqual(invalidResult.isError, true);
|
|
144
|
-
|
|
145
|
-
// Test with valid input
|
|
146
|
-
const validResult = await handler({ required: 'test' });
|
|
147
|
-
assert.strictEqual(validResult.isError, undefined);
|
|
148
|
-
const data = JSON.parse(validResult.content[0].text);
|
|
149
|
-
assert.strictEqual(data.required, 'test');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should support multiple tool registrations via function', () => {
|
|
153
|
-
class Tool1 extends McpTool {
|
|
154
|
-
static metadata = {
|
|
155
|
-
name: 'tool_1',
|
|
156
|
-
description: 'First tool',
|
|
157
|
-
inputSchema: z.object({})
|
|
158
|
-
};
|
|
159
|
-
static async execute() { return { id: 1 }; }
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
class Tool2 extends McpTool {
|
|
163
|
-
static metadata = {
|
|
164
|
-
name: 'tool_2',
|
|
165
|
-
description: 'Second tool',
|
|
166
|
-
inputSchema: z.object({})
|
|
167
|
-
};
|
|
168
|
-
static async execute() { return { id: 2 }; }
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
class Tool3 extends McpTool {
|
|
172
|
-
static metadata = {
|
|
173
|
-
name: 'tool_3',
|
|
174
|
-
description: 'Third tool',
|
|
175
|
-
inputSchema: z.object({})
|
|
176
|
-
};
|
|
177
|
-
static async execute() { return { id: 3 }; }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Register tools individually
|
|
181
|
-
registerTool(Tool1);
|
|
182
|
-
registerTool(Tool2);
|
|
183
|
-
registerTool(Tool3);
|
|
184
|
-
|
|
185
|
-
// Verify all are registered
|
|
186
|
-
assert.strictEqual(registry.getAllTools().length, 3);
|
|
187
|
-
assert.strictEqual(registry.hasTool('tool_1'), true);
|
|
188
|
-
assert.strictEqual(registry.hasTool('tool_2'), true);
|
|
189
|
-
assert.strictEqual(registry.hasTool('tool_3'), true);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should maintain tool isolation between registrations', () => {
|
|
193
|
-
class IsolatedTool extends McpTool {
|
|
194
|
-
static metadata = {
|
|
195
|
-
name: 'isolated',
|
|
196
|
-
description: 'Isolated tool',
|
|
197
|
-
inputSchema: z.object({})
|
|
198
|
-
};
|
|
199
|
-
static async execute() { return { isolated: true }; }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Register tool
|
|
203
|
-
registerTool(IsolatedTool);
|
|
204
|
-
assert.strictEqual(registry.getAllTools().length, 1);
|
|
205
|
-
|
|
206
|
-
// Clear registry
|
|
207
|
-
registry.clear();
|
|
208
|
-
assert.strictEqual(registry.getAllTools().length, 0);
|
|
209
|
-
|
|
210
|
-
// Register again
|
|
211
|
-
registerTool(IsolatedTool);
|
|
212
|
-
assert.strictEqual(registry.getAllTools().length, 1);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|