@l10nmonster/mcp 3.0.0-alpha.16
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 +261 -0
- package/index.js +15 -0
- package/package.json +31 -0
- package/server.js +235 -0
- package/tests/integration.test.js +215 -0
- package/tests/mcpToolValidation.test.js +947 -0
- package/tests/registry.test.js +169 -0
- package/tools/index.js +3 -0
- package/tools/mcpTool.js +214 -0
- package/tools/registry.js +69 -0
- package/tools/sourceQuery.js +88 -0
- package/tools/status.js +665 -0
- package/tools/translate.js +227 -0
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
|