@mimik/mcp-kit 1.0.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/READMD.md +571 -0
- package/package.json +5 -0
- package/src/constants.js +38 -0
- package/src/handlers.js +288 -0
- package/src/index.js +76 -0
- package/src/jsonrpc.js +207 -0
- package/src/schema.js +117 -0
package/READMD.md
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# mcp-kit: MCP Server Library
|
|
2
|
+
|
|
3
|
+
A lightweight, flexible library for building Model Context Protocol (MCP) servers with comprehensive schema validation and Zod-like syntax support in mimik serverless environment.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [Core Concepts](#core-concepts)
|
|
10
|
+
- [Schema Definition](#schema-definition)
|
|
11
|
+
- [API Reference](#api-reference)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
- [Error Handling](#error-handling)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @mimik/mcp-kit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
const { McpServer, z } = require('@mimik/mcp-kit');
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const { McpServer, z } = require('@mimik/mcp-kit');
|
|
30
|
+
|
|
31
|
+
// Create a server instance
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: 'my-mcp-server',
|
|
34
|
+
version: '1.0.0'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Register a tool
|
|
38
|
+
server.tool(
|
|
39
|
+
'greet',
|
|
40
|
+
'Greets a user with optional formality',
|
|
41
|
+
{
|
|
42
|
+
name: z.string().describe('The name to greet'),
|
|
43
|
+
formal: z.boolean().default(false).describe('Use formal greeting')
|
|
44
|
+
},
|
|
45
|
+
async (args) => {
|
|
46
|
+
const { name, formal } = args;
|
|
47
|
+
const greeting = formal ? `Good day, ${name}` : `Hi ${name}!`;
|
|
48
|
+
return greeting;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Handle MCP requests
|
|
53
|
+
const response = await server.handleMcpRequest(request);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Core Concepts
|
|
57
|
+
|
|
58
|
+
### Server Instance
|
|
59
|
+
|
|
60
|
+
The `McpServer` class is the main entry point for creating MCP servers. It handles JSON-RPC 2.0 protocol communication and manages tools, resources, and prompts.
|
|
61
|
+
|
|
62
|
+
### Tools
|
|
63
|
+
|
|
64
|
+
Tools are functions that can be called by MCP clients. Each tool has:
|
|
65
|
+
- **Name**: Unique identifier
|
|
66
|
+
- **Description**: Human-readable description
|
|
67
|
+
- **Input Schema**: Defines expected parameters
|
|
68
|
+
- **Handler**: Async function that processes the request
|
|
69
|
+
|
|
70
|
+
### Resources
|
|
71
|
+
|
|
72
|
+
Resources represent readable content (files, data, etc.) that clients can access.
|
|
73
|
+
|
|
74
|
+
### Prompts
|
|
75
|
+
|
|
76
|
+
Prompts are templates for generating messages with dynamic content.
|
|
77
|
+
|
|
78
|
+
## Schema Definition
|
|
79
|
+
|
|
80
|
+
This library supports multiple schema definition formats for maximum flexibility.
|
|
81
|
+
|
|
82
|
+
### Zod-like Syntax (Recommended)
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
{
|
|
86
|
+
name: z.string().describe('User name'),
|
|
87
|
+
age: z.number().default(25).describe('User age'),
|
|
88
|
+
role: z.enum(['admin', 'user', 'guest']).default('user').describe('User role'),
|
|
89
|
+
active: z.boolean().describe('Is user active'),
|
|
90
|
+
tags: z.array(z.string()).describe('User tags'),
|
|
91
|
+
profile: z.object({
|
|
92
|
+
bio: z.string().describe('Biography'),
|
|
93
|
+
website: z.optional(z.string()).describe('Website URL')
|
|
94
|
+
}),
|
|
95
|
+
nickname: z.optional(z.string()).describe('Optional nickname')
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Custom Format
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
{
|
|
103
|
+
name: { type: 'string', description: 'User name' },
|
|
104
|
+
age: { type: 'number', optional: true, default: 25, description: 'User age' },
|
|
105
|
+
role: { type: 'string', enum: ['admin', 'user', 'guest'], default: 'user' }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Pure JSON Schema
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
{
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
name: { type: 'string', description: 'User name' },
|
|
116
|
+
age: { type: 'number', default: 25, description: 'User age' }
|
|
117
|
+
},
|
|
118
|
+
required: ['name']
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## API Reference
|
|
123
|
+
|
|
124
|
+
### McpServer
|
|
125
|
+
|
|
126
|
+
#### Constructor
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
new McpServer(serverInfo?)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Parameters:**
|
|
133
|
+
- `serverInfo` (optional): Object with `name` and `version` properties
|
|
134
|
+
|
|
135
|
+
#### Methods
|
|
136
|
+
|
|
137
|
+
##### tool(name, description, inputSchema, handler)
|
|
138
|
+
|
|
139
|
+
Registers a new tool.
|
|
140
|
+
|
|
141
|
+
**Parameters:**
|
|
142
|
+
- `name` (string): Unique tool identifier
|
|
143
|
+
- `description` (string): Tool description
|
|
144
|
+
- `inputSchema` (object): Parameter schema definition
|
|
145
|
+
- `handler` (async function): Tool implementation
|
|
146
|
+
|
|
147
|
+
**Returns:** `this` (for chaining)
|
|
148
|
+
|
|
149
|
+
##### resource(uri, name, description, mimeType, handler)
|
|
150
|
+
|
|
151
|
+
Registers a new resource.
|
|
152
|
+
|
|
153
|
+
**Parameters:**
|
|
154
|
+
- `uri` (string): Unique resource URI
|
|
155
|
+
- `name` (string): Resource name
|
|
156
|
+
- `description` (string): Resource description
|
|
157
|
+
- `mimeType` (string): MIME type of the resource
|
|
158
|
+
- `handler` (async function): Resource content provider
|
|
159
|
+
|
|
160
|
+
**Returns:** `this` (for chaining)
|
|
161
|
+
|
|
162
|
+
##### prompt(name, description, argumentsSchema, handler)
|
|
163
|
+
|
|
164
|
+
Registers a new prompt.
|
|
165
|
+
|
|
166
|
+
**Parameters:**
|
|
167
|
+
- `name` (string): Unique prompt identifier
|
|
168
|
+
- `description` (string): Prompt description
|
|
169
|
+
- `argumentsSchema` (object): Arguments schema
|
|
170
|
+
- `handler` (async function): Prompt generator
|
|
171
|
+
|
|
172
|
+
**Returns:** `this` (for chaining)
|
|
173
|
+
|
|
174
|
+
##### handleMcpRequest(requestBody)
|
|
175
|
+
|
|
176
|
+
Processes an MCP request.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
- `requestBody` (string | object): JSON-RPC 2.0 request
|
|
180
|
+
|
|
181
|
+
**Returns:** Promise<object> - JSON-RPC 2.0 response
|
|
182
|
+
|
|
183
|
+
### Schema Helpers (z)
|
|
184
|
+
|
|
185
|
+
#### Basic Types
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
z.string() // String type
|
|
189
|
+
z.number() // Number type
|
|
190
|
+
z.boolean() // Boolean type
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Complex Types
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
z.enum(['option1', 'option2', 'option3']) // Enumeration
|
|
197
|
+
z.array(z.string()) // Array of strings
|
|
198
|
+
z.object({ key: z.string() }) // Object with properties
|
|
199
|
+
z.optional(z.string()) // Optional field
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Modifiers
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
.describe('Field description') // Add description
|
|
206
|
+
.default(value) // Set default value (makes field optional)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Chaining
|
|
210
|
+
|
|
211
|
+
All modifiers can be chained in any order:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
z.enum(['small', 'medium', 'large'])
|
|
215
|
+
.default('medium')
|
|
216
|
+
.describe('Size selection')
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Examples
|
|
220
|
+
|
|
221
|
+
### Basic Tool
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
server.tool(
|
|
225
|
+
'add',
|
|
226
|
+
'Adds two numbers',
|
|
227
|
+
{
|
|
228
|
+
a: z.number().describe('First number'),
|
|
229
|
+
b: z.number().describe('Second number')
|
|
230
|
+
},
|
|
231
|
+
async ({ a, b }) => {
|
|
232
|
+
return `${a} + ${b} = ${a + b}`;
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Tool with Optional Parameters
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
server.tool(
|
|
241
|
+
'search',
|
|
242
|
+
'Search for items',
|
|
243
|
+
{
|
|
244
|
+
query: z.string().describe('Search query'),
|
|
245
|
+
limit: z.number().default(10).describe('Maximum results'),
|
|
246
|
+
category: z.optional(z.string()).describe('Filter by category')
|
|
247
|
+
},
|
|
248
|
+
async ({ query, limit, category }) => {
|
|
249
|
+
// Implementation here
|
|
250
|
+
return `Searching for "${query}" (limit: ${limit}, category: ${category || 'all'})`;
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Tool with Enum and Complex Objects
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
server.tool(
|
|
259
|
+
'createOrder',
|
|
260
|
+
'Create a new order',
|
|
261
|
+
{
|
|
262
|
+
type: z.enum(['pickup', 'delivery', 'dine-in']).default('pickup'),
|
|
263
|
+
priority: z.enum(['low', 'normal', 'high']).default('normal'),
|
|
264
|
+
customer: z.object({
|
|
265
|
+
name: z.string(),
|
|
266
|
+
email: z.string(),
|
|
267
|
+
phone: z.optional(z.string())
|
|
268
|
+
}),
|
|
269
|
+
items: z.array(z.object({
|
|
270
|
+
id: z.string(),
|
|
271
|
+
quantity: z.number(),
|
|
272
|
+
notes: z.optional(z.string())
|
|
273
|
+
}))
|
|
274
|
+
},
|
|
275
|
+
async (args) => {
|
|
276
|
+
const { type, priority, customer, items } = args;
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: 'text',
|
|
282
|
+
text: `Order created: ${items.length} items for ${customer.name} (${type}, ${priority} priority)`
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Resource Example
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
server.resource(
|
|
294
|
+
'file://logs/system.log',
|
|
295
|
+
'System Log',
|
|
296
|
+
'Current system log file',
|
|
297
|
+
'text/plain',
|
|
298
|
+
async () => {
|
|
299
|
+
// Return log content
|
|
300
|
+
return 'Log entries...';
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Prompt Example
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
server.prompt(
|
|
309
|
+
'codeReview',
|
|
310
|
+
'Generate code review prompt',
|
|
311
|
+
{
|
|
312
|
+
language: z.string().describe('Programming language'),
|
|
313
|
+
level: z.enum(['beginner', 'intermediate', 'advanced']).default('intermediate')
|
|
314
|
+
},
|
|
315
|
+
async ({ language, level }) => {
|
|
316
|
+
return [
|
|
317
|
+
{
|
|
318
|
+
role: 'user',
|
|
319
|
+
content: {
|
|
320
|
+
type: 'text',
|
|
321
|
+
text: `Please review this ${language} code focusing on ${level}-level best practices.`
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Error Handling
|
|
330
|
+
|
|
331
|
+
The library provides comprehensive error handling:
|
|
332
|
+
|
|
333
|
+
### Validation Errors
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
// Missing required parameter
|
|
337
|
+
{
|
|
338
|
+
"jsonrpc": "2.0",
|
|
339
|
+
"id": 1,
|
|
340
|
+
"error": {
|
|
341
|
+
"code": -32602,
|
|
342
|
+
"message": "Invalid arguments: Missing required parameter: name"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Wrong parameter type
|
|
347
|
+
{
|
|
348
|
+
"jsonrpc": "2.0",
|
|
349
|
+
"id": 1,
|
|
350
|
+
"error": {
|
|
351
|
+
"code": -32602,
|
|
352
|
+
"message": "Invalid arguments: Parameter 'age' should be a number, got string"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Tool Execution Errors
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
{
|
|
361
|
+
"jsonrpc": "2.0",
|
|
362
|
+
"id": 1,
|
|
363
|
+
"error": {
|
|
364
|
+
"code": -32603,
|
|
365
|
+
"message": "Tool execution error",
|
|
366
|
+
"data": "Division by zero"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Common Error Codes
|
|
372
|
+
|
|
373
|
+
- `-32700`: Parse error (malformed JSON)
|
|
374
|
+
- `-32601`: Method not found
|
|
375
|
+
- `-32602`: Invalid parameters
|
|
376
|
+
- `-32603`: Internal error
|
|
377
|
+
|
|
378
|
+
## Best Practices
|
|
379
|
+
|
|
380
|
+
### 1. Use Descriptive Names and Descriptions
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
// Good
|
|
384
|
+
server.tool(
|
|
385
|
+
'calculateTax',
|
|
386
|
+
'Calculate tax amount based on income and tax rate',
|
|
387
|
+
{
|
|
388
|
+
income: z.number().describe('Annual income in dollars'),
|
|
389
|
+
rate: z.number().describe('Tax rate as decimal (0.1 for 10%)')
|
|
390
|
+
},
|
|
391
|
+
handler
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
// Avoid
|
|
395
|
+
server.tool('calc', 'Does math', { a: z.number(), b: z.number() }, handler);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 2. Provide Sensible Defaults
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
server.tool(
|
|
402
|
+
'search',
|
|
403
|
+
'Search documents',
|
|
404
|
+
{
|
|
405
|
+
query: z.string().describe('Search query'),
|
|
406
|
+
maxResults: z.number().default(20).describe('Maximum results to return'),
|
|
407
|
+
sortBy: z.enum(['relevance', 'date', 'title']).default('relevance')
|
|
408
|
+
},
|
|
409
|
+
handler
|
|
410
|
+
);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 3. Use Enums for Limited Options
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// Good - clear valid options
|
|
417
|
+
status: z.enum(['active', 'inactive', 'pending']).default('pending')
|
|
418
|
+
|
|
419
|
+
// Avoid - unclear what values are valid
|
|
420
|
+
status: z.string().describe('Status (active, inactive, or pending)')
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 4. Validate Input Early
|
|
424
|
+
|
|
425
|
+
The library automatically validates parameters against the schema before calling your handler, so focus on business logic validation:
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
async ({ email, age }) => {
|
|
429
|
+
// Schema validation already ensures email is string, age is number
|
|
430
|
+
|
|
431
|
+
// Add business logic validation
|
|
432
|
+
if (age < 0 || age > 150) {
|
|
433
|
+
throw new Error('Age must be between 0 and 150');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!email.includes('@')) {
|
|
437
|
+
throw new Error('Invalid email format');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Process the request...
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### 5. Return Structured Responses
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
// For simple text responses
|
|
448
|
+
return "Operation completed successfully";
|
|
449
|
+
|
|
450
|
+
// For complex responses
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
{ type: 'text', text: 'Results:' },
|
|
454
|
+
{ type: 'text', text: JSON.stringify(data, null, 2) }
|
|
455
|
+
]
|
|
456
|
+
};
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 6. Handle Async Operations Properly
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
server.tool(
|
|
463
|
+
'fetchData',
|
|
464
|
+
'Fetch data from external API',
|
|
465
|
+
{ url: z.string() },
|
|
466
|
+
async ({ url }) => {
|
|
467
|
+
try {
|
|
468
|
+
const response = await fetch(url);
|
|
469
|
+
const data = await response.json();
|
|
470
|
+
return JSON.stringify(data, null, 2);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
throw new Error(`Failed to fetch data: ${error.message}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## Integration Examples
|
|
479
|
+
|
|
480
|
+
### Express-like Middleware
|
|
481
|
+
|
|
482
|
+
The key to integrating with Express or any similar web framework is using `server.handleMcpRequest()` to process all MCP requests:
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
const { McpServer, z } = require('@mimik/mcp-kit');
|
|
486
|
+
|
|
487
|
+
const server = new McpServer({ name: 'my-api', version: '1.0.0' });
|
|
488
|
+
|
|
489
|
+
// Register your tools...
|
|
490
|
+
server.tool('ping', 'Simple ping', {}, async () => 'pong');
|
|
491
|
+
|
|
492
|
+
// IMPORTANT: Use server.handleMcpRequest() to handle all MCP communication
|
|
493
|
+
app.post('/mcp', async (req, res) => {
|
|
494
|
+
try {
|
|
495
|
+
const response = await server.handleMcpRequest(req.body);
|
|
496
|
+
if (response) {
|
|
497
|
+
res.json(response);
|
|
498
|
+
} else {
|
|
499
|
+
res.status(204).end(); // For notifications - no response needed
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
res.status(500).json({
|
|
503
|
+
jsonrpc: '2.0',
|
|
504
|
+
id: req.body?.id || null,
|
|
505
|
+
error: { code: -32603, message: 'Internal error', data: error.message }
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### Alternative Promise-based Approach
|
|
512
|
+
|
|
513
|
+
```javascript
|
|
514
|
+
// In your middleware app
|
|
515
|
+
app.post('/mcp', (req, res) => {
|
|
516
|
+
server.handleMcpRequest(req.body).then((response) => {
|
|
517
|
+
if (response) {
|
|
518
|
+
res.json(response);
|
|
519
|
+
} else {
|
|
520
|
+
// Notification - no response needed
|
|
521
|
+
res.status(204).end();
|
|
522
|
+
}
|
|
523
|
+
}).catch((error) => {
|
|
524
|
+
res.status(500).json({
|
|
525
|
+
jsonrpc: '2.0',
|
|
526
|
+
id: req.body?.id || null,
|
|
527
|
+
error: { code: -32603, message: 'Internal error', data: error.message }
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Key Points:**
|
|
534
|
+
- Always use `server.handleMcpRequest(req.body)` to process MCP requests
|
|
535
|
+
- Handle both responses (requests) and notifications (no response)
|
|
536
|
+
- Use status 204 for notifications, not 200
|
|
537
|
+
- Properly format error responses as JSON-RPC 2.0
|
|
538
|
+
|
|
539
|
+
### Testing Your Server
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
async function testServer() {
|
|
543
|
+
// Initialize
|
|
544
|
+
const initResponse = await server.handleMcpRequest({
|
|
545
|
+
jsonrpc: '2.0',
|
|
546
|
+
id: 1,
|
|
547
|
+
method: 'initialize',
|
|
548
|
+
params: {}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// List tools
|
|
552
|
+
const toolsResponse = await server.handleMcpRequest({
|
|
553
|
+
jsonrpc: '2.0',
|
|
554
|
+
id: 2,
|
|
555
|
+
method: 'tools/list'
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Call a tool
|
|
559
|
+
const callResponse = await server.handleMcpRequest({
|
|
560
|
+
jsonrpc: '2.0',
|
|
561
|
+
id: 3,
|
|
562
|
+
method: 'tools/call',
|
|
563
|
+
params: {
|
|
564
|
+
name: 'greet',
|
|
565
|
+
arguments: { name: 'World', formal: true }
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
console.log('Tool response:', callResponse);
|
|
570
|
+
}
|
|
571
|
+
```
|
package/package.json
ADDED
package/src/constants.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const JSONRPC_ERR_CODE_PARSE_ERROR = {
|
|
2
|
+
code: -32700,
|
|
3
|
+
message: 'Parse error',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const JSONRPC_ERR_CODE_INVALID_REQUEST = {
|
|
7
|
+
code: -32600,
|
|
8
|
+
message: 'Invalid Request',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const JSONRPC_ERR_CODE_METHOD_NOT_FOUND = {
|
|
12
|
+
code: -32601,
|
|
13
|
+
message: 'Method not found',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const JSONRPC_ERR_CODE_INVALID_PARAMS = {
|
|
17
|
+
code: -32602,
|
|
18
|
+
message: 'Invalid params',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const JSONRPC_ERR_CODE_INTERNAL_ERROR = {
|
|
22
|
+
code: -32603,
|
|
23
|
+
message: 'Internal error',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const JSONRPC_ERR_CODE_SERVER_ERROR = {
|
|
27
|
+
code: -32000,
|
|
28
|
+
message: 'Server error',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
JSONRPC_ERR_CODE_PARSE_ERROR,
|
|
33
|
+
JSONRPC_ERR_CODE_INVALID_REQUEST,
|
|
34
|
+
JSONRPC_ERR_CODE_METHOD_NOT_FOUND,
|
|
35
|
+
JSONRPC_ERR_CODE_INVALID_PARAMS,
|
|
36
|
+
JSONRPC_ERR_CODE_INTERNAL_ERROR,
|
|
37
|
+
JSONRPC_ERR_CODE_SERVER_ERROR,
|
|
38
|
+
};
|
package/src/handlers.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
const {
|
|
2
|
+
JSONRPC_ERR_CODE_PARSE_ERROR,
|
|
3
|
+
JSONRPC_ERR_CODE_METHOD_NOT_FOUND,
|
|
4
|
+
JSONRPC_ERR_CODE_INVALID_PARAMS,
|
|
5
|
+
JSONRPC_ERR_CODE_INTERNAL_ERROR,
|
|
6
|
+
} = require('./constants');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
createResponse,
|
|
10
|
+
createStandardError,
|
|
11
|
+
validateArguments,
|
|
12
|
+
} = require('./jsonrpc');
|
|
13
|
+
|
|
14
|
+
function handleMcpRequest(requestBody, mcpServer) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
try {
|
|
17
|
+
const message = typeof requestBody === 'string' ? JSON.parse(requestBody) : requestBody;
|
|
18
|
+
|
|
19
|
+
if (message.method && message.id !== undefined) {
|
|
20
|
+
handleRequest(message, mcpServer).then(resolve);
|
|
21
|
+
} else if (message.method) {
|
|
22
|
+
handleNotification(message, mcpServer).then(() => resolve(null));
|
|
23
|
+
} else {
|
|
24
|
+
throw new Error('Invalid JSON-RPC message format');
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
resolve(createResponse(
|
|
28
|
+
null,
|
|
29
|
+
null,
|
|
30
|
+
createStandardError(JSONRPC_ERR_CODE_PARSE_ERROR, null, error.message),
|
|
31
|
+
));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function handleRequest(request, mcpServer) {
|
|
37
|
+
const { method, params = {}, id } = request;
|
|
38
|
+
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const handleMethod = () => {
|
|
41
|
+
switch (method) {
|
|
42
|
+
case 'initialize':
|
|
43
|
+
return handleInitialize(params, id, mcpServer);
|
|
44
|
+
|
|
45
|
+
case 'tools/list':
|
|
46
|
+
return handleToolsList(id, mcpServer);
|
|
47
|
+
|
|
48
|
+
case 'tools/call':
|
|
49
|
+
return handleToolsCall(params, id, mcpServer);
|
|
50
|
+
|
|
51
|
+
case 'resources/list':
|
|
52
|
+
return handleResourcesList(id, mcpServer);
|
|
53
|
+
|
|
54
|
+
case 'resources/read':
|
|
55
|
+
return handleResourcesRead(params, id, mcpServer);
|
|
56
|
+
|
|
57
|
+
case 'prompts/list':
|
|
58
|
+
return handlePromptsList(id, mcpServer);
|
|
59
|
+
|
|
60
|
+
case 'prompts/get':
|
|
61
|
+
return handlePromptsGet(params, id, mcpServer);
|
|
62
|
+
|
|
63
|
+
default:
|
|
64
|
+
return Promise.resolve(createResponse(
|
|
65
|
+
id,
|
|
66
|
+
null,
|
|
67
|
+
createStandardError(JSONRPC_ERR_CODE_METHOD_NOT_FOUND),
|
|
68
|
+
));
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
handleMethod()
|
|
73
|
+
.then(resolve)
|
|
74
|
+
.catch((error) => {
|
|
75
|
+
resolve(createResponse(
|
|
76
|
+
id,
|
|
77
|
+
null,
|
|
78
|
+
createStandardError(JSONRPC_ERR_CODE_INTERNAL_ERROR, null, error.message),
|
|
79
|
+
));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleNotification(notification, mcpServer) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const { method } = notification;
|
|
87
|
+
|
|
88
|
+
switch (method) {
|
|
89
|
+
case 'notifications/initialized':
|
|
90
|
+
mcpServer.isInitialized = true;
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 'notifications/cancelled':
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
resolve();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleInitialize(params, id, mcpServer) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
const capabilities = {};
|
|
107
|
+
|
|
108
|
+
if (mcpServer.tools.size > 0) {
|
|
109
|
+
capabilities.tools = {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (mcpServer.resources.size > 0) {
|
|
113
|
+
capabilities.resources = { subscribe: true, listChanged: true };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (mcpServer.prompts.size > 0) {
|
|
117
|
+
capabilities.prompts = {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = {
|
|
121
|
+
protocolVersion: '2025-06-18',
|
|
122
|
+
capabilities,
|
|
123
|
+
serverInfo: mcpServer.serverInfo,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
resolve(createResponse(id, result));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handleToolsList(id, mcpServer) {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const tools = Array.from(mcpServer.tools.values()).map((tool) => ({
|
|
133
|
+
name: tool.name,
|
|
134
|
+
description: tool.description,
|
|
135
|
+
inputSchema: tool.inputSchema,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
resolve(createResponse(id, { tools }));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleToolsCall(params, id, mcpServer) {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const { name, arguments: args = {} } = params;
|
|
145
|
+
|
|
146
|
+
if (!mcpServer.tools.has(name)) {
|
|
147
|
+
resolve(createResponse(
|
|
148
|
+
id,
|
|
149
|
+
null,
|
|
150
|
+
createStandardError(JSONRPC_ERR_CODE_INVALID_PARAMS, `Tool '${name}' not found`),
|
|
151
|
+
));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const tool = mcpServer.tools.get(name);
|
|
156
|
+
|
|
157
|
+
const validationError = validateArguments(args, tool.inputSchema);
|
|
158
|
+
if (validationError) {
|
|
159
|
+
resolve(createResponse(
|
|
160
|
+
id,
|
|
161
|
+
null,
|
|
162
|
+
createStandardError(JSONRPC_ERR_CODE_INVALID_PARAMS, `Invalid arguments: ${validationError}`),
|
|
163
|
+
));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Promise.resolve(tool.handler(args))
|
|
168
|
+
.then((result) => {
|
|
169
|
+
let response;
|
|
170
|
+
if (result && result.content) {
|
|
171
|
+
response = result;
|
|
172
|
+
} else if (typeof result === 'string') {
|
|
173
|
+
response = {
|
|
174
|
+
content: [{ type: 'text', text: result }],
|
|
175
|
+
};
|
|
176
|
+
} else {
|
|
177
|
+
response = {
|
|
178
|
+
content: [{ type: 'text', text: String(result) }],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
resolve(createResponse(id, response));
|
|
183
|
+
})
|
|
184
|
+
.catch((error) => {
|
|
185
|
+
resolve(createResponse(
|
|
186
|
+
id,
|
|
187
|
+
null,
|
|
188
|
+
createStandardError(JSONRPC_ERR_CODE_INTERNAL_ERROR, 'Tool execution error', error.message),
|
|
189
|
+
));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function handleResourcesList(id, mcpServer) {
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
const resources = Array.from(mcpServer.resources.values()).map((resource) => ({
|
|
197
|
+
uri: resource.uri,
|
|
198
|
+
name: resource.name,
|
|
199
|
+
description: resource.description,
|
|
200
|
+
mimeType: resource.mimeType,
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
resolve(createResponse(id, { resources }));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function handleResourcesRead(params, id, mcpServer) {
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
const { uri } = params;
|
|
210
|
+
|
|
211
|
+
if (!mcpServer.resources.has(uri)) {
|
|
212
|
+
resolve(createResponse(
|
|
213
|
+
id,
|
|
214
|
+
null,
|
|
215
|
+
createStandardError(JSONRPC_ERR_CODE_INVALID_PARAMS, `Resource '${uri}' not found`),
|
|
216
|
+
));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const resource = mcpServer.resources.get(uri);
|
|
221
|
+
|
|
222
|
+
Promise.resolve(resource.handler())
|
|
223
|
+
.then((content) => {
|
|
224
|
+
resolve(createResponse(id, {
|
|
225
|
+
contents: [{
|
|
226
|
+
uri: resource.uri,
|
|
227
|
+
mimeType: resource.mimeType,
|
|
228
|
+
text: content,
|
|
229
|
+
}],
|
|
230
|
+
}));
|
|
231
|
+
})
|
|
232
|
+
.catch((error) => {
|
|
233
|
+
resolve(createResponse(
|
|
234
|
+
id,
|
|
235
|
+
null,
|
|
236
|
+
createStandardError(JSONRPC_ERR_CODE_INTERNAL_ERROR, 'Resource read error', error.message),
|
|
237
|
+
));
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function handlePromptsList(id, mcpServer) {
|
|
243
|
+
return new Promise((resolve) => {
|
|
244
|
+
const prompts = Array.from(mcpServer.prompts.values()).map((prompt) => ({
|
|
245
|
+
name: prompt.name,
|
|
246
|
+
description: prompt.description,
|
|
247
|
+
arguments: prompt.arguments,
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
resolve(createResponse(id, { prompts }));
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function handlePromptsGet(params, id, mcpServer) {
|
|
255
|
+
return new Promise((resolve) => {
|
|
256
|
+
const { name, arguments: args = {} } = params;
|
|
257
|
+
|
|
258
|
+
if (!mcpServer.prompts.has(name)) {
|
|
259
|
+
resolve(createResponse(
|
|
260
|
+
id,
|
|
261
|
+
null,
|
|
262
|
+
createStandardError(JSONRPC_ERR_CODE_INVALID_PARAMS, `Prompt '${name}' not found`),
|
|
263
|
+
));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const prompt = mcpServer.prompts.get(name);
|
|
268
|
+
|
|
269
|
+
Promise.resolve(prompt.handler(args))
|
|
270
|
+
.then((messages) => {
|
|
271
|
+
resolve(createResponse(id, {
|
|
272
|
+
description: prompt.description,
|
|
273
|
+
messages,
|
|
274
|
+
}));
|
|
275
|
+
})
|
|
276
|
+
.catch((error) => {
|
|
277
|
+
resolve(createResponse(
|
|
278
|
+
id,
|
|
279
|
+
null,
|
|
280
|
+
createStandardError(JSONRPC_ERR_CODE_INTERNAL_ERROR, 'Prompt execution error', error.message),
|
|
281
|
+
));
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
handleMcpRequest,
|
|
288
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
convertToJsonSchema,
|
|
5
|
+
} = require('./jsonrpc');
|
|
6
|
+
const { handleMcpRequest } = require('./handlers');
|
|
7
|
+
|
|
8
|
+
class McpServer {
|
|
9
|
+
constructor(aServerInfo) {
|
|
10
|
+
const serverInfo = {
|
|
11
|
+
name: 'custom-mcp-server',
|
|
12
|
+
version: '1.0.0',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
if (aServerInfo) {
|
|
16
|
+
const { name, version } = aServerInfo;
|
|
17
|
+
|
|
18
|
+
if (name) {
|
|
19
|
+
serverInfo.name = name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (version) {
|
|
23
|
+
serverInfo.version = version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.serverInfo = serverInfo;
|
|
28
|
+
this.tools = new Map();
|
|
29
|
+
this.resources = new Map();
|
|
30
|
+
this.prompts = new Map();
|
|
31
|
+
this.isInitialized = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
tool(name, description, inputSchema, handler) {
|
|
35
|
+
const jsonSchema = convertToJsonSchema(inputSchema);
|
|
36
|
+
|
|
37
|
+
this.tools.set(name, {
|
|
38
|
+
name,
|
|
39
|
+
description,
|
|
40
|
+
inputSchema: jsonSchema,
|
|
41
|
+
handler,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
resource(uri, name, description, mimeType, handler) {
|
|
49
|
+
this.resources.set(uri, {
|
|
50
|
+
uri,
|
|
51
|
+
name,
|
|
52
|
+
description,
|
|
53
|
+
mimeType,
|
|
54
|
+
handler,
|
|
55
|
+
});
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
prompt(name, description, argumentsSchema, handler) {
|
|
60
|
+
this.prompts.set(name, {
|
|
61
|
+
name,
|
|
62
|
+
description,
|
|
63
|
+
arguments: argumentsSchema,
|
|
64
|
+
handler,
|
|
65
|
+
});
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleMcpRequest(requestBody) {
|
|
70
|
+
return handleMcpRequest(requestBody, this);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { z } = require('./schema');
|
|
75
|
+
|
|
76
|
+
module.exports = { McpServer, z };
|
package/src/jsonrpc.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
function createMessage(method, params = null, id = null) {
|
|
2
|
+
const message = {
|
|
3
|
+
jsonrpc: '2.0',
|
|
4
|
+
method,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
if (params !== null && params !== undefined) {
|
|
8
|
+
message.params = params;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (id !== null && id !== undefined) {
|
|
12
|
+
message.id = id;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createResponse(id, result = null, error = null) {
|
|
19
|
+
const response = {
|
|
20
|
+
jsonrpc: '2.0',
|
|
21
|
+
id,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (error) {
|
|
25
|
+
response.error = error;
|
|
26
|
+
} else {
|
|
27
|
+
response.result = result || {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return response;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createError(code, message, data = null) {
|
|
34
|
+
const error = { code, message };
|
|
35
|
+
if (data) error.data = data;
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createStandardError(errorConstant, customMessage = null, data = null) {
|
|
40
|
+
if (!errorConstant || typeof errorConstant.code !== 'number') {
|
|
41
|
+
throw new Error('Invalid error constant provided');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return createError(
|
|
45
|
+
errorConstant.code,
|
|
46
|
+
customMessage || errorConstant.message,
|
|
47
|
+
data,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function convertToJsonSchema(inputSchema) {
|
|
52
|
+
if (inputSchema && inputSchema.type === 'object' && inputSchema.properties) {
|
|
53
|
+
return inputSchema;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const jsonSchema = {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {},
|
|
59
|
+
required: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (!inputSchema || Object.keys(inputSchema).length === 0) {
|
|
63
|
+
return jsonSchema;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const [key, schemaType] of Object.entries(inputSchema)) {
|
|
67
|
+
let propertySchema = {};
|
|
68
|
+
let isRequired = true;
|
|
69
|
+
|
|
70
|
+
if (typeof schemaType === 'object' && schemaType !== null) {
|
|
71
|
+
if (schemaType.type) {
|
|
72
|
+
propertySchema = { ...schemaType };
|
|
73
|
+
} else if (schemaType._def) {
|
|
74
|
+
const zodType = _convertZodType(schemaType);
|
|
75
|
+
propertySchema = zodType.schema;
|
|
76
|
+
isRequired = zodType.required;
|
|
77
|
+
} else if (schemaType.optional !== undefined) {
|
|
78
|
+
isRequired = !schemaType.optional;
|
|
79
|
+
propertySchema = {
|
|
80
|
+
type: schemaType.type || 'string',
|
|
81
|
+
...(schemaType.description && { description: schemaType.description }),
|
|
82
|
+
...(schemaType.default !== undefined && { default: schemaType.default }),
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
propertySchema = { type: 'string' };
|
|
86
|
+
}
|
|
87
|
+
} else if (typeof schemaType === 'string') {
|
|
88
|
+
propertySchema = { type: schemaType };
|
|
89
|
+
} else {
|
|
90
|
+
propertySchema = { type: 'string' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
jsonSchema.properties[key] = propertySchema;
|
|
94
|
+
|
|
95
|
+
if (isRequired) {
|
|
96
|
+
jsonSchema.required.push(key);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return jsonSchema;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _convertZodType(zodType) {
|
|
104
|
+
const def = zodType._def;
|
|
105
|
+
let schema = {};
|
|
106
|
+
let required = true;
|
|
107
|
+
|
|
108
|
+
switch (def.typeName) {
|
|
109
|
+
case 'ZodNumber':
|
|
110
|
+
schema = { type: 'number' };
|
|
111
|
+
break;
|
|
112
|
+
case 'ZodString':
|
|
113
|
+
schema = { type: 'string' };
|
|
114
|
+
break;
|
|
115
|
+
case 'ZodBoolean':
|
|
116
|
+
schema = { type: 'boolean' };
|
|
117
|
+
break;
|
|
118
|
+
case 'ZodArray':
|
|
119
|
+
schema = {
|
|
120
|
+
type: 'array',
|
|
121
|
+
items: _convertZodType(def.type).schema,
|
|
122
|
+
};
|
|
123
|
+
break;
|
|
124
|
+
case 'ZodObject':
|
|
125
|
+
schema = {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {},
|
|
128
|
+
required: [],
|
|
129
|
+
};
|
|
130
|
+
for (const [key, value] of Object.entries(def.shape)) {
|
|
131
|
+
const converted = _convertZodType(value);
|
|
132
|
+
schema.properties[key] = converted.schema;
|
|
133
|
+
if (converted.required) {
|
|
134
|
+
schema.required.push(key);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'ZodEnum':
|
|
139
|
+
schema = {
|
|
140
|
+
type: 'string',
|
|
141
|
+
enum: def.values,
|
|
142
|
+
};
|
|
143
|
+
break;
|
|
144
|
+
case 'ZodOptional': {
|
|
145
|
+
const innerConverted = _convertZodType(def.innerType);
|
|
146
|
+
schema = innerConverted.schema;
|
|
147
|
+
required = false;
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
schema = { type: 'string' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (def.description) {
|
|
155
|
+
schema.description = def.description;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (def.default !== undefined) {
|
|
159
|
+
schema.default = def.default;
|
|
160
|
+
required = false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { schema, required };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validateArguments(args, schema) {
|
|
167
|
+
if (!schema || !schema.required) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const requiredField of schema.required) {
|
|
172
|
+
if (args[requiredField] === undefined || args[requiredField] === null) {
|
|
173
|
+
return `Missing required parameter: ${requiredField}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (schema.properties) {
|
|
178
|
+
for (const [key, value] of Object.entries(args)) {
|
|
179
|
+
const propSchema = schema.properties[key];
|
|
180
|
+
if (propSchema && propSchema.type) {
|
|
181
|
+
const actualType = typeof value;
|
|
182
|
+
const expectedType = propSchema.type;
|
|
183
|
+
|
|
184
|
+
if (expectedType === 'number' && actualType !== 'number') {
|
|
185
|
+
return `Parameter '${key}' should be a number, got ${actualType}`;
|
|
186
|
+
}
|
|
187
|
+
if (expectedType === 'string' && actualType !== 'string') {
|
|
188
|
+
return `Parameter '${key}' should be a string, got ${actualType}`;
|
|
189
|
+
}
|
|
190
|
+
if (expectedType === 'boolean' && actualType !== 'boolean') {
|
|
191
|
+
return `Parameter '${key}' should be a boolean, got ${actualType}`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
createMessage,
|
|
202
|
+
createResponse,
|
|
203
|
+
createError,
|
|
204
|
+
createStandardError,
|
|
205
|
+
convertToJsonSchema,
|
|
206
|
+
validateArguments,
|
|
207
|
+
};
|
package/src/schema.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const z = {
|
|
2
|
+
number: () => {
|
|
3
|
+
const result = { type: 'number', _def: { typeName: 'ZodNumber' } };
|
|
4
|
+
result.describe = (description) => {
|
|
5
|
+
result._def.description = description;
|
|
6
|
+
return result;
|
|
7
|
+
};
|
|
8
|
+
result.default = (defaultValue) => {
|
|
9
|
+
result._def.default = defaultValue;
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
return result;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
string: () => {
|
|
16
|
+
const result = { type: 'string', _def: { typeName: 'ZodString' } };
|
|
17
|
+
result.describe = (description) => {
|
|
18
|
+
result._def.description = description;
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
result.default = (defaultValue) => {
|
|
22
|
+
result._def.default = defaultValue;
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
return result;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
boolean: () => {
|
|
29
|
+
const result = { type: 'boolean', _def: { typeName: 'ZodBoolean' } };
|
|
30
|
+
result.describe = (description) => {
|
|
31
|
+
result._def.description = description;
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
result.default = (defaultValue) => {
|
|
35
|
+
result._def.default = defaultValue;
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
return result;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
enum: (values) => {
|
|
42
|
+
const result = {
|
|
43
|
+
type: 'string',
|
|
44
|
+
_def: {
|
|
45
|
+
typeName: 'ZodEnum',
|
|
46
|
+
values,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
result.describe = (description) => {
|
|
50
|
+
result._def.description = description;
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
result.default = (defaultValue) => {
|
|
54
|
+
result._def.default = defaultValue;
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
array: (items) => {
|
|
61
|
+
const result = {
|
|
62
|
+
type: 'array',
|
|
63
|
+
_def: {
|
|
64
|
+
typeName: 'ZodArray',
|
|
65
|
+
type: items,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
result.describe = (description) => {
|
|
69
|
+
result._def.description = description;
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
result.default = (defaultValue) => {
|
|
73
|
+
result._def.default = defaultValue;
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
return result;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
object: (shape) => {
|
|
80
|
+
const result = {
|
|
81
|
+
type: 'object',
|
|
82
|
+
_def: {
|
|
83
|
+
typeName: 'ZodObject',
|
|
84
|
+
shape,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
result.describe = (description) => {
|
|
88
|
+
result._def.description = description;
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
result.default = (defaultValue) => {
|
|
92
|
+
result._def.default = defaultValue;
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
return result;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
optional: (type) => {
|
|
99
|
+
const result = {
|
|
100
|
+
_def: {
|
|
101
|
+
typeName: 'ZodOptional',
|
|
102
|
+
innerType: type,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
result.describe = (description) => {
|
|
106
|
+
result._def.description = description;
|
|
107
|
+
return result;
|
|
108
|
+
};
|
|
109
|
+
result.default = (defaultValue) => {
|
|
110
|
+
result._def.default = defaultValue;
|
|
111
|
+
return result;
|
|
112
|
+
};
|
|
113
|
+
return result;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports = { z };
|