@cyanheads/mcp-ts-core 0.13.11 → 0.13.13
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/AGENTS.md +9 -8
- package/CLAUDE.md +9 -8
- package/README.md +1 -1
- package/changelog/0.13.x/0.13.12.md +50 -0
- package/changelog/0.13.x/0.13.13.md +93 -0
- package/dist/core/context.d.ts +12 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +59 -14
- package/dist/core/context.js.map +1 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +21 -8
- package/dist/core/worker.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +14 -8
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js +16 -9
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/inputRequired.d.ts +18 -9
- package/dist/mcp-server/inputRequired.d.ts.map +1 -1
- package/dist/mcp-server/inputRequired.js +29 -15
- package/dist/mcp-server/inputRequired.js.map +1 -1
- package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
- package/dist/mcp-server/prompts/prompt-registration.js +10 -7
- package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +29 -19
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +8 -2
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.js +18 -7
- package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +7 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +107 -21
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts +22 -0
- package/dist/mcp-server/transports/auth/lib/authUtils.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js +29 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js +2 -1
- package/dist/mcp-server/transports/auth/lib/checkScopes.js.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.js +4 -2
- package/dist/mcp-server/transports/http/httpErrorHandler.js.map +1 -1
- package/dist/services/mirror/sqlite/handle.d.ts +13 -2
- package/dist/services/mirror/sqlite/handle.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/handle.js +17 -6
- package/dist/services/mirror/sqlite/handle.js.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.d.ts.map +1 -1
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js +3 -2
- package/dist/services/mirror/sqlite/sqliteMirrorStore.js.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.js +48 -21
- package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -1
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +31 -16
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +29 -11
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +204 -95
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/helpers.d.ts +91 -9
- package/dist/utils/internal/error-handler/helpers.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/helpers.js +243 -39
- package/dist/utils/internal/error-handler/helpers.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +21 -4
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logValue.d.ts +33 -0
- package/dist/utils/internal/logValue.d.ts.map +1 -0
- package/dist/utils/internal/logValue.js +539 -0
- package/dist/utils/internal/logValue.js.map +1 -0
- package/dist/utils/internal/logger.d.ts +38 -9
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +228 -118
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/observabilityCap.d.ts +35 -0
- package/dist/utils/internal/observabilityCap.d.ts.map +1 -0
- package/dist/utils/internal/observabilityCap.js +43 -0
- package/dist/utils/internal/observabilityCap.js.map +1 -0
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +9 -11
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/internal/requestContext.d.ts +3 -3
- package/dist/utils/internal/requestContext.js +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +20 -10
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +112 -30
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/httpError.d.ts +8 -6
- package/dist/utils/network/httpError.d.ts.map +1 -1
- package/dist/utils/network/httpError.js +23 -7
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/network/retry.d.ts +25 -2
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +47 -5
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts +24 -28
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js +25 -84
- package/dist/utils/security/sanitization.js.map +1 -1
- package/dist/utils/security/sensitiveFields.d.ts +32 -4
- package/dist/utils/security/sensitiveFields.d.ts.map +1 -1
- package/dist/utils/security/sensitiveFields.js +85 -4
- package/dist/utils/security/sensitiveFields.js.map +1 -1
- package/dist/utils/telemetry/trace.d.ts +3 -1
- package/dist/utils/telemetry/trace.d.ts.map +1 -1
- package/dist/utils/telemetry/trace.js +6 -6
- package/dist/utils/telemetry/trace.js.map +1 -1
- package/framework-skills/api-auth/SKILL.md +3 -1
- package/framework-skills/api-canvas/SKILL.md +2 -2
- package/framework-skills/api-config/SKILL.md +2 -1
- package/framework-skills/api-context/SKILL.md +6 -6
- package/framework-skills/api-errors/SKILL.md +19 -17
- package/framework-skills/api-linter/SKILL.md +11 -10
- package/framework-skills/api-mirror/SKILL.md +3 -1
- package/framework-skills/api-telemetry/SKILL.md +14 -8
- package/framework-skills/api-utils/SKILL.md +6 -6
- package/framework-skills/api-utils/references/security.md +4 -2
- package/framework-skills/field-test/SKILL.md +2 -2
- package/framework-skills/git-wrapup/SKILL.md +3 -3
- package/framework-skills/tool-defs-analysis/SKILL.md +2 -2
- package/package.json +10 -9
- package/scripts/check-framework-antipatterns.ts +3 -2
- package/templates/.env.example +2 -0
- package/templates/tests/tools/echo.tool.test.ts +19 -1
|
@@ -41,11 +41,19 @@ interface FileSinkProbe {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function probeFileSinks(dir: string): Promise<FileSinkProbe>;
|
|
43
43
|
/**
|
|
44
|
-
* Pino `formatters.log` hook
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* Pino `formatters.log` hook, and the bindings of every exported OTel record:
|
|
45
|
+
* the log-data walk ({@link toLogValue}) over the whole record. pino
|
|
46
|
+
* therefore never receives an `Error` or a class instance — the method-bearing
|
|
47
|
+
* handles on the framework `Context` and `ctx.signal` included — whatever
|
|
48
|
+
* shape a caller gave its bindings. The walk is the only redaction: it is the
|
|
49
|
+
* one path caller data takes into a written line, since neither pino logger
|
|
50
|
+
* sets a `mixin`, binds a child, or carries anything in `base` but constants.
|
|
51
|
+
* Exempt from it are the root correlation fields ({@link CORRELATION_KEYS})
|
|
52
|
+
* that the record's context supplied, and a root key named after a field pino
|
|
53
|
+
* writes on the line itself ({@link LINE_FIELDS}) is written as `data_<name>` —
|
|
54
|
+
* prefixed again while that name is taken. `err` is such a field on a line
|
|
55
|
+
* carrying an error argument, which {@link Logger} moves the same way before
|
|
56
|
+
* attaching the error.
|
|
49
57
|
*
|
|
50
58
|
* @internal Exported only for unit testing. Not part of the public API.
|
|
51
59
|
*/
|
|
@@ -95,8 +103,25 @@ export declare function setOtelLogSink(sink: OtelLogSink | undefined): void;
|
|
|
95
103
|
* with one `warning` naming it; the process and the remaining sinks carry on.
|
|
96
104
|
* - Optional OTel log export: once `initializeOpenTelemetry` attaches a Logs API
|
|
97
105
|
* sink, every record that passes the level filter and rate limit is also emitted
|
|
98
|
-
* there,
|
|
99
|
-
*
|
|
106
|
+
* there, with the same fields as the pino output — except the error argument,
|
|
107
|
+
* which is exported as the `exception.*` attributes instead of an `err` field.
|
|
108
|
+
* - One walk over every record's data ({@link sanitizeLogBindings}), and the only
|
|
109
|
+
* redaction: each `Error`, under any key, written as `type`/`message`/`stack`
|
|
110
|
+
* (plus a string or `McpError` `code`, an `McpError`'s `data`, and `cause`/`errors`
|
|
111
|
+
* in the same shape, a stack shared with the parent error written once); objects
|
|
112
|
+
* kept through 15 levels below the record root and one 16 levels down written as
|
|
113
|
+
* `'[MaxDepth]'`; at most 400,000 reads — one per object and per other field or
|
|
114
|
+
* array element, ten per object at the depth bound — so data a getter, a Proxy, or a
|
|
115
|
+
* `toJSON()` builds on every read stops at `'[Truncated]'`; at most 16 MiB
|
|
116
|
+
* (16,777,216) characters of strings, field names, and primitives written, repeated
|
|
117
|
+
* or not, then `'[Truncated]'` and the walk stops, so a 10 MB string is written whole
|
|
118
|
+
* and a 20 MB one is not; repeated content — an object reached again through a shared
|
|
119
|
+
* reference, or a string or field name of 1,024 characters or more written again —
|
|
120
|
+
* also charged about the characters it writes against 1,000,000, and written as
|
|
121
|
+
* `'[Truncated]'` where that runs out; cycles written as `'[Circular]'`; a value
|
|
122
|
+
* whose read throws written as `'[Unreadable]'`; and every key the shared matcher
|
|
123
|
+
* (`isSensitiveKey`) matches redacted at every depth kept, except the correlation
|
|
124
|
+
* fields the record's context supplied, at its root.
|
|
100
125
|
* - Rate limiting per level + message to suppress log storms, configurable via
|
|
101
126
|
* `MCP_LOG_RATE_LIMIT_THRESHOLD` (0 disables) and `MCP_LOG_RATE_LIMIT_WINDOW_MS`.
|
|
102
127
|
* Suppressed counts are flushed at `warning` once a window has elapsed, on
|
|
@@ -282,6 +307,9 @@ export declare class Logger {
|
|
|
282
307
|
* For startup paths that end the process before {@link initialize} runs: the
|
|
283
308
|
* records describe what the failing boot was doing, so they are worth more on
|
|
284
309
|
* stderr than discarded. Never writes to stdout, which stdio transport owns.
|
|
310
|
+
* An error's message is read through the walk, so one whose read throws is
|
|
311
|
+
* written as `'[Unreadable]'`, one the walk writes no message for is left
|
|
312
|
+
* off, and neither replaces the error that ended the boot.
|
|
285
313
|
*
|
|
286
314
|
* @example
|
|
287
315
|
* ```ts
|
|
@@ -350,8 +378,9 @@ export declare class Logger {
|
|
|
350
378
|
* Logs an error-level message at `error` severity (RFC 5424 level 3).
|
|
351
379
|
*
|
|
352
380
|
* Use when an operation fails but the server can continue. The `errorOrContext`
|
|
353
|
-
* parameter accepts either an `Error` (
|
|
354
|
-
* `
|
|
381
|
+
* parameter accepts either an `Error` (written under `err` as its `type`, `message`,
|
|
382
|
+
* `stack`, and the other fields every logged Error carries — see {@link sanitizeLogBindings})
|
|
383
|
+
* or a `RequestContext` when no error object is available. Maps to pino `error` level.
|
|
355
384
|
*
|
|
356
385
|
* @param msg - Human-readable description of the failure.
|
|
357
386
|
* @param errorOrContext - The `Error` to serialize, or a `RequestContext` if no error object exists.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/utils/internal/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/utils/internal/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import pino from 'pino';
|
|
2
2
|
import { config } from '../../config/index.js';
|
|
3
|
+
import { toLogValue } from './logValue.js';
|
|
3
4
|
import { requestContextService, toCanonicalContext, } from './requestContext.js';
|
|
4
5
|
import { UNTHROTTLED_MESSAGES } from './telemetryMessages.js';
|
|
5
|
-
import { DEFAULT_SENSITIVE_FIELDS, toPinoRedactPaths } from '../security/sensitiveFields.js';
|
|
6
6
|
const mcpToPinoLevel = {
|
|
7
7
|
emerg: 'fatal',
|
|
8
8
|
alert: 'fatal',
|
|
@@ -100,13 +100,13 @@ export async function probeFileSinks(dir) {
|
|
|
100
100
|
function isServerless() {
|
|
101
101
|
return typeof process === 'undefined' || process.env.IS_SERVERLESS === 'true';
|
|
102
102
|
}
|
|
103
|
-
/** Pino redact paths for sensitive fields (top-level, one-deep, two-deep). */
|
|
104
|
-
const SENSITIVE_PINO_FIELDS = toPinoRedactPaths(DEFAULT_SENSITIVE_FIELDS);
|
|
105
103
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
104
|
+
* pino serializers for the process and `interactions.log` loggers. The
|
|
105
|
+
* log-data walk has already written every `Error` as plain data, so `err`
|
|
106
|
+
* passes through: pino's default would retype that object as `Object` and
|
|
107
|
+
* copy any other field it carries.
|
|
108
108
|
*/
|
|
109
|
-
const
|
|
109
|
+
const PINO_SERIALIZERS = { err: (value) => value };
|
|
110
110
|
/**
|
|
111
111
|
* Hard ceiling on distinct rate-limit keys retained between sweeps.
|
|
112
112
|
*
|
|
@@ -133,104 +133,192 @@ const FLUSH_DRAIN_TIMEOUT_MS = 2_000;
|
|
|
133
133
|
*/
|
|
134
134
|
const PRE_INIT_BUFFER_LIMIT = 250;
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* such as `AbortSignal`, `Map`, `Set`, `Promise`, storage handles, and any
|
|
143
|
-
* other object whose prototype exposes getters that enforce receiver checks
|
|
144
|
-
* — on Node 25, `for..in` traversal inside `@pinojs/redact` triggers those
|
|
145
|
-
* getters with a non-branded receiver and throws.
|
|
136
|
+
* The fields a record carries from its request context to correlate it. At the
|
|
137
|
+
* record's root, each one the context supplied is never redacted, so a name
|
|
138
|
+
* added through `setSensitiveFields` (`session_id`, `id`) cannot cut every
|
|
139
|
+
* record off from its request. A caller's key of the same name — in `extra`
|
|
140
|
+
* when the context lacks that field, anywhere in caller data, and every key of
|
|
141
|
+
* an `interactions.log` record, which has no context — is matched like any other.
|
|
146
142
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (Array.isArray(value)) {
|
|
164
|
-
return value.map((v) => sanitizeValue(v, depth + 1));
|
|
165
|
-
}
|
|
166
|
-
const proto = Object.getPrototypeOf(value);
|
|
167
|
-
if (proto !== Object.prototype && proto !== null) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const out = {};
|
|
171
|
-
for (const key of Object.keys(value)) {
|
|
172
|
-
const sanitized = sanitizeValue(value[key], depth + 1);
|
|
173
|
-
if (sanitized !== undefined)
|
|
174
|
-
out[key] = sanitized;
|
|
175
|
-
}
|
|
176
|
-
return out;
|
|
143
|
+
const CORRELATION_KEYS = new Set([
|
|
144
|
+
'operation',
|
|
145
|
+
'requestId',
|
|
146
|
+
'sessionId',
|
|
147
|
+
'spanId',
|
|
148
|
+
'tenantId',
|
|
149
|
+
'timestamp',
|
|
150
|
+
'traceId',
|
|
151
|
+
]);
|
|
152
|
+
/** The `base` the process logger writes on every line: constants only. */
|
|
153
|
+
function processLogBase() {
|
|
154
|
+
return {
|
|
155
|
+
env: config.environment,
|
|
156
|
+
version: config.mcpServerVersion,
|
|
157
|
+
pid: !isServerless() ? process.pid : undefined,
|
|
158
|
+
};
|
|
177
159
|
}
|
|
178
160
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* `
|
|
183
|
-
*
|
|
161
|
+
* The fields the logger's pino instances write on a line themselves: `level`
|
|
162
|
+
* and `time` before the record and `msg` after it, the process logger's
|
|
163
|
+
* `base` ({@link processLogBase}), and pino's default `base` — `pid` and
|
|
164
|
+
* `hostname` — on `interactions.log`. A record key with one of these names
|
|
165
|
+
* would be a second copy in the line: a transport routes a line by the last
|
|
166
|
+
* `level` it parses, so a caller's `level: 'high'` would drop the record from
|
|
167
|
+
* stderr and `combined.log`, and a parser keeps the last `version`, so a
|
|
168
|
+
* caller's would replace the server's.
|
|
169
|
+
*/
|
|
170
|
+
const LINE_FIELDS = new Set([
|
|
171
|
+
'level',
|
|
172
|
+
'time',
|
|
173
|
+
'msg',
|
|
174
|
+
...Object.keys(processLogBase()),
|
|
175
|
+
'pid',
|
|
176
|
+
'hostname',
|
|
177
|
+
]);
|
|
178
|
+
/**
|
|
179
|
+
* The context a record's bindings were projected from, under a key only this
|
|
180
|
+
* module holds. It rides the spread into pino's record and the OTLP bindings,
|
|
181
|
+
* where {@link sanitizeLogBindings} reads which correlation fields the context
|
|
182
|
+
* supplied; the walk never writes a symbol-keyed field.
|
|
183
|
+
*/
|
|
184
|
+
const RECORD_CONTEXT = Symbol('recordContext');
|
|
185
|
+
/** Moves `record[key]` to `data_<key>`, prefixed again while that name is taken. */
|
|
186
|
+
function moveLineField(record, key) {
|
|
187
|
+
let name = `data_${key}`;
|
|
188
|
+
while (Object.hasOwn(record, name))
|
|
189
|
+
name = `data_${name}`;
|
|
190
|
+
record[name] = record[key];
|
|
191
|
+
delete record[key];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Pino `formatters.log` hook, and the bindings of every exported OTel record:
|
|
195
|
+
* the log-data walk ({@link toLogValue}) over the whole record. pino
|
|
196
|
+
* therefore never receives an `Error` or a class instance — the method-bearing
|
|
197
|
+
* handles on the framework `Context` and `ctx.signal` included — whatever
|
|
198
|
+
* shape a caller gave its bindings. The walk is the only redaction: it is the
|
|
199
|
+
* one path caller data takes into a written line, since neither pino logger
|
|
200
|
+
* sets a `mixin`, binds a child, or carries anything in `base` but constants.
|
|
201
|
+
* Exempt from it are the root correlation fields ({@link CORRELATION_KEYS})
|
|
202
|
+
* that the record's context supplied, and a root key named after a field pino
|
|
203
|
+
* writes on the line itself ({@link LINE_FIELDS}) is written as `data_<name>` —
|
|
204
|
+
* prefixed again while that name is taken. `err` is such a field on a line
|
|
205
|
+
* carrying an error argument, which {@link Logger} moves the same way before
|
|
206
|
+
* attaching the error.
|
|
184
207
|
*
|
|
185
208
|
* @internal Exported only for unit testing. Not part of the public API.
|
|
186
209
|
*/
|
|
187
210
|
export function sanitizeLogBindings(obj) {
|
|
188
|
-
|
|
211
|
+
const supplied = obj[RECORD_CONTEXT];
|
|
212
|
+
const written = toLogValue(obj, {
|
|
213
|
+
exemptRootKey: supplied && ((key) => CORRELATION_KEYS.has(key) && Object.hasOwn(supplied, key)),
|
|
214
|
+
});
|
|
215
|
+
// The walk's copy is a fresh object, so renaming in place touches no caller data.
|
|
216
|
+
for (const key of LINE_FIELDS) {
|
|
217
|
+
if (Object.hasOwn(written, key))
|
|
218
|
+
moveLineField(written, key);
|
|
219
|
+
}
|
|
220
|
+
return written;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The walk's copy of `fields` a spread could not read (a getter, a revoked
|
|
224
|
+
* Proxy): `'[Unreadable]'` where a read failed, and under `key` when nothing
|
|
225
|
+
* could be read. A log call never throws on data it cannot read.
|
|
226
|
+
*/
|
|
227
|
+
function walkedFields(fields, key) {
|
|
228
|
+
const walked = toLogValue(fields);
|
|
229
|
+
return walked !== null && typeof walked === 'object' && !Array.isArray(walked)
|
|
230
|
+
? walked
|
|
231
|
+
: { [key]: walked };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The bindings a record writes for `context`. `extra` is flattened rather than
|
|
235
|
+
* nested so the line keeps the shape callers had when `RequestContext` was an
|
|
236
|
+
* open bag. The projection runs first: `logger.info(msg, ctx)` with a handler
|
|
237
|
+
* context is a documented call, and that object carries live request machinery
|
|
238
|
+
* and the user-entered content in `inputs.responses` — none of which belongs in
|
|
239
|
+
* a log line. The canonical fields are spread again after `extra`, so a caller's
|
|
240
|
+
* key reusing a canonical name (`requestId`, `traceId`, …) never replaces the
|
|
241
|
+
* context's value — the first spread only keeps them leading the line. The
|
|
242
|
+
* canonical fields also ride along under {@link RECORD_CONTEXT}, so the walk
|
|
243
|
+
* exempts only the correlation fields the context supplied. A context the
|
|
244
|
+
* projection cannot read is projected from the walk's copy, and one with
|
|
245
|
+
* nothing readable is written as `context: '[Unreadable]'`.
|
|
246
|
+
*/
|
|
247
|
+
function toBindings(context) {
|
|
248
|
+
let projected;
|
|
249
|
+
try {
|
|
250
|
+
projected = toCanonicalContext((context ?? {}));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
const walked = toLogValue(context);
|
|
254
|
+
if (walked === null || typeof walked !== 'object')
|
|
255
|
+
return { context: walked };
|
|
256
|
+
projected = toCanonicalContext(walked);
|
|
257
|
+
}
|
|
258
|
+
const { extra, ...canonical } = projected;
|
|
259
|
+
try {
|
|
260
|
+
return { ...canonical, ...extra, ...canonical, [RECORD_CONTEXT]: canonical };
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return {
|
|
264
|
+
...canonical,
|
|
265
|
+
...walkedFields(extra, 'extra'),
|
|
266
|
+
...canonical,
|
|
267
|
+
[RECORD_CONTEXT]: canonical,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
189
270
|
}
|
|
190
|
-
const SENSITIVE_FIELD_NAMES = new Set(DEFAULT_SENSITIVE_FIELDS);
|
|
191
271
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
272
|
+
* The walk's copy of a log call's error argument, as fields: `{}` when the walk
|
|
273
|
+
* writes nothing for it, as for a function given `Error.prototype`, which passes
|
|
274
|
+
* `instanceof Error` and is dropped like any other function.
|
|
275
|
+
*/
|
|
276
|
+
function errorFields(error) {
|
|
277
|
+
const walked = toLogValue(error);
|
|
278
|
+
return walked !== null && typeof walked === 'object' ? walked : {};
|
|
279
|
+
}
|
|
280
|
+
/** Whether `value` is an `Error`; a value `instanceof` cannot inspect (a revoked Proxy) is not. */
|
|
281
|
+
function isError(value) {
|
|
282
|
+
try {
|
|
283
|
+
return value instanceof Error;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Converts one walked binding into an attribute value. The walk has already
|
|
291
|
+
* written every `Error` as plain data and redacted every sensitive field, so
|
|
292
|
+
* all that is left is a primitive JSON has no form for, which becomes its string.
|
|
195
293
|
*/
|
|
196
294
|
function toExportValue(value) {
|
|
197
|
-
if (value instanceof Error)
|
|
198
|
-
return { message: value.message, stack: value.stack, type: value.name };
|
|
199
295
|
if (Array.isArray(value))
|
|
200
296
|
return value.map(toExportValue);
|
|
201
297
|
if (value !== null && typeof value === 'object') {
|
|
202
|
-
return
|
|
298
|
+
return Object.fromEntries(Object.entries(value).map(([key, field]) => [key, toExportValue(field)]));
|
|
203
299
|
}
|
|
204
300
|
if (typeof value === 'bigint' || typeof value === 'symbol')
|
|
205
301
|
return String(value);
|
|
206
302
|
return value;
|
|
207
303
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Builds exported attributes from sanitized bindings, replacing every field
|
|
210
|
-
* named in {@link DEFAULT_SENSITIVE_FIELDS} at any depth. OTel records bypass
|
|
211
|
-
* pino's `redact`, so without this a value kept out of every file and stream
|
|
212
|
-
* would leave the process in the clear.
|
|
213
|
-
*/
|
|
214
|
-
function toExportAttributes(fields) {
|
|
215
|
-
const out = {};
|
|
216
|
-
for (const [key, field] of Object.entries(fields)) {
|
|
217
|
-
out[key] = SENSITIVE_FIELD_NAMES.has(key) ? '[REDACTED]' : toExportValue(field);
|
|
218
|
-
}
|
|
219
|
-
return out;
|
|
220
|
-
}
|
|
221
304
|
/**
|
|
222
305
|
* Builds the OTel record for one framework log line: the message as the body,
|
|
223
|
-
* the
|
|
224
|
-
* `exception.*` semantic-convention attributes
|
|
225
|
-
*
|
|
306
|
+
* the bindings the process log writes as attributes, and an `Error` argument as
|
|
307
|
+
* the `exception.*` semantic-convention attributes, read through the walk, so a
|
|
308
|
+
* field whose read throws is exported as `'[Unreadable]'` and one the walk does
|
|
309
|
+
* not write is left out, rather than failing the log call. Trace context is
|
|
310
|
+
* left to the Logs API, which takes it from the active context at emit time.
|
|
226
311
|
*/
|
|
227
312
|
function toOtelLogRecord(level, msg, bindings, error) {
|
|
228
|
-
const attributes =
|
|
313
|
+
const attributes = toExportValue(sanitizeLogBindings(bindings));
|
|
229
314
|
if (error) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
315
|
+
const { type, message, stack } = errorFields(error);
|
|
316
|
+
if (type !== undefined)
|
|
317
|
+
attributes['exception.type'] = toExportValue(type);
|
|
318
|
+
if (message !== undefined)
|
|
319
|
+
attributes['exception.message'] = toExportValue(message);
|
|
320
|
+
if (stack)
|
|
321
|
+
attributes['exception.stacktrace'] = toExportValue(stack);
|
|
234
322
|
}
|
|
235
323
|
return {
|
|
236
324
|
attributes,
|
|
@@ -267,8 +355,25 @@ export function setOtelLogSink(sink) {
|
|
|
267
355
|
* with one `warning` naming it; the process and the remaining sinks carry on.
|
|
268
356
|
* - Optional OTel log export: once `initializeOpenTelemetry` attaches a Logs API
|
|
269
357
|
* sink, every record that passes the level filter and rate limit is also emitted
|
|
270
|
-
* there,
|
|
271
|
-
*
|
|
358
|
+
* there, with the same fields as the pino output — except the error argument,
|
|
359
|
+
* which is exported as the `exception.*` attributes instead of an `err` field.
|
|
360
|
+
* - One walk over every record's data ({@link sanitizeLogBindings}), and the only
|
|
361
|
+
* redaction: each `Error`, under any key, written as `type`/`message`/`stack`
|
|
362
|
+
* (plus a string or `McpError` `code`, an `McpError`'s `data`, and `cause`/`errors`
|
|
363
|
+
* in the same shape, a stack shared with the parent error written once); objects
|
|
364
|
+
* kept through 15 levels below the record root and one 16 levels down written as
|
|
365
|
+
* `'[MaxDepth]'`; at most 400,000 reads — one per object and per other field or
|
|
366
|
+
* array element, ten per object at the depth bound — so data a getter, a Proxy, or a
|
|
367
|
+
* `toJSON()` builds on every read stops at `'[Truncated]'`; at most 16 MiB
|
|
368
|
+
* (16,777,216) characters of strings, field names, and primitives written, repeated
|
|
369
|
+
* or not, then `'[Truncated]'` and the walk stops, so a 10 MB string is written whole
|
|
370
|
+
* and a 20 MB one is not; repeated content — an object reached again through a shared
|
|
371
|
+
* reference, or a string or field name of 1,024 characters or more written again —
|
|
372
|
+
* also charged about the characters it writes against 1,000,000, and written as
|
|
373
|
+
* `'[Truncated]'` where that runs out; cycles written as `'[Circular]'`; a value
|
|
374
|
+
* whose read throws written as `'[Unreadable]'`; and every key the shared matcher
|
|
375
|
+
* (`isSensitiveKey`) matches redacted at every depth kept, except the correlation
|
|
376
|
+
* fields the record's context supplied, at its root.
|
|
272
377
|
* - Rate limiting per level + message to suppress log storms, configurable via
|
|
273
378
|
* `MCP_LOG_RATE_LIMIT_THRESHOLD` (0 disables) and `MCP_LOG_RATE_LIMIT_WINDOW_MS`.
|
|
274
379
|
* Suppressed counts are flushed at `warning` once a window has elapsed, on
|
|
@@ -324,18 +429,11 @@ export class Logger {
|
|
|
324
429
|
const pinoLevel = mcpToPinoLevel[level] ?? 'info';
|
|
325
430
|
const pinoOptions = {
|
|
326
431
|
level: pinoLevel,
|
|
327
|
-
base:
|
|
328
|
-
env: config.environment,
|
|
329
|
-
version: config.mcpServerVersion,
|
|
330
|
-
pid: !isServerless() ? process.pid : undefined,
|
|
331
|
-
},
|
|
332
|
-
redact: {
|
|
333
|
-
paths: SENSITIVE_PINO_FIELDS,
|
|
334
|
-
censor: '[REDACTED]',
|
|
335
|
-
},
|
|
432
|
+
base: processLogBase(),
|
|
336
433
|
formatters: {
|
|
337
434
|
log: sanitizeLogBindings,
|
|
338
435
|
},
|
|
436
|
+
serializers: PINO_SERIALIZERS,
|
|
339
437
|
};
|
|
340
438
|
if (isServerless()) {
|
|
341
439
|
return pino(pinoOptions);
|
|
@@ -386,13 +484,10 @@ export class Logger {
|
|
|
386
484
|
if (!destination)
|
|
387
485
|
return;
|
|
388
486
|
return pino({
|
|
389
|
-
redact: {
|
|
390
|
-
paths: SENSITIVE_PINO_FIELDS,
|
|
391
|
-
censor: '[REDACTED]',
|
|
392
|
-
},
|
|
393
487
|
formatters: {
|
|
394
488
|
log: sanitizeLogBindings,
|
|
395
489
|
},
|
|
490
|
+
serializers: PINO_SERIALIZERS,
|
|
396
491
|
transport: {
|
|
397
492
|
target: 'pino/file',
|
|
398
493
|
options: { destination },
|
|
@@ -723,6 +818,9 @@ export class Logger {
|
|
|
723
818
|
* For startup paths that end the process before {@link initialize} runs: the
|
|
724
819
|
* records describe what the failing boot was doing, so they are worth more on
|
|
725
820
|
* stderr than discarded. Never writes to stdout, which stdio transport owns.
|
|
821
|
+
* An error's message is read through the walk, so one whose read throws is
|
|
822
|
+
* written as `'[Unreadable]'`, one the walk writes no message for is left
|
|
823
|
+
* off, and neither replaces the error that ended the boot.
|
|
726
824
|
*
|
|
727
825
|
* @example
|
|
728
826
|
* ```ts
|
|
@@ -735,7 +833,12 @@ export class Logger {
|
|
|
735
833
|
// Checked before the records are cleared: a runtime with no stderr keeps them.
|
|
736
834
|
if (typeof process === 'undefined' || typeof process.stderr?.write !== 'function')
|
|
737
835
|
return;
|
|
738
|
-
const lines = this.pendingRecords.map((
|
|
836
|
+
const lines = this.pendingRecords.map(({ error, level, msg }) => {
|
|
837
|
+
const message = error && errorFields(error).message;
|
|
838
|
+
return message === undefined
|
|
839
|
+
? `[pre-init ${level}] ${msg}`
|
|
840
|
+
: `[pre-init ${level}] ${msg} — ${message}`;
|
|
841
|
+
});
|
|
739
842
|
if (this.droppedPendingRecords > 0) {
|
|
740
843
|
lines.push(`[pre-init] ${this.droppedPendingRecords} further record(s) dropped — buffer holds ${PRE_INIT_BUFFER_LIMIT}.`);
|
|
741
844
|
}
|
|
@@ -751,26 +854,24 @@ export class Logger {
|
|
|
751
854
|
if (!this.isLevelEnabled(level) || this.isRateLimited(level, msg))
|
|
752
855
|
return;
|
|
753
856
|
const pinoLevel = mcpToPinoLevel[level] ?? 'info';
|
|
754
|
-
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const bindings = { ...canonical, ...extra, ...canonical };
|
|
764
|
-
// Pass the raw Error so pino's `err` serializer (default: `pino.stdSerializers.err`)
|
|
765
|
-
// runs *after* our `formatters.log` sanitizer. Pre-serializing here would produce
|
|
766
|
-
// an object whose prototype (`pinoErrProto`) trips the sanitizer's plain-object check.
|
|
767
|
-
this.pinoLogger[pinoLevel](error ? { ...bindings, err: error } : bindings, msg);
|
|
857
|
+
const bindings = toBindings(context);
|
|
858
|
+
// The error argument rides the `err` key; pino's `formatters.log` walk writes
|
|
859
|
+
// it like any other Error in the record, and the `err` serializer passes the result through.
|
|
860
|
+
// It leads the record, so the walk, which works in key order, writes it before caller data
|
|
861
|
+
// can spend the walk's bound. On that line `err` is the logger's own field, so a caller's
|
|
862
|
+
// `err` moves aside like any line field.
|
|
863
|
+
if (error && Object.hasOwn(bindings, 'err'))
|
|
864
|
+
moveLineField(bindings, 'err');
|
|
865
|
+
this.pinoLogger[pinoLevel](error ? { err: error, ...bindings } : bindings, msg);
|
|
768
866
|
otelLogSink?.emit(toOtelLogRecord(level, msg, bindings, error));
|
|
769
867
|
}
|
|
770
868
|
logWithError(level, msg, errorOrContext, context) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
869
|
+
if (isError(errorOrContext)) {
|
|
870
|
+
this.log(level, msg, context, errorOrContext);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
this.log(level, msg, errorOrContext);
|
|
874
|
+
}
|
|
774
875
|
}
|
|
775
876
|
/**
|
|
776
877
|
* Logs a diagnostic message at `debug` severity (RFC 5424 level 7).
|
|
@@ -839,8 +940,9 @@ export class Logger {
|
|
|
839
940
|
* Logs an error-level message at `error` severity (RFC 5424 level 3).
|
|
840
941
|
*
|
|
841
942
|
* Use when an operation fails but the server can continue. The `errorOrContext`
|
|
842
|
-
* parameter accepts either an `Error` (
|
|
843
|
-
* `
|
|
943
|
+
* parameter accepts either an `Error` (written under `err` as its `type`, `message`,
|
|
944
|
+
* `stack`, and the other fields every logged Error carries — see {@link sanitizeLogBindings})
|
|
945
|
+
* or a `RequestContext` when no error object is available. Maps to pino `error` level.
|
|
844
946
|
*
|
|
845
947
|
* @param msg - Human-readable description of the failure.
|
|
846
948
|
* @param errorOrContext - The `Error` to serialize, or a `RequestContext` if no error object exists.
|
|
@@ -937,12 +1039,20 @@ export class Logger {
|
|
|
937
1039
|
* ```
|
|
938
1040
|
*/
|
|
939
1041
|
logInteraction(interactionName, data) {
|
|
1042
|
+
let record;
|
|
1043
|
+
try {
|
|
1044
|
+
record = { interactionName, ...data };
|
|
1045
|
+
}
|
|
1046
|
+
catch {
|
|
1047
|
+
record = { interactionName, ...walkedFields(data, 'data') };
|
|
1048
|
+
}
|
|
940
1049
|
if (!this.interactionLogger) {
|
|
941
|
-
if (!isServerless() && !this.interactionSinkDropped)
|
|
942
|
-
this.warning('Interaction logger not available.',
|
|
1050
|
+
if (!isServerless() && !this.interactionSinkDropped) {
|
|
1051
|
+
this.warning('Interaction logger not available.', record.context);
|
|
1052
|
+
}
|
|
943
1053
|
return;
|
|
944
1054
|
}
|
|
945
|
-
this.interactionLogger.info(
|
|
1055
|
+
this.interactionLogger.info(record);
|
|
946
1056
|
}
|
|
947
1057
|
}
|
|
948
1058
|
/**
|