2477 lines
109 KiB
Plaintext
2477 lines
109 KiB
Plaintext
const require_chunk = require('./chunk-Bnu9O96Y.cjs');
|
|
const require_src = require('./src-BhnGYybN.cjs');
|
|
let _modelcontextprotocol_server__shims = require("@modelcontextprotocol/server/_shims");
|
|
let _modelcontextprotocol_core_internal = require("@modelcontextprotocol/core/internal");
|
|
|
|
//#region src/server/completable.ts
|
|
const COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
|
/**
|
|
* Wraps a schema to provide autocompletion capabilities. Useful for, e.g., prompt arguments in MCP.
|
|
*
|
|
* @example
|
|
* ```ts source="./completable.examples.ts#completable_basicUsage"
|
|
* server.registerPrompt(
|
|
* 'review-code',
|
|
* {
|
|
* title: 'Code Review',
|
|
* argsSchema: z.object({
|
|
* language: completable(z.string().describe('Programming language'), value =>
|
|
* ['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value))
|
|
* )
|
|
* })
|
|
* },
|
|
* ({ language }) => ({
|
|
* messages: [
|
|
* {
|
|
* role: 'user' as const,
|
|
* content: {
|
|
* type: 'text' as const,
|
|
* text: `Review this ${language} code.`
|
|
* }
|
|
* }
|
|
* ]
|
|
* })
|
|
* );
|
|
* ```
|
|
*
|
|
* @see {@linkcode server/mcp.McpServer.registerPrompt | McpServer.registerPrompt} for using completable schemas in prompt argument definitions
|
|
*/
|
|
function completable(schema, complete) {
|
|
Object.defineProperty(schema, COMPLETABLE_SYMBOL, {
|
|
value: { complete },
|
|
enumerable: false,
|
|
writable: false,
|
|
configurable: false
|
|
});
|
|
return schema;
|
|
}
|
|
/**
|
|
* Checks if a schema is completable (has completion metadata).
|
|
*/
|
|
function isCompletable(schema) {
|
|
return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
|
|
}
|
|
/**
|
|
* Gets the completer callback from a completable schema, if it exists.
|
|
*/
|
|
function getCompleter(schema) {
|
|
return schema[COMPLETABLE_SYMBOL]?.complete;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/sseKeepAlive.ts
|
|
/** Default interval between SSE keep-alive comment frames. */
|
|
const DEFAULT_SSE_KEEP_ALIVE_MS = 15e3;
|
|
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
/** Arms an unref'd timer, or disables keep-alive for invalid delays. */
|
|
function armSseKeepAlive(intervalMs, onTick) {
|
|
if (!Number.isFinite(intervalMs) || intervalMs < 1) return;
|
|
const timer = setInterval(onTick, Math.min(intervalMs, MAX_TIMER_DELAY_MS));
|
|
timer.unref?.();
|
|
return timer;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/serverEventBus.ts
|
|
/**
|
|
* A `ServerEventBus` backed by an in-process listener set.
|
|
*
|
|
* `publish()` delivers synchronously to the live listener set (a listener
|
|
* unsubscribing itself mid-dispatch is safe; the entry's listen-router
|
|
* listeners never unsubscribe peers). A throwing listener does not stop
|
|
* delivery to the others.
|
|
*/
|
|
var InMemoryServerEventBus = class {
|
|
_listeners = /* @__PURE__ */ new Set();
|
|
/**
|
|
* @param onerror - Optional callback for errors thrown by listeners
|
|
* during dispatch.
|
|
*/
|
|
constructor(onerror) {
|
|
this.onerror = onerror;
|
|
}
|
|
publish(event) {
|
|
for (const listener of this._listeners) try {
|
|
listener(event);
|
|
} catch (error) {
|
|
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
}
|
|
}
|
|
subscribe(listener) {
|
|
this._listeners.add(listener);
|
|
let live = true;
|
|
return () => {
|
|
if (!live) return;
|
|
live = false;
|
|
this._listeners.delete(listener);
|
|
};
|
|
}
|
|
/** The number of currently registered listeners (test/introspection only — the routers track capacity via their own open-subscription set). */
|
|
get listenerCount() {
|
|
return this._listeners.size;
|
|
}
|
|
};
|
|
/** Build a {@linkcode ServerNotifier} over a bus. */
|
|
function createServerNotifier(bus) {
|
|
return {
|
|
toolsChanged: () => bus.publish({ kind: "tools_list_changed" }),
|
|
promptsChanged: () => bus.publish({ kind: "prompts_list_changed" }),
|
|
resourcesChanged: () => bus.publish({ kind: "resources_list_changed" }),
|
|
resourceUpdated: (uri) => bus.publish({
|
|
kind: "resource_updated",
|
|
uri
|
|
})
|
|
};
|
|
}
|
|
/**
|
|
* Whether a `subscriptions/listen` filter accepts a given change event.
|
|
*
|
|
* Pure: no I/O, no mutation. The filter governs ONLY the four
|
|
* subscription-gated change types — non-gated notifications never reach the
|
|
* bus and are not modeled here.
|
|
*
|
|
* `resource_updated` matches only when `resourceSubscriptions` is present and
|
|
* contains the event's URI exactly (per the spec: "for these resource URIs").
|
|
*/
|
|
function listenFilterAccepts(filter, event) {
|
|
switch (event.kind) {
|
|
case "tools_list_changed": return filter.toolsListChanged === true;
|
|
case "prompts_list_changed": return filter.promptsListChanged === true;
|
|
case "resources_list_changed": return filter.resourcesListChanged === true;
|
|
case "resource_updated": return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
|
|
}
|
|
}
|
|
/**
|
|
* The honored subset of a requested filter: keeps only the fields the client
|
|
* explicitly opted in to (drops `false` and absent fields), narrowed against
|
|
* the server's declared capabilities when supplied. The serving entry sends
|
|
* this back in `notifications/subscriptions/acknowledged` so the ack reflects
|
|
* what the server can actually deliver.
|
|
*
|
|
* - `toolsListChanged` is honored only when `capabilities.tools.listChanged`
|
|
* is advertised; likewise `promptsListChanged` / `resourcesListChanged`.
|
|
* - `resourceSubscriptions` is honored only when
|
|
* `capabilities.resources.subscribe` is advertised.
|
|
*
|
|
* `capabilities` is optional on this pure helper for test convenience only —
|
|
* both wired routers REQUIRE capabilities at the call site (the HTTP router's
|
|
* `serve()` takes a required parameter; `StdioListenRouter.serve()` throws
|
|
* before `setServerCapabilities()` was called), so the fail-open
|
|
* `undefined → honor everything` branch is never reachable on a wired entry.
|
|
*/
|
|
function honoredSubset(requested, capabilities) {
|
|
const honored = {};
|
|
const allow = (bit) => capabilities === void 0 || bit === true;
|
|
if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
|
|
if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
|
|
if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
|
|
if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
|
|
return honored;
|
|
}
|
|
/** Map a {@linkcode ServerEvent} onto its wire notification `{method, params}`. */
|
|
function serverEventToNotification(event) {
|
|
switch (event.kind) {
|
|
case "tools_list_changed": return { method: "notifications/tools/list_changed" };
|
|
case "prompts_list_changed": return { method: "notifications/prompts/list_changed" };
|
|
case "resources_list_changed": return { method: "notifications/resources/list_changed" };
|
|
case "resource_updated": return {
|
|
method: "notifications/resources/updated",
|
|
params: { uri: event.uri }
|
|
};
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/listenRouter.ts
|
|
/** Default capacity guard: refuse a new subscription when this many are already open. */
|
|
const DEFAULT_MAX_SUBSCRIPTIONS = 1024;
|
|
function jsonRpcError(id, code, message) {
|
|
return Response.json({
|
|
jsonrpc: "2.0",
|
|
error: {
|
|
code,
|
|
message
|
|
},
|
|
id
|
|
}, { status: 200 });
|
|
}
|
|
/** Stamp the subscription id onto a notification's `_meta`. Non-mutating. */
|
|
function stampSubscriptionId(notification, subscriptionId) {
|
|
return {
|
|
method: notification.method,
|
|
params: {
|
|
...notification.params,
|
|
_meta: {
|
|
...notification.params?._meta,
|
|
[_modelcontextprotocol_core_internal.SUBSCRIPTION_ID_META_KEY]: subscriptionId
|
|
}
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Read the requested filter off a `subscriptions/listen` request body.
|
|
* Returns the validated filter, or `undefined` when `params.notifications`
|
|
* is absent or fails the schema (the caller answers `-32602` — the spec
|
|
* marks `notifications` REQUIRED on the listen request).
|
|
*/
|
|
function parseListenFilter(message) {
|
|
const outcome = require_src.codecForVersion(require_src.MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
|
|
return outcome.ok ? outcome.value.params?.notifications : void 0;
|
|
}
|
|
function createListenRouter(options) {
|
|
const { bus, onerror } = options;
|
|
const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
|
|
const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
const open = /* @__PURE__ */ new Set();
|
|
function serve(message, signal, capabilities, serverInfo) {
|
|
if (open.size >= maxSubscriptions) {
|
|
onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
|
|
return jsonRpcError(message.id, -32603, "Subscription limit reached");
|
|
}
|
|
const filter = parseListenFilter(message);
|
|
if (filter === void 0) return jsonRpcError(message.id, -32602, "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter");
|
|
const honored = honoredSubset(filter, capabilities);
|
|
const subscriptionId = message.id;
|
|
const encoder = new TextEncoder();
|
|
let controller;
|
|
let closed = false;
|
|
let unsubscribe;
|
|
let keepAliveTimer;
|
|
let abortCleanup;
|
|
const writeFrame = (frame) => {
|
|
if (closed) return;
|
|
try {
|
|
controller.enqueue(encoder.encode(frame));
|
|
} catch (error) {
|
|
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
}
|
|
};
|
|
const writeNotification = (method, params) => {
|
|
writeFrame(`event: message\ndata: ${JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
method,
|
|
params
|
|
})}\n\n`);
|
|
};
|
|
const teardown = (graceful) => {
|
|
if (closed) return;
|
|
if (graceful) writeFrame(`event: message\ndata: ${JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: subscriptionId,
|
|
result: {
|
|
resultType: "complete",
|
|
_meta: {
|
|
[_modelcontextprotocol_core_internal.SUBSCRIPTION_ID_META_KEY]: subscriptionId,
|
|
[_modelcontextprotocol_core_internal.SERVER_INFO_META_KEY]: serverInfo
|
|
}
|
|
}
|
|
})}\n\n`);
|
|
closed = true;
|
|
try {
|
|
unsubscribe?.();
|
|
} catch (error) {
|
|
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
}
|
|
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
abortCleanup?.();
|
|
open.delete(teardown);
|
|
try {
|
|
controller.close();
|
|
} catch {}
|
|
};
|
|
const readable = new ReadableStream({
|
|
start(streamController) {
|
|
controller = streamController;
|
|
const ack = stampSubscriptionId({
|
|
method: "notifications/subscriptions/acknowledged",
|
|
params: { notifications: honored }
|
|
}, subscriptionId);
|
|
writeNotification(ack.method, ack.params);
|
|
if (Object.keys(honored).length === 0) {
|
|
teardown(true);
|
|
return;
|
|
}
|
|
unsubscribe = bus.subscribe((event) => {
|
|
if (closed || !listenFilterAccepts(honored, event)) return;
|
|
const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
|
|
writeNotification(note.method, note.params);
|
|
});
|
|
keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
|
|
open.add(teardown);
|
|
},
|
|
cancel() {
|
|
teardown(false);
|
|
}
|
|
});
|
|
if (signal !== void 0) if (signal.aborted) teardown(false);
|
|
else {
|
|
const onAbort = () => teardown(false);
|
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
abortCleanup = () => signal.removeEventListener("abort", onAbort);
|
|
}
|
|
return new Response(readable, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache, no-transform",
|
|
Connection: "keep-alive",
|
|
"X-Accel-Buffering": "no"
|
|
}
|
|
});
|
|
}
|
|
return {
|
|
serve,
|
|
closeAll() {
|
|
for (const teardown of open) teardown(true);
|
|
},
|
|
get openCount() {
|
|
return open.size;
|
|
}
|
|
};
|
|
}
|
|
const CHANGE_NOTIFICATION_METHODS = new Set([
|
|
"notifications/tools/list_changed",
|
|
"notifications/prompts/list_changed",
|
|
"notifications/resources/list_changed",
|
|
"notifications/resources/updated"
|
|
]);
|
|
/**
|
|
* Per-connection listen state for the stdio entry. One instance is held by
|
|
* `serveStdio` for the connection lifetime; it routes inbound
|
|
* `subscriptions/listen` / `notifications/cancelled` and rewrites outbound
|
|
* change notifications onto the active subscriptions. No bus — the long-lived
|
|
* pinned instance's existing `send*ListChanged()` calls feed straight into
|
|
* `routeOutbound()`.
|
|
*/
|
|
var StdioListenRouter = class {
|
|
/** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
|
|
_subs = /* @__PURE__ */ new Map();
|
|
/**
|
|
* The serving instance's declared capabilities. Filled in by the entry
|
|
* once the modern instance is constructed (the router is created before
|
|
* the instance exists), so the acknowledged filter is narrowed against
|
|
* what the server can actually deliver.
|
|
*/
|
|
_serverCapabilities;
|
|
/**
|
|
* The serving instance's identity, stamped onto the graceful-close
|
|
* results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
|
|
* `ResultMetaObject`). Handed over together with the capabilities.
|
|
*/
|
|
_serverInfo;
|
|
constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
|
|
this._maxSubscriptions = _maxSubscriptions;
|
|
this._serverCapabilities = serverCapabilities;
|
|
this._serverInfo = serverInfo;
|
|
}
|
|
/**
|
|
* Record the serving instance's declared capabilities and identity once
|
|
* it has been constructed. Called by `serveStdio`'s connect path;
|
|
* subsequent `serve()` calls narrow the honored filter against the
|
|
* capabilities, and `teardownAll()` stamps the identity.
|
|
*/
|
|
setServerCapabilities(capabilities, serverInfo) {
|
|
this._serverCapabilities = capabilities;
|
|
if (serverInfo !== void 0) this._serverInfo = serverInfo;
|
|
}
|
|
/** Whether `id` is an active listen subscription on this connection. */
|
|
has(id) {
|
|
return this._subs.has(id);
|
|
}
|
|
/**
|
|
* Serve one inbound `subscriptions/listen` request: registers the
|
|
* subscription and returns the stamped acknowledged notification (or, on
|
|
* capacity / params rejection, the in-band JSON-RPC error response).
|
|
*
|
|
* @throws when called before {@linkcode setServerCapabilities} (or the
|
|
* constructor) has supplied the serving instance's capabilities. Honoring a
|
|
* filter without knowing the server's advertised capabilities would fail
|
|
* open (deliver unadvertised types); the entry guarantees capabilities are
|
|
* set before any listen request is routed here.
|
|
*/
|
|
serve(message) {
|
|
if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
|
|
if (this._subs.size >= this._maxSubscriptions) return {
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
error: {
|
|
code: -32603,
|
|
message: "Subscription limit reached"
|
|
}
|
|
};
|
|
const filter = parseListenFilter(message);
|
|
if (filter === void 0) return {
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
error: {
|
|
code: -32602,
|
|
message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
|
|
}
|
|
};
|
|
const honored = honoredSubset(filter, this._serverCapabilities);
|
|
this._subs.set(message.id, honored);
|
|
return stampSubscriptionId({
|
|
method: "notifications/subscriptions/acknowledged",
|
|
params: { notifications: honored }
|
|
}, message.id);
|
|
}
|
|
/**
|
|
* Tear down one subscription (inbound `notifications/cancelled`). Returns
|
|
* `true` when a subscription was removed. After this call NOTHING further
|
|
* is delivered for that subscription id (the post-cancel hardening).
|
|
*/
|
|
cancel(id) {
|
|
return this._subs.delete(id);
|
|
}
|
|
/**
|
|
* Route an outbound notification through the active subscriptions.
|
|
*
|
|
* - For a subscription-gated change notification, returns one stamped copy
|
|
* per subscription that opted in to it (an empty array means it is
|
|
* dropped — the modern era never delivers an un-requested change type).
|
|
* - For any other outbound message, returns `'passthrough'` (the entry
|
|
* forwards it as-is).
|
|
*/
|
|
routeOutbound(message) {
|
|
if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
|
|
const uriParam = message.params?.["uri"];
|
|
const uri = typeof uriParam === "string" ? uriParam : void 0;
|
|
const event = notificationToServerEvent(message.method, uri);
|
|
const out = [];
|
|
for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
|
|
method: message.method,
|
|
params: message.params ?? {}
|
|
}, subscriptionId));
|
|
return out;
|
|
}
|
|
/**
|
|
* Server-side graceful teardown of every active subscription: returns the
|
|
* empty `subscriptions/listen` JSON-RPC result for each subscription id —
|
|
* the spec's graceful-close signal, `_meta` carrying the subscription id
|
|
* and the serving instance's identity — for the entry to emit before
|
|
* closing the wire. Clears the set so nothing further is delivered.
|
|
*/
|
|
teardownAll() {
|
|
const out = [];
|
|
for (const id of this._subs.keys()) out.push({
|
|
jsonrpc: "2.0",
|
|
id,
|
|
result: {
|
|
resultType: "complete",
|
|
_meta: {
|
|
[_modelcontextprotocol_core_internal.SUBSCRIPTION_ID_META_KEY]: id,
|
|
...this._serverInfo !== void 0 && { [_modelcontextprotocol_core_internal.SERVER_INFO_META_KEY]: this._serverInfo }
|
|
}
|
|
}
|
|
});
|
|
this._subs.clear();
|
|
return out;
|
|
}
|
|
};
|
|
function notificationToServerEvent(method, uri) {
|
|
switch (method) {
|
|
case "notifications/tools/list_changed": return { kind: "tools_list_changed" };
|
|
case "notifications/prompts/list_changed": return { kind: "prompts_list_changed" };
|
|
case "notifications/resources/list_changed": return { kind: "resources_list_changed" };
|
|
default: return {
|
|
kind: "resource_updated",
|
|
uri: uri ?? ""
|
|
};
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/middleware/bearerAuth.ts
|
|
function comparableResource(value) {
|
|
const text = String(value);
|
|
const hash = text.indexOf("#");
|
|
return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, "");
|
|
}
|
|
function sameResource(reported, expected) {
|
|
if (!reported) return false;
|
|
return comparableResource(reported) === comparableResource(expected);
|
|
}
|
|
function headerQuotedValue(value) {
|
|
return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
|
|
}
|
|
/**
|
|
* Build a `WWW-Authenticate: Bearer …` challenge header value (RFC 6750).
|
|
*
|
|
* The single formatter behind every challenge this package emits — the
|
|
* bearer-auth 401/403 answers and the per-operation scope-challenge 403 — so
|
|
* all challenges from one server agree on parameter order and quoting. Every
|
|
* parameter value is emitted as an HTTP quoted-string with `\` and `"`
|
|
* escaped and non-printable characters replaced.
|
|
*/
|
|
function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, resourceMetadataUrl) {
|
|
let header = `Bearer error="${headerQuotedValue(errorCode)}", error_description="${headerQuotedValue(description)}"`;
|
|
if (requiredScopes.length > 0) header += `, scope="${headerQuotedValue(requiredScopes.join(" "))}"`;
|
|
if (resourceMetadataUrl) header += `, resource_metadata="${headerQuotedValue(resourceMetadataUrl)}"`;
|
|
return header;
|
|
}
|
|
/**
|
|
* Validate a raw `Authorization` header value as a Bearer token and return
|
|
* the verified {@link AuthInfo}.
|
|
*
|
|
* The runtime-neutral core of Bearer authentication: it parses the header,
|
|
* runs the verifier, compares the token's resource with `expectedResource`,
|
|
* enforces `requiredScopes`, and rejects tokens without an
|
|
* expiration or past it. On any failure it throws an {@link OAuthError} —
|
|
* pass that to {@link bearerAuthChallengeResponse} for the matching HTTP
|
|
* answer, or use {@link requireBearerAuth} to get both steps as one call.
|
|
*
|
|
* Framework adapters build on this: `requireBearerAuth` from
|
|
* `@modelcontextprotocol/express` feeds it `req.headers.authorization`.
|
|
*/
|
|
async function verifyBearerToken(authorizationHeader, options) {
|
|
const { verifier, requiredScopes = [], expectedResource } = options;
|
|
if (!authorizationHeader) throw new require_src.OAuthError(require_src.OAuthErrorCode.InvalidToken, "Missing Authorization header");
|
|
const [type, token] = authorizationHeader.split(" ");
|
|
if (type?.toLowerCase() !== "bearer" || !token) throw new require_src.OAuthError(require_src.OAuthErrorCode.InvalidToken, "Invalid Authorization header format, expected 'Bearer TOKEN'");
|
|
const authInfo = await verifier.verifyAccessToken(token);
|
|
if (expectedResource !== void 0 && !sameResource(authInfo.resource, expectedResource)) throw new require_src.OAuthError(require_src.OAuthErrorCode.InvalidToken, "Token was not issued for this resource");
|
|
if (requiredScopes.length > 0) {
|
|
if (!requiredScopes.every((scope) => authInfo.scopes.includes(scope))) throw new require_src.OAuthError(require_src.OAuthErrorCode.InsufficientScope, "Insufficient scope");
|
|
}
|
|
if (typeof authInfo.expiresAt !== "number" || Number.isNaN(authInfo.expiresAt)) throw new require_src.OAuthError(require_src.OAuthErrorCode.InvalidToken, "Token has no expiration time");
|
|
else if (authInfo.expiresAt < Date.now() / 1e3) throw new require_src.OAuthError(require_src.OAuthErrorCode.InvalidToken, "Token has expired");
|
|
if (options.resourceMetadataUrl !== void 0 && authInfo.resourceMetadataUrl === void 0) return {
|
|
...authInfo,
|
|
resourceMetadataUrl: options.resourceMetadataUrl
|
|
};
|
|
return authInfo;
|
|
}
|
|
/**
|
|
* Build the HTTP answer for a Bearer authentication failure.
|
|
*
|
|
* Maps an {@link OAuthError} to its status — `401` for `invalid_token` and
|
|
* `403` for `insufficient_scope` (both carrying the `WWW-Authenticate: Bearer …`
|
|
* challenge, with `resource_metadata` when configured so clients can discover
|
|
* the Authorization Server), `500` for `server_error`, `400` for anything
|
|
* else. A non-`OAuthError` value answers `500 server_error`. The body is the
|
|
* OAuth error JSON.
|
|
*/
|
|
function bearerAuthChallengeResponse(error, options) {
|
|
const { requiredScopes = [], resourceMetadataUrl } = options ?? {};
|
|
if (!(error instanceof require_src.OAuthError)) {
|
|
const serverError = new require_src.OAuthError(require_src.OAuthErrorCode.ServerError, "Internal Server Error");
|
|
return Response.json(serverError.toResponseObject(), { status: 500 });
|
|
}
|
|
switch (error.code) {
|
|
case require_src.OAuthErrorCode.InvalidToken: {
|
|
const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
|
|
return Response.json(error.toResponseObject(), {
|
|
status: 401,
|
|
headers: { "WWW-Authenticate": challenge }
|
|
});
|
|
}
|
|
case require_src.OAuthErrorCode.InsufficientScope: {
|
|
const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
|
|
return Response.json(error.toResponseObject(), {
|
|
status: 403,
|
|
headers: { "WWW-Authenticate": challenge }
|
|
});
|
|
}
|
|
case require_src.OAuthErrorCode.ServerError: return Response.json(error.toResponseObject(), { status: 500 });
|
|
default: return Response.json(error.toResponseObject(), { status: 400 });
|
|
}
|
|
}
|
|
/**
|
|
* Require a valid Bearer token on web-standard requests.
|
|
*
|
|
* The framework-free counterpart of `requireBearerAuth` from
|
|
* `@modelcontextprotocol/express`, for hosts whose HTTP surface is a
|
|
* `fetch(request)` handler — Cloudflare Workers, Deno, Bun, Hono. The
|
|
* returned gate resolves to the verified {@link AuthInfo}, or to the
|
|
* ready-to-return challenge `Response` when the request must be refused.
|
|
*
|
|
* @example
|
|
* ```ts source="./bearerAuth.examples.ts#requireBearerAuth_fetchGate"
|
|
* const gate = requireBearerAuth({ verifier, requiredScopes: ['mcp'] });
|
|
*
|
|
* async function fetchHandler(request: Request): Promise<Response> {
|
|
* const auth: AuthInfo | Response = await gate(request);
|
|
* if (auth instanceof Response) return auth;
|
|
* return handler.fetch(request, { authInfo: auth });
|
|
* }
|
|
* ```
|
|
*/
|
|
function requireBearerAuth(options) {
|
|
const { verifier, requiredScopes = [], resourceMetadataUrl, expectedResource } = options;
|
|
const resolved = {
|
|
verifier,
|
|
requiredScopes,
|
|
resourceMetadataUrl,
|
|
expectedResource
|
|
};
|
|
return async (request) => {
|
|
const [authorizationHeader] = (request.headers.get("authorization") ?? "").split(",");
|
|
try {
|
|
return await verifyBearerToken(authorizationHeader || void 0, resolved);
|
|
} catch (error) {
|
|
return bearerAuthChallengeResponse(error, resolved);
|
|
}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/middleware/oauthMetadata.ts
|
|
function checkIssuerUrl(issuer, allowInsecure) {
|
|
if (issuer.protocol !== "https:" && issuer.hostname !== "localhost" && issuer.hostname !== "127.0.0.1" && !allowInsecure) throw new Error("Issuer URL must be HTTPS");
|
|
if (issuer.hash) throw new Error(`Issuer URL must not have a fragment: ${issuer}`);
|
|
if (issuer.search) throw new Error(`Issuer URL must not have a query string: ${issuer}`);
|
|
}
|
|
/**
|
|
* Derive the RFC 9728 Protected Resource Metadata document from
|
|
* {@link AuthMetadataOptions}, validating the Authorization Server issuer URL
|
|
* (HTTPS required outside localhost) in the process.
|
|
*
|
|
* `oauthMetadataResponse` and the Express `mcpAuthMetadataRouter` both build
|
|
* on this; use it directly when serving the document through your own
|
|
* routing — or call it once at startup to fail fast on a misconfigured
|
|
* issuer before any request arrives.
|
|
*/
|
|
function buildOAuthProtectedResourceMetadata(options) {
|
|
checkIssuerUrl(new URL(options.oauthMetadata.issuer), options.dangerouslyAllowInsecureIssuerUrl);
|
|
return {
|
|
resource: options.resourceServerUrl.href,
|
|
authorization_servers: [options.oauthMetadata.issuer],
|
|
scopes_supported: options.scopesSupported,
|
|
resource_name: options.resourceName,
|
|
resource_documentation: options.serviceDocumentationUrl?.href
|
|
};
|
|
}
|
|
/**
|
|
* Builds the RFC 9728 Protected Resource Metadata URL for a given MCP server
|
|
* URL by inserting `/.well-known/oauth-protected-resource` ahead of the path.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
|
|
* // → 'https://api.example.com/.well-known/oauth-protected-resource/mcp'
|
|
* ```
|
|
*/
|
|
function getOAuthProtectedResourceMetadataUrl(serverUrl) {
|
|
const metadataUrl = new URL(serverUrl);
|
|
metadataUrl.pathname = protectedResourceMetadataPath(serverUrl);
|
|
metadataUrl.hash = "";
|
|
return metadataUrl.href;
|
|
}
|
|
/** The RFC 9728 path-aware well-known path for a resource URL. */
|
|
function protectedResourceMetadataPath(resourceServerUrl) {
|
|
const rsPath = stripTrailingSlash(resourceServerUrl.pathname);
|
|
return `/.well-known/oauth-protected-resource${rsPath === "/" ? "" : rsPath}`;
|
|
}
|
|
function stripTrailingSlash(path) {
|
|
return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
}
|
|
const ALLOWED_METHODS = "GET, HEAD, OPTIONS";
|
|
function metadataDocumentResponse(request, metadata) {
|
|
if (request.method === "OPTIONS") {
|
|
const requestedHeaders = request.headers.get("access-control-request-headers");
|
|
return new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": ALLOWED_METHODS,
|
|
...requestedHeaders === null ? {} : {
|
|
"Access-Control-Allow-Headers": requestedHeaders,
|
|
Vary: "Access-Control-Request-Headers"
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
const error = new require_src.OAuthError(require_src.OAuthErrorCode.MethodNotAllowed, `The method ${request.method} is not allowed for this endpoint`);
|
|
return Response.json(error.toResponseObject(), {
|
|
status: 405,
|
|
headers: {
|
|
Allow: ALLOWED_METHODS,
|
|
"Access-Control-Allow-Origin": "*"
|
|
}
|
|
});
|
|
}
|
|
const response = Response.json(metadata, { headers: { "Access-Control-Allow-Origin": "*" } });
|
|
return request.method === "HEAD" ? new Response(null, {
|
|
status: response.status,
|
|
headers: response.headers
|
|
}) : response;
|
|
}
|
|
/**
|
|
* Serve the two OAuth discovery documents an MCP server acting as a Resource
|
|
* Server exposes, from a web-standard `fetch(request)` handler:
|
|
*
|
|
* - `/.well-known/oauth-protected-resource[/<path>]` — RFC 9728 Protected
|
|
* Resource Metadata, derived from the supplied options (path-aware: the
|
|
* resource URL's path is reflected in the route).
|
|
* - `/.well-known/oauth-authorization-server` — RFC 8414 Authorization
|
|
* Server Metadata, passed through verbatim.
|
|
*
|
|
* Returns the matched document `Response` (JSON with permissive CORS, `405`
|
|
* with an `Allow` header for non-GET methods, `204` for CORS preflight), or
|
|
* `undefined` when the request path is neither well-known route — fall
|
|
* through to your own routing. The framework-free counterpart of
|
|
* `mcpAuthMetadataRouter` from `@modelcontextprotocol/express`; pair it with
|
|
* `requireBearerAuth` and `getOAuthProtectedResourceMetadataUrl` so
|
|
* unauthenticated clients can discover the AS from the `401` challenge.
|
|
*
|
|
* @example
|
|
* ```ts source="./oauthMetadata.examples.ts#oauthMetadataResponse_fetchHandler"
|
|
* async function fetchHandler(request: Request): Promise<Response> {
|
|
* return oauthMetadataResponse(request, options) ?? serveMcp(request);
|
|
* }
|
|
* ```
|
|
*/
|
|
function oauthMetadataResponse(request, options) {
|
|
const requestPath = stripTrailingSlash(new URL(request.url).pathname);
|
|
if (requestPath === protectedResourceMetadataPath(options.resourceServerUrl)) return metadataDocumentResponse(request, buildOAuthProtectedResourceMetadata(options));
|
|
if (requestPath === "/.well-known/oauth-authorization-server") {
|
|
buildOAuthProtectedResourceMetadata(options);
|
|
return metadataDocumentResponse(request, options.oauthMetadata);
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/scopeChallenge.ts
|
|
/** @internal */
|
|
function supportsScopeChallengeResolver(transport) {
|
|
return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
|
|
}
|
|
function assertScope(scope, location) {
|
|
if (typeof scope !== "string" || !/^[\u0021\u0023-\u005B\u005D-\u007E]+$/.test(scope)) throw new TypeError(`${location} must satisfy the OAuth scope-token grammar`);
|
|
}
|
|
function validateScopeChallenge(challenge) {
|
|
if (challenge === null || typeof challenge !== "object" || !Array.isArray(challenge.scopes) || challenge.scopes.length === 0) throw new TypeError("scope challenge must contain at least one scope");
|
|
for (const [index, scope] of challenge.scopes.entries()) assertScope(scope, `scope challenge scopes[${index}]`);
|
|
if (challenge.errorDescription !== void 0 && typeof challenge.errorDescription !== "string") throw new TypeError("scope challenge errorDescription must be a string");
|
|
if (challenge.errorDescription !== void 0 && !/^[\u0020-\u0021\u0023-\u005B\u005D-\u007E]+$/.test(challenge.errorDescription)) throw new TypeError("scope challenge errorDescription must satisfy the RFC 6750 error-description grammar");
|
|
return challenge;
|
|
}
|
|
/**
|
|
* Creates a handler that requires every supplied scope exactly.
|
|
*
|
|
* Requests without authentication are left to the server's authentication gate.
|
|
*/
|
|
function requireScopes(...scopes) {
|
|
if (scopes.length === 0) throw new TypeError("requireScopes must contain at least one scope");
|
|
for (const [index, scope] of scopes.entries()) assertScope(scope, `requireScopes scope[${index}]`);
|
|
const requiredScopes = [...scopes];
|
|
return ({ authInfo }) => {
|
|
if (authInfo === void 0) return;
|
|
const activeScopes = new Set(authInfo.scopes);
|
|
if (requiredScopes.every((scope) => activeScopes.has(scope))) return;
|
|
return { scopes: requiredScopes };
|
|
};
|
|
}
|
|
/** @internal */
|
|
async function findScopeChallenge(requests, authInfo, resolve) {
|
|
for (const request of requests) {
|
|
const challenge = await resolve({
|
|
request,
|
|
...authInfo !== void 0 && { authInfo }
|
|
});
|
|
if (challenge !== void 0) return validateScopeChallenge(challenge);
|
|
}
|
|
}
|
|
/**
|
|
* The RFC 9728 Protected Resource Metadata URL to advertise on a scope
|
|
* challenge, derived from the verified {@link AuthInfo}: the URL the
|
|
* authentication gate stamped (`authInfo.resourceMetadataUrl`, set by the
|
|
* bearer-auth helpers from their `resourceMetadataUrl` option), falling back
|
|
* to the well-known location for an HTTP(S) RFC 8707 `resource` identifier, or
|
|
* `undefined` when neither is available (the `resource_metadata` parameter is
|
|
* then omitted, matching the bearer-auth challenges).
|
|
*
|
|
* @internal
|
|
*/
|
|
function scopeChallengeResourceMetadataUrl(authInfo) {
|
|
if (authInfo?.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
|
|
if (authInfo?.resource?.protocol === "https:" || authInfo?.resource?.protocol === "http:") return getOAuthProtectedResourceMetadataUrl(authInfo.resource);
|
|
}
|
|
/** @internal */
|
|
function createScopeChallengeResponse(challenge, resourceMetadataUrl) {
|
|
return bearerAuthChallengeResponse(new require_src.OAuthError(require_src.OAuthErrorCode.InsufficientScope, challenge.errorDescription ?? "Insufficient scope"), {
|
|
requiredScopes: [...challenge.scopes],
|
|
resourceMetadataUrl
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/legacyInputRequiredShim.ts
|
|
/**
|
|
* Default handler re-entries per originating request — tighter than the
|
|
* client driver's 10 because the shim holds a live wire request open.
|
|
*/
|
|
const DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
|
|
/** Default per-leg timeout: legs are human-paced, so the 60s protocol default is wrong. */
|
|
const DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
|
|
/** Resolves and validates `ServerOptions.inputRequired`, failing loudly at construction time. */
|
|
function resolveLegacyShimOptions(options) {
|
|
if (options?.maxRounds !== void 0 && (!Number.isInteger(options.maxRounds) || options.maxRounds < 1)) throw new RangeError(`inputRequired.maxRounds must be a positive integer (got ${options.maxRounds})`);
|
|
if (options?.roundTimeoutMs !== void 0 && (!Number.isFinite(options.roundTimeoutMs) || options.roundTimeoutMs <= 0)) throw new RangeError(`inputRequired.roundTimeoutMs must be a positive number (got ${options.roundTimeoutMs})`);
|
|
return {
|
|
maxRounds: options?.maxRounds ?? DEFAULT_LEGACY_SHIM_MAX_ROUNDS,
|
|
roundTimeoutMs: options?.roundTimeoutMs ?? DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS,
|
|
legacyShim: options?.legacyShim ?? true
|
|
};
|
|
}
|
|
/**
|
|
* Validates one `inputRequests` entry: malformed or unknown kinds are server
|
|
* bugs and fail loudly on both eras. Shared by the modern seam's capability
|
|
* check and the shim's gate.
|
|
*/
|
|
function coerceEmbeddedInputRequest(method, key, entry) {
|
|
if (entry === null || typeof entry !== "object" || typeof entry.method !== "string") throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an invalid input request '${key}': each inputRequests entry must be an embedded elicitation/create, sampling/createMessage, or roots/list request`);
|
|
const embedded = entry;
|
|
const required = require_src.requiredClientCapabilitiesForInputRequest(embedded);
|
|
if (required === void 0) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input request '${key}' of kind '${embedded.method}', which is not an embedded request the 2026-07-28 revision defines`);
|
|
return {
|
|
embedded,
|
|
required
|
|
};
|
|
}
|
|
/**
|
|
* The 2025-11-25 URL-mode wire shape requires an `elicitationId`; the 2026
|
|
* in-band shape has none, so URL legs mint one (CSPRNG-backed, with a
|
|
* getRandomValues fallback for runtimes without `randomUUID`).
|
|
*/
|
|
function syntheticElicitationId() {
|
|
const webCrypto = globalThis.crypto;
|
|
if (webCrypto?.randomUUID !== void 0) return webCrypto.randomUUID();
|
|
const bytes = new Uint8Array(16);
|
|
webCrypto.getRandomValues(bytes);
|
|
bytes[6] = bytes[6] & 15 | 64;
|
|
bytes[8] = bytes[8] & 63 | 128;
|
|
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
}
|
|
/** Per-family surfacing: tools/call → isError result (the 2025 idiom); prompts/resources → JSON-RPC error. */
|
|
function legacyShimFailure(method, message) {
|
|
if (method === "tools/call") return {
|
|
content: [{
|
|
type: "text",
|
|
text: message
|
|
}],
|
|
isError: true
|
|
};
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, message);
|
|
}
|
|
/** The fulfilment loop — see the module doc for the contract. */
|
|
var LegacyInputRequiredShim = class {
|
|
constructor(_host) {
|
|
this._host = _host;
|
|
}
|
|
async fulfill(method, handler, request, ctx, firstResult) {
|
|
const { maxRounds, roundTimeoutMs } = this._host;
|
|
const outerSignal = ctx.mcpReq.signal;
|
|
let current = firstResult;
|
|
let round = 0;
|
|
while (true) {
|
|
round += 1;
|
|
if (round > maxRounds) return legacyShimFailure(method, require_src.inputRequiredRoundsExceededMessage(method, maxRounds));
|
|
const inputRequests = current.inputRequests;
|
|
const hasInputRequests = inputRequests != null && Object.keys(inputRequests).length > 0;
|
|
const requestState = typeof current.requestState === "string" ? current.requestState : void 0;
|
|
if (!hasInputRequests && requestState === void 0) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input-required result with neither inputRequests nor requestState (every InputRequiredResult must include at least one of the two)`);
|
|
let responses;
|
|
if (hasInputRequests) {
|
|
const declared = this._host.resolvedClientCapabilities(ctx);
|
|
const coerced = [];
|
|
for (const [key, entry] of Object.entries(inputRequests)) {
|
|
const { embedded, required } = coerceEmbeddedInputRequest(method, key, entry);
|
|
if (embedded.method !== "roots/list" && embedded.params === void 0) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input request '${key}' of kind '${embedded.method}' without params`);
|
|
if (require_src.missingClientCapabilities(required, declared) !== void 0) return legacyShimFailure(method, `Cannot request input '${key}' (${embedded.method}): the client on this 2025-era connection did not declare the required capability${declared === void 0 ? " (no client capabilities are available on this connection — per-request legacy serving cannot receive server-to-client requests)" : ""}`);
|
|
coerced.push([key, embedded]);
|
|
}
|
|
const roundAbort = require_src.linkedRoundAbort(outerSignal);
|
|
try {
|
|
const legOptions = {
|
|
relatedRequestId: ctx.mcpReq.id,
|
|
timeout: roundTimeoutMs,
|
|
resetTimeoutOnProgress: true,
|
|
onprogress: () => {},
|
|
signal: roundAbort.signal
|
|
};
|
|
const fulfilled = await Promise.all(coerced.map(async ([key, embedded]) => {
|
|
try {
|
|
return [key, await this._dispatchLeg(embedded, legOptions)];
|
|
} catch (error) {
|
|
roundAbort.abort(error);
|
|
throw error;
|
|
}
|
|
}));
|
|
responses = Object.fromEntries(fulfilled);
|
|
} catch (error) {
|
|
if (outerSignal.aborted) throw error;
|
|
return legacyShimFailure(method, `Fulfilling input required by '${method}' failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
} finally {
|
|
roundAbort.dispose();
|
|
}
|
|
} else await require_src.sleep(require_src.REQUEST_STATE_ONLY_LEG_PACING_MS, outerSignal);
|
|
let ctxNext = {
|
|
...ctx,
|
|
mcpReq: {
|
|
...ctx.mcpReq,
|
|
inputResponses: responses,
|
|
droppedInputResponseKeys: void 0,
|
|
requestState: require_src.requestStateAccessor(requestState)
|
|
}
|
|
};
|
|
if (requestState !== void 0) {
|
|
const decoded = await this._host.verifyRequestState(requestState, ctxNext, method);
|
|
if (decoded !== void 0) ctxNext = require_src.withRequestStateValue(ctxNext, decoded);
|
|
}
|
|
const next = await handler(request, ctxNext);
|
|
if (!require_src.isInputRequiredResult(next)) return next;
|
|
current = next;
|
|
}
|
|
}
|
|
/** Routes one embedded request through the host's existing 2025-era senders (gate already ran). */
|
|
async _dispatchLeg(embedded, options) {
|
|
switch (embedded.method) {
|
|
case "elicitation/create": {
|
|
let params = embedded.params;
|
|
if (params.mode === "url" && params.elicitationId === void 0) params = {
|
|
...params,
|
|
elicitationId: syntheticElicitationId()
|
|
};
|
|
return await this._host.sendElicitation(params, options);
|
|
}
|
|
case "sampling/createMessage": return await this._host.sendSampling(embedded.params, options);
|
|
case "roots/list": return await this._host.listRoots(embedded.params, options);
|
|
}
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/server/server.ts
|
|
/**
|
|
* The request methods whose 2026-07-28 result vocabulary includes
|
|
* `input_required` (the multi round-trip methods). Returning an
|
|
* input-required result from any other handler is a server bug.
|
|
*/
|
|
const INPUT_REQUIRED_CAPABLE_METHODS = new Set([
|
|
"tools/call",
|
|
"prompts/get",
|
|
"resources/read"
|
|
]);
|
|
let writeClientIdentity;
|
|
let installDiscoverHandler;
|
|
let readServerIdentity;
|
|
/**
|
|
* Package-internal: backfills the connection-scoped client-identity fields of a
|
|
* per-request server instance from the request's validated `_meta` envelope, so the
|
|
* (deprecated) {@linkcode Server.getClientCapabilities} / {@linkcode Server.getClientVersion}
|
|
* accessors keep answering on instances that never see an `initialize` handshake.
|
|
* Not public API.
|
|
*/
|
|
function seedClientIdentityFromEnvelope(server, identity) {
|
|
writeClientIdentity(server, identity);
|
|
}
|
|
/**
|
|
* Package-internal: installs the modern-only `server/discover` handler on an instance
|
|
* the HTTP entry has marked as serving the 2026-07-28 era, and makes sure the modern
|
|
* revisions the entry serves appear in the instance's supported-versions list (so the
|
|
* discover advertisement and version-mismatch errors name them). Idempotent.
|
|
* Hand-constructed instances are unaffected: nothing else calls this, so they keep
|
|
* answering `-32601` unless their own supported-versions list opts into a modern
|
|
* revision. Not public API.
|
|
*/
|
|
function installModernOnlyHandlers(server, servedModernVersions) {
|
|
installDiscoverHandler(server, servedModernVersions);
|
|
}
|
|
/**
|
|
* Package-internal: the instance's implementation identity, for the serving
|
|
* entries to stamp onto entry-built results (the `subscriptions/listen`
|
|
* graceful-close result — built outside the encode seam, but the spec's
|
|
* `SubscriptionsListenResultMetaObject` extends `ResultMetaObject`, so it carries
|
|
* the serverInfo SHOULD like every other result). Not public API.
|
|
*/
|
|
function serverIdentityOf(server) {
|
|
return readServerIdentity(server);
|
|
}
|
|
/**
|
|
* An MCP server on top of a pluggable transport.
|
|
*
|
|
* This server will automatically respond to the initialization flow as initiated from the client.
|
|
*
|
|
* @deprecated Use {@linkcode server/mcp.McpServer | McpServer} instead for the high-level API. Only use `Server` for advanced use cases.
|
|
*/
|
|
var Server = class extends require_src.Protocol {
|
|
_clientCapabilities;
|
|
_clientVersion;
|
|
static {
|
|
writeClientIdentity = (server, identity) => {
|
|
if (identity.clientCapabilities !== void 0) server._clientCapabilities = identity.clientCapabilities;
|
|
if (identity.clientInfo !== void 0) server._clientVersion = identity.clientInfo;
|
|
};
|
|
installDiscoverHandler = (server, servedModernVersions) => {
|
|
const missing = servedModernVersions.filter((version) => !server._supportedProtocolVersions.includes(version));
|
|
if (missing.length > 0) server._supportedProtocolVersions = [...server._supportedProtocolVersions, ...missing];
|
|
server.setRequestHandler("server/discover", () => server._ondiscover());
|
|
};
|
|
readServerIdentity = (server) => server._serverInfo;
|
|
}
|
|
_capabilities;
|
|
_instructions;
|
|
_jsonSchemaValidator;
|
|
_cacheHints;
|
|
_requestStateVerify;
|
|
_inputRequiredServing;
|
|
_legacyShim;
|
|
/** Lazily-built legacy shim; the loop lives in legacyInputRequiredShim.ts behind a narrow host contract. */
|
|
_legacyInputRequiredShim() {
|
|
return this._legacyShim ??= new LegacyInputRequiredShim({
|
|
maxRounds: this._inputRequiredServing.maxRounds,
|
|
roundTimeoutMs: this._inputRequiredServing.roundTimeoutMs,
|
|
resolvedClientCapabilities: (ctx) => this._inputRequestCapabilityView(ctx),
|
|
verifyRequestState: (state, ctx, method) => this._verifyRequestState(state, ctx, method),
|
|
sendElicitation: (params, options) => this._sendElicitationLeg(params, options, { validateAcceptedContent: false }),
|
|
sendSampling: (params, options) => this.createMessage(params, options),
|
|
listRoots: (params, options) => this.listRoots(params, options)
|
|
});
|
|
}
|
|
/**
|
|
* Callback for when initialization has fully completed (i.e., the client has sent an `notifications/initialized` notification).
|
|
*/
|
|
oninitialized;
|
|
/**
|
|
* Initializes this server with the given name and version information.
|
|
*/
|
|
constructor(_serverInfo, options) {
|
|
super(options);
|
|
this._serverInfo = _serverInfo;
|
|
this._capabilities = options?.capabilities ? { ...options.capabilities } : {};
|
|
this._instructions = options?.instructions;
|
|
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new _modelcontextprotocol_server__shims.DefaultJsonSchemaValidator();
|
|
this._requestStateVerify = options?.requestState?.verify;
|
|
this._inputRequiredServing = resolveLegacyShimOptions(options?.inputRequired);
|
|
if (options?.cacheHints !== void 0) {
|
|
for (const [operation, hint] of Object.entries(options.cacheHints)) if (hint !== void 0) require_src.assertValidCacheHint(hint, `cacheHints['${operation}']`);
|
|
this._cacheHints = options.cacheHints;
|
|
}
|
|
this.setRequestHandler("initialize", (request) => this._oninitialize(request));
|
|
this.setNotificationHandler("notifications/initialized", () => this.oninitialized?.());
|
|
if (require_src.modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
|
|
if (this._capabilities.logging) this._registerLoggingHandler();
|
|
}
|
|
/** Attaches to the given transport; rejects while this instance is connected to another one. */
|
|
async connect(transport) {
|
|
if (this.transport !== void 0) throw new require_src.SdkError(require_src.SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
|
|
await super.connect(transport);
|
|
}
|
|
/**
|
|
* Registers the built-in `logging/setLevel` request handler.
|
|
*
|
|
* @deprecated Deprecated as of protocol version 2026-07-28 (SEP-2577).
|
|
* Remains functional during the deprecation window (at least twelve months).
|
|
* Migrate to stderr logging (STDIO servers) or OpenTelemetry.
|
|
*/
|
|
_registerLoggingHandler() {
|
|
this.setRequestHandler("logging/setLevel", async (request, ctx) => {
|
|
const transportSessionId = ctx.sessionId || ctx.http?.req?.headers.get("mcp-session-id") || void 0;
|
|
const { level } = request.params;
|
|
const parseResult = require_src.parseSchema(_modelcontextprotocol_core_internal.LoggingLevelSchema, level);
|
|
if (parseResult.success) this._loggingLevels.set(transportSessionId, parseResult.data);
|
|
return {};
|
|
});
|
|
}
|
|
buildContext(ctx, transportInfo) {
|
|
const hasHttpInfo = ctx.http || transportInfo?.request || transportInfo?.closeSSEStream || transportInfo?.closeStandaloneSSEStream;
|
|
return {
|
|
...ctx,
|
|
mcpReq: {
|
|
...ctx.mcpReq,
|
|
log: (level, data, logger) => {
|
|
if (!this._capabilities.logging) return Promise.resolve();
|
|
let threshold;
|
|
if (this._servedModernEra()) {
|
|
threshold = ctx.mcpReq.envelope?.[_modelcontextprotocol_core_internal.LOG_LEVEL_META_KEY];
|
|
if (threshold === void 0) return Promise.resolve();
|
|
} else threshold = this._loggingLevels.get(ctx.sessionId) ?? this._loggingLevels.get(void 0);
|
|
if (threshold !== void 0 && this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(threshold)) return Promise.resolve();
|
|
return ctx.mcpReq.notify({
|
|
method: "notifications/message",
|
|
params: {
|
|
level,
|
|
data,
|
|
logger
|
|
}
|
|
});
|
|
},
|
|
elicitInput: (params, options) => this.elicitInput(params, options),
|
|
requestSampling: (params, options) => this.createMessage(params, options)
|
|
},
|
|
http: hasHttpInfo ? {
|
|
...ctx.http,
|
|
req: transportInfo?.request,
|
|
closeSSE: transportInfo?.closeSSEStream,
|
|
closeStandaloneSSE: transportInfo?.closeStandaloneSSEStream
|
|
} : void 0
|
|
};
|
|
}
|
|
_loggingLevels = /* @__PURE__ */ new Map();
|
|
LOG_LEVEL_SEVERITY = new Map(_modelcontextprotocol_core_internal.LoggingLevelSchema.options.map((level, index) => [level, index]));
|
|
isMessageIgnored = (level, sessionId) => {
|
|
const currentLevel = this._loggingLevels.get(sessionId);
|
|
return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
|
|
};
|
|
/**
|
|
* Registers new capabilities. This can only be called before connecting to a transport.
|
|
*
|
|
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
|
|
*/
|
|
registerCapabilities(capabilities) {
|
|
if (this.transport) throw new require_src.SdkError(require_src.SdkErrorCode.AlreadyConnected, "Cannot register capabilities after connecting to transport");
|
|
const hadLogging = !!this._capabilities.logging;
|
|
this._capabilities = require_src.mergeCapabilities(this._capabilities, capabilities);
|
|
if (!hadLogging && this._capabilities.logging) this._registerLoggingHandler();
|
|
}
|
|
/**
|
|
* Enforces server-side validation for `tools/call` results regardless of how the
|
|
* handler was registered, attaches the configured per-operation cache hint
|
|
* (when one exists) so the 2026-07-28 encode seam can fill `ttlMs`/`cacheScope`
|
|
* for results that do not provide their own, and owns the multi-round-trip
|
|
* seam: on the methods whose 2026-07-28 result vocabulary includes
|
|
* `input_required` (`tools/call`, `prompts/get`, `resources/read`) an
|
|
* input-required return skips result-schema validation and is checked
|
|
* against the served era, the at-least-one rule, and the request's own
|
|
* declared client capabilities; on every other method an input-required
|
|
* return is a server bug and fails loudly. The hint rides a symbol-keyed
|
|
* property that is never serialized, so 2025-era responses are unaffected.
|
|
*/
|
|
_wrapHandler(method, handler) {
|
|
if (method !== "tools/call") {
|
|
const cacheHint = this._cacheHints?.[method];
|
|
const isInputRequiredCapable = INPUT_REQUIRED_CAPABLE_METHODS.has(method);
|
|
if (cacheHint === void 0 && !isInputRequiredCapable) return async (request, ctx) => {
|
|
const result = await handler(request, ctx);
|
|
if (require_src.isInputRequiredResult(result)) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input-required result, but only tools/call, prompts/get and resources/read support input_required (protocol revision 2026-07-28)`);
|
|
return result;
|
|
};
|
|
return async (request, ctx) => {
|
|
const result = isInputRequiredCapable ? await this._invokeInputRequiredCapableHandler(method, handler, request, ctx) : await handler(request, ctx);
|
|
if (require_src.isInputRequiredResult(result)) {
|
|
if (!isInputRequiredCapable) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input-required result, but only tools/call, prompts/get and resources/read support input_required (protocol revision 2026-07-28)`);
|
|
return result;
|
|
}
|
|
return cacheHint === void 0 ? result : require_src.attachCacheHintFallback(result, cacheHint);
|
|
};
|
|
}
|
|
return async (request, ctx) => {
|
|
const codec = require_src.codecForVersion(this._negotiatedProtocolVersion);
|
|
const validatedRequest = codec.validateRequest("tools/call", request);
|
|
if (!validatedRequest.ok) throw new require_src.ProtocolError(validatedRequest.reason === "not-in-era" ? require_src.ProtocolErrorCode.InternalError : require_src.ProtocolErrorCode.InvalidParams, validatedRequest.reason === "not-in-era" ? "No wire schema for tools/call in the resolved era" : `Invalid tools/call request: ${validatedRequest.message}`);
|
|
const result = await this._invokeInputRequiredCapableHandler("tools/call", handler, request, ctx);
|
|
if (require_src.isInputRequiredResult(result)) return result;
|
|
const normalizedResult = require_src.normalizeContentlessToolResult(result);
|
|
const validationResult = codec.validateResult("tools/call", normalizedResult);
|
|
if (!validationResult.ok) throw new require_src.ProtocolError(validationResult.reason === "not-in-era" ? require_src.ProtocolErrorCode.InternalError : require_src.ProtocolErrorCode.InvalidParams, validationResult.reason === "not-in-era" ? "No wire schema for tools/call in the resolved era" : `Invalid tools/call result: ${validationResult.message}`);
|
|
return validationResult.value;
|
|
};
|
|
}
|
|
/**
|
|
* Whether this instance is bound to a 2026-07-28-or-later protocol
|
|
* revision. Era is instance state — a serving entry (`createMcpHandler`,
|
|
* `serveStdio`) marks the instance modern at construction; a 2025-era
|
|
* `initialize` handshake binds it legacy. The multi-round-trip seam reads
|
|
* this directly: there is no per-request era consult.
|
|
*/
|
|
_servedModernEra() {
|
|
return this._negotiatedProtocolVersion !== void 0 && require_src.isModernProtocolVersion(this._negotiatedProtocolVersion);
|
|
}
|
|
/**
|
|
* Invokes a handler for one of the multi-round-trip methods and applies
|
|
* the input-required seam:
|
|
*
|
|
* - a `UrlElicitationRequiredError` (or any 2025-style server→client
|
|
* request idiom) escaping the handler on a request served on the
|
|
* 2026-07-28 era fails LOUDLY with a clear steer to
|
|
* `inputRequired.elicitUrl(...)` — the `-32042` error never reaches the
|
|
* 2026-07-28 wire and the throw is not silently converted. Requests
|
|
* served on the 2025 era keep today's `-32042` behavior byte-exact (the
|
|
* error is rethrown unchanged).
|
|
* - an input-required RETURN toward a 2026-07-28 request must satisfy
|
|
* the at-least-one rule, and every embedded request must be covered by
|
|
* the capabilities declared on the request's envelope (violations
|
|
* answer the typed `-32021` error). Toward a 2025-era request the
|
|
* return is fulfilled by the default-on legacy shim, whose own gate
|
|
* consults the initialize-declared capabilities and surfaces
|
|
* violations per family; `inputRequired.legacyShim: false` restores
|
|
* the pre-shim loud failure.
|
|
*/
|
|
async _invokeInputRequiredCapableHandler(method, handler, request, ctx) {
|
|
const servedModern = this._servedModernEra();
|
|
const rawRequestState = ctx.mcpReq.requestState();
|
|
if (rawRequestState !== void 0 && typeof rawRequestState !== "string") throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, "Invalid or expired requestState", { reason: "invalid_request_state" });
|
|
let ctxForHandler = ctx;
|
|
if (typeof rawRequestState === "string") {
|
|
const decoded = await this._verifyRequestState(rawRequestState, ctx, method);
|
|
if (decoded !== void 0) ctxForHandler = require_src.withRequestStateValue(ctx, decoded);
|
|
}
|
|
let result;
|
|
try {
|
|
result = await handler(request, ctxForHandler);
|
|
} catch (error) {
|
|
if (error instanceof require_src.ProtocolError && error.code === require_src.ProtocolErrorCode.UrlElicitationRequired) {
|
|
if (!servedModern) throw error;
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `URL elicitation cannot be signalled by throwing UrlElicitationRequiredError on protocol revision ${this._negotiatedProtocolVersion}: return inputRequired({ inputRequests: { …: inputRequired.elicitUrl(...) } }) from the handler instead. The urlElicitationRequired error (-32042) of earlier revisions is not available on this revision.`);
|
|
}
|
|
throw error;
|
|
}
|
|
if (!require_src.isInputRequiredResult(result)) return result;
|
|
if (!servedModern) {
|
|
if (!this._inputRequiredServing.legacyShim) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input-required result, but this request is served on protocol revision ${this._negotiatedProtocolVersion ?? _modelcontextprotocol_core_internal.LATEST_PROTOCOL_VERSION}, which has no input_required vocabulary`);
|
|
return await this._legacyInputRequiredShim().fulfill(method, handler, request, ctxForHandler, result);
|
|
}
|
|
const inputRequests = result.inputRequests;
|
|
const hasInputRequests = inputRequests != null && Object.keys(inputRequests).length > 0;
|
|
const hasRequestState = typeof result.requestState === "string";
|
|
if (!hasInputRequests && !hasRequestState) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Handler for ${method} returned an input-required result with neither inputRequests nor requestState (every InputRequiredResult must include at least one of the two)`);
|
|
if (hasInputRequests) {
|
|
const declared = this._inputRequestCapabilityView(ctx);
|
|
for (const [key, entry] of Object.entries(inputRequests)) {
|
|
const { embedded, required } = coerceEmbeddedInputRequest(method, key, entry);
|
|
const missing = require_src.missingClientCapabilities(required, declared);
|
|
if (missing !== void 0) throw new require_src.MissingRequiredClientCapabilityError({ requiredCapabilities: missing }, `Cannot request input '${key}' (${embedded.method}): the request's client capabilities do not declare the required capability`);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Runs the configured `requestState.verify` hook and returns its
|
|
* resolved value (`undefined` when unconfigured or the hook returns
|
|
* nothing). Deny-on-error: any hook failure answers the frozen `-32602`;
|
|
* the reason goes to `onerror` only.
|
|
*/
|
|
async _verifyRequestState(state, ctx, method) {
|
|
if (this._requestStateVerify === void 0) return;
|
|
try {
|
|
return await this._requestStateVerify(state, ctx);
|
|
} catch (error) {
|
|
this.onerror?.(/* @__PURE__ */ new Error(`requestState verification rejected ${method}: ${error instanceof Error ? error.message : String(error)}`));
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, "Invalid or expired requestState", { reason: "invalid_request_state" });
|
|
}
|
|
}
|
|
/**
|
|
* The per-request resolved client-capabilities view: the request's own
|
|
* `_meta` envelope on the 2026 era; the `initialize`-declared state on a
|
|
* 2025-era connection. Per-request instances that never saw an
|
|
* initialize (stateless legacy) hold nothing, so gates refuse there.
|
|
*/
|
|
_inputRequestCapabilityView(ctx) {
|
|
return this._servedModernEra() ? ctx.mcpReq.envelope?.[_modelcontextprotocol_core_internal.CLIENT_CAPABILITIES_META_KEY] : this._clientCapabilities;
|
|
}
|
|
/**
|
|
* Guard for the push-style server→client request APIs ({@linkcode createMessage},
|
|
* {@linkcode elicitInput}, {@linkcode listRoots}, {@linkcode ping}) on a
|
|
* modern-era instance: the 2026-07-28 revision has no server→client request
|
|
* channel, so the call fails before any wire traffic with a typed error
|
|
* whose message steers to `inputRequired(...)`. The base era gate would
|
|
* also reject it; this guard runs first to carry the steer.
|
|
*/
|
|
_assertPushApiInServedEra(method) {
|
|
if (this._servedModernEra()) throw new require_src.SdkError(require_src.SdkErrorCode.MethodNotSupportedByProtocolVersion, `Server-to-client requests are not available on protocol revision ${this._negotiatedProtocolVersion}: '${method}' cannot be sent while serving a request on that revision. Return inputRequired({ ... }) from the handler instead — the client fulfils the embedded requests and retries the original request (multi round-trip requests).`, {
|
|
method,
|
|
era: "2026-07-28"
|
|
});
|
|
}
|
|
assertCapabilityForMethod(method) {
|
|
switch (method) {
|
|
case "sampling/createMessage":
|
|
if (!this._clientCapabilities?.sampling) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Client does not support sampling (required for ${method})`);
|
|
break;
|
|
case "elicitation/create":
|
|
if (!this._clientCapabilities?.elicitation) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Client does not support elicitation (required for ${method})`);
|
|
break;
|
|
case "roots/list":
|
|
if (!this._clientCapabilities?.roots) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Client does not support listing roots (required for ${method})`);
|
|
break;
|
|
case "ping": break;
|
|
}
|
|
}
|
|
assertNotificationCapability(method) {
|
|
switch (method) {
|
|
case "notifications/message":
|
|
if (!this._capabilities.logging) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support logging (required for ${method})`);
|
|
break;
|
|
case "notifications/resources/updated":
|
|
case "notifications/resources/list_changed":
|
|
if (!this._capabilities.resources) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support notifying about resources (required for ${method})`);
|
|
break;
|
|
case "notifications/tools/list_changed":
|
|
if (!this._capabilities.tools) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support notifying of tool list changes (required for ${method})`);
|
|
break;
|
|
case "notifications/prompts/list_changed":
|
|
if (!this._capabilities.prompts) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support notifying of prompt list changes (required for ${method})`);
|
|
break;
|
|
case "notifications/elicitation/complete":
|
|
if (!this._clientCapabilities?.elicitation?.url) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Client does not support URL elicitation (required for ${method})`);
|
|
break;
|
|
case "notifications/cancelled": break;
|
|
case "notifications/progress": break;
|
|
}
|
|
}
|
|
assertRequestHandlerCapability(method) {
|
|
switch (method) {
|
|
case "completion/complete":
|
|
if (!this._capabilities.completions) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support completions (required for ${method})`);
|
|
break;
|
|
case "logging/setLevel":
|
|
if (!this._capabilities.logging) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support logging (required for ${method})`);
|
|
break;
|
|
case "prompts/get":
|
|
case "prompts/list":
|
|
if (!this._capabilities.prompts) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support prompts (required for ${method})`);
|
|
break;
|
|
case "resources/list":
|
|
case "resources/templates/list":
|
|
case "resources/read":
|
|
if (!this._capabilities.resources) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support resources (required for ${method})`);
|
|
break;
|
|
case "tools/call":
|
|
case "tools/list":
|
|
if (!this._capabilities.tools) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, `Server does not support tools (required for ${method})`);
|
|
break;
|
|
case "ping":
|
|
case "initialize": break;
|
|
}
|
|
}
|
|
async _oninitialize(request) {
|
|
const requestedVersion = request.params.protocolVersion;
|
|
this._clientCapabilities = request.params.capabilities;
|
|
this._clientVersion = request.params.clientInfo;
|
|
const legacyVersions = require_src.legacyProtocolVersions(this._supportedProtocolVersions);
|
|
const protocolVersion = legacyVersions.includes(requestedVersion) ? requestedVersion : legacyVersions[0] ?? _modelcontextprotocol_core_internal.LATEST_PROTOCOL_VERSION;
|
|
this._negotiatedProtocolVersion = protocolVersion;
|
|
this.transport?.setProtocolVersion?.(protocolVersion);
|
|
return {
|
|
protocolVersion,
|
|
capabilities: this.getCapabilities(),
|
|
serverInfo: this._serverInfo,
|
|
...this._instructions && { instructions: this._instructions }
|
|
};
|
|
}
|
|
/**
|
|
* Answers `server/discover` (protocol revision 2026-07-28). `supportedVersions`
|
|
* lists only modern revisions (2025-era versions are negotiated via `initialize`);
|
|
* the capabilities are advertised as-is, listChanged/subscribe bits included
|
|
* (see {@linkcode discoverAdvertisedCapabilities}).
|
|
*/
|
|
_ondiscover() {
|
|
return {
|
|
supportedVersions: require_src.modernProtocolVersions(this._supportedProtocolVersions),
|
|
capabilities: discoverAdvertisedCapabilities(this.getCapabilities()),
|
|
...this._instructions && { instructions: this._instructions }
|
|
};
|
|
}
|
|
/**
|
|
* The identity the 2026-era encode seam stamps into every outbound
|
|
* result's `_meta` under `io.modelcontextprotocol/serverInfo` (spec PR
|
|
* #3002: servers SHOULD identify themselves on every response).
|
|
*/
|
|
_outboundServerInfo() {
|
|
return this._serverInfo;
|
|
}
|
|
/**
|
|
* After initialization has completed, this will be populated with the client's reported capabilities.
|
|
*
|
|
* @deprecated Read client identity from the per-request handler context instead: on
|
|
* 2026-07-28 (per-request envelope) requests `ctx.mcpReq.envelope` carries the client's
|
|
* declared capabilities, while on 2025-era connections this accessor keeps returning the
|
|
* `initialize`-scoped value. The accessor remains functional — instances serving the
|
|
* 2026-07-28 era are backfilled per request from the validated envelope.
|
|
*/
|
|
getClientCapabilities() {
|
|
return this._clientCapabilities;
|
|
}
|
|
/**
|
|
* After initialization has completed, this will be populated with information about the client's name and version.
|
|
*
|
|
* @deprecated Read client identity from the per-request handler context instead: on
|
|
* 2026-07-28 (per-request envelope) requests `ctx.mcpReq.envelope` carries the client's
|
|
* name and version, while on 2025-era connections this accessor keeps returning the
|
|
* `initialize`-scoped value. The accessor remains functional — instances serving the
|
|
* 2026-07-28 era are backfilled per request from the validated envelope.
|
|
*/
|
|
getClientVersion() {
|
|
return this._clientVersion;
|
|
}
|
|
/**
|
|
* After initialization has completed, this will be populated with the protocol version negotiated
|
|
* with the client (the version the server responded with during the initialize handshake), or
|
|
* `undefined` before initialization.
|
|
*
|
|
* @deprecated Read the protocol revision from the per-request handler context instead: on
|
|
* 2026-07-28 (per-request envelope) requests `ctx.mcpReq.envelope` names the revision the
|
|
* request was sent for, while on 2025-era connections this accessor keeps returning the
|
|
* `initialize`-negotiated version. The accessor remains functional — instances serving the
|
|
* 2026-07-28 era report that revision.
|
|
*/
|
|
getNegotiatedProtocolVersion() {
|
|
return this._negotiatedProtocolVersion;
|
|
}
|
|
/**
|
|
* Project a `tools/call` result through this instance's negotiated wire
|
|
* codec — the era-agnostic SEP-2106 §4.3 TextContent auto-append, plus on
|
|
* the 2025 era the `{result:…}` wrap when `structuredContent` is a
|
|
* non-object value or the advertised `outputSchema` had a non-object root.
|
|
* Identity for object-shaped `structuredContent` on the 2026 era.
|
|
*
|
|
* `McpServer`'s built-in `tools/call` handler routes through this method.
|
|
* Low-level `setRequestHandler('tools/call', …)` authors call it
|
|
* themselves so the projection lives in one place (the codec) and the
|
|
* server-side handler stays era-blind.
|
|
*
|
|
* This is the only codec function exposed on `Server` — the full
|
|
* `WireCodec` is intentionally not part of the public surface.
|
|
*/
|
|
projectCallToolResult(result, advertisedOutputSchema) {
|
|
return this._wireCodec().projectCallToolResult(result, advertisedOutputSchema);
|
|
}
|
|
/**
|
|
* Returns the current server capabilities.
|
|
*/
|
|
getCapabilities() {
|
|
return this._capabilities;
|
|
}
|
|
/**
|
|
* Sends a `ping` request to the connected client.
|
|
*
|
|
* @deprecated The 2026-07-28 protocol removed ping; it throws on a 2026-07-28-era instance.
|
|
* If your factory serves both eras, this only works on the legacy path.
|
|
*/
|
|
async ping() {
|
|
this._assertPushApiInServedEra("ping");
|
|
return this.request({ method: "ping" });
|
|
}
|
|
async createMessage(params, options) {
|
|
this._assertPushApiInServedEra("sampling/createMessage");
|
|
if ((params.tools || params.toolChoice) && !this._clientCapabilities?.sampling?.tools) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, "Client does not support sampling tools capability.");
|
|
if (params.messages.length > 0) {
|
|
const lastMessage = params.messages.at(-1);
|
|
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
const previousMessage = params.messages.length > 1 ? params.messages.at(-2) : void 0;
|
|
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
if (hasToolResults) {
|
|
if (lastContent.some((c) => c.type !== "tool_result")) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, "The last message must contain only tool_result content if any is present");
|
|
if (!hasPreviousToolUse) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, "tool_result blocks are not matching any tool_use from the previous message");
|
|
}
|
|
if (hasPreviousToolUse) {
|
|
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, "ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
}
|
|
}
|
|
const hasTools = Boolean(params.tools || params.toolChoice);
|
|
const wide = await this.request({
|
|
method: "sampling/createMessage",
|
|
params
|
|
}, options);
|
|
const outcome = this._wireCodec().samplingResultVariant(hasTools, wide);
|
|
if (!outcome.ok) throw new require_src.SdkError(require_src.SdkErrorCode.InvalidResult, `Invalid sampling/createMessage result: ${outcome.reason === "invalid" ? outcome.message : outcome.reason}`);
|
|
return outcome.value;
|
|
}
|
|
/**
|
|
* Creates an elicitation request for the given parameters.
|
|
* For backwards compatibility, `mode` may be omitted for form requests and will default to `"form"`.
|
|
* @param params The parameters for the elicitation request.
|
|
* @param options Optional request options.
|
|
* @returns The result of the elicitation request.
|
|
*
|
|
* @deprecated Throws on a 2026-07-28-era request — use {@link index.inputRequired | inputRequired} (multi-round-trip)
|
|
* instead. The 2025 push-style server-to-client request model is replaced by input_required
|
|
* results in the 2026-07-28 protocol. If your factory serves both eras, this only works on the
|
|
* legacy path.
|
|
*/
|
|
async elicitInput(params, options) {
|
|
this._assertPushApiInServedEra("elicitation/create");
|
|
switch (params.mode ?? "form") {
|
|
case "url":
|
|
if (!this._clientCapabilities?.elicitation?.url) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, "Client does not support url elicitation.");
|
|
break;
|
|
case "form":
|
|
if (!this._clientCapabilities?.elicitation?.form) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, "Client does not support form elicitation.");
|
|
break;
|
|
}
|
|
return this._sendElicitationLeg(params, options);
|
|
}
|
|
/**
|
|
* The capability-check-free core of {@linkcode elicitInput}. The shim
|
|
* uses it because its gate differs from the public checks: a bare
|
|
* `elicitation: {}` counts as form support (the pre-mode rule), and
|
|
* accepted content passes through unvalidated for parity with the
|
|
* modern client driver (handlers validate via the schema-aware
|
|
* `acceptedContent` overload and can re-ask).
|
|
*/
|
|
async _sendElicitationLeg(params, options, behavior) {
|
|
const mode = params.mode ?? "form";
|
|
const validateAcceptedContent = behavior?.validateAcceptedContent ?? true;
|
|
switch (mode) {
|
|
case "url": {
|
|
const urlParams = params;
|
|
return this.request({
|
|
method: "elicitation/create",
|
|
params: urlParams
|
|
}, options);
|
|
}
|
|
case "form": {
|
|
const formParams = params.mode === "form" ? params : {
|
|
...params,
|
|
mode: "form"
|
|
};
|
|
const result = await this.request({
|
|
method: "elicitation/create",
|
|
params: formParams
|
|
}, options);
|
|
if (validateAcceptedContent && result.action === "accept" && result.content && formParams.requestedSchema) try {
|
|
const validationResult = this._jsonSchemaValidator.getValidator(formParams.requestedSchema)(result.content);
|
|
if (!validationResult.valid) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Elicitation response content does not match requested schema: ${validationResult.errorMessage}`);
|
|
} catch (error) {
|
|
if (error instanceof require_src.ProtocolError) throw error;
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InternalError, `Error validating elicitation response: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates a reusable callback that, when invoked, will send a `notifications/elicitation/complete`
|
|
* notification for the specified elicitation ID.
|
|
*
|
|
* The notification (and the `elicitationId` it references) exists only on protocol revision
|
|
* 2025-11-25 — the 2026-07-28 revision removed both. On a connection negotiated at 2026-07-28 the
|
|
* returned callback rejects with a typed local error before anything reaches the transport
|
|
* (the method is not part of that revision's wire registry).
|
|
*
|
|
* @param elicitationId The ID of the elicitation to mark as complete.
|
|
* @param options Optional notification options. Useful when the completion notification should be related to a prior request.
|
|
* @returns A function that emits the completion notification when awaited.
|
|
*/
|
|
createElicitationCompletionNotifier(elicitationId, options) {
|
|
if (!this._clientCapabilities?.elicitation?.url) throw new require_src.SdkError(require_src.SdkErrorCode.CapabilityNotSupported, "Client does not support URL elicitation (required for notifications/elicitation/complete)");
|
|
return () => this.notification({
|
|
method: "notifications/elicitation/complete",
|
|
params: { elicitationId }
|
|
}, options);
|
|
}
|
|
/**
|
|
* Requests the list of roots from the client.
|
|
*
|
|
* @deprecated Deprecated as of protocol version 2026-07-28 (SEP-2577).
|
|
* Throws on a 2026-07-28-era request — use {@link index.inputRequired | inputRequired} (multi-round-trip) instead,
|
|
* or migrate to passing paths via tool parameters, resource URIs, or configuration. The 2025
|
|
* push-style server-to-client request model is replaced by input_required results in the
|
|
* 2026-07-28 protocol. If your factory serves both eras, this only works on the legacy path.
|
|
*/
|
|
async listRoots(params, options) {
|
|
this._assertPushApiInServedEra("roots/list");
|
|
return this.request({
|
|
method: "roots/list",
|
|
params
|
|
}, options);
|
|
}
|
|
/**
|
|
* Sends a logging message to the client, if connected.
|
|
* Note: You only need to send the parameters object, not the entire JSON-RPC message.
|
|
* @see {@linkcode LoggingMessageNotification}
|
|
* @param params
|
|
* @param sessionId Optional for stateless transports and backward compatibility.
|
|
*
|
|
* @deprecated Deprecated as of protocol version 2026-07-28 (SEP-2577).
|
|
* Remains functional during the deprecation window (at least twelve months).
|
|
* Migrate to stderr logging (STDIO servers) or OpenTelemetry.
|
|
*/
|
|
async sendLoggingMessage(params, sessionId) {
|
|
if (this._capabilities.logging && !this.isMessageIgnored(params.level, sessionId)) return this.notification({
|
|
method: "notifications/message",
|
|
params
|
|
});
|
|
}
|
|
async sendResourceUpdated(params) {
|
|
return this.notification({
|
|
method: "notifications/resources/updated",
|
|
params
|
|
});
|
|
}
|
|
async sendResourceListChanged() {
|
|
return this.notification({ method: "notifications/resources/list_changed" });
|
|
}
|
|
async sendToolListChanged() {
|
|
return this.notification({ method: "notifications/tools/list_changed" });
|
|
}
|
|
async sendPromptListChanged() {
|
|
return this.notification({ method: "notifications/prompts/list_changed" });
|
|
}
|
|
};
|
|
/**
|
|
* The capability set a server advertises on `server/discover`. Pure — never
|
|
* mutates the input; the legacy `initialize` advertisement is untouched.
|
|
*
|
|
* The serving entries serve `subscriptions/listen` themselves, so the
|
|
* `listChanged` and `resources.subscribe` capability bits are advertised
|
|
* as-is: a modern-era client uses them to decide which notification types to
|
|
* request on its listen filter.
|
|
*/
|
|
function discoverAdvertisedCapabilities(capabilities) {
|
|
return { ...capabilities };
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/server/mcp.ts
|
|
function toolInputElementCount(value, max) {
|
|
let count = 0;
|
|
const stack = [value];
|
|
while (stack.length > 0) {
|
|
const node = stack.pop();
|
|
if (node === null || typeof node !== "object") continue;
|
|
if (Array.isArray(node)) for (const child of node) {
|
|
if (++count > max) return count;
|
|
if (child !== null && typeof child === "object") stack.push(child);
|
|
}
|
|
else for (const key in node) {
|
|
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
|
if (++count > max) return count;
|
|
const child = node[key];
|
|
if (child !== null && typeof child === "object") stack.push(child);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
function resolveMaxToolInputElements(value) {
|
|
if (value === void 0 || value === Infinity) return void 0;
|
|
if (typeof value !== "number" || Number.isNaN(value) || value < 1) throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
|
|
return value;
|
|
}
|
|
/**
|
|
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
|
|
* For advanced usage (like sending notifications or setting custom request handlers), use the underlying
|
|
* {@linkcode Server} instance available via the {@linkcode McpServer.server | server} property.
|
|
*
|
|
* @example
|
|
* ```ts source="./mcp.examples.ts#McpServer_basicUsage"
|
|
* const server = new McpServer({
|
|
* name: 'my-server',
|
|
* version: '1.0.0'
|
|
* });
|
|
* ```
|
|
*/
|
|
var McpServer = class {
|
|
/**
|
|
* The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
|
|
*/
|
|
server;
|
|
_maxToolInputElements;
|
|
_registeredResources = {};
|
|
_registeredResourceTemplates = {};
|
|
_registeredTools = {};
|
|
_registeredPrompts = {};
|
|
/** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
|
|
_toolInputSchemaJson = {};
|
|
/**
|
|
* The JSON-serialized `inputSchema` of a registered tool, or `undefined`
|
|
* when no such tool is registered. Used by the HTTP entry's pre-dispatch
|
|
* SEP-2243 `Mcp-Param-*` validation step (which needs the same JSON Schema
|
|
* `tools/list` would emit, before dispatch reaches the handler).
|
|
*
|
|
* @internal
|
|
*/
|
|
toolInputSchemaJson(name) {
|
|
const tool = this._registeredTools[name];
|
|
if (tool === void 0 || !tool.enabled) return void 0;
|
|
if (Object.hasOwn(this._toolInputSchemaJson, name)) return this._toolInputSchemaJson[name];
|
|
if (tool.inputSchema === void 0) return EMPTY_OBJECT_JSON_SCHEMA;
|
|
try {
|
|
const json = require_src.standardSchemaToJsonSchema(tool.inputSchema, "input");
|
|
this._toolInputSchemaJson[name] = json;
|
|
return json;
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
constructor(serverInfo, options) {
|
|
this.server = new Server(serverInfo, options);
|
|
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
if (options?.capabilities?.tools) this.setToolRequestHandlers();
|
|
if (options?.capabilities?.resources) this.setResourceRequestHandlers();
|
|
if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
|
|
}
|
|
/**
|
|
* Attaches to the given transport, starts it, and starts listening for messages.
|
|
*
|
|
* The `server` object assumes ownership of the {@linkcode Transport}, replacing any callbacks that have already been set, and expects that it is the only user of the {@linkcode Transport} instance going forward.
|
|
*
|
|
* @example
|
|
* ```ts source="./mcp.examples.ts#McpServer_connect_stdio"
|
|
* const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
* const transport = new StdioServerTransport();
|
|
* await server.connect(transport);
|
|
* ```
|
|
*/
|
|
async connect(transport) {
|
|
if (supportsScopeChallengeResolver(transport)) transport.setScopeChallengeResolver((context) => this.resolveScopeChallenge(context));
|
|
return await this.server.connect(transport);
|
|
}
|
|
/**
|
|
* Closes the connection.
|
|
*/
|
|
async close() {
|
|
await this.server.close();
|
|
}
|
|
/** @internal */
|
|
resolveScopeChallenge = (context) => {
|
|
switch (context.request.method) {
|
|
case "tools/call": {
|
|
const toolName = context.request.params?.name;
|
|
if (typeof toolName !== "string") return;
|
|
const tool = this._registeredTools[toolName];
|
|
if (tool === void 0 || !tool.enabled) return;
|
|
return tool.scopeChallenge?.(context);
|
|
}
|
|
case "resources/read": {
|
|
const resourceUri = context.request.params?.uri;
|
|
if (typeof resourceUri !== "string") return;
|
|
let uri;
|
|
try {
|
|
uri = new URL(resourceUri);
|
|
} catch {
|
|
return;
|
|
}
|
|
const resource = this._registeredResources[uri.toString()];
|
|
if (resource !== void 0) return resource.enabled ? resource.scopeChallenge?.(context) : void 0;
|
|
for (const template of Object.values(this._registeredResourceTemplates)) if (template.resourceTemplate.uriTemplate.match(uri.toString())) return template.enabled ? template.scopeChallenge?.(context) : void 0;
|
|
return;
|
|
}
|
|
case "prompts/get": {
|
|
const promptName = context.request.params?.name;
|
|
if (typeof promptName !== "string") return;
|
|
const prompt = this._registeredPrompts[promptName];
|
|
if (prompt === void 0 || !prompt.enabled) return;
|
|
return prompt.scopeChallenge?.(context);
|
|
}
|
|
default: return;
|
|
}
|
|
};
|
|
_toolHandlersInitialized = false;
|
|
setToolRequestHandlers() {
|
|
if (this._toolHandlersInitialized) return;
|
|
this.server.assertCanSetRequestHandler("tools/list");
|
|
this.server.assertCanSetRequestHandler("tools/call");
|
|
this.server.registerCapabilities({ tools: { listChanged: this.server.getCapabilities().tools?.listChanged ?? true } });
|
|
this.server.setRequestHandler("tools/list", () => ({ tools: Object.entries(this._registeredTools).filter(([, tool]) => tool.enabled).map(([name, tool]) => {
|
|
const toolDefinition = {
|
|
name,
|
|
title: tool.title,
|
|
description: tool.description,
|
|
inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
|
|
annotations: tool.annotations,
|
|
icons: tool.icons,
|
|
execution: tool.execution,
|
|
_meta: tool._meta
|
|
};
|
|
if (tool.outputSchema) toolDefinition.outputSchema = require_src.standardSchemaToJsonSchema(tool.outputSchema, "output");
|
|
return toolDefinition;
|
|
}) }));
|
|
this.server.setRequestHandler("tools/call", async (request, ctx) => {
|
|
const tool = this._registeredTools[request.params.name];
|
|
if (!tool) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
|
|
if (!tool.enabled) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
|
|
try {
|
|
const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
|
|
const result = await this.executeToolHandler(tool, args, ctx);
|
|
await this.validateToolOutput(tool, result, request.params.name);
|
|
if (require_src.isInputRequiredResult(result)) return result;
|
|
return this.server.projectCallToolResult(result, tool.outputSchemaJson);
|
|
} catch (error) {
|
|
if (error instanceof require_src.ProtocolError && error.code === require_src.ProtocolErrorCode.UrlElicitationRequired) throw error;
|
|
return this.createToolError(error instanceof Error ? error.message : String(error));
|
|
}
|
|
});
|
|
this._toolHandlersInitialized = true;
|
|
}
|
|
/**
|
|
* Creates a tool error result.
|
|
*
|
|
* @param errorMessage - The error message.
|
|
* @returns The tool error result.
|
|
*/
|
|
createToolError(errorMessage) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: errorMessage
|
|
}],
|
|
isError: true
|
|
};
|
|
}
|
|
/**
|
|
* Validates tool input arguments against the tool's input schema.
|
|
*/
|
|
async validateToolInput(tool, args, toolName) {
|
|
if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
|
|
if (!tool.inputSchema) return;
|
|
const parseResult = await require_src.validateStandardSchema(tool.inputSchema, args ?? {});
|
|
if (!parseResult.success) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
|
|
return parseResult.data;
|
|
}
|
|
/**
|
|
* Validates tool output against the tool's output schema.
|
|
*/
|
|
async validateToolOutput(tool, result, toolName) {
|
|
if (!tool.outputSchema) return;
|
|
if (require_src.isInputRequiredResult(result)) return;
|
|
if (result.isError) return;
|
|
if (result.structuredContent === void 0) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);
|
|
const parseResult = await require_src.validateStandardSchema(tool.outputSchema, result.structuredContent);
|
|
if (!parseResult.success) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${parseResult.error}`);
|
|
}
|
|
/**
|
|
* Executes a tool handler.
|
|
*/
|
|
async executeToolHandler(tool, args, ctx) {
|
|
return tool.executor(args, ctx);
|
|
}
|
|
_completionHandlerInitialized = false;
|
|
setCompletionRequestHandler() {
|
|
if (this._completionHandlerInitialized) return;
|
|
this.server.assertCanSetRequestHandler("completion/complete");
|
|
this.server.registerCapabilities({ completions: {} });
|
|
this.server.setRequestHandler("completion/complete", async (request) => {
|
|
switch (request.params.ref.type) {
|
|
case "ref/prompt":
|
|
require_src.assertCompleteRequestPrompt(request);
|
|
return this.handlePromptCompletion(request, request.params.ref);
|
|
case "ref/resource":
|
|
require_src.assertCompleteRequestResourceTemplate(request);
|
|
return this.handleResourceCompletion(request, request.params.ref);
|
|
default: throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Invalid completion reference: ${request.params.ref}`);
|
|
}
|
|
});
|
|
this._completionHandlerInitialized = true;
|
|
}
|
|
async handlePromptCompletion(request, ref) {
|
|
const prompt = this._registeredPrompts[ref.name];
|
|
if (!prompt) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
|
|
if (!prompt.enabled) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Prompt ${ref.name} disabled`);
|
|
if (!prompt.argsSchema) return EMPTY_COMPLETION_RESULT;
|
|
const field = unwrapOptionalSchema(getSchemaShape(prompt.argsSchema)?.[request.params.argument.name]);
|
|
if (!isCompletable(field)) return EMPTY_COMPLETION_RESULT;
|
|
const completer = getCompleter(field);
|
|
if (!completer) return EMPTY_COMPLETION_RESULT;
|
|
return createCompletionResult(await completer(request.params.argument.value, request.params.context));
|
|
}
|
|
async handleResourceCompletion(request, ref) {
|
|
const template = Object.values(this._registeredResourceTemplates).find((t) => t.resourceTemplate.uriTemplate.toString() === ref.uri);
|
|
if (!template) {
|
|
if (this._registeredResources[ref.uri]) return EMPTY_COMPLETION_RESULT;
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Resource template ${request.params.ref.uri} not found`);
|
|
}
|
|
const completer = template.resourceTemplate.completeCallback(request.params.argument.name);
|
|
if (!completer) return EMPTY_COMPLETION_RESULT;
|
|
return createCompletionResult(await completer(request.params.argument.value, request.params.context));
|
|
}
|
|
_resourceHandlersInitialized = false;
|
|
setResourceRequestHandlers() {
|
|
if (this._resourceHandlersInitialized) return;
|
|
this.server.assertCanSetRequestHandler("resources/list");
|
|
this.server.assertCanSetRequestHandler("resources/templates/list");
|
|
this.server.assertCanSetRequestHandler("resources/read");
|
|
this.server.registerCapabilities({ resources: { listChanged: this.server.getCapabilities().resources?.listChanged ?? true } });
|
|
this.server.setRequestHandler("resources/list", async (_request, ctx) => {
|
|
const resources = Object.entries(this._registeredResources).filter(([_, resource]) => resource.enabled).map(([uri, resource]) => ({
|
|
uri,
|
|
name: resource.name,
|
|
...resource.metadata
|
|
}));
|
|
const templateResources = [];
|
|
for (const template of Object.values(this._registeredResourceTemplates)) {
|
|
if (!template.resourceTemplate.listCallback) continue;
|
|
const result = await template.resourceTemplate.listCallback(ctx);
|
|
for (const resource of result.resources) templateResources.push({
|
|
...template.metadata,
|
|
...resource
|
|
});
|
|
}
|
|
return { resources: [...resources, ...templateResources] };
|
|
});
|
|
this.server.setRequestHandler("resources/templates/list", async () => {
|
|
return { resourceTemplates: Object.entries(this._registeredResourceTemplates).map(([name, template]) => ({
|
|
name,
|
|
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
|
|
...template.metadata
|
|
})) };
|
|
});
|
|
this.server.setRequestHandler("resources/read", async (request, ctx) => {
|
|
let uri;
|
|
try {
|
|
uri = new URL(request.params.uri);
|
|
} catch {
|
|
throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Resource URI ${request.params.uri} is invalid`, {
|
|
uri: request.params.uri,
|
|
reason: "invalid_uri"
|
|
});
|
|
}
|
|
const resource = this._registeredResources[uri.toString()];
|
|
if (resource) {
|
|
if (!resource.enabled) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Resource ${uri} disabled`);
|
|
return require_src.attachCacheHintFallback(await resource.readCallback(uri, ctx), resource.cacheHint);
|
|
}
|
|
for (const template of Object.values(this._registeredResourceTemplates)) {
|
|
const variables = template.resourceTemplate.uriTemplate.match(uri.toString());
|
|
if (variables) {
|
|
if (!template.enabled) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Resource template ${template.resourceTemplate.uriTemplate} disabled`);
|
|
return require_src.attachCacheHintFallback(await template.readCallback(uri, variables, ctx), template.cacheHint);
|
|
}
|
|
}
|
|
throw new require_src.ResourceNotFoundError(request.params.uri);
|
|
});
|
|
this._resourceHandlersInitialized = true;
|
|
}
|
|
_promptHandlersInitialized = false;
|
|
setPromptRequestHandlers() {
|
|
if (this._promptHandlersInitialized) return;
|
|
this.server.assertCanSetRequestHandler("prompts/list");
|
|
this.server.assertCanSetRequestHandler("prompts/get");
|
|
this.server.registerCapabilities({ prompts: { listChanged: this.server.getCapabilities().prompts?.listChanged ?? true } });
|
|
this.server.setRequestHandler("prompts/list", () => ({ prompts: Object.entries(this._registeredPrompts).filter(([, prompt]) => prompt.enabled).map(([name, prompt]) => {
|
|
return {
|
|
name,
|
|
title: prompt.title,
|
|
description: prompt.description,
|
|
arguments: prompt.argsSchema ? require_src.promptArgumentsFromStandardSchema(prompt.argsSchema) : void 0,
|
|
icons: prompt.icons,
|
|
_meta: prompt._meta
|
|
};
|
|
}) }));
|
|
this.server.setRequestHandler("prompts/get", async (request, ctx) => {
|
|
const prompt = this._registeredPrompts[request.params.name];
|
|
if (!prompt) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
|
|
if (!prompt.enabled) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Prompt ${request.params.name} disabled`);
|
|
return prompt.handler(request.params.arguments, ctx);
|
|
});
|
|
this._promptHandlersInitialized = true;
|
|
}
|
|
registerResource(name, uriOrTemplate, config, readCallback) {
|
|
const { cacheHint, scopeChallenge, ...resourceMetadata } = config;
|
|
const metadata = resourceMetadata;
|
|
if (cacheHint !== void 0) require_src.assertValidCacheHint(cacheHint, `resource ${name}`);
|
|
if (typeof uriOrTemplate === "string") {
|
|
if (this._registeredResources[uriOrTemplate]) throw new Error(`Resource ${uriOrTemplate} is already registered`);
|
|
const registeredResource = this._createRegisteredResource(name, config.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
|
|
if (cacheHint !== void 0) registeredResource.cacheHint = cacheHint;
|
|
this.setResourceRequestHandlers();
|
|
this.sendResourceListChanged();
|
|
return registeredResource;
|
|
} else {
|
|
if (this._registeredResourceTemplates[name]) throw new Error(`Resource template ${name} is already registered`);
|
|
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
|
|
if (cacheHint !== void 0) registeredResourceTemplate.cacheHint = cacheHint;
|
|
this.setResourceRequestHandlers();
|
|
this.sendResourceListChanged();
|
|
return registeredResourceTemplate;
|
|
}
|
|
}
|
|
_createRegisteredResource(name, title, uri, metadata, scopeChallenge, readCallback) {
|
|
const registeredResource = {
|
|
name,
|
|
title,
|
|
metadata,
|
|
readCallback,
|
|
scopeChallenge,
|
|
enabled: true,
|
|
disable: () => registeredResource.update({ enabled: false }),
|
|
enable: () => registeredResource.update({ enabled: true }),
|
|
remove: () => registeredResource.update({ uri: null }),
|
|
update: (updates) => {
|
|
if (updates.uri !== void 0 && updates.uri !== uri) {
|
|
delete this._registeredResources[uri];
|
|
if (updates.uri) this._registeredResources[updates.uri] = registeredResource;
|
|
}
|
|
if (updates.name !== void 0) registeredResource.name = updates.name;
|
|
if (updates.title !== void 0) registeredResource.title = updates.title;
|
|
if (updates.metadata !== void 0) registeredResource.metadata = updates.metadata;
|
|
if (updates.callback !== void 0) registeredResource.readCallback = updates.callback;
|
|
if (updates.scopeChallenge !== void 0) registeredResource.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
if (updates.enabled !== void 0) registeredResource.enabled = updates.enabled;
|
|
this.sendResourceListChanged();
|
|
}
|
|
};
|
|
this._registeredResources[uri] = registeredResource;
|
|
return registeredResource;
|
|
}
|
|
_createRegisteredResourceTemplate(name, title, template, metadata, scopeChallenge, readCallback) {
|
|
const registeredResourceTemplate = {
|
|
resourceTemplate: template,
|
|
title,
|
|
metadata,
|
|
readCallback,
|
|
scopeChallenge,
|
|
enabled: true,
|
|
disable: () => registeredResourceTemplate.update({ enabled: false }),
|
|
enable: () => registeredResourceTemplate.update({ enabled: true }),
|
|
remove: () => registeredResourceTemplate.update({ name: null }),
|
|
update: (updates) => {
|
|
if (updates.name !== void 0 && updates.name !== name) {
|
|
delete this._registeredResourceTemplates[name];
|
|
if (updates.name) this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
|
|
}
|
|
if (updates.title !== void 0) registeredResourceTemplate.title = updates.title;
|
|
if (updates.template !== void 0) registeredResourceTemplate.resourceTemplate = updates.template;
|
|
if (updates.metadata !== void 0) registeredResourceTemplate.metadata = updates.metadata;
|
|
if (updates.callback !== void 0) registeredResourceTemplate.readCallback = updates.callback;
|
|
if (updates.scopeChallenge !== void 0) registeredResourceTemplate.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
if (updates.enabled !== void 0) registeredResourceTemplate.enabled = updates.enabled;
|
|
this.sendResourceListChanged();
|
|
}
|
|
};
|
|
this._registeredResourceTemplates[name] = registeredResourceTemplate;
|
|
const variableNames = template.uriTemplate.variableNames;
|
|
if (Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v))) this.setCompletionRequestHandler();
|
|
return registeredResourceTemplate;
|
|
}
|
|
_createRegisteredPrompt(name, title, description, argsSchema, callback, icons, scopeChallenge, _meta) {
|
|
let currentArgsSchema = argsSchema;
|
|
let currentCallback = callback;
|
|
const registeredPrompt = {
|
|
title,
|
|
description,
|
|
argsSchema,
|
|
icons,
|
|
scopeChallenge,
|
|
_meta,
|
|
handler: createPromptHandler(name, argsSchema, callback),
|
|
enabled: true,
|
|
disable: () => registeredPrompt.update({ enabled: false }),
|
|
enable: () => registeredPrompt.update({ enabled: true }),
|
|
remove: () => registeredPrompt.update({ name: null }),
|
|
update: (updates) => {
|
|
if (updates.name !== void 0 && updates.name !== name) {
|
|
delete this._registeredPrompts[name];
|
|
if (updates.name) this._registeredPrompts[updates.name] = registeredPrompt;
|
|
}
|
|
if (updates.title !== void 0) registeredPrompt.title = updates.title;
|
|
if (updates.description !== void 0) registeredPrompt.description = updates.description;
|
|
if (updates.icons !== void 0) registeredPrompt.icons = updates.icons;
|
|
if (updates.scopeChallenge !== void 0) registeredPrompt.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
if (updates._meta !== void 0) registeredPrompt._meta = updates._meta;
|
|
let needsHandlerRegen = false;
|
|
if (updates.argsSchema !== void 0) {
|
|
registeredPrompt.argsSchema = updates.argsSchema;
|
|
currentArgsSchema = updates.argsSchema;
|
|
needsHandlerRegen = true;
|
|
}
|
|
if (updates.callback !== void 0) {
|
|
currentCallback = updates.callback;
|
|
needsHandlerRegen = true;
|
|
}
|
|
if (needsHandlerRegen) registeredPrompt.handler = createPromptHandler(name, currentArgsSchema, currentCallback);
|
|
if (updates.enabled !== void 0) registeredPrompt.enabled = updates.enabled;
|
|
this.sendPromptListChanged();
|
|
}
|
|
};
|
|
this._registeredPrompts[name] = registeredPrompt;
|
|
if (argsSchema) {
|
|
const shape = getSchemaShape(argsSchema);
|
|
if (shape) {
|
|
if (Object.values(shape).some((field) => {
|
|
return isCompletable(unwrapOptionalSchema(field));
|
|
})) this.setCompletionRequestHandler();
|
|
}
|
|
}
|
|
return registeredPrompt;
|
|
}
|
|
_createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
|
|
require_src.validateAndWarnToolName(name);
|
|
let currentHandler = handler;
|
|
let outputSchemaJson;
|
|
const registeredTool = {
|
|
title,
|
|
description,
|
|
inputSchema,
|
|
outputSchema,
|
|
get outputSchemaJson() {
|
|
return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
|
|
},
|
|
set outputSchemaJson(value) {
|
|
outputSchemaJson = value;
|
|
},
|
|
annotations,
|
|
icons,
|
|
execution,
|
|
scopeChallenge,
|
|
_meta,
|
|
handler,
|
|
executor: createToolExecutor(inputSchema, handler),
|
|
enabled: true,
|
|
disable: () => registeredTool.update({ enabled: false }),
|
|
enable: () => registeredTool.update({ enabled: true }),
|
|
remove: () => registeredTool.update({ name: null }),
|
|
update: (updates) => {
|
|
if (updates.name !== void 0 && updates.name !== name) {
|
|
if (typeof updates.name === "string") require_src.validateAndWarnToolName(updates.name);
|
|
delete this._registeredTools[name];
|
|
delete this._toolInputSchemaJson[name];
|
|
if (updates.name) {
|
|
delete this._toolInputSchemaJson[updates.name];
|
|
this._registeredTools[updates.name] = registeredTool;
|
|
name = updates.name;
|
|
}
|
|
}
|
|
if (updates.title !== void 0) registeredTool.title = updates.title;
|
|
if (updates.description !== void 0) registeredTool.description = updates.description;
|
|
let needsExecutorRegen = false;
|
|
if (updates.paramsSchema !== void 0) {
|
|
registeredTool.inputSchema = updates.paramsSchema;
|
|
delete this._toolInputSchemaJson[name];
|
|
needsExecutorRegen = true;
|
|
}
|
|
if (updates.callback !== void 0) {
|
|
registeredTool.handler = updates.callback;
|
|
currentHandler = updates.callback;
|
|
needsExecutorRegen = true;
|
|
}
|
|
if (needsExecutorRegen) registeredTool.executor = createToolExecutor(registeredTool.inputSchema, currentHandler);
|
|
if (updates.outputSchema !== void 0) {
|
|
registeredTool.outputSchema = updates.outputSchema;
|
|
registeredTool.outputSchemaJson = convertOutputSchemaJson(updates.outputSchema);
|
|
}
|
|
if (updates.annotations !== void 0) registeredTool.annotations = updates.annotations;
|
|
if (updates.icons !== void 0) registeredTool.icons = updates.icons;
|
|
if (updates.scopeChallenge !== void 0) registeredTool.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
|
|
if (updates._meta !== void 0) registeredTool._meta = updates._meta;
|
|
if (updates.enabled !== void 0) registeredTool.enabled = updates.enabled;
|
|
this.sendToolListChanged();
|
|
}
|
|
};
|
|
this._registeredTools[name] = registeredTool;
|
|
this.setToolRequestHandlers();
|
|
this.sendToolListChanged();
|
|
return registeredTool;
|
|
}
|
|
registerTool(name, config, cb) {
|
|
if (this._registeredTools[name]) throw new Error(`Tool ${name} is already registered`);
|
|
const { title, description, inputSchema, outputSchema, annotations, icons, scopeChallenge, _meta } = config;
|
|
return this._createRegisteredTool(name, title, description, require_src.normalizeRawShapeSchema(inputSchema), require_src.normalizeRawShapeSchema(outputSchema), annotations, icons, void 0, scopeChallenge, _meta, cb);
|
|
}
|
|
registerPrompt(name, config, cb) {
|
|
if (this._registeredPrompts[name]) throw new Error(`Prompt ${name} is already registered`);
|
|
const { title, description, argsSchema, icons, scopeChallenge, _meta } = config;
|
|
const registeredPrompt = this._createRegisteredPrompt(name, title, description, require_src.normalizeRawShapeSchema(argsSchema), cb, icons, scopeChallenge, _meta);
|
|
this.setPromptRequestHandlers();
|
|
this.sendPromptListChanged();
|
|
return registeredPrompt;
|
|
}
|
|
/**
|
|
* Checks if the server is connected to a transport.
|
|
* @returns `true` if the server is connected
|
|
*/
|
|
isConnected() {
|
|
return this.server.transport !== void 0;
|
|
}
|
|
/**
|
|
* Sends a logging message to the client, if connected.
|
|
* Note: You only need to send the parameters object, not the entire JSON-RPC message.
|
|
* @see {@linkcode LoggingMessageNotification}
|
|
* @param params
|
|
* @param sessionId Optional for stateless transports and backward compatibility.
|
|
*
|
|
* @example
|
|
* ```ts source="./mcp.examples.ts#McpServer_sendLoggingMessage_basic"
|
|
* await server.sendLoggingMessage({
|
|
* level: 'info',
|
|
* data: 'Processing complete'
|
|
* });
|
|
* ```
|
|
*
|
|
* @deprecated Deprecated as of protocol version 2026-07-28 (SEP-2577).
|
|
* Remains functional during the deprecation window (at least twelve months).
|
|
* Migrate to stderr logging (STDIO servers) or OpenTelemetry.
|
|
*/
|
|
async sendLoggingMessage(params, sessionId) {
|
|
return this.server.sendLoggingMessage(params, sessionId);
|
|
}
|
|
/**
|
|
* Sends a resource list changed event to the client, if connected.
|
|
*/
|
|
sendResourceListChanged() {
|
|
if (this.isConnected()) this.server.sendResourceListChanged();
|
|
}
|
|
/**
|
|
* Sends a tool list changed event to the client, if connected.
|
|
*/
|
|
sendToolListChanged() {
|
|
if (this.isConnected()) this.server.sendToolListChanged();
|
|
}
|
|
/**
|
|
* Sends a prompt list changed event to the client, if connected.
|
|
*/
|
|
sendPromptListChanged() {
|
|
if (this.isConnected()) this.server.sendPromptListChanged();
|
|
}
|
|
};
|
|
/**
|
|
* A resource template combines a URI pattern with optional functionality to enumerate
|
|
* all resources matching that pattern.
|
|
*/
|
|
var ResourceTemplate = class {
|
|
_uriTemplate;
|
|
constructor(uriTemplate, _callbacks) {
|
|
this._callbacks = _callbacks;
|
|
this._uriTemplate = typeof uriTemplate === "string" ? new require_src.UriTemplate(uriTemplate) : uriTemplate;
|
|
}
|
|
/**
|
|
* Gets the URI template pattern.
|
|
*/
|
|
get uriTemplate() {
|
|
return this._uriTemplate;
|
|
}
|
|
/**
|
|
* Gets the list callback, if one was provided.
|
|
*/
|
|
get listCallback() {
|
|
return this._callbacks.list;
|
|
}
|
|
/**
|
|
* Gets the callback for completing a specific URI template variable, if one was provided.
|
|
*/
|
|
completeCallback(variable) {
|
|
return this._callbacks.complete?.[variable];
|
|
}
|
|
};
|
|
/**
|
|
* Creates an executor that invokes the handler with the appropriate arguments.
|
|
* When `inputSchema` is defined, the handler is called with `(args, ctx)`.
|
|
* When `inputSchema` is undefined, the handler is called with just `(ctx)`.
|
|
*/
|
|
function createToolExecutor(inputSchema, handler) {
|
|
if (inputSchema) {
|
|
const callback$1 = handler;
|
|
return async (args, ctx) => callback$1(args, ctx);
|
|
}
|
|
const callback = handler;
|
|
return async (_args, ctx) => callback(ctx);
|
|
}
|
|
const EMPTY_OBJECT_JSON_SCHEMA = {
|
|
type: "object",
|
|
properties: {}
|
|
};
|
|
/** Converts a tool's `inputSchema` for `tools/list` and warns on an invalid SEP-2243 `x-mcp-header` declaration. */
|
|
function convertListedInputSchema(name, inputSchema) {
|
|
const json = require_src.standardSchemaToJsonSchema(inputSchema, "input");
|
|
const scan = require_src.scanXMcpHeaderDeclarations(json);
|
|
if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
|
|
return json;
|
|
}
|
|
/**
|
|
* Convert a registered `outputSchema` to JSON Schema, memoised on {@link RegisteredTool.outputSchemaJson}
|
|
* so `tools/call` passes the SAME advertised schema to the wire codec's `projectCallToolResult` that
|
|
* `tools/list` emits (and that the 2025 codec's `encodeResult('tools/list', …)` may wrap). A conversion
|
|
* failure yields `undefined` so the failure surfaces where it always has (`tools/list`).
|
|
*/
|
|
function convertOutputSchemaJson(outputSchema) {
|
|
if (outputSchema === void 0) return void 0;
|
|
try {
|
|
return require_src.standardSchemaToJsonSchema(outputSchema, "output");
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
/**
|
|
* Creates a type-safe prompt handler that captures the schema and callback in a closure.
|
|
* This eliminates the need for type assertions at the call site.
|
|
*/
|
|
function createPromptHandler(name, argsSchema, callback) {
|
|
if (argsSchema) {
|
|
const typedCallback = callback;
|
|
return async (args, ctx) => {
|
|
const parseResult = await require_src.validateStandardSchema(argsSchema, args ?? {});
|
|
if (!parseResult.success) throw new require_src.ProtocolError(require_src.ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
|
|
return typedCallback(parseResult.data, ctx);
|
|
};
|
|
} else {
|
|
const typedCallback = callback;
|
|
return async (_args, ctx) => {
|
|
return typedCallback(ctx);
|
|
};
|
|
}
|
|
}
|
|
function createCompletionResult(suggestions) {
|
|
return { completion: {
|
|
values: suggestions.map(String).slice(0, 100),
|
|
total: suggestions.length,
|
|
hasMore: suggestions.length > 100
|
|
} };
|
|
}
|
|
const EMPTY_COMPLETION_RESULT = { completion: {
|
|
values: [],
|
|
hasMore: false
|
|
} };
|
|
/** @internal Gets the shape of a Zod object schema */
|
|
function getSchemaShape(schema) {
|
|
const candidate = schema;
|
|
if (candidate.shape && typeof candidate.shape === "object") return candidate.shape;
|
|
}
|
|
/** @internal Checks if a Zod schema is optional */
|
|
function isOptionalSchema(schema) {
|
|
return schema?.type === "optional";
|
|
}
|
|
/** @internal Unwraps an optional Zod schema */
|
|
function unwrapOptionalSchema(schema) {
|
|
if (!isOptionalSchema(schema)) return schema;
|
|
return schema.def?.innerType ?? schema;
|
|
}
|
|
|
|
//#endregion
|
|
Object.defineProperty(exports, 'DEFAULT_MAX_SUBSCRIPTIONS', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return DEFAULT_MAX_SUBSCRIPTIONS;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'DEFAULT_SSE_KEEP_ALIVE_MS', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'InMemoryServerEventBus', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return InMemoryServerEventBus;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'McpServer', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return McpServer;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'ResourceTemplate', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return ResourceTemplate;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'Server', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return Server;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'StdioListenRouter', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return StdioListenRouter;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'armSseKeepAlive', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return armSseKeepAlive;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'bearerAuthChallengeResponse', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return bearerAuthChallengeResponse;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'buildOAuthProtectedResourceMetadata', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return buildOAuthProtectedResourceMetadata;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'completable', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return completable;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'createListenRouter', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return createListenRouter;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'createScopeChallengeResponse', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return createScopeChallengeResponse;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'createServerNotifier', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return createServerNotifier;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'findScopeChallenge', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return findScopeChallenge;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'getOAuthProtectedResourceMetadataUrl', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return getOAuthProtectedResourceMetadataUrl;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'installModernOnlyHandlers', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return installModernOnlyHandlers;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'isCompletable', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return isCompletable;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'oauthMetadataResponse', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return oauthMetadataResponse;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'requireBearerAuth', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return requireBearerAuth;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'requireScopes', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return requireScopes;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'scopeChallengeResourceMetadataUrl', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return scopeChallengeResourceMetadataUrl;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'seedClientIdentityFromEnvelope', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return seedClientIdentityFromEnvelope;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'serverIdentityOf', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return serverIdentityOf;
|
|
}
|
|
});
|
|
Object.defineProperty(exports, 'verifyBearerToken', {
|
|
enumerable: true,
|
|
get: function () {
|
|
return verifyBearerToken;
|
|
}
|
|
});
|
|
//# sourceMappingURL=mcp-BumlNux0.cjs.map |