@ttctl/mcp 0.0.0 → 0.1.0-rc.1
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 +72 -9
- package/dist/auth.d.ts +40 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +69 -0
- package/dist/auth.js.map +1 -0
- package/dist/data-handling.d.ts +91 -0
- package/dist/data-handling.d.ts.map +1 -0
- package/dist/data-handling.js +129 -0
- package/dist/data-handling.js.map +1 -0
- package/dist/diagnostic.d.ts +262 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +362 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/errors.d.ts +54 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +48 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-switch-hook.d.ts +67 -0
- package/dist/kill-switch-hook.d.ts.map +1 -0
- package/dist/kill-switch-hook.js +61 -0
- package/dist/kill-switch-hook.js.map +1 -0
- package/dist/server.d.ts +100 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +157 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/_shared.d.ts +227 -0
- package/dist/tools/_shared.d.ts.map +1 -0
- package/dist/tools/_shared.js +238 -0
- package/dist/tools/_shared.js.map +1 -0
- package/dist/tools/applications.d.ts +27 -0
- package/dist/tools/applications.d.ts.map +1 -0
- package/dist/tools/applications.js +192 -0
- package/dist/tools/applications.js.map +1 -0
- package/dist/tools/availability.d.ts +33 -0
- package/dist/tools/availability.d.ts.map +1 -0
- package/dist/tools/availability.js +272 -0
- package/dist/tools/availability.js.map +1 -0
- package/dist/tools/contracts.d.ts +29 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +157 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/engagements.d.ts +36 -0
- package/dist/tools/engagements.d.ts.map +1 -0
- package/dist/tools/engagements.js +408 -0
- package/dist/tools/engagements.js.map +1 -0
- package/dist/tools/file-upload.d.ts +133 -0
- package/dist/tools/file-upload.d.ts.map +1 -0
- package/dist/tools/file-upload.js +247 -0
- package/dist/tools/file-upload.js.map +1 -0
- package/dist/tools/index.d.ts +28 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +131 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/jobs.d.ts +37 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +505 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/output-schemas.d.ts +115 -0
- package/dist/tools/output-schemas.d.ts.map +1 -0
- package/dist/tools/output-schemas.js +130 -0
- package/dist/tools/output-schemas.js.map +1 -0
- package/dist/tools/payments.d.ts +36 -0
- package/dist/tools/payments.d.ts.map +1 -0
- package/dist/tools/payments.js +373 -0
- package/dist/tools/payments.js.map +1 -0
- package/dist/tools/profile/certifications.d.ts +18 -0
- package/dist/tools/profile/certifications.d.ts.map +1 -0
- package/dist/tools/profile/certifications.js +193 -0
- package/dist/tools/profile/certifications.js.map +1 -0
- package/dist/tools/profile/education.d.ts +23 -0
- package/dist/tools/profile/education.d.ts.map +1 -0
- package/dist/tools/profile/education.js +196 -0
- package/dist/tools/profile/education.js.map +1 -0
- package/dist/tools/profile/employment.d.ts +23 -0
- package/dist/tools/profile/employment.d.ts.map +1 -0
- package/dist/tools/profile/employment.js +228 -0
- package/dist/tools/profile/employment.js.map +1 -0
- package/dist/tools/profile/industries.d.ts +22 -0
- package/dist/tools/profile/industries.d.ts.map +1 -0
- package/dist/tools/profile/industries.js +168 -0
- package/dist/tools/profile/industries.js.map +1 -0
- package/dist/tools/profile/portfolio.d.ts +22 -0
- package/dist/tools/profile/portfolio.d.ts.map +1 -0
- package/dist/tools/profile/portfolio.js +341 -0
- package/dist/tools/profile/portfolio.js.map +1 -0
- package/dist/tools/profile/resume.d.ts +16 -0
- package/dist/tools/profile/resume.d.ts.map +1 -0
- package/dist/tools/profile/resume.js +107 -0
- package/dist/tools/profile/resume.js.map +1 -0
- package/dist/tools/profile/shared.d.ts +85 -0
- package/dist/tools/profile/shared.d.ts.map +1 -0
- package/dist/tools/profile/shared.js +128 -0
- package/dist/tools/profile/shared.js.map +1 -0
- package/dist/tools/profile/visas.d.ts +15 -0
- package/dist/tools/profile/visas.d.ts.map +1 -0
- package/dist/tools/profile/visas.js +170 -0
- package/dist/tools/profile/visas.js.map +1 -0
- package/dist/tools/profile_basic_photo_show.d.ts +14 -0
- package/dist/tools/profile_basic_photo_show.d.ts.map +1 -0
- package/dist/tools/profile_basic_photo_show.js +59 -0
- package/dist/tools/profile_basic_photo_show.js.map +1 -0
- package/dist/tools/profile_basic_photo_upload.d.ts +24 -0
- package/dist/tools/profile_basic_photo_upload.d.ts.map +1 -0
- package/dist/tools/profile_basic_photo_upload.js +90 -0
- package/dist/tools/profile_basic_photo_upload.js.map +1 -0
- package/dist/tools/profile_basic_show.d.ts +19 -0
- package/dist/tools/profile_basic_show.d.ts.map +1 -0
- package/dist/tools/profile_basic_show.js +64 -0
- package/dist/tools/profile_basic_show.js.map +1 -0
- package/dist/tools/profile_basic_update.d.ts +37 -0
- package/dist/tools/profile_basic_update.d.ts.map +1 -0
- package/dist/tools/profile_basic_update.js +97 -0
- package/dist/tools/profile_basic_update.js.map +1 -0
- package/dist/tools/profile_external_advanced_wizard_show.d.ts +14 -0
- package/dist/tools/profile_external_advanced_wizard_show.d.ts.map +1 -0
- package/dist/tools/profile_external_advanced_wizard_show.js +56 -0
- package/dist/tools/profile_external_advanced_wizard_show.js.map +1 -0
- package/dist/tools/profile_external_custom_requirements_set.d.ts +13 -0
- package/dist/tools/profile_external_custom_requirements_set.d.ts.map +1 -0
- package/dist/tools/profile_external_custom_requirements_set.js +75 -0
- package/dist/tools/profile_external_custom_requirements_set.js.map +1 -0
- package/dist/tools/profile_external_custom_requirements_show.d.ts +14 -0
- package/dist/tools/profile_external_custom_requirements_show.d.ts.map +1 -0
- package/dist/tools/profile_external_custom_requirements_show.js +56 -0
- package/dist/tools/profile_external_custom_requirements_show.js.map +1 -0
- package/dist/tools/profile_external_readiness.d.ts +12 -0
- package/dist/tools/profile_external_readiness.d.ts.map +1 -0
- package/dist/tools/profile_external_readiness.js +54 -0
- package/dist/tools/profile_external_readiness.js.map +1 -0
- package/dist/tools/profile_external_recommendations.d.ts +15 -0
- package/dist/tools/profile_external_recommendations.d.ts.map +1 -0
- package/dist/tools/profile_external_recommendations.js +57 -0
- package/dist/tools/profile_external_recommendations.js.map +1 -0
- package/dist/tools/profile_external_update.d.ts +14 -0
- package/dist/tools/profile_external_update.d.ts.map +1 -0
- package/dist/tools/profile_external_update.js +79 -0
- package/dist/tools/profile_external_update.js.map +1 -0
- package/dist/tools/profile_reviews_approve_item.d.ts +17 -0
- package/dist/tools/profile_reviews_approve_item.d.ts.map +1 -0
- package/dist/tools/profile_reviews_approve_item.js +77 -0
- package/dist/tools/profile_reviews_approve_item.js.map +1 -0
- package/dist/tools/profile_reviews_approve_section.d.ts +15 -0
- package/dist/tools/profile_reviews_approve_section.d.ts.map +1 -0
- package/dist/tools/profile_reviews_approve_section.js +70 -0
- package/dist/tools/profile_reviews_approve_section.js.map +1 -0
- package/dist/tools/profile_reviews_list.d.ts +16 -0
- package/dist/tools/profile_reviews_list.d.ts.map +1 -0
- package/dist/tools/profile_reviews_list.js +58 -0
- package/dist/tools/profile_reviews_list.js.map +1 -0
- package/dist/tools/profile_reviews_submit_for_review.d.ts +14 -0
- package/dist/tools/profile_reviews_submit_for_review.d.ts.map +1 -0
- package/dist/tools/profile_reviews_submit_for_review.js +56 -0
- package/dist/tools/profile_reviews_submit_for_review.js.map +1 -0
- package/dist/tools/profile_skills_add.d.ts +4 -0
- package/dist/tools/profile_skills_add.d.ts.map +1 -0
- package/dist/tools/profile_skills_add.js +52 -0
- package/dist/tools/profile_skills_add.js.map +1 -0
- package/dist/tools/profile_skills_autocomplete.d.ts +4 -0
- package/dist/tools/profile_skills_autocomplete.d.ts.map +1 -0
- package/dist/tools/profile_skills_autocomplete.js +78 -0
- package/dist/tools/profile_skills_autocomplete.js.map +1 -0
- package/dist/tools/profile_skills_list.d.ts +16 -0
- package/dist/tools/profile_skills_list.d.ts.map +1 -0
- package/dist/tools/profile_skills_list.js +65 -0
- package/dist/tools/profile_skills_list.js.map +1 -0
- package/dist/tools/profile_skills_readiness.d.ts +4 -0
- package/dist/tools/profile_skills_readiness.d.ts.map +1 -0
- package/dist/tools/profile_skills_readiness.js +53 -0
- package/dist/tools/profile_skills_readiness.js.map +1 -0
- package/dist/tools/profile_skills_remove.d.ts +4 -0
- package/dist/tools/profile_skills_remove.d.ts.map +1 -0
- package/dist/tools/profile_skills_remove.js +53 -0
- package/dist/tools/profile_skills_remove.js.map +1 -0
- package/dist/tools/profile_skills_show.d.ts +4 -0
- package/dist/tools/profile_skills_show.d.ts.map +1 -0
- package/dist/tools/profile_skills_show.js +51 -0
- package/dist/tools/profile_skills_show.js.map +1 -0
- package/dist/tools/profile_skills_update.d.ts +11 -0
- package/dist/tools/profile_skills_update.d.ts.map +1 -0
- package/dist/tools/profile_skills_update.js +97 -0
- package/dist/tools/profile_skills_update.js.map +1 -0
- package/dist/tools/timesheet.d.ts +29 -0
- package/dist/tools/timesheet.d.ts.map +1 -0
- package/dist/tools/timesheet.js +257 -0
- package/dist/tools/timesheet.js.map +1 -0
- package/package.json +33 -13
- package/index.js +0 -7
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status discriminator for {@link McpToolInvokeEndRecord}. Three values
|
|
3
|
+
* cover the per-call outcome lattice:
|
|
4
|
+
*
|
|
5
|
+
* - `"ok"` — tool callback resolved and returned a success-shaped
|
|
6
|
+
* `ToolSuccessResponse` (no `isError: true`).
|
|
7
|
+
* - `"error"` — tool callback resolved with a `ToolErrorResponse`
|
|
8
|
+
* (typed domain failure, e.g. `(UNAUTHENTICATED): No auth token
|
|
9
|
+
* found`). The MCP wire protocol carries this back to the client
|
|
10
|
+
* verbatim; it is NOT an uncaught exception.
|
|
11
|
+
* - `"throw"` — tool callback threw an exception that bubbled out of
|
|
12
|
+
* the wrapper. The wrapper re-throws after emission so the MCP
|
|
13
|
+
* SDK's default error path runs.
|
|
14
|
+
*
|
|
15
|
+
* Separating `"error"` from `"throw"` lets operators triage typed
|
|
16
|
+
* domain failures (expected, recoverable) from uncaught exceptions
|
|
17
|
+
* (unexpected, often a bug).
|
|
18
|
+
*/
|
|
19
|
+
export type McpToolInvokeStatus = "ok" | "error" | "throw";
|
|
20
|
+
/**
|
|
21
|
+
* Common base for every MCP-side diagnostic record. Mirrors the base
|
|
22
|
+
* fields on `configWriter.ts`'s `DebugRecordBase` so a single
|
|
23
|
+
* `jq 'select(.event == ...)'` filter works across both taxonomies.
|
|
24
|
+
*
|
|
25
|
+
* - `ts` — wall-clock ISO-8601 timestamp; useful for cross-process
|
|
26
|
+
* correlation when an MCP session and a CLI invocation interleave.
|
|
27
|
+
* - `event` — discriminator naming the variant.
|
|
28
|
+
*/
|
|
29
|
+
interface McpDebugRecordBase {
|
|
30
|
+
ts: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `mcp_tool_invoke_start` — emitted BEFORE the tool callback runs.
|
|
34
|
+
* Pairs 1:1 with a downstream `mcp_tool_invoke_end` (`{tool, ts}` join key,
|
|
35
|
+
* plus `duration_ms` on the end record).
|
|
36
|
+
*
|
|
37
|
+
* `args_redacted` is the redacted form of the tool input. The redaction
|
|
38
|
+
* runs through `redactBody` from `@ttctl/core` so the canonical bearer
|
|
39
|
+
* pattern (`user_<24hex>_<20alnum>`), cookie strings, and secret-named
|
|
40
|
+
* fields (`password`, `token`, etc. per `SECRET_BODY_FIELD_NAMES`) are
|
|
41
|
+
* replaced with `***REDACTED***` BEFORE the record is constructed. The
|
|
42
|
+
* resulting object is safe to serialize.
|
|
43
|
+
*/
|
|
44
|
+
export interface McpToolInvokeStartRecord extends McpDebugRecordBase {
|
|
45
|
+
event: "mcp_tool_invoke_start";
|
|
46
|
+
tool: string;
|
|
47
|
+
args_redacted: unknown;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `mcp_tool_invoke_end` — emitted AFTER the tool callback completes (or
|
|
51
|
+
* throws). `duration_ms` is `performance.now()`-derived monotonic elapsed
|
|
52
|
+
* milliseconds rounded to integer, so the value stays accurate across
|
|
53
|
+
* wall-clock adjustments during the call.
|
|
54
|
+
*
|
|
55
|
+
* The `status` field discriminates the three outcomes (see
|
|
56
|
+
* {@link McpToolInvokeStatus}).
|
|
57
|
+
*/
|
|
58
|
+
export interface McpToolInvokeEndRecord extends McpDebugRecordBase {
|
|
59
|
+
event: "mcp_tool_invoke_end";
|
|
60
|
+
tool: string;
|
|
61
|
+
duration_ms: number;
|
|
62
|
+
status: McpToolInvokeStatus;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `mcp_auth_resolve` — emitted on every auth-resolver invocation (every
|
|
66
|
+
* tool call hits the resolver). Useful for cache-miss diagnostics in
|
|
67
|
+
* long-running MCP sessions: a token rotation via a sibling
|
|
68
|
+
* `ttctl auth signin` shows up as `token_fresh: true` on the FIRST
|
|
69
|
+
* post-rotation tool call.
|
|
70
|
+
*
|
|
71
|
+
* Fields:
|
|
72
|
+
* - `mtime_ms` — mtime of the captured config path at resolve time,
|
|
73
|
+
* or `null` if the stat failed (file removed, permissions changed
|
|
74
|
+
* mid-session). Stat overhead is bounded — POSIX stat is microseconds
|
|
75
|
+
* and only fires when emission is active.
|
|
76
|
+
* - `token_fresh` — `true` when this resolve produced a token AND the
|
|
77
|
+
* mtime differs from the prior resolve's mtime, `false` otherwise.
|
|
78
|
+
* The "first resolve" case (no prior mtime) reports `true` if a
|
|
79
|
+
* token was found, `false` if `auth.token` was undefined.
|
|
80
|
+
* - `outcome` — discriminates the three resolve outcomes: `"ok"` when
|
|
81
|
+
* a token was returned, `"unauthenticated"` when `auth.token` was
|
|
82
|
+
* undefined, `"config_error"` when the resolver rejected with a
|
|
83
|
+
* `ConfigError` (NO_CREDS, PARSE, VALIDATION, PERMISSION, LOCKED).
|
|
84
|
+
*
|
|
85
|
+
* The bearer value itself is NEVER in the record — only the boolean
|
|
86
|
+
* presence signal.
|
|
87
|
+
*/
|
|
88
|
+
export interface McpAuthResolveRecord extends McpDebugRecordBase {
|
|
89
|
+
event: "mcp_auth_resolve";
|
|
90
|
+
mtime_ms: number | null;
|
|
91
|
+
token_fresh: boolean;
|
|
92
|
+
outcome: "ok" | "unauthenticated" | "config_error";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `mcp_transport_error` — emitted when a transport-layer error
|
|
96
|
+
* (`Cf403Error`, `Cf403PersistentError`, `SchedulerBearerExpired`,
|
|
97
|
+
* other named transport throws) bubbles out of a tool callback.
|
|
98
|
+
* Separates transport-cause failures from domain-cause failures so
|
|
99
|
+
* Cloudflare regressions show up as a single grep target.
|
|
100
|
+
*
|
|
101
|
+
* Fields:
|
|
102
|
+
* - `tool` — name of the tool whose handler observed the error.
|
|
103
|
+
* - `surface` — `"talent-profile"` / `"mobile-gateway"` / `"scheduler"`
|
|
104
|
+
* / `"unknown"`. Extracted from the error class where possible
|
|
105
|
+
* (`Cf403Error` carries `surface` verbatim); otherwise `"unknown"`.
|
|
106
|
+
* - `error_class` — `err.name` (`"Cf403Error"`,
|
|
107
|
+
* `"Cf403PersistentError"`, …). Stable across messages.
|
|
108
|
+
* - `status` — HTTP status code when the error carries one, else
|
|
109
|
+
* `null`. `Cf403Error`/`Cf403PersistentError` -> 403,
|
|
110
|
+
* `SchedulerBearerExpired` -> 401.
|
|
111
|
+
*/
|
|
112
|
+
export interface McpTransportErrorRecord extends McpDebugRecordBase {
|
|
113
|
+
event: "mcp_transport_error";
|
|
114
|
+
tool: string;
|
|
115
|
+
surface: "talent-profile" | "mobile-gateway" | "scheduler" | "unknown";
|
|
116
|
+
error_class: string;
|
|
117
|
+
status: number | null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Discriminated union of every MCP-side diagnostic record. The type
|
|
121
|
+
* system is the FIRST line of bearer-absence enforcement: no variant
|
|
122
|
+
* here admits a bearer-shaped field. The runtime substring-assertion in
|
|
123
|
+
* `__tests__/diagnostic.test.ts` is the SECOND line — catches any
|
|
124
|
+
* accidental bearer leakage via `args_redacted` (the only slot where
|
|
125
|
+
* client-supplied data flows in).
|
|
126
|
+
*/
|
|
127
|
+
export type McpDebugRecord = McpToolInvokeStartRecord | McpToolInvokeEndRecord | McpAuthResolveRecord | McpTransportErrorRecord;
|
|
128
|
+
/**
|
|
129
|
+
* Logger signature: receives a fully-constructed record and emits it
|
|
130
|
+
* somewhere observable (stderr by default; an in-memory array in tests).
|
|
131
|
+
*
|
|
132
|
+
* The logger MUST NOT throw — diagnostic emission is a fire-and-forget
|
|
133
|
+
* side channel and an exception here would unwind the tool call. The
|
|
134
|
+
* default emitter catches and swallows any `process.stderr.write` error
|
|
135
|
+
* (rare on stdio; pipe-broken EPIPE during teardown is the only realistic
|
|
136
|
+
* case). Custom loggers should follow the same posture.
|
|
137
|
+
*/
|
|
138
|
+
export type McpDiagnosticLogger = (record: McpDebugRecord) => void;
|
|
139
|
+
/**
|
|
140
|
+
* Replace the active MCP diagnostic logger. Wired in production by
|
|
141
|
+
* `runMcpStdio()` at server startup; called by tests in `beforeEach`
|
|
142
|
+
* to inject a capturing logger.
|
|
143
|
+
*
|
|
144
|
+
* Production callers pass `defaultLogger` (re-exported via the index)
|
|
145
|
+
* or any custom logger conforming to {@link McpDiagnosticLogger}.
|
|
146
|
+
*/
|
|
147
|
+
export declare function setMcpDiagnosticLogger(logger: McpDiagnosticLogger): void;
|
|
148
|
+
/**
|
|
149
|
+
* Read the currently-installed logger. Tests use this for round-trip
|
|
150
|
+
* verification ("set, then read, then call"). Production code reads via
|
|
151
|
+
* {@link emitMcpDebug} instead.
|
|
152
|
+
*/
|
|
153
|
+
export declare function getMcpDiagnosticLogger(): McpDiagnosticLogger;
|
|
154
|
+
/**
|
|
155
|
+
* Restore the default env-gated stderr logger AND clear the mtime
|
|
156
|
+
* tracker. Tests MUST call this in `afterEach` to avoid state bleeding
|
|
157
|
+
* across cases — both the logger override and the mtime baseline are
|
|
158
|
+
* module-scoped state.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resetMcpDiagnosticLogger(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Emit a structured MCP debug record via the current logger. Lazy
|
|
163
|
+
* construction via the `makeRecord` thunk keeps the disabled path
|
|
164
|
+
* zero-allocation when `currentLogger === defaultLogger` and
|
|
165
|
+
* `DEBUG_ENABLED === false`: V8 inlines `defaultLogger` to a no-op and
|
|
166
|
+
* the thunk body is dead code.
|
|
167
|
+
*
|
|
168
|
+
* When a custom logger is installed (test injection), the thunk runs
|
|
169
|
+
* unconditionally — the logger sees every event regardless of env state.
|
|
170
|
+
* This is intentional: tests assert on emission shape without needing
|
|
171
|
+
* to manipulate `process.env`.
|
|
172
|
+
*/
|
|
173
|
+
export declare function emitMcpDebug(makeRecord: () => McpDebugRecord): void;
|
|
174
|
+
/**
|
|
175
|
+
* Emit a `mcp_auth_resolve` record summarizing an auth-resolver
|
|
176
|
+
* invocation. Called by the three resolver factories
|
|
177
|
+
* (`createToolAuthResolver`, `createTokenLoader`, `createTokenResolver`)
|
|
178
|
+
* AFTER each per-call `resolveConfig` returns (or throws). The bearer
|
|
179
|
+
* itself is NEVER passed in — only `hasToken: boolean`.
|
|
180
|
+
*
|
|
181
|
+
* `mtime_ms` is captured via a synchronous `statSync` on the captured
|
|
182
|
+
* config path. Sync stat is microseconds and only fires when emission
|
|
183
|
+
* is active (the env-gate / injected-logger fast-path skips the stat
|
|
184
|
+
* entirely on the disabled path). On stat failure (file removed
|
|
185
|
+
* mid-session, permissions changed), `mtime_ms` is `null` and
|
|
186
|
+
* `token_fresh` is `false` (cannot prove freshness without a baseline).
|
|
187
|
+
*/
|
|
188
|
+
export declare function emitMcpAuthResolve(configPath: string, outcome: McpAuthResolveRecord["outcome"], hasToken: boolean): void;
|
|
189
|
+
/**
|
|
190
|
+
* Helper: redact tool args for {@link McpToolInvokeStartRecord}. Two-pass
|
|
191
|
+
* defense:
|
|
192
|
+
*
|
|
193
|
+
* 1. `redactBody` from `@ttctl/core` replaces FIELD values whose key
|
|
194
|
+
* matches `SECRET_BODY_FIELD_NAMES` (`password`, `token`, etc.).
|
|
195
|
+
* 2. {@link scrubBearerPatternInStrings} walks the result and replaces
|
|
196
|
+
* any STRING value that matches the canonical bearer pattern
|
|
197
|
+
* (`user_<24hex>_<20alnum>`). This catches the case where an LLM
|
|
198
|
+
* client pastes a bearer-shaped value into a free-text arg like
|
|
199
|
+
* `{ note: "user_abc..." }` that the field-name pass would miss.
|
|
200
|
+
*
|
|
201
|
+
* The MCP tool surface accepts arbitrary client-supplied JSON, so the
|
|
202
|
+
* second pass is the load-bearing defense for bearer-absence in
|
|
203
|
+
* `args_redacted`. The transport-side `redactBody` doesn't need it
|
|
204
|
+
* because GraphQL variables are typed and the bearer never appears in
|
|
205
|
+
* any documented variable slot — MCP args have no such guarantee.
|
|
206
|
+
*/
|
|
207
|
+
export declare function redactToolArgs(args: unknown): unknown;
|
|
208
|
+
/**
|
|
209
|
+
* Extract the transport `surface` from an error-shaped value. The
|
|
210
|
+
* `Cf403Error` / `Cf403PersistentError` classes carry `surface` verbatim;
|
|
211
|
+
* `SchedulerBearerExpired` implies `"scheduler"`. Anything else returns
|
|
212
|
+
* `"unknown"` — the operator triages from `error_class` directly.
|
|
213
|
+
*
|
|
214
|
+
* Exported for tests; production code routes through
|
|
215
|
+
* {@link emitMcpTransportError}.
|
|
216
|
+
*/
|
|
217
|
+
export declare function extractTransportSurface(err: unknown): McpTransportErrorRecord["surface"];
|
|
218
|
+
/**
|
|
219
|
+
* Extract HTTP status from a transport error. `Cf403Error` /
|
|
220
|
+
* `Cf403PersistentError` are 403 by construction; `SchedulerBearerExpired`
|
|
221
|
+
* is 401. Errors carrying a numeric `status` field return that value.
|
|
222
|
+
* Anything else returns `null`.
|
|
223
|
+
*/
|
|
224
|
+
export declare function extractTransportStatus(err: unknown): number | null;
|
|
225
|
+
/**
|
|
226
|
+
* Recognize transport-class errors so the wrapper can emit
|
|
227
|
+
* {@link McpTransportErrorRecord} only for transport-cause failures
|
|
228
|
+
* (Cloudflare 403, scheduler bearer expired, etc.) and skip domain-cause
|
|
229
|
+
* throws (which the MCP error contract surfaces via `ToolErrorResponse`).
|
|
230
|
+
*/
|
|
231
|
+
export declare function isTransportError(err: unknown): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Helper for wrapping a tool callback with the start/end/transport-error
|
|
234
|
+
* emission contract. The wrapped handler:
|
|
235
|
+
*
|
|
236
|
+
* 1. Captures a monotonic start time.
|
|
237
|
+
* 2. Emits `mcp_tool_invoke_start` with `redactToolArgs(input)`.
|
|
238
|
+
* 3. Runs the original handler with all forwarded arguments.
|
|
239
|
+
* 4. On success: emits `mcp_tool_invoke_end` with status `"ok"` /
|
|
240
|
+
* `"error"` derived from the response shape, plus `duration_ms`.
|
|
241
|
+
* 5. On throw: emits `mcp_tool_invoke_end` with status `"throw"`,
|
|
242
|
+
* then `mcp_transport_error` if the error class is transport-typed,
|
|
243
|
+
* then re-throws.
|
|
244
|
+
*
|
|
245
|
+
* The wrapper is a closure over the tool name so the same factory
|
|
246
|
+
* produces a per-tool wrapper that names itself correctly in every
|
|
247
|
+
* emission.
|
|
248
|
+
*
|
|
249
|
+
* The MCP SDK's `ToolCallback<Args>` accepts a two-argument callback
|
|
250
|
+
* `(input, extra)` (where `extra` is `RequestHandlerExtra` carrying
|
|
251
|
+
* the active session / abort signal). Some tool callbacks accept zero
|
|
252
|
+
* arguments (no inputSchema) — the SDK calls them with `(extra)` only.
|
|
253
|
+
* The wrapper forwards `...rest` so both shapes work without per-shape
|
|
254
|
+
* branching at the call site.
|
|
255
|
+
*/
|
|
256
|
+
export declare function wrapToolHandler<THandler extends (...args: any[]) => Promise<{
|
|
257
|
+
isError?: boolean;
|
|
258
|
+
}> | {
|
|
259
|
+
isError?: boolean;
|
|
260
|
+
}>(toolName: string, handler: THandler): THandler;
|
|
261
|
+
export {};
|
|
262
|
+
//# sourceMappingURL=diagnostic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic.d.ts","sourceRoot":"","sources":["../src/diagnostic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
/**
|
|
4
|
+
* MCP-side structured-event diagnostic taxonomy (issue #224). Mirrors the
|
|
5
|
+
* config-write debug taxonomy in `@ttctl/core/configWriter.ts`
|
|
6
|
+
* (`TTCTL_DEBUG_CONFIG=1`) but scoped to the MCP entry point: tool
|
|
7
|
+
* invocations, auth-resolver re-reads, and transport errors observed
|
|
8
|
+
* inside the long-lived MCP process.
|
|
9
|
+
*
|
|
10
|
+
* Two emission paths:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Default env-gated stderr emitter.** Reads `TTCTL_DEBUG_MCP=1`
|
|
13
|
+
* ONCE at module load so the disabled path constant-folds to a single
|
|
14
|
+
* comparison in V8 (NFR: zero-cost-when-disabled). Writes one
|
|
15
|
+
* JSON-encoded record per line to `process.stderr` — the stdio
|
|
16
|
+
* data channel (`process.stdout`, owned by the MCP protocol) is
|
|
17
|
+
* never touched.
|
|
18
|
+
* 2. **Injected logger.** Tests (and any future SSE/HTTP transport
|
|
19
|
+
* wrapper) call {@link setMcpDiagnosticLogger} to replace the
|
|
20
|
+
* default emitter with a fake that captures records into an array.
|
|
21
|
+
* The injection point in production code is
|
|
22
|
+
* {@link runMcpStdio} — wiring the logger ONCE at the entry point
|
|
23
|
+
* so per-tool callsites stay free of conditional logger plumbing.
|
|
24
|
+
*
|
|
25
|
+
* Bearer-absence is a load-bearing invariant: no record variant in the
|
|
26
|
+
* {@link McpDebugRecord} discriminated union carries a bearer-shaped
|
|
27
|
+
* field, and the runtime `redactBody` pass on `args_redacted` covers
|
|
28
|
+
* the one slot that could plausibly admit one (tool args contributed by
|
|
29
|
+
* the LLM client). The test suite asserts substring-absence across every
|
|
30
|
+
* emission path; the type system enforces it at the call-site.
|
|
31
|
+
*/
|
|
32
|
+
import { statSync } from "node:fs";
|
|
33
|
+
import { performance } from "node:perf_hooks";
|
|
34
|
+
import { BEARER_PATTERN_SOURCE, REDACTED, redactBody } from "@ttctl/core";
|
|
35
|
+
/**
|
|
36
|
+
* Module-load capture of `TTCTL_DEBUG_MCP=1`. Read ONCE so the disabled
|
|
37
|
+
* path constant-folds to a single `if (DEBUG_ENABLED)` comparison in V8 —
|
|
38
|
+
* the env-unset path pays zero runtime overhead per-event.
|
|
39
|
+
*
|
|
40
|
+
* Tests that exercise both env-set and env-unset paths re-import the
|
|
41
|
+
* module via dynamic `import()` after mutating `process.env`. The
|
|
42
|
+
* injection path via {@link setMcpDiagnosticLogger} sidesteps the env
|
|
43
|
+
* gate entirely (the injected logger is always called) and is the
|
|
44
|
+
* preferred pattern for new tests.
|
|
45
|
+
*/
|
|
46
|
+
const DEBUG_ENABLED = process.env["TTCTL_DEBUG_MCP"] === "1";
|
|
47
|
+
/**
|
|
48
|
+
* Default logger: env-gated stderr emitter. When `TTCTL_DEBUG_MCP=1`,
|
|
49
|
+
* writes one JSON-encoded record per line to `process.stderr`. When
|
|
50
|
+
* unset (or any other value), no-op.
|
|
51
|
+
*
|
|
52
|
+
* The env gate is captured at module load (see {@link DEBUG_ENABLED})
|
|
53
|
+
* so a JIT'd V8 inlines this function down to either:
|
|
54
|
+
* - `() => undefined` (env unset; constant-folded dead branch), or
|
|
55
|
+
* - `(record) => process.stderr.write(JSON.stringify(record) + '\n')`
|
|
56
|
+
* (env set).
|
|
57
|
+
*
|
|
58
|
+
* Stderr is chosen so the stdio data channel (stdout, owned by the MCP
|
|
59
|
+
* JSON-RPC protocol) stays uncontaminated.
|
|
60
|
+
*/
|
|
61
|
+
const defaultLogger = (record) => {
|
|
62
|
+
if (!DEBUG_ENABLED)
|
|
63
|
+
return;
|
|
64
|
+
try {
|
|
65
|
+
process.stderr.write(JSON.stringify(record) + "\n");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// EPIPE during teardown; nothing meaningful to do. Diagnostic
|
|
69
|
+
// emission must never unwind a tool call.
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Module-scoped logger holder. Mirrors the `currentLevel` pattern in
|
|
74
|
+
* `@ttctl/core/lib/diagnostic-log.ts`: one global, set at entry-point
|
|
75
|
+
* wiring time, read on every emit. Module isolation keeps per-tool
|
|
76
|
+
* callsites focused on their domain (no logger parameter threading)
|
|
77
|
+
* while exposing a single test-injection point.
|
|
78
|
+
*/
|
|
79
|
+
let currentLogger = defaultLogger;
|
|
80
|
+
/**
|
|
81
|
+
* Replace the active MCP diagnostic logger. Wired in production by
|
|
82
|
+
* `runMcpStdio()` at server startup; called by tests in `beforeEach`
|
|
83
|
+
* to inject a capturing logger.
|
|
84
|
+
*
|
|
85
|
+
* Production callers pass `defaultLogger` (re-exported via the index)
|
|
86
|
+
* or any custom logger conforming to {@link McpDiagnosticLogger}.
|
|
87
|
+
*/
|
|
88
|
+
export function setMcpDiagnosticLogger(logger) {
|
|
89
|
+
currentLogger = logger;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read the currently-installed logger. Tests use this for round-trip
|
|
93
|
+
* verification ("set, then read, then call"). Production code reads via
|
|
94
|
+
* {@link emitMcpDebug} instead.
|
|
95
|
+
*/
|
|
96
|
+
export function getMcpDiagnosticLogger() {
|
|
97
|
+
return currentLogger;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Restore the default env-gated stderr logger AND clear the mtime
|
|
101
|
+
* tracker. Tests MUST call this in `afterEach` to avoid state bleeding
|
|
102
|
+
* across cases — both the logger override and the mtime baseline are
|
|
103
|
+
* module-scoped state.
|
|
104
|
+
*/
|
|
105
|
+
export function resetMcpDiagnosticLogger() {
|
|
106
|
+
currentLogger = defaultLogger;
|
|
107
|
+
lastSeenMtime.clear();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Emit a structured MCP debug record via the current logger. Lazy
|
|
111
|
+
* construction via the `makeRecord` thunk keeps the disabled path
|
|
112
|
+
* zero-allocation when `currentLogger === defaultLogger` and
|
|
113
|
+
* `DEBUG_ENABLED === false`: V8 inlines `defaultLogger` to a no-op and
|
|
114
|
+
* the thunk body is dead code.
|
|
115
|
+
*
|
|
116
|
+
* When a custom logger is installed (test injection), the thunk runs
|
|
117
|
+
* unconditionally — the logger sees every event regardless of env state.
|
|
118
|
+
* This is intentional: tests assert on emission shape without needing
|
|
119
|
+
* to manipulate `process.env`.
|
|
120
|
+
*/
|
|
121
|
+
export function emitMcpDebug(makeRecord) {
|
|
122
|
+
// Fast-path: default logger + env unset → skip the thunk entirely.
|
|
123
|
+
// Any custom logger (test injection) bypasses the gate.
|
|
124
|
+
if (currentLogger === defaultLogger && !DEBUG_ENABLED)
|
|
125
|
+
return;
|
|
126
|
+
currentLogger(makeRecord());
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Module-scoped tracker of the last-observed mtime per config path.
|
|
130
|
+
* Lets {@link emitMcpAuthResolve} report `token_fresh: true` on the
|
|
131
|
+
* FIRST tool call after a sibling `ttctl auth signin` rotates the
|
|
132
|
+
* bearer (mtime moves; fresh resolve picks up the new value).
|
|
133
|
+
*
|
|
134
|
+
* Keyed by absolute config path so multi-session callers (theoretical
|
|
135
|
+
* future SSE transport hosting two clients) don't collide. Production
|
|
136
|
+
* use today is single-path: one MCP session = one captured path.
|
|
137
|
+
*
|
|
138
|
+
* Reset by {@link resetMcpDiagnosticLogger} so tests get a clean slate
|
|
139
|
+
* between cases.
|
|
140
|
+
*/
|
|
141
|
+
const lastSeenMtime = new Map();
|
|
142
|
+
/**
|
|
143
|
+
* Emit a `mcp_auth_resolve` record summarizing an auth-resolver
|
|
144
|
+
* invocation. Called by the three resolver factories
|
|
145
|
+
* (`createToolAuthResolver`, `createTokenLoader`, `createTokenResolver`)
|
|
146
|
+
* AFTER each per-call `resolveConfig` returns (or throws). The bearer
|
|
147
|
+
* itself is NEVER passed in — only `hasToken: boolean`.
|
|
148
|
+
*
|
|
149
|
+
* `mtime_ms` is captured via a synchronous `statSync` on the captured
|
|
150
|
+
* config path. Sync stat is microseconds and only fires when emission
|
|
151
|
+
* is active (the env-gate / injected-logger fast-path skips the stat
|
|
152
|
+
* entirely on the disabled path). On stat failure (file removed
|
|
153
|
+
* mid-session, permissions changed), `mtime_ms` is `null` and
|
|
154
|
+
* `token_fresh` is `false` (cannot prove freshness without a baseline).
|
|
155
|
+
*/
|
|
156
|
+
export function emitMcpAuthResolve(configPath, outcome, hasToken) {
|
|
157
|
+
if (currentLogger === defaultLogger && !DEBUG_ENABLED)
|
|
158
|
+
return;
|
|
159
|
+
let mtime_ms;
|
|
160
|
+
try {
|
|
161
|
+
mtime_ms = statSync(configPath).mtimeMs;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
mtime_ms = null;
|
|
165
|
+
}
|
|
166
|
+
const prior = lastSeenMtime.get(configPath);
|
|
167
|
+
// token_fresh is true ONLY when we have a NEW mtime AND a token.
|
|
168
|
+
// - First call (no prior): true if hasToken (the token "appears fresh"
|
|
169
|
+
// from the resolver's POV).
|
|
170
|
+
// - mtime moved forward: true if hasToken.
|
|
171
|
+
// - mtime unchanged: false (same file, no rotation since last call).
|
|
172
|
+
// - mtime null: false (cannot prove freshness).
|
|
173
|
+
const token_fresh = mtime_ms !== null && hasToken && (prior === undefined || mtime_ms !== prior);
|
|
174
|
+
if (mtime_ms !== null)
|
|
175
|
+
lastSeenMtime.set(configPath, mtime_ms);
|
|
176
|
+
currentLogger({
|
|
177
|
+
ts: new Date().toISOString(),
|
|
178
|
+
event: "mcp_auth_resolve",
|
|
179
|
+
mtime_ms,
|
|
180
|
+
token_fresh,
|
|
181
|
+
outcome,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Helper: redact tool args for {@link McpToolInvokeStartRecord}. Two-pass
|
|
186
|
+
* defense:
|
|
187
|
+
*
|
|
188
|
+
* 1. `redactBody` from `@ttctl/core` replaces FIELD values whose key
|
|
189
|
+
* matches `SECRET_BODY_FIELD_NAMES` (`password`, `token`, etc.).
|
|
190
|
+
* 2. {@link scrubBearerPatternInStrings} walks the result and replaces
|
|
191
|
+
* any STRING value that matches the canonical bearer pattern
|
|
192
|
+
* (`user_<24hex>_<20alnum>`). This catches the case where an LLM
|
|
193
|
+
* client pastes a bearer-shaped value into a free-text arg like
|
|
194
|
+
* `{ note: "user_abc..." }` that the field-name pass would miss.
|
|
195
|
+
*
|
|
196
|
+
* The MCP tool surface accepts arbitrary client-supplied JSON, so the
|
|
197
|
+
* second pass is the load-bearing defense for bearer-absence in
|
|
198
|
+
* `args_redacted`. The transport-side `redactBody` doesn't need it
|
|
199
|
+
* because GraphQL variables are typed and the bearer never appears in
|
|
200
|
+
* any documented variable slot — MCP args have no such guarantee.
|
|
201
|
+
*/
|
|
202
|
+
export function redactToolArgs(args) {
|
|
203
|
+
return scrubBearerPatternInStrings(redactBody(args));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Walk an arbitrary structure (object / array / scalar) and replace any
|
|
207
|
+
* string value matching the canonical Toptal session bearer pattern
|
|
208
|
+
* with {@link REDACTED}. The pattern source comes from `@ttctl/core` so
|
|
209
|
+
* we never duplicate the bearer regex — the lint-time leakage check
|
|
210
|
+
* uses the same constant.
|
|
211
|
+
*
|
|
212
|
+
* Pure — does not mutate the input. Designed to compose AFTER
|
|
213
|
+
* `redactBody`: any field already replaced with `***REDACTED***` is a
|
|
214
|
+
* non-matching scalar and passes through unchanged.
|
|
215
|
+
*/
|
|
216
|
+
function scrubBearerPatternInStrings(input) {
|
|
217
|
+
if (input === null || input === undefined)
|
|
218
|
+
return input;
|
|
219
|
+
if (typeof input === "string") {
|
|
220
|
+
return input.match(new RegExp(BEARER_PATTERN_SOURCE)) !== null ? REDACTED : input;
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(input))
|
|
223
|
+
return input.map((item) => scrubBearerPatternInStrings(item));
|
|
224
|
+
if (typeof input !== "object")
|
|
225
|
+
return input;
|
|
226
|
+
const out = {};
|
|
227
|
+
for (const [key, value] of Object.entries(input)) {
|
|
228
|
+
out[key] = scrubBearerPatternInStrings(value);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Extract the transport `surface` from an error-shaped value. The
|
|
234
|
+
* `Cf403Error` / `Cf403PersistentError` classes carry `surface` verbatim;
|
|
235
|
+
* `SchedulerBearerExpired` implies `"scheduler"`. Anything else returns
|
|
236
|
+
* `"unknown"` — the operator triages from `error_class` directly.
|
|
237
|
+
*
|
|
238
|
+
* Exported for tests; production code routes through
|
|
239
|
+
* {@link emitMcpTransportError}.
|
|
240
|
+
*/
|
|
241
|
+
export function extractTransportSurface(err) {
|
|
242
|
+
if (typeof err !== "object" || err === null)
|
|
243
|
+
return "unknown";
|
|
244
|
+
const name = err.name;
|
|
245
|
+
if (name === "SchedulerBearerExpired")
|
|
246
|
+
return "scheduler";
|
|
247
|
+
const surface = err.surface;
|
|
248
|
+
if (surface === "talent-profile" || surface === "mobile-gateway" || surface === "scheduler")
|
|
249
|
+
return surface;
|
|
250
|
+
return "unknown";
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Extract HTTP status from a transport error. `Cf403Error` /
|
|
254
|
+
* `Cf403PersistentError` are 403 by construction; `SchedulerBearerExpired`
|
|
255
|
+
* is 401. Errors carrying a numeric `status` field return that value.
|
|
256
|
+
* Anything else returns `null`.
|
|
257
|
+
*/
|
|
258
|
+
export function extractTransportStatus(err) {
|
|
259
|
+
if (typeof err !== "object" || err === null)
|
|
260
|
+
return null;
|
|
261
|
+
const name = err.name;
|
|
262
|
+
if (name === "Cf403Error" || name === "Cf403PersistentError")
|
|
263
|
+
return 403;
|
|
264
|
+
if (name === "SchedulerBearerExpired")
|
|
265
|
+
return 401;
|
|
266
|
+
const status = err.status;
|
|
267
|
+
if (typeof status === "number")
|
|
268
|
+
return status;
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Recognize transport-class errors so the wrapper can emit
|
|
273
|
+
* {@link McpTransportErrorRecord} only for transport-cause failures
|
|
274
|
+
* (Cloudflare 403, scheduler bearer expired, etc.) and skip domain-cause
|
|
275
|
+
* throws (which the MCP error contract surfaces via `ToolErrorResponse`).
|
|
276
|
+
*/
|
|
277
|
+
export function isTransportError(err) {
|
|
278
|
+
if (typeof err !== "object" || err === null)
|
|
279
|
+
return false;
|
|
280
|
+
const name = err.name;
|
|
281
|
+
return name === "Cf403Error" || name === "Cf403PersistentError" || name === "SchedulerBearerExpired";
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Helper for wrapping a tool callback with the start/end/transport-error
|
|
285
|
+
* emission contract. The wrapped handler:
|
|
286
|
+
*
|
|
287
|
+
* 1. Captures a monotonic start time.
|
|
288
|
+
* 2. Emits `mcp_tool_invoke_start` with `redactToolArgs(input)`.
|
|
289
|
+
* 3. Runs the original handler with all forwarded arguments.
|
|
290
|
+
* 4. On success: emits `mcp_tool_invoke_end` with status `"ok"` /
|
|
291
|
+
* `"error"` derived from the response shape, plus `duration_ms`.
|
|
292
|
+
* 5. On throw: emits `mcp_tool_invoke_end` with status `"throw"`,
|
|
293
|
+
* then `mcp_transport_error` if the error class is transport-typed,
|
|
294
|
+
* then re-throws.
|
|
295
|
+
*
|
|
296
|
+
* The wrapper is a closure over the tool name so the same factory
|
|
297
|
+
* produces a per-tool wrapper that names itself correctly in every
|
|
298
|
+
* emission.
|
|
299
|
+
*
|
|
300
|
+
* The MCP SDK's `ToolCallback<Args>` accepts a two-argument callback
|
|
301
|
+
* `(input, extra)` (where `extra` is `RequestHandlerExtra` carrying
|
|
302
|
+
* the active session / abort signal). Some tool callbacks accept zero
|
|
303
|
+
* arguments (no inputSchema) — the SDK calls them with `(extra)` only.
|
|
304
|
+
* The wrapper forwards `...rest` so both shapes work without per-shape
|
|
305
|
+
* branching at the call site.
|
|
306
|
+
*/
|
|
307
|
+
export function wrapToolHandler(toolName, handler) {
|
|
308
|
+
const wrapped = async (...args) => {
|
|
309
|
+
const input = args[0];
|
|
310
|
+
const start = performance.now();
|
|
311
|
+
emitMcpDebug(() => {
|
|
312
|
+
return {
|
|
313
|
+
ts: new Date().toISOString(),
|
|
314
|
+
event: "mcp_tool_invoke_start",
|
|
315
|
+
tool: toolName,
|
|
316
|
+
args_redacted: redactToolArgs(input),
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
try {
|
|
320
|
+
const result = (await handler(...args));
|
|
321
|
+
const duration_ms = Math.round(performance.now() - start);
|
|
322
|
+
const status = result.isError === true ? "error" : "ok";
|
|
323
|
+
emitMcpDebug(() => {
|
|
324
|
+
return {
|
|
325
|
+
ts: new Date().toISOString(),
|
|
326
|
+
event: "mcp_tool_invoke_end",
|
|
327
|
+
tool: toolName,
|
|
328
|
+
duration_ms,
|
|
329
|
+
status,
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
const duration_ms = Math.round(performance.now() - start);
|
|
336
|
+
emitMcpDebug(() => {
|
|
337
|
+
return {
|
|
338
|
+
ts: new Date().toISOString(),
|
|
339
|
+
event: "mcp_tool_invoke_end",
|
|
340
|
+
tool: toolName,
|
|
341
|
+
duration_ms,
|
|
342
|
+
status: "throw",
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
if (isTransportError(err)) {
|
|
346
|
+
emitMcpDebug(() => {
|
|
347
|
+
return {
|
|
348
|
+
ts: new Date().toISOString(),
|
|
349
|
+
event: "mcp_transport_error",
|
|
350
|
+
tool: toolName,
|
|
351
|
+
surface: extractTransportSurface(err),
|
|
352
|
+
error_class: err.name ?? "Error",
|
|
353
|
+
status: extractTransportStatus(err),
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
throw err;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
return wrapped;
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=diagnostic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic.js","sourceRoot":"","sources":["../src/diagnostic.ts"],"names":[],"mappings":"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"}
|