@cogitator-ai/mcp 17.0.10 → 18.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/README.md +63 -22
- package/dist/adapter/tool-adapter.d.ts +7 -3
- package/dist/adapter/tool-adapter.d.ts.map +1 -1
- package/dist/adapter/tool-adapter.js +114 -28
- package/dist/adapter/tool-adapter.js.map +1 -1
- package/dist/client/mcp-client.d.ts +32 -5
- package/dist/client/mcp-client.d.ts.map +1 -1
- package/dist/client/mcp-client.js +241 -112
- package/dist/client/mcp-client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/mcp-server.d.ts +30 -3
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +258 -157
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/types.d.ts +20 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -28,7 +28,7 @@ import type { MCPServerConfig, MCPResourceConfig, MCPPromptConfig } from '../typ
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export declare class MCPServer {
|
|
31
|
-
private server
|
|
31
|
+
private server?;
|
|
32
32
|
private config;
|
|
33
33
|
private tools;
|
|
34
34
|
private resources;
|
|
@@ -88,18 +88,40 @@ export declare class MCPServer {
|
|
|
88
88
|
* Get list of registered prompt names
|
|
89
89
|
*/
|
|
90
90
|
getRegisteredPrompts(): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Build a fresh SDK McpServer instance and register every tool, resource
|
|
93
|
+
* and prompt from the local maps onto it.
|
|
94
|
+
*
|
|
95
|
+
* The maps are the single source of truth: registration with the SDK is
|
|
96
|
+
* deferred until start() (stdio) or until each incoming request (HTTP), so
|
|
97
|
+
* unregister* calls made before start() are fully honoured and concurrent
|
|
98
|
+
* HTTP requests never share a single server/transport binding.
|
|
99
|
+
*/
|
|
100
|
+
private buildServer;
|
|
91
101
|
/**
|
|
92
102
|
* Register a tool with the MCP server
|
|
93
103
|
*/
|
|
94
104
|
private registerMCPTool;
|
|
95
105
|
/**
|
|
96
|
-
*
|
|
106
|
+
* Resolve the Zod schema the SDK uses to advertise and validate tool input.
|
|
107
|
+
*
|
|
108
|
+
* The full tool schema is handed to the SDK (not just its shape) so object
|
|
109
|
+
* modifiers, refinements and transforms are honoured, and the SDK passes
|
|
110
|
+
* already-parsed arguments to the handler. Tools without a schema accept
|
|
111
|
+
* any object.
|
|
97
112
|
*/
|
|
98
113
|
private buildInputSchema;
|
|
99
114
|
/**
|
|
100
|
-
* Execute a tool and return MCP-formatted result
|
|
115
|
+
* Execute a tool with SDK-validated arguments and return an MCP-formatted result
|
|
101
116
|
*/
|
|
102
117
|
private executeTool;
|
|
118
|
+
/**
|
|
119
|
+
* Write a diagnostic line to stderr when logging is enabled.
|
|
120
|
+
*
|
|
121
|
+
* stdout is reserved for JSON-RPC frames on the stdio transport, so every
|
|
122
|
+
* log line goes to stderr regardless of level.
|
|
123
|
+
*/
|
|
124
|
+
private log;
|
|
103
125
|
/**
|
|
104
126
|
* Register a resource with the MCP server
|
|
105
127
|
*/
|
|
@@ -116,6 +138,11 @@ export declare class MCPServer {
|
|
|
116
138
|
* Start HTTP server for MCP
|
|
117
139
|
*/
|
|
118
140
|
private startHttpServer;
|
|
141
|
+
/**
|
|
142
|
+
* Port the HTTP transport is listening on (useful with `port: 0`).
|
|
143
|
+
* Returns undefined for stdio or before the server has started.
|
|
144
|
+
*/
|
|
145
|
+
getPort(): number | undefined;
|
|
119
146
|
/**
|
|
120
147
|
* Stop the server
|
|
121
148
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAEjB,eAAe,EAGhB,MAAM,UAAU,CAAC;AAwDlB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAC,CAAa;gBAEpB,MAAM,EAAE,eAAe;IAInC;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAQ9B;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAMlC;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAOrC;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAQjD;;OAEG;IACH,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAMrD;;;OAGG;IACH,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAOxC;;OAEG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAQ7C;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI;IAMjD;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAOvC;;OAEG;IACH,oBAAoB,IAAI,MAAM,EAAE;IAIhC;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;IAmBnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;YACW,WAAW;IAyBzB;;;;;OAKG;IACH,OAAO,CAAC,GAAG;IAQX;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA2D3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B;;OAEG;YACW,eAAe;IA8G7B;;;OAGG;IACH,OAAO,IAAI,MAAM,GAAG,SAAS;IAK7B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;OAEG;IACH,SAAS,IAAI,OAAO;CAGrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAK9F"}
|
|
@@ -4,10 +4,45 @@
|
|
|
4
4
|
* Exposes Cogitator tools as an MCP server that can be used by
|
|
5
5
|
* other MCP clients (e.g., Claude Desktop, other AI assistants).
|
|
6
6
|
*/
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
7
8
|
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
-
import { resultToMCPContent
|
|
10
|
+
import { resultToMCPContent } from '../adapter/tool-adapter.js';
|
|
10
11
|
import { z } from 'zod';
|
|
12
|
+
function toServerContent(item) {
|
|
13
|
+
switch (item.type) {
|
|
14
|
+
case 'text':
|
|
15
|
+
return { type: 'text', text: item.text ?? '' };
|
|
16
|
+
case 'image':
|
|
17
|
+
case 'audio':
|
|
18
|
+
if (typeof item.data === 'string' && typeof item.mimeType === 'string') {
|
|
19
|
+
return { type: item.type, data: item.data, mimeType: item.mimeType };
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
case 'resource':
|
|
23
|
+
case 'resource_link': {
|
|
24
|
+
const resource = item.resource;
|
|
25
|
+
if (resource && typeof resource.blob === 'string') {
|
|
26
|
+
return {
|
|
27
|
+
type: 'resource',
|
|
28
|
+
resource: { uri: resource.uri, mimeType: resource.mimeType, blob: resource.blob },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (resource && typeof resource.text === 'string') {
|
|
32
|
+
return {
|
|
33
|
+
type: 'resource',
|
|
34
|
+
resource: { uri: resource.uri, mimeType: resource.mimeType, text: resource.text },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { type: 'text', text: JSON.stringify(item) };
|
|
41
|
+
}
|
|
42
|
+
function errorMessageOf(error) {
|
|
43
|
+
return error instanceof Error ? error.message : String(error);
|
|
44
|
+
}
|
|
45
|
+
const PERMISSIVE_INPUT_SCHEMA = z.looseObject({});
|
|
11
46
|
/**
|
|
12
47
|
* MCP Server for exposing Cogitator tools
|
|
13
48
|
*
|
|
@@ -39,10 +74,6 @@ export class MCPServer {
|
|
|
39
74
|
httpServer;
|
|
40
75
|
constructor(config) {
|
|
41
76
|
this.config = config;
|
|
42
|
-
this.server = new McpServer({
|
|
43
|
-
name: config.name,
|
|
44
|
-
version: config.version,
|
|
45
|
-
});
|
|
46
77
|
}
|
|
47
78
|
/**
|
|
48
79
|
* Register a single Cogitator tool
|
|
@@ -52,7 +83,6 @@ export class MCPServer {
|
|
|
52
83
|
throw new Error('Cannot register tools after server has started');
|
|
53
84
|
}
|
|
54
85
|
this.tools.set(tool.name, tool);
|
|
55
|
-
this.registerMCPTool(tool);
|
|
56
86
|
}
|
|
57
87
|
/**
|
|
58
88
|
* Register multiple Cogitator tools
|
|
@@ -87,7 +117,6 @@ export class MCPServer {
|
|
|
87
117
|
throw new Error('Cannot register resources after server has started');
|
|
88
118
|
}
|
|
89
119
|
this.resources.set(config.uri, config);
|
|
90
|
-
this.registerMCPResource(config);
|
|
91
120
|
}
|
|
92
121
|
/**
|
|
93
122
|
* Register multiple resources
|
|
@@ -121,7 +150,6 @@ export class MCPServer {
|
|
|
121
150
|
throw new Error('Cannot register prompts after server has started');
|
|
122
151
|
}
|
|
123
152
|
this.prompts.set(config.name, config);
|
|
124
|
-
this.registerMCPPrompt(config);
|
|
125
153
|
}
|
|
126
154
|
/**
|
|
127
155
|
* Register multiple prompts
|
|
@@ -147,77 +175,93 @@ export class MCPServer {
|
|
|
147
175
|
getRegisteredPrompts() {
|
|
148
176
|
return Array.from(this.prompts.keys());
|
|
149
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Build a fresh SDK McpServer instance and register every tool, resource
|
|
180
|
+
* and prompt from the local maps onto it.
|
|
181
|
+
*
|
|
182
|
+
* The maps are the single source of truth: registration with the SDK is
|
|
183
|
+
* deferred until start() (stdio) or until each incoming request (HTTP), so
|
|
184
|
+
* unregister* calls made before start() are fully honoured and concurrent
|
|
185
|
+
* HTTP requests never share a single server/transport binding.
|
|
186
|
+
*/
|
|
187
|
+
buildServer() {
|
|
188
|
+
const server = new McpServer({
|
|
189
|
+
name: this.config.name,
|
|
190
|
+
version: this.config.version,
|
|
191
|
+
});
|
|
192
|
+
for (const tool of this.tools.values()) {
|
|
193
|
+
this.registerMCPTool(server, tool);
|
|
194
|
+
}
|
|
195
|
+
for (const resource of this.resources.values()) {
|
|
196
|
+
this.registerMCPResource(server, resource);
|
|
197
|
+
}
|
|
198
|
+
for (const prompt of this.prompts.values()) {
|
|
199
|
+
this.registerMCPPrompt(server, prompt);
|
|
200
|
+
}
|
|
201
|
+
return server;
|
|
202
|
+
}
|
|
150
203
|
/**
|
|
151
204
|
* Register a tool with the MCP server
|
|
152
205
|
*/
|
|
153
|
-
registerMCPTool(tool) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
206
|
+
registerMCPTool(server, tool) {
|
|
207
|
+
server.registerTool(tool.name, {
|
|
208
|
+
description: tool.description,
|
|
209
|
+
inputSchema: this.buildInputSchema(tool),
|
|
210
|
+
}, async (args, extra) => {
|
|
211
|
+
return this.executeTool(tool, args, extra.signal);
|
|
157
212
|
});
|
|
158
213
|
}
|
|
159
214
|
/**
|
|
160
|
-
*
|
|
215
|
+
* Resolve the Zod schema the SDK uses to advertise and validate tool input.
|
|
216
|
+
*
|
|
217
|
+
* The full tool schema is handed to the SDK (not just its shape) so object
|
|
218
|
+
* modifiers, refinements and transforms are honoured, and the SDK passes
|
|
219
|
+
* already-parsed arguments to the handler. Tools without a schema accept
|
|
220
|
+
* any object.
|
|
161
221
|
*/
|
|
162
222
|
buildInputSchema(tool) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return jsonSchema.properties ?? {};
|
|
166
|
-
}
|
|
167
|
-
const schema = tool.toJSON();
|
|
168
|
-
return schema.parameters.properties;
|
|
223
|
+
const params = tool.parameters;
|
|
224
|
+
return params instanceof z.ZodType ? params : PERMISSIVE_INPUT_SCHEMA;
|
|
169
225
|
}
|
|
170
226
|
/**
|
|
171
|
-
* Execute a tool and return MCP-formatted result
|
|
227
|
+
* Execute a tool with SDK-validated arguments and return an MCP-formatted result
|
|
172
228
|
*/
|
|
173
|
-
async executeTool(tool, args) {
|
|
229
|
+
async executeTool(tool, args, signal) {
|
|
174
230
|
const context = {
|
|
175
231
|
agentId: 'mcp-server',
|
|
176
|
-
runId: `mcp_${
|
|
177
|
-
signal
|
|
232
|
+
runId: `mcp_${randomUUID()}`,
|
|
233
|
+
signal,
|
|
178
234
|
};
|
|
179
235
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const parseResult = tool.parameters.safeParse(args);
|
|
183
|
-
if (!parseResult.success) {
|
|
184
|
-
return {
|
|
185
|
-
content: [
|
|
186
|
-
{
|
|
187
|
-
type: 'text',
|
|
188
|
-
text: `Validation error: ${parseResult.error.message}`,
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
isError: true,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
validatedArgs = parseResult.data;
|
|
195
|
-
}
|
|
196
|
-
const result = await tool.execute(validatedArgs, context);
|
|
197
|
-
const rawContent = resultToMCPContent(result);
|
|
198
|
-
const content = rawContent.map((item) => {
|
|
199
|
-
if (item.type === 'text') {
|
|
200
|
-
return { type: 'text', text: item.text };
|
|
201
|
-
}
|
|
202
|
-
return { type: 'text', text: JSON.stringify(item) };
|
|
203
|
-
});
|
|
204
|
-
return { content };
|
|
236
|
+
const result = await tool.execute(args ?? {}, context);
|
|
237
|
+
return { content: resultToMCPContent(result).map(toServerContent) };
|
|
205
238
|
}
|
|
206
239
|
catch (error) {
|
|
207
|
-
const errorMessage =
|
|
208
|
-
|
|
209
|
-
console.error(`[MCPServer] Tool ${tool.name} error:`, errorMessage);
|
|
210
|
-
}
|
|
240
|
+
const errorMessage = errorMessageOf(error);
|
|
241
|
+
this.log('error', `Tool ${tool.name} error: ${errorMessage}`);
|
|
211
242
|
return {
|
|
212
243
|
content: [{ type: 'text', text: `Error: ${errorMessage}` }],
|
|
213
244
|
isError: true,
|
|
214
245
|
};
|
|
215
246
|
}
|
|
216
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Write a diagnostic line to stderr when logging is enabled.
|
|
250
|
+
*
|
|
251
|
+
* stdout is reserved for JSON-RPC frames on the stdio transport, so every
|
|
252
|
+
* log line goes to stderr regardless of level.
|
|
253
|
+
*/
|
|
254
|
+
log(level, message) {
|
|
255
|
+
if (!this.config.logging) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const prefix = level === 'error' ? '[MCPServer] ERROR' : '[MCPServer]';
|
|
259
|
+
console.error(`${prefix} ${message}`);
|
|
260
|
+
}
|
|
217
261
|
/**
|
|
218
262
|
* Register a resource with the MCP server
|
|
219
263
|
*/
|
|
220
|
-
registerMCPResource(config) {
|
|
264
|
+
registerMCPResource(server, config) {
|
|
221
265
|
const isTemplate = config.uri.includes('{');
|
|
222
266
|
const formatContents = (result, uriHref) => {
|
|
223
267
|
const contents = Array.isArray(result) ? result : [result];
|
|
@@ -228,100 +272,84 @@ export class MCPServer {
|
|
|
228
272
|
if (c.mimeType || config.mimeType) {
|
|
229
273
|
base.mimeType = c.mimeType || config.mimeType;
|
|
230
274
|
}
|
|
231
|
-
if (c.blob) {
|
|
275
|
+
if (typeof c.blob === 'string') {
|
|
232
276
|
return { ...base, blob: c.blob };
|
|
233
277
|
}
|
|
234
|
-
return { ...base, text: c.text
|
|
278
|
+
return { ...base, text: c.text ?? '' };
|
|
235
279
|
});
|
|
236
280
|
};
|
|
281
|
+
const readResource = async (params) => {
|
|
282
|
+
try {
|
|
283
|
+
return await config.read(params);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
this.log('error', `Resource ${config.name} error: ${errorMessageOf(error)}`);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
237
290
|
if (isTemplate) {
|
|
238
|
-
|
|
291
|
+
server.registerResource(config.name, new ResourceTemplate(config.uri, { list: undefined }), {
|
|
239
292
|
description: config.description,
|
|
240
293
|
mimeType: config.mimeType,
|
|
241
294
|
}, async (uri, variables) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
params[key] = Array.isArray(value) ? value[0] || '' : value;
|
|
246
|
-
}
|
|
247
|
-
const result = await config.read(params);
|
|
248
|
-
return { contents: formatContents(result, uri.href) };
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
252
|
-
if (this.config.logging) {
|
|
253
|
-
console.error(`[MCPServer] Resource ${config.name} error:`, errorMessage);
|
|
254
|
-
}
|
|
255
|
-
return { contents: [{ uri: uri.href, text: `Error: ${errorMessage}` }] };
|
|
295
|
+
const params = {};
|
|
296
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
297
|
+
params[key] = Array.isArray(value) ? value.join(',') : value;
|
|
256
298
|
}
|
|
299
|
+
return { contents: formatContents(await readResource(params), uri.href) };
|
|
257
300
|
});
|
|
258
301
|
}
|
|
259
302
|
else {
|
|
260
|
-
|
|
303
|
+
server.registerResource(config.name, config.uri, {
|
|
261
304
|
description: config.description,
|
|
262
305
|
mimeType: config.mimeType,
|
|
263
306
|
}, async (uri) => {
|
|
264
|
-
|
|
265
|
-
const result = await config.read({});
|
|
266
|
-
return { contents: formatContents(result, uri.href) };
|
|
267
|
-
}
|
|
268
|
-
catch (error) {
|
|
269
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
270
|
-
if (this.config.logging) {
|
|
271
|
-
console.error(`[MCPServer] Resource ${config.name} error:`, errorMessage);
|
|
272
|
-
}
|
|
273
|
-
return { contents: [{ uri: uri.href, text: `Error: ${errorMessage}` }] };
|
|
274
|
-
}
|
|
307
|
+
return { contents: formatContents(await readResource({}), uri.href) };
|
|
275
308
|
});
|
|
276
309
|
}
|
|
277
310
|
}
|
|
278
311
|
/**
|
|
279
312
|
* Register a prompt with the MCP server
|
|
280
313
|
*/
|
|
281
|
-
registerMCPPrompt(config) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
let schema = z.string();
|
|
285
|
-
if (arg.description) {
|
|
286
|
-
schema = schema.describe(arg.description);
|
|
287
|
-
}
|
|
288
|
-
if (!arg.required) {
|
|
289
|
-
schema = schema.optional();
|
|
290
|
-
}
|
|
291
|
-
argsSchema[arg.name] = schema;
|
|
292
|
-
}
|
|
293
|
-
this.server.registerPrompt(config.name, {
|
|
294
|
-
title: config.title || config.name,
|
|
295
|
-
description: config.description,
|
|
296
|
-
argsSchema,
|
|
297
|
-
}, async (args) => {
|
|
314
|
+
registerMCPPrompt(server, config) {
|
|
315
|
+
const render = async (args) => {
|
|
316
|
+
let result;
|
|
298
317
|
try {
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
description: result.description,
|
|
302
|
-
messages: result.messages.map((m) => ({
|
|
303
|
-
role: m.role,
|
|
304
|
-
content: {
|
|
305
|
-
type: 'text',
|
|
306
|
-
text: typeof m.content === 'string' ? m.content : m.content.text || '',
|
|
307
|
-
},
|
|
308
|
-
})),
|
|
309
|
-
};
|
|
318
|
+
result = await config.get(args);
|
|
310
319
|
}
|
|
311
320
|
catch (error) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
321
|
+
this.log('error', `Prompt ${config.name} error: ${errorMessageOf(error)}`);
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
description: result.description,
|
|
326
|
+
messages: result.messages.map((m) => ({
|
|
327
|
+
role: m.role,
|
|
328
|
+
content: typeof m.content === 'string'
|
|
329
|
+
? { type: 'text', text: m.content }
|
|
330
|
+
: toServerContent(m.content),
|
|
331
|
+
})),
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
const metadata = { title: config.title || config.name, description: config.description };
|
|
335
|
+
const promptArgs = config.arguments ?? [];
|
|
336
|
+
if (promptArgs.length === 0) {
|
|
337
|
+
server.registerPrompt(config.name, metadata, async () => render({}));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const argsSchema = {};
|
|
341
|
+
for (const arg of promptArgs) {
|
|
342
|
+
const base = arg.description ? z.string().describe(arg.description) : z.string();
|
|
343
|
+
argsSchema[arg.name] = arg.required ? base : base.optional();
|
|
344
|
+
}
|
|
345
|
+
server.registerPrompt(config.name, { ...metadata, argsSchema }, async (args) => {
|
|
346
|
+
const stringArgs = {};
|
|
347
|
+
for (const [key, value] of Object.entries(args ?? {})) {
|
|
348
|
+
if (typeof value === 'string') {
|
|
349
|
+
stringArgs[key] = value;
|
|
315
350
|
}
|
|
316
|
-
return {
|
|
317
|
-
messages: [
|
|
318
|
-
{
|
|
319
|
-
role: 'assistant',
|
|
320
|
-
content: { type: 'text', text: `Error: ${errorMessage}` },
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
};
|
|
324
351
|
}
|
|
352
|
+
return render(stringArgs);
|
|
325
353
|
});
|
|
326
354
|
}
|
|
327
355
|
/**
|
|
@@ -331,14 +359,13 @@ export class MCPServer {
|
|
|
331
359
|
if (this.started) {
|
|
332
360
|
throw new Error('Server already started');
|
|
333
361
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
console.log(`[MCPServer] Registered prompts: ${this.getRegisteredPrompts().join(', ') || '(none)'}`);
|
|
339
|
-
}
|
|
362
|
+
this.log('info', `Starting ${this.config.name} v${this.config.version}`);
|
|
363
|
+
this.log('info', `Registered tools: ${this.getRegisteredTools().join(', ') || '(none)'}`);
|
|
364
|
+
this.log('info', `Registered resources: ${this.getRegisteredResources().join(', ') || '(none)'}`);
|
|
365
|
+
this.log('info', `Registered prompts: ${this.getRegisteredPrompts().join(', ') || '(none)'}`);
|
|
340
366
|
switch (this.config.transport) {
|
|
341
367
|
case 'stdio': {
|
|
368
|
+
this.server = this.buildServer();
|
|
342
369
|
const transport = new StdioServerTransport();
|
|
343
370
|
await this.server.connect(transport);
|
|
344
371
|
break;
|
|
@@ -352,9 +379,7 @@ export class MCPServer {
|
|
|
352
379
|
throw new Error(`Unknown transport: ${this.config.transport}`);
|
|
353
380
|
}
|
|
354
381
|
this.started = true;
|
|
355
|
-
|
|
356
|
-
console.log(`[MCPServer] Server started on ${this.config.transport} transport`);
|
|
357
|
-
}
|
|
382
|
+
this.log('info', `Server started on ${this.config.transport} transport`);
|
|
358
383
|
}
|
|
359
384
|
/**
|
|
360
385
|
* Start HTTP server for MCP
|
|
@@ -364,39 +389,107 @@ export class MCPServer {
|
|
|
364
389
|
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
365
390
|
const port = this.config.port ?? 3000;
|
|
366
391
|
const host = this.config.host ?? 'localhost';
|
|
392
|
+
const maxBodySize = this.config.maxBodySize ?? 10 * 1024 * 1024;
|
|
393
|
+
const corsOrigin = this.config.corsOrigin ?? '*';
|
|
367
394
|
this.httpServer = createServer(async (req, res) => {
|
|
368
|
-
res.setHeader('Access-Control-Allow-Origin',
|
|
369
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
370
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
395
|
+
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
|
|
396
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
397
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
|
|
398
|
+
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
371
399
|
if (req.method === 'OPTIONS') {
|
|
372
400
|
res.writeHead(204);
|
|
373
401
|
res.end();
|
|
374
402
|
return;
|
|
375
403
|
}
|
|
376
|
-
|
|
404
|
+
const pathname = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
405
|
+
if (pathname !== '/mcp') {
|
|
377
406
|
res.writeHead(404);
|
|
378
407
|
res.end('Not Found');
|
|
379
408
|
return;
|
|
380
409
|
}
|
|
381
|
-
const
|
|
382
|
-
for await (const chunk of req) {
|
|
383
|
-
chunks.push(chunk);
|
|
384
|
-
}
|
|
385
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
410
|
+
const server = this.buildServer();
|
|
386
411
|
const transport = new StreamableHTTPServerTransport({
|
|
387
412
|
sessionIdGenerator: undefined,
|
|
388
413
|
});
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
await new Promise((resolve) => {
|
|
393
|
-
this.httpServer.listen(port, host, () => {
|
|
394
|
-
if (this.config.logging) {
|
|
395
|
-
console.log(`[MCPServer] HTTP server listening on http://${host}:${port}/mcp`);
|
|
396
|
-
}
|
|
397
|
-
resolve();
|
|
414
|
+
res.on('close', () => {
|
|
415
|
+
transport.close().catch(() => { });
|
|
416
|
+
server.close().catch(() => { });
|
|
398
417
|
});
|
|
418
|
+
try {
|
|
419
|
+
await server.connect(transport);
|
|
420
|
+
if (req.method === 'POST') {
|
|
421
|
+
const chunks = [];
|
|
422
|
+
let totalSize = 0;
|
|
423
|
+
let tooLarge = false;
|
|
424
|
+
for await (const chunk of req) {
|
|
425
|
+
const buffer = chunk;
|
|
426
|
+
totalSize += buffer.length;
|
|
427
|
+
if (totalSize > maxBodySize) {
|
|
428
|
+
tooLarge = true;
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
chunks.push(buffer);
|
|
432
|
+
}
|
|
433
|
+
if (tooLarge) {
|
|
434
|
+
res.writeHead(413, { 'Content-Type': 'text/plain' });
|
|
435
|
+
res.end('Payload Too Large');
|
|
436
|
+
req.destroy();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
let body;
|
|
440
|
+
try {
|
|
441
|
+
body = JSON.parse(Buffer.concat(chunks).toString());
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
445
|
+
res.end(JSON.stringify({
|
|
446
|
+
jsonrpc: '2.0',
|
|
447
|
+
error: { code: -32700, message: 'Parse error: invalid JSON' },
|
|
448
|
+
id: null,
|
|
449
|
+
}));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
await transport.handleRequest(req, res, body);
|
|
453
|
+
}
|
|
454
|
+
else if (req.method === 'GET' || req.method === 'DELETE') {
|
|
455
|
+
await transport.handleRequest(req, res);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
|
459
|
+
res.end('Method Not Allowed');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
this.log('error', `HTTP request error: ${errorMessageOf(error)}`);
|
|
464
|
+
if (!res.headersSent) {
|
|
465
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
466
|
+
res.end('Internal Server Error');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
399
469
|
});
|
|
470
|
+
const httpServer = this.httpServer;
|
|
471
|
+
try {
|
|
472
|
+
await new Promise((resolve, reject) => {
|
|
473
|
+
httpServer.once('error', reject);
|
|
474
|
+
httpServer.listen(port, host, () => {
|
|
475
|
+
httpServer.off('error', reject);
|
|
476
|
+
resolve();
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
this.httpServer = undefined;
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
this.log('info', `HTTP server listening on http://${host}:${this.getPort() ?? port}/mcp`);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Port the HTTP transport is listening on (useful with `port: 0`).
|
|
488
|
+
* Returns undefined for stdio or before the server has started.
|
|
489
|
+
*/
|
|
490
|
+
getPort() {
|
|
491
|
+
const address = this.httpServer?.address();
|
|
492
|
+
return address && typeof address === 'object' ? address.port : undefined;
|
|
400
493
|
}
|
|
401
494
|
/**
|
|
402
495
|
* Stop the server
|
|
@@ -405,17 +498,25 @@ export class MCPServer {
|
|
|
405
498
|
if (!this.started) {
|
|
406
499
|
return;
|
|
407
500
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
501
|
+
try {
|
|
502
|
+
const httpServer = this.httpServer;
|
|
503
|
+
if (httpServer) {
|
|
504
|
+
this.httpServer = undefined;
|
|
505
|
+
await new Promise((resolve, reject) => {
|
|
506
|
+
httpServer.close((err) => (err ? reject(err) : resolve()));
|
|
507
|
+
httpServer.closeAllConnections();
|
|
508
|
+
});
|
|
509
|
+
}
|
|
413
510
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
511
|
+
finally {
|
|
512
|
+
const server = this.server;
|
|
513
|
+
this.server = undefined;
|
|
514
|
+
this.started = false;
|
|
515
|
+
if (server) {
|
|
516
|
+
await server.close();
|
|
517
|
+
}
|
|
418
518
|
}
|
|
519
|
+
this.log('info', 'Server stopped');
|
|
419
520
|
}
|
|
420
521
|
/**
|
|
421
522
|
* Check if server is running
|