@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 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
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@mimik/mcp-kit",
3
+ "version": "1.0.0",
4
+ "main": "./src/index.js"
5
+ }
@@ -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
+ };
@@ -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 };