@procedures/mcp-server 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +366 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +11 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +16 -0
- package/dist/module.js.map +1 -0
- package/dist/procedures/mcp/index.d.ts +6 -0
- package/dist/procedures/mcp/index.d.ts.map +1 -0
- package/dist/procedures/mcp/index.js +6 -0
- package/dist/procedures/mcp/index.js.map +1 -0
- package/dist/procedures/mcp/list-tools.d.ts +8 -0
- package/dist/procedures/mcp/list-tools.d.ts.map +1 -0
- package/dist/procedures/mcp/list-tools.js +60 -0
- package/dist/procedures/mcp/list-tools.js.map +1 -0
- package/dist/procedures/mcp/serve.d.ts +38 -0
- package/dist/procedures/mcp/serve.d.ts.map +1 -0
- package/dist/procedures/mcp/serve.js +123 -0
- package/dist/procedures/mcp/serve.js.map +1 -0
- package/dist/register.d.ts +11 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +21 -0
- package/dist/register.js.map +1 -0
- package/dist/surface.d.ts +36 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +74 -0
- package/dist/surface.js.map +1 -0
- package/dist/transport/index.d.ts +7 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +7 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/mcp-transport.d.ts +94 -0
- package/dist/transport/mcp-transport.d.ts.map +1 -0
- package/dist/transport/mcp-transport.js +368 -0
- package/dist/transport/mcp-transport.js.map +1 -0
- package/dist/transport/sse.d.ts +20 -0
- package/dist/transport/sse.d.ts.map +1 -0
- package/dist/transport/sse.js +23 -0
- package/dist/transport/sse.js.map +1 -0
- package/dist/transport/stdio.d.ts +16 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +18 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -3
- package/src/index.ts +48 -0
- package/src/module.ts +18 -0
- package/src/procedures/mcp/index.ts +6 -0
- package/src/procedures/mcp/list-tools.ts +84 -0
- package/src/procedures/mcp/serve.ts +170 -0
- package/src/register.ts +24 -0
- package/src/surface.ts +85 -0
- package/src/transport/index.ts +7 -0
- package/src/transport/mcp-transport.ts +418 -0
- package/src/transport/sse.ts +29 -0
- package/src/transport/stdio.ts +19 -0
- package/src/types.ts +64 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Transport
|
|
3
|
+
*
|
|
4
|
+
* Implements ServerTransport interface for MCP protocol.
|
|
5
|
+
* Maps PROCEDURE_REGISTRY to MCP tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Server as McpServer } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema,
|
|
12
|
+
type ServerNotification,
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import type { Transport as SdkTransport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
15
|
+
import type { ServerTransport, Server, ProcedureRegistry } from "@procedures/client";
|
|
16
|
+
import { PROCEDURE_REGISTRY, pathToMethod } from "@procedures/client";
|
|
17
|
+
import {
|
|
18
|
+
proceduresToMcpTools,
|
|
19
|
+
toToolDefinition,
|
|
20
|
+
decodePath,
|
|
21
|
+
type McpTool,
|
|
22
|
+
} from "@procedures/mcp";
|
|
23
|
+
import type { McpServerTransportOptions, McpServerTransportState } from "../types.js";
|
|
24
|
+
import { createStdioTransport } from "./stdio.js";
|
|
25
|
+
import { createSseTransport } from "./sse.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a unique request ID.
|
|
29
|
+
*/
|
|
30
|
+
/** The most text blocks of a stream result: the last items of a long stream. */
|
|
31
|
+
const MAX_STREAM_BLOCKS = 100;
|
|
32
|
+
|
|
33
|
+
function toText(value: unknown): string {
|
|
34
|
+
return typeof value === "string" ? value : (JSON.stringify(value, null, 2) ?? String(value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The structured content of a result: the result as JSON, when it is an object. JSON drops
|
|
39
|
+
* what JSON cannot show (a function, `undefined`) and turns a class instance into its JSON form
|
|
40
|
+
* (an ObjectId into its hex text), as the text block does.
|
|
41
|
+
*/
|
|
42
|
+
function structuredContentOf(value: unknown): Record<string, unknown> | undefined {
|
|
43
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
44
|
+
try {
|
|
45
|
+
const json = JSON.parse(JSON.stringify(value)) as unknown;
|
|
46
|
+
return json !== null && typeof json === "object" && !Array.isArray(json) ? (json as Record<string, unknown>) : undefined;
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function generateRequestId(): string {
|
|
53
|
+
return `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* MCP Server Transport
|
|
58
|
+
*
|
|
59
|
+
* Implements the ServerTransport interface for MCP (Model Context Protocol).
|
|
60
|
+
* Maps procedures from PROCEDURE_REGISTRY to MCP tools, handling:
|
|
61
|
+
* - ListToolsRequest: Returns all registered procedures as tools
|
|
62
|
+
* - CallToolRequest: Executes the corresponding procedure
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const server = new ProcedureServer({ autoRegister: true });
|
|
67
|
+
* const mcpTransport = new McpServerTransport(server, {
|
|
68
|
+
* transport: "stdio",
|
|
69
|
+
* serverInfo: { name: "my-server", version: "1.0.0" }
|
|
70
|
+
* });
|
|
71
|
+
* server.addTransport(mcpTransport);
|
|
72
|
+
* await server.start();
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class McpServerTransport implements ServerTransport {
|
|
76
|
+
readonly name = "mcp";
|
|
77
|
+
|
|
78
|
+
private mcpServer: McpServer;
|
|
79
|
+
private server: Server;
|
|
80
|
+
private registry: ProcedureRegistry;
|
|
81
|
+
private options: Required<Pick<McpServerTransportOptions, "transport" | "serverInfo">> &
|
|
82
|
+
McpServerTransportOptions;
|
|
83
|
+
private running = false;
|
|
84
|
+
private tools: Map<string, McpTool> = new Map();
|
|
85
|
+
private registerListener: ((procedure: unknown) => void) | undefined;
|
|
86
|
+
private unregisterListener: ((procedure: unknown) => void) | undefined;
|
|
87
|
+
/** A pending list_changed notification: a burst of registrations sends one */
|
|
88
|
+
private listChangedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
89
|
+
|
|
90
|
+
constructor(server: Server, options: McpServerTransportOptions = {}) {
|
|
91
|
+
this.server = server;
|
|
92
|
+
this.options = {
|
|
93
|
+
transport: options.transport ?? "stdio",
|
|
94
|
+
serverInfo: options.serverInfo ?? {
|
|
95
|
+
name: "procedure-server",
|
|
96
|
+
version: "1.0.0",
|
|
97
|
+
},
|
|
98
|
+
...options,
|
|
99
|
+
};
|
|
100
|
+
// The registry of the server, when it has one (a ProcedureServer with an explicit registry):
|
|
101
|
+
// before, the tools came from the global registry, also when the server served another one
|
|
102
|
+
this.registry =
|
|
103
|
+
options.registry ??
|
|
104
|
+
(server as { getRegistry?: () => ProcedureRegistry }).getRegistry?.() ??
|
|
105
|
+
PROCEDURE_REGISTRY;
|
|
106
|
+
|
|
107
|
+
// Create MCP server with capabilities. The tool list follows the registry: when it changes,
|
|
108
|
+
// the server sends notifications/tools/list_changed (deep dive roadmap 0.4)
|
|
109
|
+
this.mcpServer = new McpServer(this.options.serverInfo, {
|
|
110
|
+
capabilities: {
|
|
111
|
+
tools: { listChanged: true },
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Setup request handlers
|
|
116
|
+
this.setupHandlers();
|
|
117
|
+
|
|
118
|
+
// The registry listeners are attached in start() and removed in stop(): attaching them here
|
|
119
|
+
// leaked them for a transport that never started or failed to start (BUGS-2026-07 L28)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Setup MCP request handlers.
|
|
124
|
+
*/
|
|
125
|
+
private setupHandlers(): void {
|
|
126
|
+
// ListToolsRequestSchema -> List all procedures as tools
|
|
127
|
+
this.mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
128
|
+
this.log("ListToolsRequest received");
|
|
129
|
+
|
|
130
|
+
const tools = Array.from(this.tools.values()).map(toToolDefinition);
|
|
131
|
+
|
|
132
|
+
this.log(`Returning ${tools.length} tools`);
|
|
133
|
+
|
|
134
|
+
return { tools };
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// CallToolRequestSchema -> Execute procedure
|
|
138
|
+
this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
139
|
+
const { name, arguments: args } = request.params;
|
|
140
|
+
|
|
141
|
+
this.log(`CallToolRequest: ${name}`);
|
|
142
|
+
|
|
143
|
+
// Find tool
|
|
144
|
+
const tool = this.tools.get(name);
|
|
145
|
+
if (!tool) {
|
|
146
|
+
this.log(`Tool not found: ${name}`);
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text" as const, text: `Tool not found: ${name}` }],
|
|
149
|
+
isError: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Decode path from tool name. The method is the core mapping of every host (deep dive
|
|
154
|
+
// architecture review 3.3)
|
|
155
|
+
const path = decodePath(name);
|
|
156
|
+
|
|
157
|
+
// Build ServerRequest
|
|
158
|
+
const serverRequest = {
|
|
159
|
+
id: generateRequestId(),
|
|
160
|
+
method: pathToMethod(path),
|
|
161
|
+
payload: args ?? {},
|
|
162
|
+
metadata: {
|
|
163
|
+
transport: "mcp" as const,
|
|
164
|
+
mcpRequestId: generateRequestId(),
|
|
165
|
+
},
|
|
166
|
+
// A cancelled tool call (notifications/cancelled) aborts the procedure
|
|
167
|
+
signal: extra.signal,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Process through universal server
|
|
172
|
+
const response = await this.server.handle(serverRequest);
|
|
173
|
+
|
|
174
|
+
this.log(`Tool ${name} completed: ${response.status.type}`);
|
|
175
|
+
|
|
176
|
+
// Convert to MCP result
|
|
177
|
+
if (response.status.type === "success" && response.stream) {
|
|
178
|
+
return await this.streamResult(response.stream, extra);
|
|
179
|
+
}
|
|
180
|
+
if (response.status.type === "success") {
|
|
181
|
+
const structured = tool.outputSchema ? structuredContentOf(response.payload) : undefined;
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text" as const, text: toText(response.payload) }],
|
|
184
|
+
// MCP structured output: the result as an object, for a tool with an output schema.
|
|
185
|
+
// The text block stays, for clients that do not read structured content.
|
|
186
|
+
...(structured ? { structuredContent: structured } : {}),
|
|
187
|
+
};
|
|
188
|
+
} else {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text" as const,
|
|
193
|
+
text: response.status.message ?? "Unknown error",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
this.log(`Tool ${name} error: ${message}`);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
content: [{ type: "text" as const, text: message }],
|
|
205
|
+
isError: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Setup registry event listeners for dynamic tool updates.
|
|
213
|
+
*/
|
|
214
|
+
/**
|
|
215
|
+
* The result of a streaming procedure. An MCP tool result is one message, so the transport
|
|
216
|
+
* reads the whole stream. Each item becomes a text block (the last MAX_STREAM_BLOCKS items),
|
|
217
|
+
* and a progress notification when the caller sent a progress token, so a client can show
|
|
218
|
+
* the items while the stream runs.
|
|
219
|
+
*/
|
|
220
|
+
private async streamResult(
|
|
221
|
+
stream: AsyncIterable<unknown>,
|
|
222
|
+
extra: {
|
|
223
|
+
_meta?: { progressToken?: string | number | undefined } | undefined;
|
|
224
|
+
sendNotification: (notification: ServerNotification) => Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
|
|
227
|
+
const progressToken = extra._meta?.progressToken;
|
|
228
|
+
const blocks: Array<{ type: "text"; text: string }> = [];
|
|
229
|
+
let count = 0;
|
|
230
|
+
try {
|
|
231
|
+
for await (const item of stream) {
|
|
232
|
+
count++;
|
|
233
|
+
const text = toText(item);
|
|
234
|
+
blocks.push({ type: "text", text });
|
|
235
|
+
if (blocks.length > MAX_STREAM_BLOCKS) blocks.shift();
|
|
236
|
+
if (progressToken !== undefined) {
|
|
237
|
+
await extra
|
|
238
|
+
.sendNotification({
|
|
239
|
+
method: "notifications/progress",
|
|
240
|
+
params: { progressToken, progress: count, message: text.slice(0, 1000) },
|
|
241
|
+
})
|
|
242
|
+
.catch(() => undefined);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch (error) {
|
|
246
|
+
blocks.push({ type: "text", text: error instanceof Error ? error.message : String(error) });
|
|
247
|
+
return { content: blocks, isError: true };
|
|
248
|
+
}
|
|
249
|
+
if (count > MAX_STREAM_BLOCKS) {
|
|
250
|
+
blocks.unshift({ type: "text", text: `(${count} items: the first ${count - MAX_STREAM_BLOCKS} are not shown)` });
|
|
251
|
+
}
|
|
252
|
+
if (count === 0) blocks.push({ type: "text", text: "(no items)" });
|
|
253
|
+
return { content: blocks };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private setupRegistryListeners(): void {
|
|
257
|
+
// Create listener functions that we can later remove
|
|
258
|
+
this.registerListener = () => {
|
|
259
|
+
this.log("Procedure registered, refreshing tools");
|
|
260
|
+
this.refreshTools();
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
this.unregisterListener = () => {
|
|
264
|
+
this.log("Procedure unregistered, refreshing tools");
|
|
265
|
+
this.refreshTools();
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Refresh tools when procedures are registered/unregistered
|
|
269
|
+
this.registry.on("register", this.registerListener);
|
|
270
|
+
this.registry.on("unregister", this.unregisterListener);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Remove the registry event listeners, if attached.
|
|
275
|
+
*/
|
|
276
|
+
private removeRegistryListeners(): void {
|
|
277
|
+
if (this.registerListener) {
|
|
278
|
+
this.registry.off("register", this.registerListener);
|
|
279
|
+
this.registerListener = undefined;
|
|
280
|
+
}
|
|
281
|
+
if (this.unregisterListener) {
|
|
282
|
+
this.registry.off("unregister", this.unregisterListener);
|
|
283
|
+
this.unregisterListener = undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Refresh the tool list from the registry.
|
|
289
|
+
*/
|
|
290
|
+
private refreshTools(): void {
|
|
291
|
+
const procedures = this.registry.getAll();
|
|
292
|
+
const mcpTools = proceduresToMcpTools(procedures, this.options.toolFilter);
|
|
293
|
+
const before = this.toolSignature();
|
|
294
|
+
|
|
295
|
+
this.tools.clear();
|
|
296
|
+
for (const tool of mcpTools) {
|
|
297
|
+
this.tools.set(tool.name, tool);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.log(`Refreshed tools: ${this.tools.size} available`);
|
|
301
|
+
if (this.running && this.toolSignature() !== before) this.scheduleListChanged();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The tool names and descriptions: a change of this text is a change of the tool list. */
|
|
305
|
+
private toolSignature(): string {
|
|
306
|
+
const entries = Array.from(this.tools.values(), (tool) => [tool.name, tool.description ?? ""]);
|
|
307
|
+
return JSON.stringify(entries.sort((a, b) => (a[0]! < b[0]! ? -1 : a[0]! > b[0]! ? 1 : 0)));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Send one notifications/tools/list_changed for a burst of registry events. */
|
|
311
|
+
private scheduleListChanged(): void {
|
|
312
|
+
if (this.listChangedTimer) return;
|
|
313
|
+
this.listChangedTimer = setTimeout(() => {
|
|
314
|
+
this.listChangedTimer = undefined;
|
|
315
|
+
if (!this.running) return;
|
|
316
|
+
this.mcpServer.sendToolListChanged().catch((error: unknown) => {
|
|
317
|
+
this.log(`list_changed failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
318
|
+
});
|
|
319
|
+
}, 0);
|
|
320
|
+
this.listChangedTimer.unref?.();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Log a debug message if debug mode is enabled.
|
|
325
|
+
*/
|
|
326
|
+
private log(message: string): void {
|
|
327
|
+
if (this.options.debug) {
|
|
328
|
+
// MUST be stderr: the stdio MCP transport uses stdout for JSON-RPC framing, so any
|
|
329
|
+
// stdout write here corrupts the protocol stream. See documentation/BUGS-2026-07.md (M39/Bug4).
|
|
330
|
+
console.error(`[${this.name}] ${message}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Start the MCP transport.
|
|
336
|
+
*/
|
|
337
|
+
async start(): Promise<void> {
|
|
338
|
+
if (this.running) {
|
|
339
|
+
this.log("Already running");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Refresh tools before starting, and keep them current while running
|
|
344
|
+
this.refreshTools();
|
|
345
|
+
this.setupRegistryListeners();
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// Create appropriate transport
|
|
349
|
+
let transport: SdkTransport;
|
|
350
|
+
if (this.options.sdkTransport) {
|
|
351
|
+
transport = this.options.sdkTransport;
|
|
352
|
+
this.log("Using the given transport");
|
|
353
|
+
} else if (this.options.transport === "stdio") {
|
|
354
|
+
transport = createStdioTransport();
|
|
355
|
+
this.log("Using stdio transport");
|
|
356
|
+
} else if (this.options.transport === "sse") {
|
|
357
|
+
transport = createSseTransport(this.options.sseOptions);
|
|
358
|
+
this.log(`Using SSE transport on ${this.options.sseOptions?.path ?? "/mcp/sse"}`);
|
|
359
|
+
} else {
|
|
360
|
+
throw new Error(`Unknown MCP transport: ${this.options.transport}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Connect MCP server to transport
|
|
364
|
+
await this.mcpServer.connect(transport);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
this.removeRegistryListeners();
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this.running = true;
|
|
371
|
+
this.log(`MCP server started (${this.options.transport}), ${this.tools.size} tools available`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Stop the MCP transport.
|
|
376
|
+
*/
|
|
377
|
+
async stop(): Promise<void> {
|
|
378
|
+
if (!this.running) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Cleanup registry listeners
|
|
383
|
+
this.removeRegistryListeners();
|
|
384
|
+
if (this.listChangedTimer) {
|
|
385
|
+
clearTimeout(this.listChangedTimer);
|
|
386
|
+
this.listChangedTimer = undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
await this.mcpServer.close();
|
|
390
|
+
this.running = false;
|
|
391
|
+
this.log("MCP server stopped");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Check if the transport is running.
|
|
396
|
+
*/
|
|
397
|
+
isRunning(): boolean {
|
|
398
|
+
return this.running;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get current transport state.
|
|
403
|
+
*/
|
|
404
|
+
getState(): McpServerTransportState {
|
|
405
|
+
return {
|
|
406
|
+
running: this.running,
|
|
407
|
+
toolCount: this.tools.size,
|
|
408
|
+
transport: this.options.transport,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get the list of available tools.
|
|
414
|
+
*/
|
|
415
|
+
getTools(): McpTool[] {
|
|
416
|
+
return Array.from(this.tools.values());
|
|
417
|
+
}
|
|
418
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE Transport Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Creates an MCP SSE (Server-Sent Events) transport for HTTP streaming.
|
|
5
|
+
* NOTE: SSE transport requires HTTP server integration - use with Express or similar.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SseTransportOptions } from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SSE transport is not yet implemented as a standalone transport.
|
|
12
|
+
*
|
|
13
|
+
* SSE requires HTTP server integration with:
|
|
14
|
+
* - GET endpoint for SSE connection
|
|
15
|
+
* - POST endpoint for messages
|
|
16
|
+
*
|
|
17
|
+
* For now, use stdio transport or implement SSE with your HTTP framework directly.
|
|
18
|
+
*
|
|
19
|
+
* @throws Error - SSE transport requires HTTP server integration
|
|
20
|
+
*/
|
|
21
|
+
export function createSseTransport(
|
|
22
|
+
_options?: SseTransportOptions
|
|
23
|
+
): never {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"SSE transport requires HTTP server integration. " +
|
|
26
|
+
"Use stdio transport for standalone MCP server, or integrate SSE " +
|
|
27
|
+
"with your HTTP framework (Express, etc.) using SSEServerTransport directly."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio Transport Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Creates an MCP stdio transport for CLI usage.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a stdio transport for MCP server.
|
|
11
|
+
*
|
|
12
|
+
* Stdio transport uses standard input/output for communication,
|
|
13
|
+
* suitable for CLI tools and desktop app integrations.
|
|
14
|
+
*
|
|
15
|
+
* @returns MCP stdio transport instance
|
|
16
|
+
*/
|
|
17
|
+
export function createStdioTransport(): StdioServerTransport {
|
|
18
|
+
return new StdioServerTransport();
|
|
19
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Transport Types
|
|
3
|
+
*
|
|
4
|
+
* Configuration options for the MCP server transport.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Transport as SdkTransport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
8
|
+
import type { ProcedureRegistry } from "@procedures/client";
|
|
9
|
+
import type { McpServerInfo, McpToolFilter } from "@procedures/mcp";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SSE transport configuration options.
|
|
13
|
+
*/
|
|
14
|
+
export interface SseTransportOptions {
|
|
15
|
+
/** Port to listen on (default: 3002) */
|
|
16
|
+
port?: number;
|
|
17
|
+
/** Host to bind to (default: "0.0.0.0") */
|
|
18
|
+
host?: string;
|
|
19
|
+
/** Path for SSE endpoint (default: "/mcp/sse") */
|
|
20
|
+
path?: string;
|
|
21
|
+
/** Enable CORS (default: true) */
|
|
22
|
+
cors?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* MCP Server Transport configuration options.
|
|
27
|
+
*/
|
|
28
|
+
export interface McpServerTransportOptions {
|
|
29
|
+
/** Transport type: stdio or sse (default: "stdio") */
|
|
30
|
+
transport?: "stdio" | "sse";
|
|
31
|
+
|
|
32
|
+
/** Procedure registry to use (defaults to PROCEDURE_REGISTRY) */
|
|
33
|
+
registry?: ProcedureRegistry;
|
|
34
|
+
|
|
35
|
+
/** Server info for MCP protocol */
|
|
36
|
+
serverInfo?: McpServerInfo;
|
|
37
|
+
|
|
38
|
+
/** Tool filtering options */
|
|
39
|
+
toolFilter?: McpToolFilter;
|
|
40
|
+
|
|
41
|
+
/** SSE-specific options (when transport is "sse") */
|
|
42
|
+
sseOptions?: SseTransportOptions;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* An MCP SDK transport to connect, in place of `transport`. Use it to embed the server in
|
|
46
|
+
* another process, or in a test (the SDK's `InMemoryTransport`).
|
|
47
|
+
*/
|
|
48
|
+
sdkTransport?: SdkTransport;
|
|
49
|
+
|
|
50
|
+
/** Enable debug logging */
|
|
51
|
+
debug?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* MCP Server Transport state.
|
|
56
|
+
*/
|
|
57
|
+
export interface McpServerTransportState {
|
|
58
|
+
/** Whether the transport is running */
|
|
59
|
+
running: boolean;
|
|
60
|
+
/** Number of registered tools */
|
|
61
|
+
toolCount: number;
|
|
62
|
+
/** Transport type in use */
|
|
63
|
+
transport: "stdio" | "sse";
|
|
64
|
+
}
|