@novorb/mcp-core 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -161
- package/bin/mcp-core-jest.mjs +112 -0
- package/dist/auth.d.ts +57 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +74 -0
- package/dist/auth.js.map +1 -0
- package/dist/bootstrap.d.ts +129 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +206 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/client-input.d.ts +72 -0
- package/dist/client-input.d.ts.map +1 -0
- package/dist/client-input.js +109 -0
- package/dist/client-input.js.map +1 -0
- package/dist/credential-checks.d.ts +80 -0
- package/dist/credential-checks.d.ts.map +1 -0
- package/dist/credential-checks.js +189 -0
- package/dist/credential-checks.js.map +1 -0
- package/dist/credential-health.d.ts +12 -0
- package/dist/credential-health.d.ts.map +1 -0
- package/dist/credential-health.js +12 -0
- package/dist/credential-health.js.map +1 -0
- package/dist/credential-report.d.ts +46 -0
- package/dist/credential-report.d.ts.map +1 -0
- package/dist/credential-report.js +135 -0
- package/dist/credential-report.js.map +1 -0
- package/dist/credential-status-tool.d.ts +20 -0
- package/dist/credential-status-tool.d.ts.map +1 -0
- package/dist/credential-status-tool.js +33 -0
- package/dist/credential-status-tool.js.map +1 -0
- package/dist/credentials.d.ts +288 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +652 -0
- package/dist/credentials.js.map +1 -0
- package/dist/express-peers.d.ts +29 -0
- package/dist/express-peers.d.ts.map +1 -0
- package/dist/express-peers.js +36 -0
- package/dist/express-peers.js.map +1 -0
- package/dist/express.d.ts +74 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +164 -0
- package/dist/express.js.map +1 -0
- package/dist/http-client.d.ts +278 -43
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +620 -182
- package/dist/http-client.js.map +1 -1
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -10
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +28 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +37 -0
- package/dist/limits.js.map +1 -0
- package/dist/list-pagination.d.ts +41 -0
- package/dist/list-pagination.d.ts.map +1 -0
- package/dist/list-pagination.js +104 -0
- package/dist/list-pagination.js.map +1 -0
- package/dist/net.d.ts +112 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/net.js +490 -0
- package/dist/net.js.map +1 -0
- package/dist/notifications.d.ts +78 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +173 -0
- package/dist/notifications.js.map +1 -0
- package/dist/pagination.d.ts +236 -5
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +393 -6
- package/dist/pagination.js.map +1 -1
- package/dist/prompts.d.ts +70 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +109 -0
- package/dist/prompts.js.map +1 -0
- package/dist/protocol-errors.d.ts +15 -0
- package/dist/protocol-errors.d.ts.map +1 -0
- package/dist/protocol-errors.js +28 -0
- package/dist/protocol-errors.js.map +1 -0
- package/dist/protocol-logging.d.ts +37 -0
- package/dist/protocol-logging.d.ts.map +1 -0
- package/dist/protocol-logging.js +77 -0
- package/dist/protocol-logging.js.map +1 -0
- package/dist/rate-limiter.d.ts +36 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +79 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/request-state.d.ts +74 -0
- package/dist/request-state.d.ts.map +1 -0
- package/dist/request-state.js +190 -0
- package/dist/request-state.js.map +1 -0
- package/dist/resources.d.ts +181 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +302 -0
- package/dist/resources.js.map +1 -0
- package/dist/result-limits.d.ts +80 -0
- package/dist/result-limits.d.ts.map +1 -0
- package/dist/result-limits.js +248 -0
- package/dist/result-limits.js.map +1 -0
- package/dist/retry.d.ts +79 -23
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +170 -57
- package/dist/retry.js.map +1 -1
- package/dist/sdk/client-stdio.d.ts +6 -0
- package/dist/sdk/client-stdio.d.ts.map +1 -0
- package/dist/sdk/client-stdio.js +6 -0
- package/dist/sdk/client-stdio.js.map +1 -0
- package/dist/sdk/client.d.ts +6 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +6 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/express.d.ts +3 -0
- package/dist/sdk/express.d.ts.map +1 -0
- package/dist/sdk/express.js +11 -0
- package/dist/sdk/express.js.map +1 -0
- package/dist/sdk/node.d.ts +6 -0
- package/dist/sdk/node.d.ts.map +1 -0
- package/dist/sdk/node.js +6 -0
- package/dist/sdk/node.js.map +1 -0
- package/dist/sdk/server-stdio.d.ts +6 -0
- package/dist/sdk/server-stdio.d.ts.map +1 -0
- package/dist/sdk/server-stdio.js +6 -0
- package/dist/sdk/server-stdio.js.map +1 -0
- package/dist/sdk/server.d.ts +6 -0
- package/dist/sdk/server.d.ts.map +1 -0
- package/dist/sdk/server.js +6 -0
- package/dist/sdk/server.js.map +1 -0
- package/dist/server.d.ts +88 -36
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +98 -44
- package/dist/server.js.map +1 -1
- package/dist/task-request.d.ts +20 -0
- package/dist/task-request.d.ts.map +1 -0
- package/dist/task-request.js +43 -0
- package/dist/task-request.js.map +1 -0
- package/dist/task-store.d.ts +230 -0
- package/dist/task-store.d.ts.map +1 -0
- package/dist/task-store.js +459 -0
- package/dist/task-store.js.map +1 -0
- package/dist/task-tools.d.ts +115 -0
- package/dist/task-tools.d.ts.map +1 -0
- package/dist/task-tools.js +397 -0
- package/dist/task-tools.js.map +1 -0
- package/dist/testing/client.d.ts +25 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +11 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/in-memory.d.ts +26 -0
- package/dist/testing/in-memory.d.ts.map +1 -0
- package/dist/testing/in-memory.js +41 -0
- package/dist/testing/in-memory.js.map +1 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +20 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/isolated-dirs.d.ts +27 -0
- package/dist/testing/isolated-dirs.d.ts.map +1 -0
- package/dist/testing/isolated-dirs.js +67 -0
- package/dist/testing/isolated-dirs.js.map +1 -0
- package/dist/testing/logs.d.ts +61 -0
- package/dist/testing/logs.d.ts.map +1 -0
- package/dist/testing/logs.js +113 -0
- package/dist/testing/logs.js.map +1 -0
- package/dist/testing/raw-http.d.ts +25 -0
- package/dist/testing/raw-http.d.ts.map +1 -0
- package/dist/testing/raw-http.js +41 -0
- package/dist/testing/raw-http.js.map +1 -0
- package/dist/testing/raw-peer.d.ts +43 -0
- package/dist/testing/raw-peer.d.ts.map +1 -0
- package/dist/testing/raw-peer.js +101 -0
- package/dist/testing/raw-peer.js.map +1 -0
- package/dist/testing/recording-server.d.ts +38 -0
- package/dist/testing/recording-server.d.ts.map +1 -0
- package/dist/testing/recording-server.js +38 -0
- package/dist/testing/recording-server.js.map +1 -0
- package/dist/testing/server-process.d.ts +76 -0
- package/dist/testing/server-process.d.ts.map +1 -0
- package/dist/testing/server-process.js +241 -0
- package/dist/testing/server-process.js.map +1 -0
- package/dist/testing/tools.d.ts +53 -0
- package/dist/testing/tools.d.ts.map +1 -0
- package/dist/testing/tools.js +98 -0
- package/dist/testing/tools.js.map +1 -0
- package/dist/testing/unit.d.ts +52 -0
- package/dist/testing/unit.d.ts.map +1 -0
- package/dist/testing/unit.js +95 -0
- package/dist/testing/unit.js.map +1 -0
- package/dist/tools.d.ts +205 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +370 -0
- package/dist/tools.js.map +1 -0
- package/dist/transport.d.ts +144 -53
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +305 -122
- package/dist/transport.js.map +1 -1
- package/dist/url-path.d.ts +49 -0
- package/dist/url-path.d.ts.map +1 -0
- package/dist/url-path.js +165 -0
- package/dist/url-path.js.map +1 -0
- package/dist/utils/dataStore.d.ts +143 -0
- package/dist/utils/dataStore.d.ts.map +1 -0
- package/dist/utils/dataStore.js +429 -0
- package/dist/utils/dataStore.js.map +1 -0
- package/dist/utils/envLoader.d.ts +129 -0
- package/dist/utils/envLoader.d.ts.map +1 -0
- package/dist/utils/envLoader.js +399 -0
- package/dist/utils/envLoader.js.map +1 -0
- package/dist/utils/logger.d.ts +181 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +664 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/redact.d.ts +50 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +318 -0
- package/dist/utils/redact.js.map +1 -0
- package/docs/api-reference.md +29 -0
- package/docs/building-a-server.md +63 -0
- package/docs/credentials.md +131 -0
- package/docs/data-store.md +37 -0
- package/docs/env.md +38 -0
- package/docs/features.md +28 -0
- package/docs/http-client.md +116 -0
- package/docs/logger.md +100 -0
- package/docs/migrating-to-v3.md +45 -0
- package/docs/outbound-urls.md +104 -0
- package/docs/pagination.md +45 -0
- package/docs/protocol-features.md +62 -0
- package/docs/resources-prompts-tasks.md +95 -0
- package/docs/sdk.md +33 -0
- package/docs/server.md +68 -0
- package/docs/testing-harness.md +139 -0
- package/docs/testing.md +151 -0
- package/docs/tools.md +88 -0
- package/docs/transport.md +85 -0
- package/examples/testing/tools.integration.test.example.ts +312 -0
- package/examples/testing/tools.unit.test.example.ts +149 -0
- package/package.json +140 -18
- package/dist/logger.d.ts +0 -80
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -236
- package/dist/logger.js.map +0 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Errors for primitives answered with JSON-RPC errors (resources, prompts)
|
|
3
|
+
*
|
|
4
|
+
* Tools answer failures in-band (`isError` results). `resources/read` and `prompts/get` have no such
|
|
5
|
+
* result shape, so their failures are JSON-RPC errors: a `ProtocolError` the handler throws is kept
|
|
6
|
+
* (its code), anything else becomes `-32603` with the `mapError` text. Every message is passed through
|
|
7
|
+
* `maskClientText` (secrets redacted, registered secret values masked), exactly like tool errors.
|
|
8
|
+
*/
|
|
9
|
+
import { ProtocolError } from '@modelcontextprotocol/server';
|
|
10
|
+
import { type MappedToolError } from './tools.js';
|
|
11
|
+
/** The `mapError` signature shared with `registerTools`. */
|
|
12
|
+
export type MapError = (err: unknown) => MappedToolError;
|
|
13
|
+
/** A thrown handler error as the JSON-RPC error a client receives (masked, never a stack). */
|
|
14
|
+
export declare function toProtocolError(err: unknown, mapError?: MapError): ProtocolError;
|
|
15
|
+
//# sourceMappingURL=protocol-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-errors.d.ts","sourceRoot":"","sources":["../src/protocol-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAqB,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAmB,KAAK,eAAe,EAAkB,MAAM,YAAY,CAAC;AAEnF,4DAA4D;AAC5D,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,eAAe,CAAC;AAEzD,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,GAAE,QAA0B,GAAG,aAAa,CAajG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Errors for primitives answered with JSON-RPC errors (resources, prompts)
|
|
3
|
+
*
|
|
4
|
+
* Tools answer failures in-band (`isError` results). `resources/read` and `prompts/get` have no such
|
|
5
|
+
* result shape, so their failures are JSON-RPC errors: a `ProtocolError` the handler throws is kept
|
|
6
|
+
* (its code), anything else becomes `-32603` with the `mapError` text. Every message is passed through
|
|
7
|
+
* `maskClientText` (secrets redacted, registered secret values masked), exactly like tool errors.
|
|
8
|
+
*/
|
|
9
|
+
import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
|
|
10
|
+
import { defaultMapError, maskClientText } from './tools.js';
|
|
11
|
+
/** A thrown handler error as the JSON-RPC error a client receives (masked, never a stack). */
|
|
12
|
+
export function toProtocolError(err, mapError = defaultMapError) {
|
|
13
|
+
if (ProtocolError.isInstance(err)) {
|
|
14
|
+
if (err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
15
|
+
return err;
|
|
16
|
+
return new ProtocolError(err.code, maskClientText(err.message), err.data);
|
|
17
|
+
}
|
|
18
|
+
let mapped;
|
|
19
|
+
try {
|
|
20
|
+
mapped = mapError(err);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
mapped = { message: 'Internal error' };
|
|
24
|
+
}
|
|
25
|
+
const text = mapped.code ? `${mapped.code}: ${mapped.message}` : mapped.message;
|
|
26
|
+
return new ProtocolError(ProtocolErrorCode.InternalError, maskClientText(text));
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=protocol-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-errors.js","sourceRoot":"","sources":["../src/protocol-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAwB,cAAc,EAAE,MAAM,YAAY,CAAC;AAKnF,8FAA8F;AAC9F,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,WAAqB,eAAe;IAChF,IAAI,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB,CAAC,sBAAsB;YAAE,OAAO,GAAG,CAAC;QACtE,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,MAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAChF,OAAO,IAAI,aAAa,CAAC,iBAAiB,CAAC,aAAa,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Protocol logging (MCP `notifications/message`)
|
|
3
|
+
*
|
|
4
|
+
* Forwards mcp-core logger entries to the MCP client as logging notifications while a request is being
|
|
5
|
+
* handled, through the SDK's own `ctx.mcpReq.log(level, data, logger)`. The SDK decides per era:
|
|
6
|
+
*
|
|
7
|
+
* - 2025-era connections honour the client's `logging/setLevel` (stdio keeps the level for the
|
|
8
|
+
* connection; stateless HTTP builds a fresh instance per request, so a level set in one request is
|
|
9
|
+
* not seen by the next one and every level is sent);
|
|
10
|
+
* - 2026-07-28 requests emit only when the request carries the per-request log level
|
|
11
|
+
* (`_meta['io.modelcontextprotocol/logLevel']`), never otherwise.
|
|
12
|
+
*
|
|
13
|
+
* Protocol logging is deprecated by the 2026-07-28 revision (SEP-2577) but remains functional for at
|
|
14
|
+
* least twelve months, and 2025 clients use it, so it stays available as an opt-in: `LOG_PROTOCOL=true`
|
|
15
|
+
* (or `createMcpServer(..., { protocolLogging: true })`) advertises the `logging` capability and
|
|
16
|
+
* installs the forwarder. Default off.
|
|
17
|
+
*
|
|
18
|
+
* What is forwarded is exactly what the log files receive (redacted, PII-masked, registered secret
|
|
19
|
+
* values masked), and the logger never carries tool inputs or outputs (`logToolCall` is shape only).
|
|
20
|
+
* The request is found through an AsyncLocalStorage scope that `registerTools` opens around every
|
|
21
|
+
* handler (or `runWithRequestContext` for hand-registered handlers).
|
|
22
|
+
*/
|
|
23
|
+
import type { LoggingLevel, ServerContext } from '@modelcontextprotocol/server';
|
|
24
|
+
import { LogLevel } from './utils/logger.js';
|
|
25
|
+
/** Run `fn` with `ctx` as the current request (what the protocol log forwarder sends through). */
|
|
26
|
+
export declare function runWithRequestContext<T>(ctx: ServerContext, fn: () => T): T;
|
|
27
|
+
/** The SDK handler context of the request being handled, if any. */
|
|
28
|
+
export declare function currentRequestContext(): ServerContext | undefined;
|
|
29
|
+
/** The MCP logging level for an mcp-core log level. */
|
|
30
|
+
export declare function toMcpLoggingLevel(level: LogLevel): LoggingLevel;
|
|
31
|
+
/** Whether protocol logging is on: the explicit option, else `LOG_PROTOCOL`, else false. */
|
|
32
|
+
export declare function protocolLoggingEnabled(option?: boolean, env?: NodeJS.ProcessEnv): boolean;
|
|
33
|
+
/** Install the forwarder (idempotent). `createMcpServer` calls this when protocol logging is on. */
|
|
34
|
+
export declare function enableProtocolLogging(): void;
|
|
35
|
+
/** Remove the forwarder (tests). */
|
|
36
|
+
export declare function disableProtocolLogging(): void;
|
|
37
|
+
//# sourceMappingURL=protocol-logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-logging.d.ts","sourceRoot":"","sources":["../src/protocol-logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,EAAiC,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI5E,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE3E;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,IAAI,aAAa,GAAG,SAAS,CAEjE;AASD,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,YAAY,CAE/D;AASD,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEtG;AAaD,oGAAoG;AACpG,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED,oCAAoC;AACpC,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Protocol logging (MCP `notifications/message`)
|
|
3
|
+
*
|
|
4
|
+
* Forwards mcp-core logger entries to the MCP client as logging notifications while a request is being
|
|
5
|
+
* handled, through the SDK's own `ctx.mcpReq.log(level, data, logger)`. The SDK decides per era:
|
|
6
|
+
*
|
|
7
|
+
* - 2025-era connections honour the client's `logging/setLevel` (stdio keeps the level for the
|
|
8
|
+
* connection; stateless HTTP builds a fresh instance per request, so a level set in one request is
|
|
9
|
+
* not seen by the next one and every level is sent);
|
|
10
|
+
* - 2026-07-28 requests emit only when the request carries the per-request log level
|
|
11
|
+
* (`_meta['io.modelcontextprotocol/logLevel']`), never otherwise.
|
|
12
|
+
*
|
|
13
|
+
* Protocol logging is deprecated by the 2026-07-28 revision (SEP-2577) but remains functional for at
|
|
14
|
+
* least twelve months, and 2025 clients use it, so it stays available as an opt-in: `LOG_PROTOCOL=true`
|
|
15
|
+
* (or `createMcpServer(..., { protocolLogging: true })`) advertises the `logging` capability and
|
|
16
|
+
* installs the forwarder. Default off.
|
|
17
|
+
*
|
|
18
|
+
* What is forwarded is exactly what the log files receive (redacted, PII-masked, registered secret
|
|
19
|
+
* values masked), and the logger never carries tool inputs or outputs (`logToolCall` is shape only).
|
|
20
|
+
* The request is found through an AsyncLocalStorage scope that `registerTools` opens around every
|
|
21
|
+
* handler (or `runWithRequestContext` for hand-registered handlers).
|
|
22
|
+
*/
|
|
23
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
24
|
+
import { addLogListener, LogLevel } from './utils/logger.js';
|
|
25
|
+
const requestScope = new AsyncLocalStorage();
|
|
26
|
+
/** Run `fn` with `ctx` as the current request (what the protocol log forwarder sends through). */
|
|
27
|
+
export function runWithRequestContext(ctx, fn) {
|
|
28
|
+
return requestScope.run(ctx, fn);
|
|
29
|
+
}
|
|
30
|
+
/** The SDK handler context of the request being handled, if any. */
|
|
31
|
+
export function currentRequestContext() {
|
|
32
|
+
return requestScope.getStore();
|
|
33
|
+
}
|
|
34
|
+
const LEVEL_MAP = {
|
|
35
|
+
[LogLevel.DEBUG]: 'debug',
|
|
36
|
+
[LogLevel.INFO]: 'info',
|
|
37
|
+
[LogLevel.WARN]: 'warning',
|
|
38
|
+
[LogLevel.ERROR]: 'error',
|
|
39
|
+
};
|
|
40
|
+
/** The MCP logging level for an mcp-core log level. */
|
|
41
|
+
export function toMcpLoggingLevel(level) {
|
|
42
|
+
return LEVEL_MAP[level];
|
|
43
|
+
}
|
|
44
|
+
function parseBool(value) {
|
|
45
|
+
const v = value?.trim().toLowerCase();
|
|
46
|
+
if (v === 'true' || v === '1' || v === 'yes' || v === 'on')
|
|
47
|
+
return true;
|
|
48
|
+
if (v === 'false' || v === '0' || v === 'no' || v === 'off')
|
|
49
|
+
return false;
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Whether protocol logging is on: the explicit option, else `LOG_PROTOCOL`, else false. */
|
|
53
|
+
export function protocolLoggingEnabled(option, env = process.env) {
|
|
54
|
+
return option ?? parseBool(env.LOG_PROTOCOL) ?? false;
|
|
55
|
+
}
|
|
56
|
+
let unsubscribe;
|
|
57
|
+
function forward(entry) {
|
|
58
|
+
const ctx = currentRequestContext();
|
|
59
|
+
if (!ctx)
|
|
60
|
+
return;
|
|
61
|
+
const data = { message: entry.message, context: entry.context };
|
|
62
|
+
if (entry.meta !== undefined)
|
|
63
|
+
data.meta = entry.meta;
|
|
64
|
+
// The SDK applies the capability, the era and the client's level; a failed send never surfaces.
|
|
65
|
+
ctx.mcpReq.log(toMcpLoggingLevel(entry.level), data, entry.logger).catch(() => undefined);
|
|
66
|
+
}
|
|
67
|
+
/** Install the forwarder (idempotent). `createMcpServer` calls this when protocol logging is on. */
|
|
68
|
+
export function enableProtocolLogging() {
|
|
69
|
+
// The listener set dedupes `forward`, so this stays idempotent (and survives a `resetLogger`).
|
|
70
|
+
unsubscribe = addLogListener(forward);
|
|
71
|
+
}
|
|
72
|
+
/** Remove the forwarder (tests). */
|
|
73
|
+
export function disableProtocolLogging() {
|
|
74
|
+
unsubscribe?.();
|
|
75
|
+
unsubscribe = undefined;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=protocol-logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-logging.js","sourceRoot":"","sources":["../src/protocol-logging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,cAAc,EAAiB,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5E,MAAM,YAAY,GAAG,IAAI,iBAAiB,EAAiB,CAAC;AAE5D,kGAAkG;AAClG,MAAM,UAAU,qBAAqB,CAAI,GAAkB,EAAE,EAAW;IACtE,OAAO,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB;IACnC,OAAO,YAAY,CAAC,QAAQ,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,SAAS,GAAmC;IAChD,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACzB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;IACvB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS;IAC1B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;CAC1B,CAAC;AAEF,uDAAuD;AACvD,MAAM,UAAU,iBAAiB,CAAC,KAAe;IAC/C,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAC,KAAyB;IAC1C,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxE,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC1E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,sBAAsB,CAAC,MAAgB,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC3F,OAAO,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC;AACxD,CAAC;AAED,IAAI,WAAqC,CAAC;AAE1C,SAAS,OAAO,CAAC,KAAe;IAC9B,MAAM,GAAG,GAAG,qBAAqB,EAAE,CAAC;IACpC,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,IAAI,GAA4B,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACzF,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACrD,gGAAgG;IAChG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAC5F,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,qBAAqB;IACnC,+FAA+F;IAC/F,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,sBAAsB;IACpC,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,GAAG,SAAS,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Token bucket rate limiter
|
|
3
|
+
*
|
|
4
|
+
* A client-side limiter shared by every request of one HTTP client instance (e.g. Trello: 100 requests
|
|
5
|
+
* per 10 seconds per token = capacity 100, refillPerSecond 10). Waiters are served in FIFO order.
|
|
6
|
+
* Build the client once per process, not per MCP request, or the bucket limits nothing.
|
|
7
|
+
*/
|
|
8
|
+
export interface TokenBucketOptions {
|
|
9
|
+
/** Maximum burst (bucket size); must be >= 1. */
|
|
10
|
+
capacity: number;
|
|
11
|
+
/** Tokens added per second; must be > 0. */
|
|
12
|
+
refillPerSecond: number;
|
|
13
|
+
/** Clock in milliseconds (default Date.now; injectable for tests). */
|
|
14
|
+
now?: () => number;
|
|
15
|
+
}
|
|
16
|
+
export declare class TokenBucket {
|
|
17
|
+
private tokens;
|
|
18
|
+
private last;
|
|
19
|
+
private queue;
|
|
20
|
+
private readonly capacity;
|
|
21
|
+
private readonly ratePerMs;
|
|
22
|
+
private readonly now;
|
|
23
|
+
constructor(options: TokenBucketOptions);
|
|
24
|
+
private refill;
|
|
25
|
+
/** Tokens available right now (after refill). */
|
|
26
|
+
available(): number;
|
|
27
|
+
/**
|
|
28
|
+
* Waits for one token. Rejects with the abort reason as soon as `signal` aborts, also while still
|
|
29
|
+
* queued behind other waiters (its turn then takes no token).
|
|
30
|
+
*/
|
|
31
|
+
acquire(signal?: AbortSignal): Promise<void>;
|
|
32
|
+
private take;
|
|
33
|
+
}
|
|
34
|
+
/** Creates a token bucket. */
|
|
35
|
+
export declare function createTokenBucket(options: TokenBucketOptions): TokenBucket;
|
|
36
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,OAAO,EAAE,kBAAkB;IAUvC,OAAO,CAAC,MAAM;IAMd,iDAAiD;IACjD,SAAS,IAAI,MAAM;IAKnB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAyB9B,IAAI;CAWnB;AAED,8BAA8B;AAC9B,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAE1E"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Token bucket rate limiter
|
|
3
|
+
*
|
|
4
|
+
* A client-side limiter shared by every request of one HTTP client instance (e.g. Trello: 100 requests
|
|
5
|
+
* per 10 seconds per token = capacity 100, refillPerSecond 10). Waiters are served in FIFO order.
|
|
6
|
+
* Build the client once per process, not per MCP request, or the bucket limits nothing.
|
|
7
|
+
*/
|
|
8
|
+
import { sleep, throwIfAborted } from './retry.js';
|
|
9
|
+
export class TokenBucket {
|
|
10
|
+
tokens;
|
|
11
|
+
last;
|
|
12
|
+
queue = Promise.resolve();
|
|
13
|
+
capacity;
|
|
14
|
+
ratePerMs;
|
|
15
|
+
now;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
if (!(options.capacity >= 1))
|
|
18
|
+
throw new RangeError('capacity must be >= 1');
|
|
19
|
+
if (!(options.refillPerSecond > 0))
|
|
20
|
+
throw new RangeError('refillPerSecond must be > 0');
|
|
21
|
+
this.capacity = options.capacity;
|
|
22
|
+
this.ratePerMs = options.refillPerSecond / 1000;
|
|
23
|
+
this.now = options.now ?? Date.now;
|
|
24
|
+
this.tokens = options.capacity;
|
|
25
|
+
this.last = this.now();
|
|
26
|
+
}
|
|
27
|
+
refill() {
|
|
28
|
+
const now = this.now();
|
|
29
|
+
this.tokens = Math.min(this.capacity, this.tokens + (now - this.last) * this.ratePerMs);
|
|
30
|
+
this.last = now;
|
|
31
|
+
}
|
|
32
|
+
/** Tokens available right now (after refill). */
|
|
33
|
+
available() {
|
|
34
|
+
this.refill();
|
|
35
|
+
return this.tokens;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Waits for one token. Rejects with the abort reason as soon as `signal` aborts, also while still
|
|
39
|
+
* queued behind other waiters (its turn then takes no token).
|
|
40
|
+
*/
|
|
41
|
+
acquire(signal) {
|
|
42
|
+
const turn = this.queue.then(() => this.take(signal));
|
|
43
|
+
// Keep the chain alive when one waiter aborts so later waiters still get served.
|
|
44
|
+
this.queue = turn.catch(() => undefined);
|
|
45
|
+
if (!signal)
|
|
46
|
+
return turn;
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const onAbort = () => reject(signal.reason);
|
|
49
|
+
if (signal.aborted) {
|
|
50
|
+
onAbort();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
54
|
+
turn.then(() => {
|
|
55
|
+
signal.removeEventListener('abort', onAbort);
|
|
56
|
+
resolve();
|
|
57
|
+
}, (error) => {
|
|
58
|
+
signal.removeEventListener('abort', onAbort);
|
|
59
|
+
reject(error);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async take(signal) {
|
|
64
|
+
for (;;) {
|
|
65
|
+
throwIfAborted(signal);
|
|
66
|
+
this.refill();
|
|
67
|
+
if (this.tokens >= 1) {
|
|
68
|
+
this.tokens -= 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await sleep(Math.ceil((1 - this.tokens) / this.ratePerMs), signal);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Creates a token bucket. */
|
|
76
|
+
export function createTokenBucket(options) {
|
|
77
|
+
return new TokenBucket(options);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Sealed requestState (multi-round-trip flows)
|
|
3
|
+
*
|
|
4
|
+
* A multi-step tool returns `inputRequired({ inputRequests?, requestState })`; the client echoes
|
|
5
|
+
* `requestState` back on the retry (2026-07-28 MRTR; the SDK's legacy shim does the same in-process for
|
|
6
|
+
* 2025-era connections). The echoed value is attacker-controlled. The SDK ships an HMAC codec
|
|
7
|
+
* (`createRequestStateCodec`: signed, NOT encrypted, the client can read the payload) and the
|
|
8
|
+
* `ServerOptions.requestState.verify` seam; mcp-core adds the authenticated-encryption variant with the
|
|
9
|
+
* SAME `RequestStateCodec` interface (`mint` / `verify`), so it drops into that seam unchanged:
|
|
10
|
+
*
|
|
11
|
+
* - AES-256-GCM with a per-key sub-key derived by HKDF-SHA256 (the operator secret is never used as
|
|
12
|
+
* the cipher key directly); the payload is confidential and tamper-evident;
|
|
13
|
+
* - expiry (`ttlSeconds`, default 600) inside the sealed envelope;
|
|
14
|
+
* - context binding (default: the request method and the caller's `clientId`), compared in constant
|
|
15
|
+
* time, so a state minted for one caller is refused for another;
|
|
16
|
+
* - key rotation: the current key seals, the current and every previous key open (`kid` routing);
|
|
17
|
+
* - tamper, expiry, binding and unknown-key failures throw `RequestStateError` with a reason; the SDK
|
|
18
|
+
* answers the client the frozen `-32602 Invalid or expired requestState` and reports the reason to
|
|
19
|
+
* the server's `onerror` (the mcp-core logger).
|
|
20
|
+
*
|
|
21
|
+
* Wire: `ms1.<kid>.<iv>.<ciphertext+tag>` (base64url parts; AAD = `ms1.<kid>`).
|
|
22
|
+
*
|
|
23
|
+
* The key comes from `MCP_REQUEST_STATE_KEY` (secret, at least 32 bytes, for example
|
|
24
|
+
* `openssl rand -base64 48`); `MCP_REQUEST_STATE_PREVIOUS_KEY` (multi: `_1`, `_2`, ...) keeps
|
|
25
|
+
* in-flight flows valid across a rotation. The SAME key must be configured on every instance that can
|
|
26
|
+
* receive a retry (stateless HTTP may route each round to a different replica). Without a key the
|
|
27
|
+
* feature refuses to start (`requestStateCodecFromEnv` throws).
|
|
28
|
+
*/
|
|
29
|
+
import type { RequestStateCodec, ServerContext } from '@modelcontextprotocol/server';
|
|
30
|
+
import type { LoadedEnv } from './utils/envLoader.js';
|
|
31
|
+
export type { RequestStateCodec } from '@modelcontextprotocol/server';
|
|
32
|
+
/** Minimum key length in bytes (the SDK's HMAC codec uses the same bound). */
|
|
33
|
+
export declare const MIN_REQUEST_STATE_KEY_BYTES = 32;
|
|
34
|
+
/** Default lifetime of a minted state, in seconds. */
|
|
35
|
+
export declare const DEFAULT_REQUEST_STATE_TTL_SECONDS = 600;
|
|
36
|
+
export type RequestStateFailure = 'malformed' | 'unknown-key' | 'tampered' | 'expired' | 'bind';
|
|
37
|
+
/** A refused requestState. `reason` is a fixed code; the message never carries the payload. */
|
|
38
|
+
export declare class RequestStateError extends Error {
|
|
39
|
+
readonly reason: RequestStateFailure;
|
|
40
|
+
constructor(reason: RequestStateFailure);
|
|
41
|
+
}
|
|
42
|
+
/** Thrown when the feature is used without a valid key. */
|
|
43
|
+
export declare class RequestStateKeyError extends Error {
|
|
44
|
+
constructor(message: string);
|
|
45
|
+
}
|
|
46
|
+
export interface SealedRequestStateOptions {
|
|
47
|
+
/** The operator secret (UTF-8 string or bytes), at least 32 bytes. */
|
|
48
|
+
key: string | Uint8Array;
|
|
49
|
+
/** Older secrets still accepted for opening (rotation). */
|
|
50
|
+
previousKeys?: ReadonlyArray<string | Uint8Array>;
|
|
51
|
+
/** Lifetime of a minted state in seconds (default 600). */
|
|
52
|
+
ttlSeconds?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Context binding, evaluated at mint and verify (default: method + caller clientId). `false`
|
|
55
|
+
* disables binding (only for state that does not influence authorization or resource access).
|
|
56
|
+
*/
|
|
57
|
+
bind?: ((ctx: ServerContext) => string) | false;
|
|
58
|
+
/** Clock in milliseconds (tests). */
|
|
59
|
+
now?: () => number;
|
|
60
|
+
}
|
|
61
|
+
/** The default binding: the request method and the authenticated caller. */
|
|
62
|
+
export declare function defaultRequestStateBinding(ctx: ServerContext): string;
|
|
63
|
+
/**
|
|
64
|
+
* An authenticated-encryption `RequestStateCodec` (AES-256-GCM, HKDF sub-keys, expiry, binding,
|
|
65
|
+
* rotation). Pass it as `createMcpServer(..., { requestStateCodec })`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createSealedRequestStateCodec<T = unknown>(options: SealedRequestStateOptions): RequestStateCodec<T>;
|
|
68
|
+
/**
|
|
69
|
+
* The sealed codec from `MCP_REQUEST_STATE_KEY`, `MCP_REQUEST_STATE_PREVIOUS_KEY[_n]` and
|
|
70
|
+
* `MCP_REQUEST_STATE_TTL_SECONDS`. Throws `RequestStateKeyError` when the key is missing or too short
|
|
71
|
+
* (the feature never runs with a default or empty key). Every key is registered with the logger mask.
|
|
72
|
+
*/
|
|
73
|
+
export declare function requestStateCodecFromEnv<T = unknown>(env?: LoadedEnv | NodeJS.ProcessEnv, options?: Omit<SealedRequestStateOptions, 'key' | 'previousKeys' | 'ttlSeconds'>): RequestStateCodec<T>;
|
|
74
|
+
//# sourceMappingURL=request-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-state.d.ts","sourceRoot":"","sources":["../src/request-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEtE,8EAA8E;AAC9E,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAMrD,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhG,+FAA+F;AAC/F,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;gBACzB,MAAM,EAAE,mBAAmB;CAaxC;AAED,2DAA2D;AAC3D,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,yBAAyB;IACxC,sEAAsE;IACtE,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;IACzB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAClD,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,aAAa,KAAK,MAAM,CAAC,GAAG,KAAK,CAAC;IAChD,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAyBD,4EAA4E;AAC5E,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAErE;AAYD;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,yBAAyB,GAAG,iBAAiB,CAAC,CAAC,CAAC,CA0DnH;AAcD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,GAAG,OAAO,EAClD,GAAG,GAAE,SAAS,GAAG,MAAM,CAAC,UAAwB,EAChD,OAAO,GAAE,IAAI,CAAC,yBAAyB,EAAE,KAAK,GAAG,cAAc,GAAG,YAAY,CAAM,GACnF,iBAAiB,CAAC,CAAC,CAAC,CAkBtB"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @novorb/mcp-core - Sealed requestState (multi-round-trip flows)
|
|
3
|
+
*
|
|
4
|
+
* A multi-step tool returns `inputRequired({ inputRequests?, requestState })`; the client echoes
|
|
5
|
+
* `requestState` back on the retry (2026-07-28 MRTR; the SDK's legacy shim does the same in-process for
|
|
6
|
+
* 2025-era connections). The echoed value is attacker-controlled. The SDK ships an HMAC codec
|
|
7
|
+
* (`createRequestStateCodec`: signed, NOT encrypted, the client can read the payload) and the
|
|
8
|
+
* `ServerOptions.requestState.verify` seam; mcp-core adds the authenticated-encryption variant with the
|
|
9
|
+
* SAME `RequestStateCodec` interface (`mint` / `verify`), so it drops into that seam unchanged:
|
|
10
|
+
*
|
|
11
|
+
* - AES-256-GCM with a per-key sub-key derived by HKDF-SHA256 (the operator secret is never used as
|
|
12
|
+
* the cipher key directly); the payload is confidential and tamper-evident;
|
|
13
|
+
* - expiry (`ttlSeconds`, default 600) inside the sealed envelope;
|
|
14
|
+
* - context binding (default: the request method and the caller's `clientId`), compared in constant
|
|
15
|
+
* time, so a state minted for one caller is refused for another;
|
|
16
|
+
* - key rotation: the current key seals, the current and every previous key open (`kid` routing);
|
|
17
|
+
* - tamper, expiry, binding and unknown-key failures throw `RequestStateError` with a reason; the SDK
|
|
18
|
+
* answers the client the frozen `-32602 Invalid or expired requestState` and reports the reason to
|
|
19
|
+
* the server's `onerror` (the mcp-core logger).
|
|
20
|
+
*
|
|
21
|
+
* Wire: `ms1.<kid>.<iv>.<ciphertext+tag>` (base64url parts; AAD = `ms1.<kid>`).
|
|
22
|
+
*
|
|
23
|
+
* The key comes from `MCP_REQUEST_STATE_KEY` (secret, at least 32 bytes, for example
|
|
24
|
+
* `openssl rand -base64 48`); `MCP_REQUEST_STATE_PREVIOUS_KEY` (multi: `_1`, `_2`, ...) keeps
|
|
25
|
+
* in-flight flows valid across a rotation. The SAME key must be configured on every instance that can
|
|
26
|
+
* receive a retry (stateless HTTP may route each round to a different replica). Without a key the
|
|
27
|
+
* feature refuses to start (`requestStateCodecFromEnv` throws).
|
|
28
|
+
*/
|
|
29
|
+
import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
30
|
+
import { registerSecretValues } from './utils/logger.js';
|
|
31
|
+
/** Minimum key length in bytes (the SDK's HMAC codec uses the same bound). */
|
|
32
|
+
export const MIN_REQUEST_STATE_KEY_BYTES = 32;
|
|
33
|
+
/** Default lifetime of a minted state, in seconds. */
|
|
34
|
+
export const DEFAULT_REQUEST_STATE_TTL_SECONDS = 600;
|
|
35
|
+
const VERSION = 'ms1';
|
|
36
|
+
const IV_BYTES = 12;
|
|
37
|
+
const TAG_BYTES = 16;
|
|
38
|
+
/** A refused requestState. `reason` is a fixed code; the message never carries the payload. */
|
|
39
|
+
export class RequestStateError extends Error {
|
|
40
|
+
reason;
|
|
41
|
+
constructor(reason) {
|
|
42
|
+
const text = {
|
|
43
|
+
malformed: 'requestState is malformed',
|
|
44
|
+
'unknown-key': 'requestState was sealed with a key this server does not hold (rotated out, or another deployment)',
|
|
45
|
+
tampered: 'requestState failed authentication (tampered or corrupted)',
|
|
46
|
+
expired: 'requestState has expired',
|
|
47
|
+
bind: 'requestState was issued for another caller or method',
|
|
48
|
+
};
|
|
49
|
+
super(text[reason]);
|
|
50
|
+
this.name = 'RequestStateError';
|
|
51
|
+
this.reason = reason;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Thrown when the feature is used without a valid key. */
|
|
55
|
+
export class RequestStateKeyError extends Error {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = 'RequestStateKeyError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function keyBytes(key, label) {
|
|
62
|
+
const bytes = typeof key === 'string' ? Buffer.from(key, 'utf8') : Buffer.from(key);
|
|
63
|
+
if (bytes.length < MIN_REQUEST_STATE_KEY_BYTES) {
|
|
64
|
+
throw new RequestStateKeyError(`${label} must be at least ${MIN_REQUEST_STATE_KEY_BYTES} bytes (got ${bytes.length}); generate one with \`openssl rand -base64 48\``);
|
|
65
|
+
}
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
function derive(secret) {
|
|
69
|
+
const kid = Buffer.from(hkdfSync('sha256', secret, Buffer.alloc(0), 'mcp-core/request-state/kid', 6)).toString('base64url');
|
|
70
|
+
const key = Buffer.from(hkdfSync('sha256', secret, Buffer.alloc(0), 'mcp-core/request-state/aes-256-gcm', 32));
|
|
71
|
+
return { kid, key };
|
|
72
|
+
}
|
|
73
|
+
/** The default binding: the request method and the authenticated caller. */
|
|
74
|
+
export function defaultRequestStateBinding(ctx) {
|
|
75
|
+
return `${ctx.mcpReq.method}\0${ctx.http?.authInfo?.clientId ?? ''}`;
|
|
76
|
+
}
|
|
77
|
+
function bindTag(value) {
|
|
78
|
+
return createHash('sha256').update(`mcp-core.requestState.bind:${value}`).digest('base64url');
|
|
79
|
+
}
|
|
80
|
+
function sameTag(a, b) {
|
|
81
|
+
const x = createHash('sha256').update(a).digest();
|
|
82
|
+
const y = createHash('sha256').update(b).digest();
|
|
83
|
+
return timingSafeEqual(x, y);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* An authenticated-encryption `RequestStateCodec` (AES-256-GCM, HKDF sub-keys, expiry, binding,
|
|
87
|
+
* rotation). Pass it as `createMcpServer(..., { requestStateCodec })`.
|
|
88
|
+
*/
|
|
89
|
+
export function createSealedRequestStateCodec(options) {
|
|
90
|
+
const current = derive(keyBytes(options.key, 'MCP_REQUEST_STATE_KEY'));
|
|
91
|
+
const opening = new Map([[current.kid, current.key]]);
|
|
92
|
+
for (const [i, previous] of (options.previousKeys ?? []).entries()) {
|
|
93
|
+
const d = derive(keyBytes(previous, `MCP_REQUEST_STATE_PREVIOUS_KEY (${i + 1})`));
|
|
94
|
+
if (!opening.has(d.kid))
|
|
95
|
+
opening.set(d.kid, d.key);
|
|
96
|
+
}
|
|
97
|
+
const ttlSeconds = options.ttlSeconds ?? DEFAULT_REQUEST_STATE_TTL_SECONDS;
|
|
98
|
+
if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0)
|
|
99
|
+
throw new RangeError('ttlSeconds must be a positive integer');
|
|
100
|
+
const bind = options.bind === false ? undefined : (options.bind ?? defaultRequestStateBinding);
|
|
101
|
+
const now = options.now ?? Date.now;
|
|
102
|
+
return {
|
|
103
|
+
async mint(payload, ctx) {
|
|
104
|
+
if (bind && !ctx)
|
|
105
|
+
throw new TypeError('requestState mint needs the handler context for its binding');
|
|
106
|
+
const body = { p: payload, exp: Math.floor(now() / 1000) + ttlSeconds };
|
|
107
|
+
if (bind && ctx)
|
|
108
|
+
body.b = bindTag(bind(ctx));
|
|
109
|
+
const iv = randomBytes(IV_BYTES);
|
|
110
|
+
const header = `${VERSION}.${current.kid}`;
|
|
111
|
+
const cipher = createCipheriv('aes-256-gcm', current.key, iv);
|
|
112
|
+
cipher.setAAD(Buffer.from(header, 'utf8'));
|
|
113
|
+
const sealed = Buffer.concat([cipher.update(JSON.stringify(body), 'utf8'), cipher.final(), cipher.getAuthTag()]);
|
|
114
|
+
return `${header}.${iv.toString('base64url')}.${sealed.toString('base64url')}`;
|
|
115
|
+
},
|
|
116
|
+
async verify(state, ctx) {
|
|
117
|
+
const parts = state.split('.');
|
|
118
|
+
if (parts.length !== 4 || parts[0] !== VERSION)
|
|
119
|
+
throw new RequestStateError('malformed');
|
|
120
|
+
const [version, kid, ivText, sealedText] = parts;
|
|
121
|
+
const key = opening.get(kid);
|
|
122
|
+
if (!key)
|
|
123
|
+
throw new RequestStateError('unknown-key');
|
|
124
|
+
const iv = Buffer.from(ivText, 'base64url');
|
|
125
|
+
const sealed = Buffer.from(sealedText, 'base64url');
|
|
126
|
+
if (iv.length !== IV_BYTES || sealed.length <= TAG_BYTES)
|
|
127
|
+
throw new RequestStateError('malformed');
|
|
128
|
+
let plain;
|
|
129
|
+
try {
|
|
130
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
131
|
+
decipher.setAAD(Buffer.from(`${version}.${kid}`, 'utf8'));
|
|
132
|
+
decipher.setAuthTag(sealed.subarray(sealed.length - TAG_BYTES));
|
|
133
|
+
plain = Buffer.concat([
|
|
134
|
+
decipher.update(sealed.subarray(0, sealed.length - TAG_BYTES)),
|
|
135
|
+
decipher.final(),
|
|
136
|
+
]).toString('utf8');
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
throw new RequestStateError('tampered');
|
|
140
|
+
}
|
|
141
|
+
let body;
|
|
142
|
+
try {
|
|
143
|
+
body = JSON.parse(plain);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new RequestStateError('malformed');
|
|
147
|
+
}
|
|
148
|
+
if (typeof body.exp !== 'number' || body.exp * 1000 <= now())
|
|
149
|
+
throw new RequestStateError('expired');
|
|
150
|
+
if (bind) {
|
|
151
|
+
if (typeof body.b !== 'string' || !sameTag(body.b, bindTag(bind(ctx))))
|
|
152
|
+
throw new RequestStateError('bind');
|
|
153
|
+
}
|
|
154
|
+
return body.p;
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function isLoadedEnv(env) {
|
|
159
|
+
return typeof env.get === 'function' && typeof env.getAll === 'function';
|
|
160
|
+
}
|
|
161
|
+
function previousFromProcessEnv(env) {
|
|
162
|
+
const base = 'MCP_REQUEST_STATE_PREVIOUS_KEY';
|
|
163
|
+
const keys = Object.keys(env)
|
|
164
|
+
.filter((k) => k === base || k.startsWith(`${base}_`))
|
|
165
|
+
.sort((a, b) => (a === base ? -1 : b === base ? 1 : a.localeCompare(b, 'en', { numeric: true })));
|
|
166
|
+
return keys.map((k) => env[k]).filter((v) => typeof v === 'string' && v !== '');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The sealed codec from `MCP_REQUEST_STATE_KEY`, `MCP_REQUEST_STATE_PREVIOUS_KEY[_n]` and
|
|
170
|
+
* `MCP_REQUEST_STATE_TTL_SECONDS`. Throws `RequestStateKeyError` when the key is missing or too short
|
|
171
|
+
* (the feature never runs with a default or empty key). Every key is registered with the logger mask.
|
|
172
|
+
*/
|
|
173
|
+
export function requestStateCodecFromEnv(env = process.env, options = {}) {
|
|
174
|
+
const loaded = isLoadedEnv(env);
|
|
175
|
+
const key = loaded ? env.get('MCP_REQUEST_STATE_KEY') : env.MCP_REQUEST_STATE_KEY || undefined;
|
|
176
|
+
if (key === undefined) {
|
|
177
|
+
throw new RequestStateKeyError('requestState is not available: set MCP_REQUEST_STATE_KEY (a secret of at least 32 bytes, the same on every instance)');
|
|
178
|
+
}
|
|
179
|
+
const previous = loaded ? env.getAll('MCP_REQUEST_STATE_PREVIOUS_KEY') : previousFromProcessEnv(env);
|
|
180
|
+
registerSecretValues([key, ...previous]);
|
|
181
|
+
const ttlRaw = loaded ? env.get('MCP_REQUEST_STATE_TTL_SECONDS') : env.MCP_REQUEST_STATE_TTL_SECONDS;
|
|
182
|
+
const ttl = ttlRaw === undefined || ttlRaw === '' ? undefined : Number(ttlRaw);
|
|
183
|
+
return createSealedRequestStateCodec({
|
|
184
|
+
...options,
|
|
185
|
+
key,
|
|
186
|
+
previousKeys: previous,
|
|
187
|
+
...(ttl !== undefined ? { ttlSeconds: ttl } : {}),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=request-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-state.js","sourceRoot":"","sources":["../src/request-state.ts"],"names":[],"mappings":"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"}
|