@faapi/mcp 0.0.0-canary.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/index.d.ts +600 -0
- package/dist/index.js +1414 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1414 @@
|
|
|
1
|
+
// src/mcpServer.ts
|
|
2
|
+
import { z, toJSONSchema } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/jsonRpc.ts
|
|
5
|
+
var ErrorCode = {
|
|
6
|
+
// JSON-RPC 标准错误码
|
|
7
|
+
ParseError: -32700,
|
|
8
|
+
InvalidRequest: -32600,
|
|
9
|
+
MethodNotFound: -32601,
|
|
10
|
+
InvalidParams: -32602,
|
|
11
|
+
InternalError: -32603,
|
|
12
|
+
// MCP 扩展错误码
|
|
13
|
+
ConnectionClosed: -32e3,
|
|
14
|
+
RequestTimeout: -32001
|
|
15
|
+
};
|
|
16
|
+
function isRequest(msg) {
|
|
17
|
+
return "method" in msg && "id" in msg && !("result" in msg) && !("error" in msg);
|
|
18
|
+
}
|
|
19
|
+
function isNotification(msg) {
|
|
20
|
+
return "method" in msg && !("id" in msg);
|
|
21
|
+
}
|
|
22
|
+
function isResultResponse(msg) {
|
|
23
|
+
return "result" in msg && "id" in msg;
|
|
24
|
+
}
|
|
25
|
+
function isErrorResponse(msg) {
|
|
26
|
+
return "error" in msg && "id" in msg;
|
|
27
|
+
}
|
|
28
|
+
function createResultResponse(id, result) {
|
|
29
|
+
return { jsonrpc: "2.0", id, result };
|
|
30
|
+
}
|
|
31
|
+
function createErrorResponse(id, code, message, data) {
|
|
32
|
+
const error = { code, message };
|
|
33
|
+
if (data !== void 0) error.data = data;
|
|
34
|
+
return { jsonrpc: "2.0", id, error };
|
|
35
|
+
}
|
|
36
|
+
function parseJsonRpcMessage(data) {
|
|
37
|
+
if (Array.isArray(data)) {
|
|
38
|
+
if (data.length === 0) {
|
|
39
|
+
throw new JsonRpcParseError("Invalid Request: empty batch");
|
|
40
|
+
}
|
|
41
|
+
return data.map((item) => parseSingleMessage(item));
|
|
42
|
+
}
|
|
43
|
+
return [parseSingleMessage(data)];
|
|
44
|
+
}
|
|
45
|
+
var JsonRpcParseError = class extends Error {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "JsonRpcParseError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function parseSingleMessage(data) {
|
|
52
|
+
if (typeof data !== "object" || data === null) {
|
|
53
|
+
throw new JsonRpcParseError("Invalid Request: not an object");
|
|
54
|
+
}
|
|
55
|
+
const obj = data;
|
|
56
|
+
if (obj.jsonrpc !== "2.0") {
|
|
57
|
+
throw new JsonRpcParseError('Invalid Request: jsonrpc must be "2.0"');
|
|
58
|
+
}
|
|
59
|
+
if (typeof obj.method === "string" && (typeof obj.id === "string" || typeof obj.id === "number")) {
|
|
60
|
+
return {
|
|
61
|
+
jsonrpc: "2.0",
|
|
62
|
+
id: obj.id,
|
|
63
|
+
method: obj.method,
|
|
64
|
+
...obj.params !== void 0 && { params: obj.params }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (typeof obj.method === "string" && obj.id === void 0) {
|
|
68
|
+
return {
|
|
69
|
+
jsonrpc: "2.0",
|
|
70
|
+
method: obj.method,
|
|
71
|
+
...obj.params !== void 0 && { params: obj.params }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if ("result" in obj && (typeof obj.id === "string" || typeof obj.id === "number")) {
|
|
75
|
+
return {
|
|
76
|
+
jsonrpc: "2.0",
|
|
77
|
+
id: obj.id,
|
|
78
|
+
result: obj.result
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if ("error" in obj && obj.error !== null && typeof obj.error === "object") {
|
|
82
|
+
return {
|
|
83
|
+
jsonrpc: "2.0",
|
|
84
|
+
id: obj.id ?? null,
|
|
85
|
+
error: obj.error
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
throw new JsonRpcParseError("Invalid Request: unknown message shape");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/session.ts
|
|
92
|
+
import { randomUUID } from "crypto";
|
|
93
|
+
var LOGGING_LEVEL_ORDER = {
|
|
94
|
+
debug: 10,
|
|
95
|
+
info: 20,
|
|
96
|
+
notice: 30,
|
|
97
|
+
warning: 40,
|
|
98
|
+
error: 50,
|
|
99
|
+
critical: 60,
|
|
100
|
+
alert: 70,
|
|
101
|
+
emergency: 80
|
|
102
|
+
};
|
|
103
|
+
var DEFAULT_TTL = 30 * 60 * 1e3;
|
|
104
|
+
var SessionManager = class {
|
|
105
|
+
sessions = /* @__PURE__ */ new Map();
|
|
106
|
+
ttl;
|
|
107
|
+
constructor(ttl = DEFAULT_TTL) {
|
|
108
|
+
this.ttl = ttl;
|
|
109
|
+
}
|
|
110
|
+
/** 是否启用 TTL 过期检查(ttl > 0) */
|
|
111
|
+
get ttlEnabled() {
|
|
112
|
+
return this.ttl > 0;
|
|
113
|
+
}
|
|
114
|
+
/** 创建新会话,返回 session 对象 */
|
|
115
|
+
create() {
|
|
116
|
+
if (this.ttlEnabled) this.cleanupExpired();
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const session = {
|
|
119
|
+
id: randomUUID(),
|
|
120
|
+
initialized: false,
|
|
121
|
+
protocolVersion: "",
|
|
122
|
+
createdAt: now,
|
|
123
|
+
lastActivity: now,
|
|
124
|
+
loggingLevel: "info",
|
|
125
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
126
|
+
subscribedResources: /* @__PURE__ */ new Set()
|
|
127
|
+
};
|
|
128
|
+
this.sessions.set(session.id, session);
|
|
129
|
+
return session;
|
|
130
|
+
}
|
|
131
|
+
/** 按 ID 获取会话(更新最后活动时间,过期返回 undefined) */
|
|
132
|
+
get(id) {
|
|
133
|
+
const session = this.sessions.get(id);
|
|
134
|
+
if (!session) return void 0;
|
|
135
|
+
if (this.ttlEnabled && Date.now() - session.lastActivity > this.ttl) {
|
|
136
|
+
this.closeSubscribers(session);
|
|
137
|
+
this.sessions.delete(id);
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
session.lastActivity = Date.now();
|
|
141
|
+
return session;
|
|
142
|
+
}
|
|
143
|
+
/** 是否存在(不更新活动时间,过期会话返回 false) */
|
|
144
|
+
has(id) {
|
|
145
|
+
const session = this.sessions.get(id);
|
|
146
|
+
if (!session) return false;
|
|
147
|
+
if (this.ttlEnabled && Date.now() - session.lastActivity > this.ttl) {
|
|
148
|
+
this.closeSubscribers(session);
|
|
149
|
+
this.sessions.delete(id);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
/** 销毁会话(关闭所有 SSE 订阅者) */
|
|
155
|
+
delete(id) {
|
|
156
|
+
const session = this.sessions.get(id);
|
|
157
|
+
if (session) {
|
|
158
|
+
this.closeSubscribers(session);
|
|
159
|
+
}
|
|
160
|
+
return this.sessions.delete(id);
|
|
161
|
+
}
|
|
162
|
+
/** 当前会话数(含可能尚未清理的过期会话) */
|
|
163
|
+
get size() {
|
|
164
|
+
return this.sessions.size;
|
|
165
|
+
}
|
|
166
|
+
/** 获取所有 session ID 列表(用于全局广播通知) */
|
|
167
|
+
allSessionIds() {
|
|
168
|
+
return [...this.sessions.keys()];
|
|
169
|
+
}
|
|
170
|
+
/** 清空所有会话 */
|
|
171
|
+
clear() {
|
|
172
|
+
for (const session of this.sessions.values()) {
|
|
173
|
+
this.closeSubscribers(session);
|
|
174
|
+
}
|
|
175
|
+
this.sessions.clear();
|
|
176
|
+
}
|
|
177
|
+
/** 注册 SSE 订阅者到 session */
|
|
178
|
+
addSubscriber(sessionId, controller) {
|
|
179
|
+
const session = this.get(sessionId);
|
|
180
|
+
if (!session) return void 0;
|
|
181
|
+
const subscriber = { controller, sessionId };
|
|
182
|
+
session.subscribers.add(subscriber);
|
|
183
|
+
return subscriber;
|
|
184
|
+
}
|
|
185
|
+
/** 注销 SSE 订阅者 */
|
|
186
|
+
removeSubscriber(subscriber) {
|
|
187
|
+
const session = this.sessions.get(subscriber.sessionId);
|
|
188
|
+
if (session) {
|
|
189
|
+
session.subscribers.delete(subscriber);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** 向 session 的所有订阅者推送 SSE 数据 */
|
|
193
|
+
broadcastToSession(sessionId, data) {
|
|
194
|
+
const session = this.sessions.get(sessionId);
|
|
195
|
+
if (!session) return;
|
|
196
|
+
const encoder = new TextEncoder();
|
|
197
|
+
for (const sub of session.subscribers) {
|
|
198
|
+
try {
|
|
199
|
+
sub.controller.enqueue(encoder.encode(data));
|
|
200
|
+
} catch {
|
|
201
|
+
session.subscribers.delete(sub);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** 判断指定级别的日志是否应该推送(>= session.loggingLevel) */
|
|
206
|
+
shouldLog(sessionId, level) {
|
|
207
|
+
const session = this.sessions.get(sessionId);
|
|
208
|
+
if (!session) return false;
|
|
209
|
+
return LOGGING_LEVEL_ORDER[level] >= LOGGING_LEVEL_ORDER[session.loggingLevel];
|
|
210
|
+
}
|
|
211
|
+
/** 添加资源订阅(将 uri 加入 session.subscribedResources) */
|
|
212
|
+
subscribeResource(sessionId, uri) {
|
|
213
|
+
const session = this.sessions.get(sessionId);
|
|
214
|
+
if (!session) return false;
|
|
215
|
+
session.subscribedResources.add(uri);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
/** 取消资源订阅(从 session.subscribedResources 移除 uri) */
|
|
219
|
+
unsubscribeResource(sessionId, uri) {
|
|
220
|
+
const session = this.sessions.get(sessionId);
|
|
221
|
+
if (!session) return false;
|
|
222
|
+
session.subscribedResources.delete(uri);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
/** 找出所有订阅了指定 URI 的 session id 列表 */
|
|
226
|
+
findSubscribersOfUri(uri) {
|
|
227
|
+
const result = [];
|
|
228
|
+
for (const session of this.sessions.values()) {
|
|
229
|
+
if (session.subscribedResources.has(uri)) {
|
|
230
|
+
result.push(session.id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
/** 关闭 session 的所有订阅者(用于 session 销毁/过期) */
|
|
236
|
+
closeSubscribers(session) {
|
|
237
|
+
for (const sub of session.subscribers) {
|
|
238
|
+
try {
|
|
239
|
+
sub.controller.close();
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
session.subscribers.clear();
|
|
244
|
+
}
|
|
245
|
+
/** 清理所有过期会话 */
|
|
246
|
+
cleanupExpired() {
|
|
247
|
+
if (!this.ttlEnabled) return;
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
for (const [id, session] of this.sessions) {
|
|
250
|
+
if (now - session.lastActivity > this.ttl) {
|
|
251
|
+
this.closeSubscribers(session);
|
|
252
|
+
this.sessions.delete(id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/mcpServer.ts
|
|
259
|
+
var PROTOCOL_VERSION = "2025-06-18";
|
|
260
|
+
var SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26"];
|
|
261
|
+
var BUILTIN_METHODS = /* @__PURE__ */ new Set([
|
|
262
|
+
"initialize",
|
|
263
|
+
"ping",
|
|
264
|
+
"notifications/initialized",
|
|
265
|
+
"notifications/cancelled",
|
|
266
|
+
"tools/list",
|
|
267
|
+
"tools/call",
|
|
268
|
+
"resources/list",
|
|
269
|
+
"resources/read",
|
|
270
|
+
"resources/templates/list",
|
|
271
|
+
"resources/subscribe",
|
|
272
|
+
"resources/unsubscribe",
|
|
273
|
+
"prompts/list",
|
|
274
|
+
"prompts/get",
|
|
275
|
+
"logging/setLevel",
|
|
276
|
+
"completion/complete"
|
|
277
|
+
]);
|
|
278
|
+
var DEFAULT_PAGE_SIZE = 100;
|
|
279
|
+
function encodeCursor(offset) {
|
|
280
|
+
return Buffer.from(String(offset)).toString("base64");
|
|
281
|
+
}
|
|
282
|
+
function decodeCursor(cursor) {
|
|
283
|
+
const decoded = Buffer.from(cursor, "base64").toString("utf-8");
|
|
284
|
+
const offset = Number.parseInt(decoded, 10);
|
|
285
|
+
if (!Number.isFinite(offset) || offset < 0 || decoded !== String(offset)) {
|
|
286
|
+
throw new Error(`Invalid cursor: ${cursor}`);
|
|
287
|
+
}
|
|
288
|
+
return offset;
|
|
289
|
+
}
|
|
290
|
+
function paginate(items, cursor, pageSize) {
|
|
291
|
+
const offset = cursor ? decodeCursor(cursor) : 0;
|
|
292
|
+
if (offset > items.length) {
|
|
293
|
+
throw new Error(`cursor out of range: offset ${offset} > total ${items.length}`);
|
|
294
|
+
}
|
|
295
|
+
const slice = items.slice(offset, offset + pageSize);
|
|
296
|
+
const nextOffset = offset + pageSize;
|
|
297
|
+
const nextCursor = nextOffset < items.length ? encodeCursor(nextOffset) : void 0;
|
|
298
|
+
return { items: slice, nextCursor };
|
|
299
|
+
}
|
|
300
|
+
var VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
301
|
+
var PLACEHOLDER_RE = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
302
|
+
function compileUriTemplate(uriTemplate) {
|
|
303
|
+
const paramNames = [];
|
|
304
|
+
let match;
|
|
305
|
+
PLACEHOLDER_RE.lastIndex = 0;
|
|
306
|
+
while ((match = PLACEHOLDER_RE.exec(uriTemplate)) !== null) {
|
|
307
|
+
const name = match[1];
|
|
308
|
+
if (!VAR_NAME_RE.test(name)) {
|
|
309
|
+
throw new Error(`Invalid URI template variable name: ${name}`);
|
|
310
|
+
}
|
|
311
|
+
paramNames.push(name);
|
|
312
|
+
}
|
|
313
|
+
if (paramNames.length === 0) {
|
|
314
|
+
throw new Error(`URI template must contain at least one {var} placeholder: ${uriTemplate}`);
|
|
315
|
+
}
|
|
316
|
+
const seen = /* @__PURE__ */ new Set();
|
|
317
|
+
for (const name of paramNames) {
|
|
318
|
+
if (seen.has(name)) {
|
|
319
|
+
throw new Error(`Duplicate URI template variable: ${name}`);
|
|
320
|
+
}
|
|
321
|
+
seen.add(name);
|
|
322
|
+
}
|
|
323
|
+
const isSingle = paramNames.length === 1;
|
|
324
|
+
const parts = [];
|
|
325
|
+
let lastIdx = 0;
|
|
326
|
+
PLACEHOLDER_RE.lastIndex = 0;
|
|
327
|
+
let m;
|
|
328
|
+
while ((m = PLACEHOLDER_RE.exec(uriTemplate)) !== null) {
|
|
329
|
+
const literal = uriTemplate.slice(lastIdx, m.index);
|
|
330
|
+
parts.push(escapeRegex(literal));
|
|
331
|
+
parts.push(isSingle ? "(.+)" : "([^/]+)");
|
|
332
|
+
lastIdx = m.index + m[0].length;
|
|
333
|
+
}
|
|
334
|
+
parts.push(escapeRegex(uriTemplate.slice(lastIdx)));
|
|
335
|
+
const regex = new RegExp(`^${parts.join("")}$`);
|
|
336
|
+
return { regex, paramNames };
|
|
337
|
+
}
|
|
338
|
+
function escapeRegex(s) {
|
|
339
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
340
|
+
}
|
|
341
|
+
function matchUriTemplate(regex, paramNames, uri) {
|
|
342
|
+
const m = regex.exec(uri);
|
|
343
|
+
if (!m) return null;
|
|
344
|
+
const params = {};
|
|
345
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
346
|
+
params[paramNames[i]] = m[i + 1];
|
|
347
|
+
}
|
|
348
|
+
return params;
|
|
349
|
+
}
|
|
350
|
+
var McpServer = class {
|
|
351
|
+
constructor(options) {
|
|
352
|
+
this.options = options;
|
|
353
|
+
this.sessions = new SessionManager(this.options.sessionTtl);
|
|
354
|
+
this.pageSize = this.options.defaultPageSize ?? DEFAULT_PAGE_SIZE;
|
|
355
|
+
this.toolsListChanged = this.options.toolsListChanged ?? false;
|
|
356
|
+
this.resourcesListChanged = this.options.resourcesListChanged ?? false;
|
|
357
|
+
this.promptsListChanged = this.options.promptsListChanged ?? false;
|
|
358
|
+
}
|
|
359
|
+
options;
|
|
360
|
+
tools = /* @__PURE__ */ new Map();
|
|
361
|
+
resources = /* @__PURE__ */ new Map();
|
|
362
|
+
resourceTemplates = /* @__PURE__ */ new Map();
|
|
363
|
+
prompts = /* @__PURE__ */ new Map();
|
|
364
|
+
completions = /* @__PURE__ */ new Map();
|
|
365
|
+
methods = /* @__PURE__ */ new Map();
|
|
366
|
+
sessions;
|
|
367
|
+
pageSize;
|
|
368
|
+
toolsListChanged;
|
|
369
|
+
resourcesListChanged;
|
|
370
|
+
promptsListChanged;
|
|
371
|
+
/** 注册 tool */
|
|
372
|
+
tool(name, definition) {
|
|
373
|
+
if (this.tools.has(name)) {
|
|
374
|
+
throw new Error(`Tool "${name}" is already registered`);
|
|
375
|
+
}
|
|
376
|
+
let inputSchema;
|
|
377
|
+
let jsonSchema;
|
|
378
|
+
if (definition.input) {
|
|
379
|
+
inputSchema = buildZodObject(definition.input);
|
|
380
|
+
jsonSchema = toJSONSchema(inputSchema, { target: "draft-7" });
|
|
381
|
+
delete jsonSchema.$schema;
|
|
382
|
+
delete jsonSchema.additionalProperties;
|
|
383
|
+
}
|
|
384
|
+
this.tools.set(name, { name, definition, inputSchema, jsonSchema });
|
|
385
|
+
}
|
|
386
|
+
/** 注册 resource */
|
|
387
|
+
resource(uri, definition) {
|
|
388
|
+
if (this.resources.has(uri)) {
|
|
389
|
+
throw new Error(`Resource "${uri}" is already registered`);
|
|
390
|
+
}
|
|
391
|
+
this.resources.set(uri, { uri, definition });
|
|
392
|
+
}
|
|
393
|
+
/** 注册 resource template(RFC 6570 URI 模板) */
|
|
394
|
+
resourceTemplate(uriTemplate, definition) {
|
|
395
|
+
if (this.resourceTemplates.has(uriTemplate)) {
|
|
396
|
+
throw new Error(`Resource template "${uriTemplate}" is already registered`);
|
|
397
|
+
}
|
|
398
|
+
const { regex, paramNames } = compileUriTemplate(uriTemplate);
|
|
399
|
+
this.resourceTemplates.set(uriTemplate, {
|
|
400
|
+
uriTemplate,
|
|
401
|
+
regex,
|
|
402
|
+
paramNames,
|
|
403
|
+
definition
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/** 注册 prompt */
|
|
407
|
+
prompt(name, definition) {
|
|
408
|
+
if (this.prompts.has(name)) {
|
|
409
|
+
throw new Error(`Prompt "${name}" is already registered`);
|
|
410
|
+
}
|
|
411
|
+
this.prompts.set(name, { name, definition });
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 注册参数补全 handler
|
|
415
|
+
*
|
|
416
|
+
* - `ref`:`{ type: 'ref/prompt', name }` 或 `{ type: 'ref/resource', uri }`(对资源模板,uri 是模板字符串)
|
|
417
|
+
* - `argumentName`:补全的参数名(对应 prompt arguments 中的 name,或 resource template URI 模板中的变量名)
|
|
418
|
+
* - 同一 (ref, argumentName) 重复注册抛错
|
|
419
|
+
*/
|
|
420
|
+
completion(ref, argumentName, handler) {
|
|
421
|
+
const refType = ref.type;
|
|
422
|
+
const refId = refType === "ref/prompt" ? ref.name : ref.uri;
|
|
423
|
+
const key = completionKey(refType, refId, argumentName);
|
|
424
|
+
if (this.completions.has(key)) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Completion for ${refType} "${refId}" argument "${argumentName}" is already registered`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
this.completions.set(key, { refType, refId, argumentName, handler });
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 注册自定义 JSON-RPC 方法 handler(业务拓展)
|
|
433
|
+
*
|
|
434
|
+
* - 方法名建议使用 `appName/action` 格式,避免与 MCP 标准方法冲突
|
|
435
|
+
* - 与 MCP 内置方法(initialize/ping/tools/* 等)冲突时抛错
|
|
436
|
+
* - 重复注册同名方法抛错
|
|
437
|
+
*
|
|
438
|
+
* handler 返回值规则:
|
|
439
|
+
* - 返回普通对象:作为 JSON-RPC result 字段
|
|
440
|
+
* - 返回 JsonRpcErrorResponse(含 error 字段):作为错误响应
|
|
441
|
+
*/
|
|
442
|
+
method(name, handler) {
|
|
443
|
+
if (BUILTIN_METHODS.has(name)) {
|
|
444
|
+
throw new Error(`Cannot register built-in method "${name}": it is reserved by MCP protocol`);
|
|
445
|
+
}
|
|
446
|
+
if (this.methods.has(name)) {
|
|
447
|
+
throw new Error(`Method "${name}" is already registered`);
|
|
448
|
+
}
|
|
449
|
+
this.methods.set(name, { name, handler });
|
|
450
|
+
}
|
|
451
|
+
/** 获取已注册 tool 名称列表 */
|
|
452
|
+
listTools() {
|
|
453
|
+
return [...this.tools.keys()];
|
|
454
|
+
}
|
|
455
|
+
/** 获取已注册 resource URI 列表 */
|
|
456
|
+
listResources() {
|
|
457
|
+
return [...this.resources.keys()];
|
|
458
|
+
}
|
|
459
|
+
/** 获取已注册 prompt 名称列表 */
|
|
460
|
+
listPrompts() {
|
|
461
|
+
return [...this.prompts.keys()];
|
|
462
|
+
}
|
|
463
|
+
/** 获取已注册自定义方法名列表(业务拓展) */
|
|
464
|
+
listMethods() {
|
|
465
|
+
return [...this.methods.keys()];
|
|
466
|
+
}
|
|
467
|
+
// ─── remove 方法(运行时删除注册项) ───────────────────
|
|
468
|
+
/**
|
|
469
|
+
* 删除已注册 tool
|
|
470
|
+
*
|
|
471
|
+
* - `toolsListChanged: true` 时自动推送 `notifications/tools/list_changed`
|
|
472
|
+
* - `toolsListChanged: false` 时静默删除(用于 dev 热替换,客户端不感知)
|
|
473
|
+
*/
|
|
474
|
+
removeTool(name) {
|
|
475
|
+
const deleted = this.tools.delete(name);
|
|
476
|
+
if (deleted && this.toolsListChanged) {
|
|
477
|
+
this.notifyToolsListChanged();
|
|
478
|
+
}
|
|
479
|
+
return deleted;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 删除已注册 resource
|
|
483
|
+
*
|
|
484
|
+
* - `resourcesListChanged: true` 时自动推送 `notifications/resources/list_changed`
|
|
485
|
+
*/
|
|
486
|
+
removeResource(uri) {
|
|
487
|
+
const deleted = this.resources.delete(uri);
|
|
488
|
+
if (deleted && this.resourcesListChanged) {
|
|
489
|
+
this.notifyResourcesListChanged();
|
|
490
|
+
}
|
|
491
|
+
return deleted;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* 删除已注册 resource template
|
|
495
|
+
*
|
|
496
|
+
* - `resourcesListChanged: true` 时自动推送 `notifications/resources/list_changed`
|
|
497
|
+
*/
|
|
498
|
+
removeResourceTemplate(uriTemplate) {
|
|
499
|
+
const deleted = this.resourceTemplates.delete(uriTemplate);
|
|
500
|
+
if (deleted && this.resourcesListChanged) {
|
|
501
|
+
this.notifyResourcesListChanged();
|
|
502
|
+
}
|
|
503
|
+
return deleted;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 删除已注册 prompt
|
|
507
|
+
*
|
|
508
|
+
* - `promptsListChanged: true` 时自动推送 `notifications/prompts/list_changed`
|
|
509
|
+
*/
|
|
510
|
+
removePrompt(name) {
|
|
511
|
+
const deleted = this.prompts.delete(name);
|
|
512
|
+
if (deleted && this.promptsListChanged) {
|
|
513
|
+
this.notifyPromptsListChanged();
|
|
514
|
+
}
|
|
515
|
+
return deleted;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* 删除已注册 completion handler
|
|
519
|
+
*
|
|
520
|
+
* - completion 无 list_changed 通知机制(客户端按需请求,无需感知列表变化)
|
|
521
|
+
*/
|
|
522
|
+
removeCompletion(ref, argumentName) {
|
|
523
|
+
const refType = ref.type;
|
|
524
|
+
const refId = refType === "ref/prompt" ? ref.name : ref.uri;
|
|
525
|
+
const key = completionKey(refType, refId, argumentName);
|
|
526
|
+
return this.completions.delete(key);
|
|
527
|
+
}
|
|
528
|
+
/** 删除已注册自定义方法 */
|
|
529
|
+
removeMethod(name) {
|
|
530
|
+
return this.methods.delete(name);
|
|
531
|
+
}
|
|
532
|
+
// ─── list_changed 通知 ───────────────────────────────
|
|
533
|
+
/**
|
|
534
|
+
* 推送 `notifications/tools/list_changed` 到所有 session 的 SSE 订阅者
|
|
535
|
+
*
|
|
536
|
+
* 客户端收到后应重新调用 `tools/list` 拉取最新列表。
|
|
537
|
+
* `removeTool` 在 `toolsListChanged: true` 时自动调用本方法,
|
|
538
|
+
* 业务方也可手动调用(如批量删除后只推送一次)。
|
|
539
|
+
*/
|
|
540
|
+
notifyToolsListChanged() {
|
|
541
|
+
this.broadcastNotificationToAllSessions("notifications/tools/list_changed");
|
|
542
|
+
}
|
|
543
|
+
/** 推送 `notifications/resources/list_changed` 到所有 session 的 SSE 订阅者 */
|
|
544
|
+
notifyResourcesListChanged() {
|
|
545
|
+
this.broadcastNotificationToAllSessions("notifications/resources/list_changed");
|
|
546
|
+
}
|
|
547
|
+
/** 推送 `notifications/prompts/list_changed` 到所有 session 的 SSE 订阅者 */
|
|
548
|
+
notifyPromptsListChanged() {
|
|
549
|
+
this.broadcastNotificationToAllSessions("notifications/prompts/list_changed");
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* 向所有 session 的所有 SSE 订阅者广播通知(内部工具方法)
|
|
553
|
+
*
|
|
554
|
+
* 用于 list_changed 这种"全局广播"语义——所有 session 都应感知列表变化。
|
|
555
|
+
*/
|
|
556
|
+
broadcastNotificationToAllSessions(method, params) {
|
|
557
|
+
const notification = {
|
|
558
|
+
jsonrpc: "2.0",
|
|
559
|
+
method
|
|
560
|
+
};
|
|
561
|
+
if (params !== void 0) {
|
|
562
|
+
notification.params = params;
|
|
563
|
+
}
|
|
564
|
+
const sseData = `data: ${JSON.stringify(notification)}
|
|
565
|
+
|
|
566
|
+
`;
|
|
567
|
+
for (const sessionId of this.sessions.allSessionIds()) {
|
|
568
|
+
this.sessions.broadcastToSession(sessionId, sseData);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/** 获取会话管理器 */
|
|
572
|
+
getSessionManager() {
|
|
573
|
+
return this.sessions;
|
|
574
|
+
}
|
|
575
|
+
/** 获取 GET SSE 流心跳间隔(毫秒) */
|
|
576
|
+
getSseHeartbeatMs() {
|
|
577
|
+
return this.options.sseHeartbeatMs ?? 3e4;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* 处理 JSON-RPC 请求,返回响应消息
|
|
581
|
+
*
|
|
582
|
+
* 通知(无 id)不返回响应,返回 null。
|
|
583
|
+
*/
|
|
584
|
+
async handleJsonRpc(message, session) {
|
|
585
|
+
if (isNotification(message)) {
|
|
586
|
+
return this.handleNotification(message, session);
|
|
587
|
+
}
|
|
588
|
+
if ("id" in message && "method" in message && !("result" in message) && !("error" in message)) {
|
|
589
|
+
return this.handleRequest(message, session);
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
handleNotification(message, session) {
|
|
594
|
+
switch (message.method) {
|
|
595
|
+
case "notifications/initialized":
|
|
596
|
+
if (session) session.initialized = true;
|
|
597
|
+
break;
|
|
598
|
+
case "notifications/cancelled":
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
async handleRequest(request, session) {
|
|
604
|
+
try {
|
|
605
|
+
switch (request.method) {
|
|
606
|
+
case "initialize":
|
|
607
|
+
return this.handleInitialize(request, session);
|
|
608
|
+
case "ping":
|
|
609
|
+
return createResultResponse(request.id, {});
|
|
610
|
+
case "tools/list":
|
|
611
|
+
return this.handleToolsList(request);
|
|
612
|
+
case "tools/call":
|
|
613
|
+
return await this.handleToolsCall(request, session);
|
|
614
|
+
case "resources/list":
|
|
615
|
+
return this.handleResourcesList(request);
|
|
616
|
+
case "resources/read":
|
|
617
|
+
return await this.handleResourcesRead(request, session);
|
|
618
|
+
case "prompts/list":
|
|
619
|
+
return this.handlePromptsList(request);
|
|
620
|
+
case "prompts/get":
|
|
621
|
+
return await this.handlePromptsGet(request, session);
|
|
622
|
+
case "logging/setLevel":
|
|
623
|
+
return this.handleLoggingSetLevel(request, session);
|
|
624
|
+
case "resources/subscribe":
|
|
625
|
+
return this.handleResourcesSubscribe(request, session);
|
|
626
|
+
case "resources/unsubscribe":
|
|
627
|
+
return this.handleResourcesUnsubscribe(request, session);
|
|
628
|
+
case "resources/templates/list":
|
|
629
|
+
return this.handleResourcesTemplatesList(request);
|
|
630
|
+
case "completion/complete":
|
|
631
|
+
return await this.handleCompletionComplete(request);
|
|
632
|
+
default:
|
|
633
|
+
if (this.methods.has(request.method)) {
|
|
634
|
+
return await this.handleCustomMethod(request, session);
|
|
635
|
+
}
|
|
636
|
+
return createErrorResponse(
|
|
637
|
+
request.id,
|
|
638
|
+
ErrorCode.MethodNotFound,
|
|
639
|
+
`Method not found: ${request.method}`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
} catch (err) {
|
|
643
|
+
return createErrorResponse(
|
|
644
|
+
request.id,
|
|
645
|
+
ErrorCode.InternalError,
|
|
646
|
+
err instanceof Error ? err.message : String(err)
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// ─── initialize ──────────────────────────────────────
|
|
651
|
+
handleInitialize(request, session) {
|
|
652
|
+
const params = request.params ?? {};
|
|
653
|
+
const requestedVersion = params.protocolVersion ?? PROTOCOL_VERSION;
|
|
654
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(
|
|
655
|
+
requestedVersion
|
|
656
|
+
) ? requestedVersion : PROTOCOL_VERSION;
|
|
657
|
+
if (!session) {
|
|
658
|
+
session = this.sessions.create();
|
|
659
|
+
}
|
|
660
|
+
session.protocolVersion = protocolVersion;
|
|
661
|
+
session.clientInfo = params.clientInfo;
|
|
662
|
+
const capabilities = {
|
|
663
|
+
tools: { listChanged: this.toolsListChanged },
|
|
664
|
+
// logging capability 始终声明——服务端内置日志推送能力
|
|
665
|
+
logging: {}
|
|
666
|
+
};
|
|
667
|
+
if (this.resources.size > 0 || this.resourceTemplates.size > 0) {
|
|
668
|
+
capabilities.resources = { listChanged: this.resourcesListChanged, subscribe: true };
|
|
669
|
+
}
|
|
670
|
+
if (this.prompts.size > 0) {
|
|
671
|
+
capabilities.prompts = { listChanged: this.promptsListChanged };
|
|
672
|
+
}
|
|
673
|
+
const result = {
|
|
674
|
+
protocolVersion,
|
|
675
|
+
capabilities,
|
|
676
|
+
serverInfo: {
|
|
677
|
+
name: this.options.name,
|
|
678
|
+
version: this.options.version,
|
|
679
|
+
...this.options.title && { title: this.options.title }
|
|
680
|
+
},
|
|
681
|
+
...this.options.instructions && { instructions: this.options.instructions }
|
|
682
|
+
};
|
|
683
|
+
return createResultResponse(request.id, result);
|
|
684
|
+
}
|
|
685
|
+
// ─── tools/list ──────────────────────────────────────
|
|
686
|
+
handleToolsList(request) {
|
|
687
|
+
const params = request.params ?? {};
|
|
688
|
+
const allTools = Array.from(this.tools.values()).map((tool) => ({
|
|
689
|
+
name: tool.name,
|
|
690
|
+
...tool.definition.description && { description: tool.definition.description },
|
|
691
|
+
inputSchema: tool.jsonSchema ?? { type: "object", properties: {} },
|
|
692
|
+
...tool.definition.annotations && { annotations: tool.definition.annotations }
|
|
693
|
+
}));
|
|
694
|
+
let paged;
|
|
695
|
+
try {
|
|
696
|
+
paged = paginate(allTools, params.cursor, this.pageSize);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
return createErrorResponse(
|
|
699
|
+
request.id,
|
|
700
|
+
ErrorCode.InvalidParams,
|
|
701
|
+
err instanceof Error ? err.message : "Invalid cursor"
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
return createResultResponse(request.id, {
|
|
705
|
+
tools: paged.items,
|
|
706
|
+
...paged.nextCursor && { nextCursor: paged.nextCursor }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
// ─── tools/call ──────────────────────────────────────
|
|
710
|
+
async handleToolsCall(request, session) {
|
|
711
|
+
const params = request.params ?? {};
|
|
712
|
+
if (!params.name) {
|
|
713
|
+
return createErrorResponse(
|
|
714
|
+
request.id,
|
|
715
|
+
ErrorCode.InvalidParams,
|
|
716
|
+
"Missing required parameter: name"
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const tool = this.tools.get(params.name);
|
|
720
|
+
if (!tool) {
|
|
721
|
+
return createErrorResponse(
|
|
722
|
+
request.id,
|
|
723
|
+
ErrorCode.InvalidParams,
|
|
724
|
+
`Unknown tool: ${params.name}`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
const args = params.arguments ?? {};
|
|
728
|
+
if (tool.inputSchema) {
|
|
729
|
+
const parsed = tool.inputSchema.safeParse(args);
|
|
730
|
+
if (!parsed.success) {
|
|
731
|
+
return createErrorResponse(
|
|
732
|
+
request.id,
|
|
733
|
+
ErrorCode.InvalidParams,
|
|
734
|
+
"Invalid tool arguments",
|
|
735
|
+
parsed.error.issues
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
Object.assign(args, parsed.data);
|
|
739
|
+
}
|
|
740
|
+
const progressToken = params._meta?.progressToken;
|
|
741
|
+
const extra = {
|
|
742
|
+
sessionId: session?.id ?? "",
|
|
743
|
+
sendLogging: (level, data, logger) => {
|
|
744
|
+
if (session) this.sendLogging(session.id, level, data, logger);
|
|
745
|
+
},
|
|
746
|
+
sendProgress: (progress, total) => {
|
|
747
|
+
if (session) this.sendProgress(session.id, progressToken, progress, total);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
let result;
|
|
751
|
+
try {
|
|
752
|
+
result = await tool.definition.handler(args, extra);
|
|
753
|
+
} catch (err) {
|
|
754
|
+
result = {
|
|
755
|
+
content: [
|
|
756
|
+
{
|
|
757
|
+
type: "text",
|
|
758
|
+
text: `Tool execution failed: ${err instanceof Error ? err.message : String(err)}`
|
|
759
|
+
}
|
|
760
|
+
],
|
|
761
|
+
isError: true
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
return createResultResponse(request.id, result);
|
|
765
|
+
}
|
|
766
|
+
// ─── resources/list ─────────────────────────────────
|
|
767
|
+
handleResourcesList(request) {
|
|
768
|
+
const params = request.params ?? {};
|
|
769
|
+
const allResources = Array.from(this.resources.values()).map((r) => ({
|
|
770
|
+
uri: r.uri,
|
|
771
|
+
name: r.definition.name,
|
|
772
|
+
...r.definition.description && { description: r.definition.description },
|
|
773
|
+
...r.definition.mimeType && { mimeType: r.definition.mimeType }
|
|
774
|
+
}));
|
|
775
|
+
let paged;
|
|
776
|
+
try {
|
|
777
|
+
paged = paginate(allResources, params.cursor, this.pageSize);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
return createErrorResponse(
|
|
780
|
+
request.id,
|
|
781
|
+
ErrorCode.InvalidParams,
|
|
782
|
+
err instanceof Error ? err.message : "Invalid cursor"
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return createResultResponse(request.id, {
|
|
786
|
+
resources: paged.items,
|
|
787
|
+
...paged.nextCursor && { nextCursor: paged.nextCursor }
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
// ─── resources/read ─────────────────────────────────
|
|
791
|
+
async handleResourcesRead(request, session) {
|
|
792
|
+
const params = request.params ?? {};
|
|
793
|
+
if (!params.uri) {
|
|
794
|
+
return createErrorResponse(
|
|
795
|
+
request.id,
|
|
796
|
+
ErrorCode.InvalidParams,
|
|
797
|
+
"Missing required parameter: uri"
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const progressToken = params._meta?.progressToken;
|
|
801
|
+
const sendProgressFn = (progress, total) => {
|
|
802
|
+
if (session) this.sendProgress(session.id, progressToken, progress, total);
|
|
803
|
+
};
|
|
804
|
+
const extra = {
|
|
805
|
+
sessionId: session?.id ?? "",
|
|
806
|
+
sendLogging: (level, data, logger) => {
|
|
807
|
+
if (session) this.sendLogging(session.id, level, data, logger);
|
|
808
|
+
},
|
|
809
|
+
sendProgress: sendProgressFn
|
|
810
|
+
};
|
|
811
|
+
const resource = this.resources.get(params.uri);
|
|
812
|
+
if (resource) {
|
|
813
|
+
const result = await resource.definition.read(params.uri, extra);
|
|
814
|
+
return createResultResponse(request.id, result);
|
|
815
|
+
}
|
|
816
|
+
for (const tpl of this.resourceTemplates.values()) {
|
|
817
|
+
const params2 = matchUriTemplate(tpl.regex, tpl.paramNames, params.uri);
|
|
818
|
+
if (params2) {
|
|
819
|
+
const tplExtra = {
|
|
820
|
+
sessionId: session?.id ?? "",
|
|
821
|
+
sendLogging: (level, data, logger) => {
|
|
822
|
+
if (session) this.sendLogging(session.id, level, data, logger);
|
|
823
|
+
},
|
|
824
|
+
sendProgress: sendProgressFn
|
|
825
|
+
};
|
|
826
|
+
const result = await tpl.definition.read(params.uri, params2, tplExtra);
|
|
827
|
+
return createResultResponse(request.id, result);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return createErrorResponse(
|
|
831
|
+
request.id,
|
|
832
|
+
ErrorCode.InvalidParams,
|
|
833
|
+
`Unknown resource: ${params.uri}`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
// ─── resources/templates/list ──────────────────────
|
|
837
|
+
handleResourcesTemplatesList(request) {
|
|
838
|
+
const params = request.params ?? {};
|
|
839
|
+
const allTemplates = Array.from(this.resourceTemplates.values()).map((t) => ({
|
|
840
|
+
uriTemplate: t.uriTemplate,
|
|
841
|
+
name: t.definition.name,
|
|
842
|
+
...t.definition.description && { description: t.definition.description },
|
|
843
|
+
...t.definition.mimeType && { mimeType: t.definition.mimeType }
|
|
844
|
+
}));
|
|
845
|
+
let paged;
|
|
846
|
+
try {
|
|
847
|
+
paged = paginate(allTemplates, params.cursor, this.pageSize);
|
|
848
|
+
} catch (err) {
|
|
849
|
+
return createErrorResponse(
|
|
850
|
+
request.id,
|
|
851
|
+
ErrorCode.InvalidParams,
|
|
852
|
+
err instanceof Error ? err.message : "Invalid cursor"
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
return createResultResponse(request.id, {
|
|
856
|
+
resourceTemplates: paged.items,
|
|
857
|
+
...paged.nextCursor && { nextCursor: paged.nextCursor }
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
// ─── prompts/list ───────────────────────────────────
|
|
861
|
+
handlePromptsList(request) {
|
|
862
|
+
const params = request.params ?? {};
|
|
863
|
+
const allPrompts = Array.from(this.prompts.values()).map((p) => ({
|
|
864
|
+
name: p.name,
|
|
865
|
+
...p.definition.description && { description: p.definition.description },
|
|
866
|
+
...p.definition.arguments && { arguments: p.definition.arguments }
|
|
867
|
+
}));
|
|
868
|
+
let paged;
|
|
869
|
+
try {
|
|
870
|
+
paged = paginate(allPrompts, params.cursor, this.pageSize);
|
|
871
|
+
} catch (err) {
|
|
872
|
+
return createErrorResponse(
|
|
873
|
+
request.id,
|
|
874
|
+
ErrorCode.InvalidParams,
|
|
875
|
+
err instanceof Error ? err.message : "Invalid cursor"
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
return createResultResponse(request.id, {
|
|
879
|
+
prompts: paged.items,
|
|
880
|
+
...paged.nextCursor && { nextCursor: paged.nextCursor }
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// ─── prompts/get ────────────────────────────────────
|
|
884
|
+
async handlePromptsGet(request, session) {
|
|
885
|
+
const params = request.params ?? {};
|
|
886
|
+
if (!params.name) {
|
|
887
|
+
return createErrorResponse(
|
|
888
|
+
request.id,
|
|
889
|
+
ErrorCode.InvalidParams,
|
|
890
|
+
"Missing required parameter: name"
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
const prompt = this.prompts.get(params.name);
|
|
894
|
+
if (!prompt) {
|
|
895
|
+
return createErrorResponse(
|
|
896
|
+
request.id,
|
|
897
|
+
ErrorCode.InvalidParams,
|
|
898
|
+
`Unknown prompt: ${params.name}`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
const progressToken = params._meta?.progressToken;
|
|
902
|
+
const extra = {
|
|
903
|
+
sessionId: session?.id ?? "",
|
|
904
|
+
sendLogging: (level, data, logger) => {
|
|
905
|
+
if (session) this.sendLogging(session.id, level, data, logger);
|
|
906
|
+
},
|
|
907
|
+
sendProgress: (progress, total) => {
|
|
908
|
+
if (session) this.sendProgress(session.id, progressToken, progress, total);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
const args = params.arguments ?? {};
|
|
912
|
+
const result = await prompt.definition.get(args, extra);
|
|
913
|
+
return createResultResponse(request.id, result);
|
|
914
|
+
}
|
|
915
|
+
// ─── logging/setLevel ──────────────────────────────
|
|
916
|
+
handleLoggingSetLevel(request, session) {
|
|
917
|
+
const params = request.params ?? {};
|
|
918
|
+
if (!params.level) {
|
|
919
|
+
return createErrorResponse(
|
|
920
|
+
request.id,
|
|
921
|
+
ErrorCode.InvalidParams,
|
|
922
|
+
"Missing required parameter: level"
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const validLevels = [
|
|
926
|
+
"debug",
|
|
927
|
+
"info",
|
|
928
|
+
"notice",
|
|
929
|
+
"warning",
|
|
930
|
+
"error",
|
|
931
|
+
"critical",
|
|
932
|
+
"alert",
|
|
933
|
+
"emergency"
|
|
934
|
+
];
|
|
935
|
+
if (!validLevels.includes(params.level)) {
|
|
936
|
+
return createErrorResponse(
|
|
937
|
+
request.id,
|
|
938
|
+
ErrorCode.InvalidParams,
|
|
939
|
+
`Invalid logging level: ${params.level}`
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
if (session) {
|
|
943
|
+
session.loggingLevel = params.level;
|
|
944
|
+
}
|
|
945
|
+
return createResultResponse(request.id, {});
|
|
946
|
+
}
|
|
947
|
+
// ─── resources/subscribe ───────────────────────────
|
|
948
|
+
handleResourcesSubscribe(request, session) {
|
|
949
|
+
const params = request.params ?? {};
|
|
950
|
+
if (!params.uri) {
|
|
951
|
+
return createErrorResponse(
|
|
952
|
+
request.id,
|
|
953
|
+
ErrorCode.InvalidParams,
|
|
954
|
+
"Missing required parameter: uri"
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
if (!session) {
|
|
958
|
+
return createErrorResponse(request.id, ErrorCode.InvalidRequest, "No session for subscribe");
|
|
959
|
+
}
|
|
960
|
+
this.sessions.subscribeResource(session.id, params.uri);
|
|
961
|
+
return createResultResponse(request.id, {});
|
|
962
|
+
}
|
|
963
|
+
// ─── resources/unsubscribe ─────────────────────────
|
|
964
|
+
handleResourcesUnsubscribe(request, session) {
|
|
965
|
+
const params = request.params ?? {};
|
|
966
|
+
if (!params.uri) {
|
|
967
|
+
return createErrorResponse(
|
|
968
|
+
request.id,
|
|
969
|
+
ErrorCode.InvalidParams,
|
|
970
|
+
"Missing required parameter: uri"
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
if (!session) {
|
|
974
|
+
return createErrorResponse(
|
|
975
|
+
request.id,
|
|
976
|
+
ErrorCode.InvalidRequest,
|
|
977
|
+
"No session for unsubscribe"
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
this.sessions.unsubscribeResource(session.id, params.uri);
|
|
981
|
+
return createResultResponse(request.id, {});
|
|
982
|
+
}
|
|
983
|
+
// ─── completion/complete ───────────────────────────
|
|
984
|
+
async handleCompletionComplete(request) {
|
|
985
|
+
const params = request.params ?? {};
|
|
986
|
+
if (!params.ref || !params.argument) {
|
|
987
|
+
return createErrorResponse(
|
|
988
|
+
request.id,
|
|
989
|
+
ErrorCode.InvalidParams,
|
|
990
|
+
"Missing required parameter: ref and argument"
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
const ref = params.ref;
|
|
994
|
+
let refType;
|
|
995
|
+
let refId;
|
|
996
|
+
if (ref.type === "ref/prompt" && typeof ref.name === "string") {
|
|
997
|
+
refType = "ref/prompt";
|
|
998
|
+
refId = ref.name;
|
|
999
|
+
} else if (ref.type === "ref/resource" && typeof ref.uri === "string") {
|
|
1000
|
+
refType = "ref/resource";
|
|
1001
|
+
refId = ref.uri;
|
|
1002
|
+
} else {
|
|
1003
|
+
return createErrorResponse(
|
|
1004
|
+
request.id,
|
|
1005
|
+
ErrorCode.InvalidParams,
|
|
1006
|
+
`Invalid ref: ${JSON.stringify(ref)}`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
if (typeof params.argument.name !== "string") {
|
|
1010
|
+
return createErrorResponse(
|
|
1011
|
+
request.id,
|
|
1012
|
+
ErrorCode.InvalidParams,
|
|
1013
|
+
"Missing required parameter: argument.name"
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
const key = completionKey(refType, refId, params.argument.name);
|
|
1017
|
+
const completion = this.completions.get(key);
|
|
1018
|
+
if (!completion) {
|
|
1019
|
+
return createErrorResponse(
|
|
1020
|
+
request.id,
|
|
1021
|
+
ErrorCode.MethodNotFound,
|
|
1022
|
+
`No completion handler for ${refType} "${refId}" argument "${params.argument.name}"`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
const value = params.argument.value ?? "";
|
|
1026
|
+
const context = {
|
|
1027
|
+
arguments: params.arguments ?? {}
|
|
1028
|
+
};
|
|
1029
|
+
let result;
|
|
1030
|
+
try {
|
|
1031
|
+
result = await completion.handler(value, context);
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
return createErrorResponse(
|
|
1034
|
+
request.id,
|
|
1035
|
+
ErrorCode.InternalError,
|
|
1036
|
+
err instanceof Error ? err.message : String(err)
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
return createResultResponse(request.id, { completion: result });
|
|
1040
|
+
}
|
|
1041
|
+
// ─── 自定义方法分发(业务拓展) ───────────────────────
|
|
1042
|
+
async handleCustomMethod(request, session) {
|
|
1043
|
+
const registered = this.methods.get(request.method);
|
|
1044
|
+
if (!registered) {
|
|
1045
|
+
return createErrorResponse(
|
|
1046
|
+
request.id,
|
|
1047
|
+
ErrorCode.MethodNotFound,
|
|
1048
|
+
`Method not found: ${request.method}`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
const extra = {
|
|
1052
|
+
sessionId: session?.id ?? "",
|
|
1053
|
+
sendLogging: (level, data, logger) => {
|
|
1054
|
+
if (session) this.sendLogging(session.id, level, data, logger);
|
|
1055
|
+
},
|
|
1056
|
+
sendProgress: (progress, total) => {
|
|
1057
|
+
if (session) this.sendProgress(session.id, void 0, progress, total);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
let result;
|
|
1061
|
+
try {
|
|
1062
|
+
result = await registered.handler(request.params, session, extra);
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
return createErrorResponse(
|
|
1065
|
+
request.id,
|
|
1066
|
+
ErrorCode.InternalError,
|
|
1067
|
+
err instanceof Error ? err.message : String(err)
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (result !== null && typeof result === "object" && "error" in result && result.error !== null && typeof result.error === "object") {
|
|
1071
|
+
const errResp = result;
|
|
1072
|
+
return { ...errResp, jsonrpc: "2.0", id: request.id };
|
|
1073
|
+
}
|
|
1074
|
+
return createResultResponse(request.id, result);
|
|
1075
|
+
}
|
|
1076
|
+
// ─── sendLogging(应用级 + handler extra 共用) ─────
|
|
1077
|
+
/**
|
|
1078
|
+
* 推送 notifications/message 到 session 的所有 SSE 订阅者
|
|
1079
|
+
*
|
|
1080
|
+
* - 无 session 或无订阅者:静默丢弃
|
|
1081
|
+
* - level 低于 session.loggingLevel:静默丢弃
|
|
1082
|
+
* - SSE 行格式:`data: ${JSON.stringify(notification)}\n\n`
|
|
1083
|
+
*/
|
|
1084
|
+
sendLogging(sessionId, level, data, logger) {
|
|
1085
|
+
if (!this.sessions.shouldLog(sessionId, level)) return;
|
|
1086
|
+
const notification = {
|
|
1087
|
+
jsonrpc: "2.0",
|
|
1088
|
+
method: "notifications/message",
|
|
1089
|
+
params: { level, data }
|
|
1090
|
+
};
|
|
1091
|
+
if (logger !== void 0) {
|
|
1092
|
+
notification.params.logger = logger;
|
|
1093
|
+
}
|
|
1094
|
+
const sseData = `data: ${JSON.stringify(notification)}
|
|
1095
|
+
|
|
1096
|
+
`;
|
|
1097
|
+
this.sessions.broadcastToSession(sessionId, sseData);
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* 推送 notifications/resources/updated 到所有订阅了该 URI 的 session
|
|
1101
|
+
*
|
|
1102
|
+
* - 找出所有 subscribedResources 包含该 URI 的 session
|
|
1103
|
+
* - 对每个 session 的所有 SSE 订阅者推送通知
|
|
1104
|
+
* - 无订阅者的 session 静默跳过
|
|
1105
|
+
*/
|
|
1106
|
+
sendResourceUpdated(uri) {
|
|
1107
|
+
const sessionIds = this.sessions.findSubscribersOfUri(uri);
|
|
1108
|
+
if (sessionIds.length === 0) return;
|
|
1109
|
+
const notification = {
|
|
1110
|
+
jsonrpc: "2.0",
|
|
1111
|
+
method: "notifications/resources/updated",
|
|
1112
|
+
params: { uri }
|
|
1113
|
+
};
|
|
1114
|
+
const sseData = `data: ${JSON.stringify(notification)}
|
|
1115
|
+
|
|
1116
|
+
`;
|
|
1117
|
+
for (const sessionId of sessionIds) {
|
|
1118
|
+
this.sessions.broadcastToSession(sessionId, sseData);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* 推送 notifications/progress 到 session 的所有 SSE 订阅者
|
|
1123
|
+
*
|
|
1124
|
+
* - 无 session 或无订阅者:静默丢弃
|
|
1125
|
+
* - progressToken 为 undefined/null:静默丢弃(无法关联进度与请求)
|
|
1126
|
+
*
|
|
1127
|
+
* @param sessionId 会话 ID
|
|
1128
|
+
* @param progressToken 客户端在请求 _meta.progressToken 中传入的 token(任意 JSON 值)
|
|
1129
|
+
* @param progress 当前进度(数值)
|
|
1130
|
+
* @param total 总数(可选)
|
|
1131
|
+
*/
|
|
1132
|
+
sendProgress(sessionId, progressToken, progress, total) {
|
|
1133
|
+
if (progressToken === void 0 || progressToken === null) return;
|
|
1134
|
+
const params = { progressToken, progress };
|
|
1135
|
+
if (total !== void 0) {
|
|
1136
|
+
params.total = total;
|
|
1137
|
+
}
|
|
1138
|
+
const notification = {
|
|
1139
|
+
jsonrpc: "2.0",
|
|
1140
|
+
method: "notifications/progress",
|
|
1141
|
+
params
|
|
1142
|
+
};
|
|
1143
|
+
const sseData = `data: ${JSON.stringify(notification)}
|
|
1144
|
+
|
|
1145
|
+
`;
|
|
1146
|
+
this.sessions.broadcastToSession(sessionId, sseData);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* 通用通知推送(业务拓展)——向指定 session 的所有 SSE 订阅者推送任意通知
|
|
1150
|
+
*
|
|
1151
|
+
* - 无 session 或无订阅者:静默丢弃
|
|
1152
|
+
* - 不校验 method 是否符合 MCP 规范——业务方自行负责
|
|
1153
|
+
* - SSE 行格式:`data: ${JSON.stringify(notification)}\n\n`
|
|
1154
|
+
*
|
|
1155
|
+
* @param sessionId 目标 session ID
|
|
1156
|
+
* @param method 通知方法名(如 `notifications/myapp/sync`)
|
|
1157
|
+
* @param params 通知参数(可选)
|
|
1158
|
+
*/
|
|
1159
|
+
sendNotification(sessionId, method, params) {
|
|
1160
|
+
const notification = {
|
|
1161
|
+
jsonrpc: "2.0",
|
|
1162
|
+
method
|
|
1163
|
+
};
|
|
1164
|
+
if (params !== void 0) {
|
|
1165
|
+
notification.params = params;
|
|
1166
|
+
}
|
|
1167
|
+
const sseData = `data: ${JSON.stringify(notification)}
|
|
1168
|
+
|
|
1169
|
+
`;
|
|
1170
|
+
this.sessions.broadcastToSession(sessionId, sseData);
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
function buildZodObject(shape) {
|
|
1174
|
+
return z.object(shape);
|
|
1175
|
+
}
|
|
1176
|
+
function completionKey(refType, refId, argumentName) {
|
|
1177
|
+
return `${refType}:${refId}:${argumentName}`;
|
|
1178
|
+
}
|
|
1179
|
+
function createMcpServer(options) {
|
|
1180
|
+
return new McpServer(options);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/streamableHttp.ts
|
|
1184
|
+
var DEFAULT_SSE_HEARTBEAT_MS = 3e4;
|
|
1185
|
+
async function handleMcpRequest(request, server) {
|
|
1186
|
+
switch (request.method) {
|
|
1187
|
+
case "POST":
|
|
1188
|
+
return handlePost(request, server);
|
|
1189
|
+
case "GET":
|
|
1190
|
+
return handleGet(server, request);
|
|
1191
|
+
case "DELETE":
|
|
1192
|
+
return handleDelete(request, server);
|
|
1193
|
+
default:
|
|
1194
|
+
return new Response(null, { status: 405 });
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function handleGet(server, request) {
|
|
1198
|
+
const heartbeatMs = server.getSseHeartbeatMs() ?? DEFAULT_SSE_HEARTBEAT_MS;
|
|
1199
|
+
const encoder = new TextEncoder();
|
|
1200
|
+
let interval;
|
|
1201
|
+
let subscriber;
|
|
1202
|
+
const sessionId = request.headers.get("mcp-session-id") ?? void 0;
|
|
1203
|
+
const stream = new ReadableStream({
|
|
1204
|
+
start(controller) {
|
|
1205
|
+
controller.enqueue(encoder.encode(": connected\n\n"));
|
|
1206
|
+
if (sessionId) {
|
|
1207
|
+
subscriber = server.getSessionManager().addSubscriber(sessionId, controller);
|
|
1208
|
+
}
|
|
1209
|
+
interval = setInterval(() => {
|
|
1210
|
+
try {
|
|
1211
|
+
controller.enqueue(encoder.encode(`: keepalive ${Date.now()}
|
|
1212
|
+
|
|
1213
|
+
`));
|
|
1214
|
+
} catch {
|
|
1215
|
+
if (interval) clearInterval(interval);
|
|
1216
|
+
}
|
|
1217
|
+
}, heartbeatMs);
|
|
1218
|
+
},
|
|
1219
|
+
cancel() {
|
|
1220
|
+
if (interval) clearInterval(interval);
|
|
1221
|
+
if (subscriber) {
|
|
1222
|
+
server.getSessionManager().removeSubscriber(subscriber);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
return new Response(stream, {
|
|
1227
|
+
status: 200,
|
|
1228
|
+
headers: {
|
|
1229
|
+
"Content-Type": "text/event-stream",
|
|
1230
|
+
"Cache-Control": "no-cache",
|
|
1231
|
+
Connection: "keep-alive"
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
async function handlePost(request, server) {
|
|
1236
|
+
let body;
|
|
1237
|
+
try {
|
|
1238
|
+
body = await request.json();
|
|
1239
|
+
} catch {
|
|
1240
|
+
return jsonResponse(
|
|
1241
|
+
400,
|
|
1242
|
+
createErrorResponse(null, ErrorCode.ParseError, "Parse error: Invalid JSON")
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
let messages;
|
|
1246
|
+
try {
|
|
1247
|
+
messages = parseJsonRpcMessage(body);
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
const message = err instanceof JsonRpcParseError ? err.message : "Invalid JSON-RPC message";
|
|
1250
|
+
return jsonResponse(400, createErrorResponse(null, ErrorCode.ParseError, message));
|
|
1251
|
+
}
|
|
1252
|
+
const requests = messages.filter(isRequest);
|
|
1253
|
+
const notifications = messages.filter(isNotification);
|
|
1254
|
+
const hasInitialize = requests.some((r) => r.method === "initialize");
|
|
1255
|
+
if (!hasInitialize) {
|
|
1256
|
+
const acceptHeader = request.headers.get("accept") ?? "";
|
|
1257
|
+
const accepts = acceptHeader.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0);
|
|
1258
|
+
const hasJson = accepts.some(
|
|
1259
|
+
(s) => s === "application/json" || s.startsWith("application/json;")
|
|
1260
|
+
);
|
|
1261
|
+
const hasSse = accepts.some(
|
|
1262
|
+
(s) => s === "text/event-stream" || s.startsWith("text/event-stream;")
|
|
1263
|
+
);
|
|
1264
|
+
if (!hasJson || !hasSse) {
|
|
1265
|
+
return jsonResponse(
|
|
1266
|
+
400,
|
|
1267
|
+
createErrorResponse(
|
|
1268
|
+
null,
|
|
1269
|
+
ErrorCode.InvalidRequest,
|
|
1270
|
+
"Accept header must include both application/json and text/event-stream"
|
|
1271
|
+
)
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
const sessionId = request.headers.get("mcp-session-id") ?? void 0;
|
|
1276
|
+
const sessionManager = server.getSessionManager();
|
|
1277
|
+
let session = sessionId ? sessionManager.get(sessionId) : void 0;
|
|
1278
|
+
if (hasInitialize) {
|
|
1279
|
+
if (session) {
|
|
1280
|
+
return jsonResponse(
|
|
1281
|
+
400,
|
|
1282
|
+
createErrorResponse(null, ErrorCode.InvalidRequest, "Server already initialized")
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
session = sessionManager.create();
|
|
1286
|
+
} else if (requests.length > 0 && !session && sessionId) {
|
|
1287
|
+
return jsonResponse(
|
|
1288
|
+
404,
|
|
1289
|
+
createErrorResponse(null, ErrorCode.RequestTimeout, "Session not found")
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
for (const notification of notifications) {
|
|
1293
|
+
await server.handleJsonRpc(notification, session);
|
|
1294
|
+
}
|
|
1295
|
+
const responses = [];
|
|
1296
|
+
for (const req of requests) {
|
|
1297
|
+
const response = await server.handleJsonRpc(req, session);
|
|
1298
|
+
if (response !== null) {
|
|
1299
|
+
responses.push(response);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (requests.length === 0) {
|
|
1303
|
+
return new Response(null, { status: 202 });
|
|
1304
|
+
}
|
|
1305
|
+
const responseHeaders = {
|
|
1306
|
+
"Content-Type": "application/json"
|
|
1307
|
+
};
|
|
1308
|
+
if (hasInitialize && session) {
|
|
1309
|
+
responseHeaders["Mcp-Session-Id"] = session.id;
|
|
1310
|
+
}
|
|
1311
|
+
const responseBody = responses.length === 1 ? responses[0] : responses;
|
|
1312
|
+
return new Response(JSON.stringify(responseBody), {
|
|
1313
|
+
status: 200,
|
|
1314
|
+
headers: responseHeaders
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
function handleDelete(request, server) {
|
|
1318
|
+
const sessionId = request.headers.get("mcp-session-id");
|
|
1319
|
+
if (!sessionId) {
|
|
1320
|
+
return Promise.resolve(
|
|
1321
|
+
jsonResponse(
|
|
1322
|
+
400,
|
|
1323
|
+
createErrorResponse(null, ErrorCode.InvalidRequest, "Missing Mcp-Session-Id header")
|
|
1324
|
+
)
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
const deleted = server.getSessionManager().delete(sessionId);
|
|
1328
|
+
if (!deleted) {
|
|
1329
|
+
return Promise.resolve(
|
|
1330
|
+
jsonResponse(404, createErrorResponse(null, ErrorCode.RequestTimeout, "Session not found"))
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
return Promise.resolve(new Response(null, { status: 200 }));
|
|
1334
|
+
}
|
|
1335
|
+
function jsonResponse(status, body) {
|
|
1336
|
+
return new Response(JSON.stringify(body), {
|
|
1337
|
+
status,
|
|
1338
|
+
headers: { "Content-Type": "application/json" }
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/faapiAdapter.ts
|
|
1343
|
+
import { Readable } from "stream";
|
|
1344
|
+
function createMcpHandler(mcp) {
|
|
1345
|
+
const handler = async (ctx) => {
|
|
1346
|
+
return handleMcpRequest(ctx.request, mcp);
|
|
1347
|
+
};
|
|
1348
|
+
return { POST: handler, GET: handler, DELETE: handler };
|
|
1349
|
+
}
|
|
1350
|
+
function createMcpNodeHandler(mcp) {
|
|
1351
|
+
return async (req, res) => {
|
|
1352
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
1353
|
+
const headers = new Headers();
|
|
1354
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
1355
|
+
if (Array.isArray(value)) {
|
|
1356
|
+
for (const v of value) headers.set(key, v);
|
|
1357
|
+
} else if (value !== void 0) {
|
|
1358
|
+
headers.set(key, value);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const method = req.method ?? "GET";
|
|
1362
|
+
const hasBody = method !== "GET" && method !== "HEAD" && method !== "DELETE";
|
|
1363
|
+
const body = hasBody ? Readable.toWeb(req) : void 0;
|
|
1364
|
+
const request = new Request(url, {
|
|
1365
|
+
method,
|
|
1366
|
+
headers,
|
|
1367
|
+
...body && { body, duplex: "half" }
|
|
1368
|
+
});
|
|
1369
|
+
const response = await handleMcpRequest(request, mcp);
|
|
1370
|
+
res.statusCode = response.status;
|
|
1371
|
+
response.headers.forEach((value, key) => {
|
|
1372
|
+
res.setHeader(key, value);
|
|
1373
|
+
});
|
|
1374
|
+
if (response.body) {
|
|
1375
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
1376
|
+
await new Promise((resolve, reject) => {
|
|
1377
|
+
let settled = false;
|
|
1378
|
+
const settle = (err) => {
|
|
1379
|
+
if (settled) return;
|
|
1380
|
+
settled = true;
|
|
1381
|
+
if (err) reject(err);
|
|
1382
|
+
else resolve();
|
|
1383
|
+
};
|
|
1384
|
+
nodeStream.on("error", (err) => settle(err));
|
|
1385
|
+
res.on("error", (err) => settle(err));
|
|
1386
|
+
res.on("finish", () => settle());
|
|
1387
|
+
res.on("close", () => settle());
|
|
1388
|
+
nodeStream.pipe(res);
|
|
1389
|
+
});
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
res.end();
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
export {
|
|
1396
|
+
ErrorCode,
|
|
1397
|
+
JsonRpcParseError,
|
|
1398
|
+
McpServer,
|
|
1399
|
+
PROTOCOL_VERSION,
|
|
1400
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
1401
|
+
SessionManager,
|
|
1402
|
+
createErrorResponse,
|
|
1403
|
+
createMcpHandler,
|
|
1404
|
+
createMcpNodeHandler,
|
|
1405
|
+
createMcpServer,
|
|
1406
|
+
createResultResponse,
|
|
1407
|
+
handleMcpRequest,
|
|
1408
|
+
isErrorResponse,
|
|
1409
|
+
isNotification,
|
|
1410
|
+
isRequest,
|
|
1411
|
+
isResultResponse,
|
|
1412
|
+
parseJsonRpcMessage
|
|
1413
|
+
};
|
|
1414
|
+
//# sourceMappingURL=index.js.map
|