@emseepea/server 0.7.0 → 0.9.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 +60 -6
- package/dist/index.d.ts +53 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +263 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createMcpFastifyApp } from "@modelcontextprotocol/fastify";
|
|
2
2
|
import { GetPromptResultSchema, ReadResourceResultSchema, } from "@modelcontextprotocol/core";
|
|
3
3
|
import { toNodeHandler } from "@modelcontextprotocol/node";
|
|
4
|
-
import { McpServer, InMemoryServerEventBus, OAuthError, OAuthErrorCode, ProtocolError, ProtocolErrorCode, ResourceTemplate, bearerAuthChallengeResponse, buildOAuthProtectedResourceMetadata, checkResourceAllowed, completable, createMcpHandler, getOAuthProtectedResourceMetadataUrl, inputRequired as sdkInputRequired, acceptedContent as sdkAcceptedContent, inputResponse as sdkInputResponse, isInputRequiredResult, oauthMetadataResponse, specTypeSchemas, verifyBearerToken, } from "@modelcontextprotocol/server";
|
|
4
|
+
import { McpServer, InMemoryServerEventBus, OAuthError, OAuthErrorCode, ProtocolError, ProtocolErrorCode, ResourceTemplate, bearerAuthChallengeResponse, buildOAuthProtectedResourceMetadata, checkResourceAllowed, classifyInboundRequest, completable, createMcpHandler, createRequestStateCodec, getOAuthProtectedResourceMetadataUrl, inputRequired as sdkInputRequired, acceptedContent as sdkAcceptedContent, inputResponse as sdkInputResponse, isInputRequiredResult, oauthMetadataResponse, specTypeSchemas, verifyBearerToken, } from "@modelcontextprotocol/server";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { readdir, realpath } from "node:fs/promises";
|
|
@@ -12,7 +12,7 @@ import { z } from "zod";
|
|
|
12
12
|
import { installObservability, } from "./telemetry.js";
|
|
13
13
|
export { openTelemetry, structuredLogging, } from "./telemetry.js";
|
|
14
14
|
export * from "./ui.js";
|
|
15
|
-
export const inputRequired = Object.assign((spec) => sdkInputRequired(
|
|
15
|
+
export const inputRequired = Object.assign((spec) => sdkInputRequired(spec), {
|
|
16
16
|
elicit: sdkInputRequired.elicit,
|
|
17
17
|
elicitUrl: sdkInputRequired.elicitUrl,
|
|
18
18
|
});
|
|
@@ -24,6 +24,14 @@ export function inputResponse(responses, key) {
|
|
|
24
24
|
return response.kind === "elicit" ? response : { kind: "missing" };
|
|
25
25
|
}
|
|
26
26
|
const PROTOCOL_VERSION = "2026-07-28";
|
|
27
|
+
const LEGACY_PROTOCOL_VERSIONS = Object.freeze([
|
|
28
|
+
"2025-11-25",
|
|
29
|
+
"2025-06-18",
|
|
30
|
+
"2025-03-26",
|
|
31
|
+
"2024-11-05",
|
|
32
|
+
"2024-10-07",
|
|
33
|
+
]);
|
|
34
|
+
const SUPPORTED_PROTOCOLS = Object.freeze([PROTOCOL_VERSION, ...LEGACY_PROTOCOL_VERSIONS]);
|
|
27
35
|
const REGISTER = Symbol("register");
|
|
28
36
|
const DISCOVERABLE = Symbol("discoverable");
|
|
29
37
|
const TOOL_NAME = Symbol("toolName");
|
|
@@ -51,6 +59,45 @@ const CACHEABLE_METHODS = [
|
|
|
51
59
|
];
|
|
52
60
|
const runtimes = new WeakMap();
|
|
53
61
|
const requestOperations = new AsyncLocalStorage();
|
|
62
|
+
function createRequestStateRuntime(options) {
|
|
63
|
+
if (typeof options.key !== "string" && !(options.key instanceof Uint8Array)) {
|
|
64
|
+
throw new TypeError("requestState.key must be a string or Uint8Array");
|
|
65
|
+
}
|
|
66
|
+
const ttlSeconds = positiveInteger("requestState.ttlSeconds", options.ttlSeconds);
|
|
67
|
+
const maxBytes = positiveInteger("requestState.maxBytes", options.maxBytes ?? 4 * 1024);
|
|
68
|
+
const codec = createRequestStateCodec({
|
|
69
|
+
key: typeof options.key === "string" ? options.key : Uint8Array.from(options.key),
|
|
70
|
+
ttlSeconds,
|
|
71
|
+
bind(context) {
|
|
72
|
+
const operation = requestOperations.getStore();
|
|
73
|
+
if (!operation?.capability)
|
|
74
|
+
throw new Error("request state has no capability binding");
|
|
75
|
+
const principal = operation.principal;
|
|
76
|
+
return JSON.stringify([
|
|
77
|
+
context.mcpReq.method,
|
|
78
|
+
operation.capability,
|
|
79
|
+
principal
|
|
80
|
+
? [principal.clientId, [...principal.permissions].sort(), principal.resource ?? null]
|
|
81
|
+
: null,
|
|
82
|
+
]);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const assertWireSize = (state) => {
|
|
86
|
+
if (Buffer.byteLength(state, "utf8") > maxBytes)
|
|
87
|
+
throw new Error("request state is oversized");
|
|
88
|
+
};
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
async mint(payload, context) {
|
|
91
|
+
const state = await codec.mint(payload, context);
|
|
92
|
+
assertWireSize(state);
|
|
93
|
+
return state;
|
|
94
|
+
},
|
|
95
|
+
async verify(state, context) {
|
|
96
|
+
assertWireSize(state);
|
|
97
|
+
return codec.verify(state, context);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
54
101
|
export function defineTool(definition) {
|
|
55
102
|
const handler = definition.handler;
|
|
56
103
|
return createCheckedTool(definition, handler, false, true);
|
|
@@ -126,21 +173,16 @@ export function defineResource(definition) {
|
|
|
126
173
|
value: listing,
|
|
127
174
|
}),
|
|
128
175
|
[HAS_COMPLETION]: false,
|
|
129
|
-
[REGISTER](server, timeoutMs, maxApplicationResultBytes) {
|
|
176
|
+
[REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
|
|
130
177
|
server.registerResource(name, uri, cacheHint ? { ...metadata, cacheHint } : metadata, async (_requestedUri, context) => {
|
|
131
178
|
try {
|
|
132
179
|
const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
|
|
133
180
|
return await runWithDeadline(context.mcpReq.signal, deadlineMs, async (signal) => {
|
|
134
181
|
signal.throwIfAborted();
|
|
135
|
-
const result = await handler(
|
|
136
|
-
signal,
|
|
137
|
-
deadlineMs,
|
|
138
|
-
principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
|
|
139
|
-
inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
|
|
140
|
-
});
|
|
182
|
+
const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler(directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
|
|
141
183
|
signal.throwIfAborted();
|
|
142
184
|
if (isInputRequiredResult(result)) {
|
|
143
|
-
|
|
185
|
+
await assertInputRequired(result, requestState, context);
|
|
144
186
|
assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal);
|
|
145
187
|
return result;
|
|
146
188
|
}
|
|
@@ -203,7 +245,7 @@ export function defineResourceTemplate(definition) {
|
|
|
203
245
|
value: listing,
|
|
204
246
|
}),
|
|
205
247
|
[HAS_COMPLETION]: completions.size > 0,
|
|
206
|
-
[REGISTER](server, timeoutMs, maxApplicationResultBytes) {
|
|
248
|
+
[REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
|
|
207
249
|
const registeredTemplate = completions.size === 0
|
|
208
250
|
? template
|
|
209
251
|
: new ResourceTemplate(uriTemplate, {
|
|
@@ -218,15 +260,10 @@ export function defineResourceTemplate(definition) {
|
|
|
218
260
|
const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
|
|
219
261
|
return await runWithDeadline(context.mcpReq.signal, deadlineMs, async (signal) => {
|
|
220
262
|
signal.throwIfAborted();
|
|
221
|
-
const result = await handler({ uri: requestedUri.href, variables },
|
|
222
|
-
signal,
|
|
223
|
-
deadlineMs,
|
|
224
|
-
principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
|
|
225
|
-
inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
|
|
226
|
-
});
|
|
263
|
+
const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler({ uri: requestedUri.href, variables }, directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
|
|
227
264
|
signal.throwIfAborted();
|
|
228
265
|
if (isInputRequiredResult(result)) {
|
|
229
|
-
|
|
266
|
+
await assertInputRequired(result, requestState, context);
|
|
230
267
|
assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal);
|
|
231
268
|
return result;
|
|
232
269
|
}
|
|
@@ -274,7 +311,7 @@ export function definePrompt(definition) {
|
|
|
274
311
|
[PROMPT_ACCESS]: access,
|
|
275
312
|
[HAS_COMPLETION]: completions.size > 0,
|
|
276
313
|
[PROMPT_LISTING]: listing,
|
|
277
|
-
[REGISTER](server, timeoutMs, maxApplicationResultBytes) {
|
|
314
|
+
[REGISTER](server, timeoutMs, maxApplicationResultBytes, requestState, clientLogging) {
|
|
278
315
|
server.registerPrompt(name, {
|
|
279
316
|
...metadata,
|
|
280
317
|
argsSchema: sdkPromptMetadataSchema(argsSchema, completions, argumentNames, timeoutMs, maxApplicationResultBytes),
|
|
@@ -286,15 +323,10 @@ export function definePrompt(definition) {
|
|
|
286
323
|
if (!parsedArgs.success)
|
|
287
324
|
throw new Error("Prompt received invalid arguments");
|
|
288
325
|
signal.throwIfAborted();
|
|
289
|
-
const result = await handler(parsedArgs.data,
|
|
290
|
-
signal,
|
|
291
|
-
deadlineMs,
|
|
292
|
-
principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
|
|
293
|
-
inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
|
|
294
|
-
});
|
|
326
|
+
const result = await withClientLogReporter(context, signal, clientLogging, (reportLog) => handler(parsedArgs.data, directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog)));
|
|
295
327
|
signal.throwIfAborted();
|
|
296
328
|
if (isInputRequiredResult(result)) {
|
|
297
|
-
|
|
329
|
+
await assertInputRequired(result, requestState, context);
|
|
298
330
|
assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal);
|
|
299
331
|
return result;
|
|
300
332
|
}
|
|
@@ -464,7 +496,10 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
|
|
|
464
496
|
[TOOL_ACCESS]: access,
|
|
465
497
|
[TOOL_STREAMING]: streaming,
|
|
466
498
|
[TOOL_LISTING]: listing,
|
|
467
|
-
[REGISTER](server, timeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes) {
|
|
499
|
+
[REGISTER](server, timeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState, clientLogging) {
|
|
500
|
+
const runLoggedTool = clientLogging
|
|
501
|
+
? createLoggedToolRunner(clientLogging, execute, allowsInputRequired, access, requestState)
|
|
502
|
+
: undefined;
|
|
468
503
|
server.registerTool(name, metadata, async (input, context) => {
|
|
469
504
|
try {
|
|
470
505
|
const deadlineMs = requestOperations.getStore()?.deadlineMs ?? Date.now() + timeoutMs;
|
|
@@ -478,26 +513,32 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
|
|
|
478
513
|
? progressReporter(context, signal, maxProgressEvents, maxProgressEventBytes)
|
|
479
514
|
: undefined;
|
|
480
515
|
let result;
|
|
481
|
-
|
|
482
|
-
result = await
|
|
483
|
-
signal,
|
|
484
|
-
deadlineMs,
|
|
485
|
-
principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
|
|
486
|
-
...(allowsInputRequired
|
|
487
|
-
? {
|
|
488
|
-
inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
|
|
489
|
-
}
|
|
490
|
-
: {}),
|
|
491
|
-
...(reporter ? { reportProgress: reporter.report } : {}),
|
|
492
|
-
});
|
|
516
|
+
if (runLoggedTool) {
|
|
517
|
+
result = await runLoggedTool(parsedInput.data, context, signal, deadlineMs, reporter);
|
|
493
518
|
}
|
|
494
|
-
|
|
495
|
-
|
|
519
|
+
else {
|
|
520
|
+
try {
|
|
521
|
+
const handlerContext = allowsInputRequired
|
|
522
|
+
? directHandlerContext(access, context, signal, deadlineMs, requestState)
|
|
523
|
+
: {
|
|
524
|
+
signal,
|
|
525
|
+
deadlineMs,
|
|
526
|
+
principal: access === "public"
|
|
527
|
+
? undefined
|
|
528
|
+
: principalFrom(context.http?.authInfo),
|
|
529
|
+
};
|
|
530
|
+
if (reporter)
|
|
531
|
+
Object.assign(handlerContext, { reportProgress: reporter.report });
|
|
532
|
+
result = await execute(parsedInput.data, handlerContext);
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
await reporter?.finish();
|
|
536
|
+
}
|
|
537
|
+
reporter?.throwIfFailed();
|
|
496
538
|
}
|
|
497
|
-
reporter?.throwIfFailed();
|
|
498
539
|
if (allowsInputRequired && isInputRequiredResult(result)) {
|
|
499
540
|
signal.throwIfAborted();
|
|
500
|
-
|
|
541
|
+
await assertInputRequired(result, requestState, context);
|
|
501
542
|
assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal);
|
|
502
543
|
return result;
|
|
503
544
|
}
|
|
@@ -528,6 +569,33 @@ function createCheckedTool(definition, execute, streaming, allowsInputRequired)
|
|
|
528
569
|
};
|
|
529
570
|
return Object.freeze(registration);
|
|
530
571
|
}
|
|
572
|
+
function createLoggedToolRunner(clientLogging, execute, allowsInputRequired, access, requestState) {
|
|
573
|
+
return async (input, context, signal, deadlineMs, reporter) => {
|
|
574
|
+
const logReporter = clientLogReporter(context, signal, clientLogging.maxEvents, clientLogging.maxEventBytes);
|
|
575
|
+
let result;
|
|
576
|
+
try {
|
|
577
|
+
result = await execute(input, {
|
|
578
|
+
...(allowsInputRequired
|
|
579
|
+
? directHandlerContext(access, context, signal, deadlineMs, requestState, logReporter.report)
|
|
580
|
+
: {
|
|
581
|
+
signal,
|
|
582
|
+
deadlineMs,
|
|
583
|
+
principal: access === "public"
|
|
584
|
+
? undefined
|
|
585
|
+
: principalFrom(context.http?.authInfo),
|
|
586
|
+
reportLog: logReporter.report,
|
|
587
|
+
}),
|
|
588
|
+
...(reporter ? { reportProgress: reporter.report } : {}),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
await Promise.all([reporter?.finish(), logReporter.finish()]);
|
|
593
|
+
}
|
|
594
|
+
reporter?.throwIfFailed();
|
|
595
|
+
logReporter.throwIfFailed();
|
|
596
|
+
return result;
|
|
597
|
+
};
|
|
598
|
+
}
|
|
531
599
|
function progressReporter(context, signal, maxEvents, maxEventBytes) {
|
|
532
600
|
const token = context.mcpReq._meta?.progressToken;
|
|
533
601
|
let closed = false;
|
|
@@ -583,6 +651,74 @@ function progressReporter(context, signal, maxEvents, maxEventBytes) {
|
|
|
583
651
|
throw failure; },
|
|
584
652
|
};
|
|
585
653
|
}
|
|
654
|
+
function clientLogReporter(context, signal, maxEvents, maxEventBytes) {
|
|
655
|
+
let closed = false;
|
|
656
|
+
let failure;
|
|
657
|
+
let attempts = 0;
|
|
658
|
+
const pending = new Set();
|
|
659
|
+
return {
|
|
660
|
+
report(message) {
|
|
661
|
+
if (closed)
|
|
662
|
+
return Promise.reject(new Error("Client logging is no longer available"));
|
|
663
|
+
if (failure)
|
|
664
|
+
return Promise.reject(failure);
|
|
665
|
+
const operation = (async () => {
|
|
666
|
+
try {
|
|
667
|
+
signal.throwIfAborted();
|
|
668
|
+
attempts += 1;
|
|
669
|
+
if (attempts > maxEvents)
|
|
670
|
+
throw new Error("Client log event limit exceeded");
|
|
671
|
+
const notification = {
|
|
672
|
+
method: "notifications/message",
|
|
673
|
+
params: {
|
|
674
|
+
level: message.level,
|
|
675
|
+
data: message.data,
|
|
676
|
+
...(message.logger === undefined ? {} : { logger: message.logger }),
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
assertJsonValue(notification, new WeakSet());
|
|
680
|
+
const encoded = JSON.stringify(notification);
|
|
681
|
+
if (Buffer.byteLength(encoded, "utf8") > maxEventBytes) {
|
|
682
|
+
throw new Error("Client log event exceeds configured size limit");
|
|
683
|
+
}
|
|
684
|
+
const parsed = specTypeSchemas.LoggingMessageNotification["~standard"].validate(JSON.parse(encoded));
|
|
685
|
+
if (parsed instanceof Promise || "issues" in parsed) {
|
|
686
|
+
throw new Error("Client log event is invalid");
|
|
687
|
+
}
|
|
688
|
+
const params = parsed.value.params;
|
|
689
|
+
await context.mcpReq.log(params.level, params.data, params.logger);
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
failure = error instanceof Error ? error : new Error("Client log emission failed");
|
|
693
|
+
throw failure;
|
|
694
|
+
}
|
|
695
|
+
})();
|
|
696
|
+
pending.add(operation);
|
|
697
|
+
void operation.then(() => pending.delete(operation), () => pending.delete(operation));
|
|
698
|
+
return operation;
|
|
699
|
+
},
|
|
700
|
+
async finish() {
|
|
701
|
+
closed = true;
|
|
702
|
+
await Promise.allSettled(pending);
|
|
703
|
+
},
|
|
704
|
+
throwIfFailed() { if (failure)
|
|
705
|
+
throw failure; },
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async function withClientLogReporter(context, signal, options, run) {
|
|
709
|
+
const reporter = options
|
|
710
|
+
? clientLogReporter(context, signal, options.maxEvents, options.maxEventBytes)
|
|
711
|
+
: undefined;
|
|
712
|
+
let result;
|
|
713
|
+
try {
|
|
714
|
+
result = await run(reporter?.report);
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
await reporter?.finish();
|
|
718
|
+
}
|
|
719
|
+
reporter?.throwIfFailed();
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
586
722
|
function catalogueListings(tools, resources, prompts, supportedTools = tools, supportedResources = resources, supportedPrompts = prompts) {
|
|
587
723
|
const catalogues = new Map();
|
|
588
724
|
if (supportedTools.length)
|
|
@@ -745,7 +881,20 @@ export function createEmseepea(options) {
|
|
|
745
881
|
? normalizeListPagination(options.listPagination)
|
|
746
882
|
: undefined;
|
|
747
883
|
const maxRequestBytes = positiveInteger("maxRequestBytes", options.maxRequestBytes ?? 1024 * 1024);
|
|
884
|
+
const requestState = options.requestState
|
|
885
|
+
? createRequestStateRuntime(options.requestState)
|
|
886
|
+
: undefined;
|
|
748
887
|
const maxApplicationResultBytes = positiveInteger("maxApplicationResultBytes", options.maxApplicationResultBytes ?? 1024 * 1024);
|
|
888
|
+
const clientLoggingOptions = options.clientLogging;
|
|
889
|
+
if (clientLoggingOptions !== undefined && !isRecord(clientLoggingOptions)) {
|
|
890
|
+
throw new TypeError("clientLogging must be an object");
|
|
891
|
+
}
|
|
892
|
+
const clientLogging = clientLoggingOptions === undefined
|
|
893
|
+
? undefined
|
|
894
|
+
: Object.freeze({
|
|
895
|
+
maxEvents: positiveInteger("clientLogging.maxEvents", clientLoggingOptions.maxEvents ?? 32),
|
|
896
|
+
maxEventBytes: positiveInteger("clientLogging.maxEventBytes", clientLoggingOptions.maxEventBytes ?? 8 * 1024),
|
|
897
|
+
});
|
|
749
898
|
const maxProgressEvents = positiveInteger("maxProgressEvents", options.maxProgressEvents ?? 32);
|
|
750
899
|
const maxProgressEventBytes = positiveInteger("maxProgressEventBytes", options.maxProgressEventBytes ?? 8 * 1024);
|
|
751
900
|
const operationTimeoutMs = positiveInteger("operationTimeoutMs", options.operationTimeoutMs ?? 30_000);
|
|
@@ -825,26 +974,34 @@ export function createEmseepea(options) {
|
|
|
825
974
|
: discoverablePrompts;
|
|
826
975
|
const activeHasCompletion = activeResources.some((resource) => resource[HAS_COMPLETION]) ||
|
|
827
976
|
activePrompts.some((prompt) => prompt[HAS_COMPLETION]);
|
|
977
|
+
const activeClientLogging = request?.legacy ? undefined : clientLogging;
|
|
828
978
|
const server = new McpServer(serverInfo, {
|
|
829
979
|
capabilities: {
|
|
830
980
|
...(activeTools.length ? { tools: { listChanged: false } } : {}),
|
|
831
981
|
...(activeResources.length ? {
|
|
832
|
-
resources: {
|
|
982
|
+
resources: {
|
|
983
|
+
subscribe: Boolean(resourceSubscriptions) && !request?.legacy,
|
|
984
|
+
listChanged: false,
|
|
985
|
+
},
|
|
833
986
|
} : {}),
|
|
834
987
|
...(activePrompts.length ? { prompts: { listChanged: false } } : {}),
|
|
835
988
|
...(activeHasCompletion ? { completions: {} } : {}),
|
|
989
|
+
...(activeClientLogging ? { logging: {} } : {}),
|
|
836
990
|
},
|
|
837
991
|
instructions: options.instructions,
|
|
838
|
-
cacheHints: request?.filterCatalogues ? undefined : cacheHints,
|
|
839
|
-
supportedProtocolVersions: [
|
|
992
|
+
cacheHints: request?.filterCatalogues || request?.legacy ? undefined : cacheHints,
|
|
993
|
+
supportedProtocolVersions: [...SUPPORTED_PROTOCOLS],
|
|
994
|
+
...(requestState && !request?.legacy ? { requestState: { verify: requestState.verify } } : {}),
|
|
840
995
|
});
|
|
841
996
|
for (const tool of activeTools) {
|
|
842
|
-
tool[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes);
|
|
997
|
+
tool[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, maxProgressEvents, maxProgressEventBytes, requestState, activeClientLogging);
|
|
998
|
+
}
|
|
999
|
+
for (const resource of activeResources) {
|
|
1000
|
+
resource[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState, activeClientLogging);
|
|
1001
|
+
}
|
|
1002
|
+
for (const prompt of activePrompts) {
|
|
1003
|
+
prompt[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes, requestState, activeClientLogging);
|
|
843
1004
|
}
|
|
844
|
-
for (const resource of activeResources)
|
|
845
|
-
resource[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes);
|
|
846
|
-
for (const prompt of activePrompts)
|
|
847
|
-
prompt[REGISTER](server, operationTimeoutMs, maxApplicationResultBytes);
|
|
848
1005
|
const filteredCatalogues = request?.filterCatalogues
|
|
849
1006
|
? catalogueListings(activeDiscoverableTools, activeDiscoverableResources, activeDiscoverablePrompts, activeTools, activeResources, activePrompts)
|
|
850
1007
|
: undefined;
|
|
@@ -861,8 +1018,7 @@ export function createEmseepea(options) {
|
|
|
861
1018
|
installListPagination(server, pagination);
|
|
862
1019
|
return server;
|
|
863
1020
|
}, {
|
|
864
|
-
|
|
865
|
-
responseMode: hasStreaming || resourceSubscriptions ? "auto" : "json",
|
|
1021
|
+
responseMode: hasStreaming || resourceSubscriptions || clientLogging ? "auto" : "json",
|
|
866
1022
|
keepAliveMs: 0,
|
|
867
1023
|
});
|
|
868
1024
|
const nodeHandler = toNodeHandler(sdkHandler);
|
|
@@ -979,16 +1135,40 @@ export function createEmseepea(options) {
|
|
|
979
1135
|
if (deployment.mode === "production-behind-proxy" &&
|
|
980
1136
|
!validateProductionRequest(request, reply, deployment, limiter))
|
|
981
1137
|
return;
|
|
982
|
-
|
|
1138
|
+
const protocolVersionHeader = singleHeader(request.raw.rawHeaders, "mcp-protocol-version");
|
|
1139
|
+
const mcpMethodHeader = singleHeader(request.raw.rawHeaders, "mcp-method");
|
|
1140
|
+
const mcpNameHeader = singleHeader(request.raw.rawHeaders, "mcp-name");
|
|
1141
|
+
if (protocolVersionHeader === null || mcpMethodHeader === null || mcpNameHeader === null) {
|
|
1142
|
+
await sendRpcError(reply, 400, -32020, "Conflicting MCP headers", requestId(isRecord(request.body) ? request.body.id : undefined));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const classification = classifyInboundRequest({
|
|
1146
|
+
httpMethod: request.method,
|
|
1147
|
+
...(protocolVersionHeader ? { protocolVersionHeader } : {}),
|
|
1148
|
+
...(mcpMethodHeader ? { mcpMethodHeader } : {}),
|
|
1149
|
+
...(mcpNameHeader ? { mcpNameHeader } : {}),
|
|
1150
|
+
body: request.body,
|
|
1151
|
+
});
|
|
1152
|
+
if (classification.kind === "reject") {
|
|
1153
|
+
await sendRpcError(reply, classification.httpStatus, classification.code, classification.message, requestId(isRecord(request.body) ? request.body.id : undefined), isRecord(classification.data) ? classification.data : undefined);
|
|
983
1154
|
return;
|
|
984
|
-
|
|
1155
|
+
}
|
|
1156
|
+
const legacy = classification.kind === "legacy";
|
|
1157
|
+
if (legacy && classification.requestedVersion &&
|
|
1158
|
+
!LEGACY_PROTOCOL_VERSIONS.includes(classification.requestedVersion)) {
|
|
1159
|
+
await sendRpcError(reply, 400, ProtocolErrorCode.UnsupportedProtocolVersion, `Unsupported protocol version: ${classification.requestedVersion}`, requestId(isRecord(request.body) ? request.body.id : undefined), { requested: classification.requestedVersion, supported: [...SUPPORTED_PROTOCOLS] });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (!await validateMcpRequestHeaders(request, reply, legacy))
|
|
1163
|
+
return;
|
|
1164
|
+
if (!legacy && isRecord(request.body) && typeof request.body.method === "string" &&
|
|
985
1165
|
!enabledMethods.has(request.body.method)) {
|
|
986
1166
|
await sendRpcError(reply, 404, -32601, "Method not found", requestId(request.body.id));
|
|
987
1167
|
return;
|
|
988
1168
|
}
|
|
989
1169
|
const protectsCatalogue = authentication?.discovery === "protected";
|
|
990
1170
|
let subscriptionTarget;
|
|
991
|
-
if (isRecord(request.body) && request.body.method === "subscriptions/listen") {
|
|
1171
|
+
if (!legacy && isRecord(request.body) && request.body.method === "subscriptions/listen") {
|
|
992
1172
|
try {
|
|
993
1173
|
subscriptionTarget = resourceSubscriptionTarget(request.body, resourceSubscriptions.maxUriBytes, resourcesByUri, resourceTemplates);
|
|
994
1174
|
}
|
|
@@ -1030,6 +1210,7 @@ export function createEmseepea(options) {
|
|
|
1030
1210
|
await serveResourceSubscription(request, reply, subscriptionTarget.uri);
|
|
1031
1211
|
return;
|
|
1032
1212
|
}
|
|
1213
|
+
const capability = capabilityNameForRequest(request.body, toolsByName, resourcesByUri, resourceTemplates, promptsByName);
|
|
1033
1214
|
const disconnected = new AbortController();
|
|
1034
1215
|
const abort = () => disconnected.abort();
|
|
1035
1216
|
request.raw.once("aborted", abort);
|
|
@@ -1043,6 +1224,8 @@ export function createEmseepea(options) {
|
|
|
1043
1224
|
signal: disconnected.signal,
|
|
1044
1225
|
principal,
|
|
1045
1226
|
filterCatalogues: protectsCatalogue,
|
|
1227
|
+
capability,
|
|
1228
|
+
legacy,
|
|
1046
1229
|
}, () => nodeHandler(request.raw, reply.raw, request.body));
|
|
1047
1230
|
}
|
|
1048
1231
|
finally {
|
|
@@ -1503,14 +1686,14 @@ function singleHeader(rawHeaders, name) {
|
|
|
1503
1686
|
}
|
|
1504
1687
|
return values.length === 0 ? undefined : values.length === 1 ? values[0] : null;
|
|
1505
1688
|
}
|
|
1506
|
-
async function validateMcpRequestHeaders(request, reply) {
|
|
1689
|
+
async function validateMcpRequestHeaders(request, reply, legacy) {
|
|
1507
1690
|
const body = request.body;
|
|
1508
1691
|
const id = isRecord(body) ? requestId(body.id) : null;
|
|
1509
1692
|
if (!acceptsMcpResponses(request.headers.accept)) {
|
|
1510
1693
|
await sendRpcError(reply, 406, -32000, "Not Acceptable: Client must accept both application/json and text/event-stream", id);
|
|
1511
1694
|
return false;
|
|
1512
1695
|
}
|
|
1513
|
-
if (!isRecord(body) || typeof body.method !== "string")
|
|
1696
|
+
if (legacy || !isRecord(body) || typeof body.method !== "string")
|
|
1514
1697
|
return true;
|
|
1515
1698
|
const header = (name) => singleHeader(request.raw.rawHeaders, name);
|
|
1516
1699
|
const rejectMismatch = async (name) => {
|
|
@@ -2015,13 +2198,31 @@ function nonNegativePort(value) {
|
|
|
2015
2198
|
}
|
|
2016
2199
|
return value;
|
|
2017
2200
|
}
|
|
2018
|
-
function
|
|
2019
|
-
|
|
2020
|
-
|
|
2201
|
+
function directHandlerContext(access, context, signal, deadlineMs, requestState, reportLog) {
|
|
2202
|
+
return {
|
|
2203
|
+
signal,
|
|
2204
|
+
deadlineMs,
|
|
2205
|
+
principal: access === "public" ? undefined : principalFrom(context.http?.authInfo),
|
|
2206
|
+
inputResponses: checkedInputResponses(context.mcpReq.inputResponses),
|
|
2207
|
+
...(requestState ? {
|
|
2208
|
+
requestState: context.mcpReq.requestState(),
|
|
2209
|
+
mintRequestState: (payload) => requestState.mint(payload, context),
|
|
2210
|
+
} : {}),
|
|
2211
|
+
...(reportLog ? { reportLog } : {}),
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
async function assertInputRequired(result, requestState, context) {
|
|
2215
|
+
if (!result.inputRequests && result.requestState === undefined) {
|
|
2216
|
+
throw new Error("Client-input result must include requests or state");
|
|
2021
2217
|
}
|
|
2022
|
-
if (Object.values(result.inputRequests).some((request) => request.method !== "elicitation/create")) {
|
|
2218
|
+
if (result.inputRequests && Object.values(result.inputRequests).some((request) => request.method !== "elicitation/create")) {
|
|
2023
2219
|
throw new Error("Client-input request kind is not supported");
|
|
2024
2220
|
}
|
|
2221
|
+
if (result.requestState !== undefined) {
|
|
2222
|
+
if (!requestState)
|
|
2223
|
+
throw new Error("Client-input request state is not configured");
|
|
2224
|
+
await requestState.verify(result.requestState, context);
|
|
2225
|
+
}
|
|
2025
2226
|
}
|
|
2026
2227
|
function assertResultSize(result, maxApplicationResultBytes, deadlineMs, signal) {
|
|
2027
2228
|
let serialized;
|