mcpspan 0.1.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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/index.cjs +1566 -0
- package/dist/index.d.cts +264 -0
- package/dist/index.d.mts +264 -0
- package/dist/index.mjs +1561 -0
- package/package.json +75 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//#region src/config.d.ts
|
|
2
|
+
interface McpspanConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Key identifying the server these events belong to.
|
|
5
|
+
*
|
|
6
|
+
* Falls back to `MCPSPAN_API_KEY`. Without either, the SDK collects nothing.
|
|
7
|
+
*/
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Base URL of the ingest API.
|
|
11
|
+
*
|
|
12
|
+
* Falls back to `MCPSPAN_ENDPOINT`. There is no default: without either,
|
|
13
|
+
* the SDK collects nothing and says so once.
|
|
14
|
+
*/
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The version to record calls under: a release, a tag, a commit.
|
|
18
|
+
*
|
|
19
|
+
* Falls back to `MCPSPAN_SERVER_VERSION`, then to the version the MCP server
|
|
20
|
+
* gives itself (`new McpServer({ name, version })`), which is usually all
|
|
21
|
+
* that is needed. The dashboard marks where each version began.
|
|
22
|
+
*/
|
|
23
|
+
serverVersion?: string;
|
|
24
|
+
/** Writes delivery diagnostics to stderr. Off by default. */
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Receives diagnostics instead of stderr.
|
|
28
|
+
*
|
|
29
|
+
* For servers with their own logger, so our messages arrive in the same
|
|
30
|
+
* stream and the same format as everything else. Implies `debug`.
|
|
31
|
+
*
|
|
32
|
+
* A callback that throws is ignored: a problem with reporting a problem
|
|
33
|
+
* cannot be allowed to become one.
|
|
34
|
+
*/
|
|
35
|
+
onDiagnostic?: (message: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Sends whatever is queued when the process is about to exit.
|
|
38
|
+
*
|
|
39
|
+
* On by default. Without it the last partly filled batch dies with the
|
|
40
|
+
* process, and on a stdio server that lives as long as one conversation
|
|
41
|
+
* that can be most of a session.
|
|
42
|
+
*
|
|
43
|
+
* The hook runs when Node's event loop empties. It does not keep the
|
|
44
|
+
* process alive, does not intercept signals, and does not interfere with a
|
|
45
|
+
* server's own shutdown handling.
|
|
46
|
+
*/
|
|
47
|
+
flushOnExit?: boolean;
|
|
48
|
+
/** How long a partly filled batch waits before being sent anyway. */
|
|
49
|
+
flushIntervalMs?: number;
|
|
50
|
+
/** Largest number of events in a single request. */
|
|
51
|
+
maxBatchSize?: number;
|
|
52
|
+
/** Largest number of events held while delivery is failing. */
|
|
53
|
+
maxQueueSize?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Records which parameters a tool was called with, by name and type only.
|
|
56
|
+
*
|
|
57
|
+
* Off by default. Values are never read, in this mode or any other: the name
|
|
58
|
+
* of this option is the whole promise. Turning it on tells you that
|
|
59
|
+
* `search_flights` is always called with `destination` and never with
|
|
60
|
+
* `departureDate`, which usually means a tool description is not landing.
|
|
61
|
+
*/
|
|
62
|
+
captureParameterNames?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Starts collecting, or stops if there is nothing to collect with.
|
|
66
|
+
*
|
|
67
|
+
* Calling this again with a different configuration replaces the previous
|
|
68
|
+
* one, sending whatever the old one still held. Calling it again with the same
|
|
69
|
+
* configuration changes nothing. That is the common case, not an edge: an HTTP
|
|
70
|
+
* server that builds a fresh MCP server for every request - the stateless
|
|
71
|
+
* pattern, and how v2 of the official SDK serves the 2026-07-28 protocol -
|
|
72
|
+
* calls instrument() on each of them, and starting over each time would
|
|
73
|
+
* announce the server once per request and throw away the batching.
|
|
74
|
+
*
|
|
75
|
+
* **Never throws.** This runs inside a developer's server during startup, so a
|
|
76
|
+
* mistyped option must not be the reason their server fails to boot. Bad
|
|
77
|
+
* values are reported on stderr and replaced with defaults.
|
|
78
|
+
*/
|
|
79
|
+
export declare function configure(config?: McpspanConfig): void;
|
|
80
|
+
/**
|
|
81
|
+
* Stops collecting and makes a final attempt to deliver what is queued.
|
|
82
|
+
*
|
|
83
|
+
* Worth calling from a server's own shutdown path. Without it the last partly
|
|
84
|
+
* filled batch dies with the process, which on a short-lived stdio server can
|
|
85
|
+
* be most of a session.
|
|
86
|
+
*/
|
|
87
|
+
export declare function shutdown(): Promise<void>;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/instrument.d.ts
|
|
90
|
+
/**
|
|
91
|
+
* Records every tool a server registers from this point on.
|
|
92
|
+
*
|
|
93
|
+
* This is the whole integration:
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const server = new McpServer({ name: 'flights', version: '1.0.0' });
|
|
97
|
+
* instrument(server, { apiKey: process.env.MCPSPAN_API_KEY });
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* Tools registered afterwards are wrapped as they are registered, and tools
|
|
101
|
+
* registered before it are wrapped where they already are, so nothing about
|
|
102
|
+
* how they are declared, or where this line sits, has to change.
|
|
103
|
+
*
|
|
104
|
+
* **Never throws.** An unfamiliar server object, or one with no registration
|
|
105
|
+
* method at all, leaves the server exactly as it was and collects nothing. A
|
|
106
|
+
* telemetry library that can stop somebody's server from starting has failed
|
|
107
|
+
* at the only thing it truly must not do.
|
|
108
|
+
*/
|
|
109
|
+
export declare function instrument<TServer extends object>(server: TServer, config?: McpspanConfig): TServer;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/types.d.ts
|
|
112
|
+
/**
|
|
113
|
+
* Client application a tool call originated from, as far as the transport
|
|
114
|
+
* allows us to tell.
|
|
115
|
+
*
|
|
116
|
+
* Over stdio the client is usually invisible to the server, which yields
|
|
117
|
+
* `unknown`. `other` is different: a client did identify itself, we just do
|
|
118
|
+
* not recognise it.
|
|
119
|
+
*/
|
|
120
|
+
type ClientType = 'claude' | 'claude-code' | 'cursor' | 'chatgpt' | 'mcp-inspector' | 'other' | 'unknown';
|
|
121
|
+
/**
|
|
122
|
+
* How a failed tool call announced itself.
|
|
123
|
+
*
|
|
124
|
+
* MCP asks tools to report their own failures inside the result, with
|
|
125
|
+
* `isError` set, so that the model can see what went wrong. A thrown exception
|
|
126
|
+
* is the deviation from that, and usually means the handler crashed rather
|
|
127
|
+
* than failing on purpose. Keeping the two apart lets a developer tell a
|
|
128
|
+
* handled business error from a bug.
|
|
129
|
+
*
|
|
130
|
+
* The other two never reach a handler. The server refuses them itself, and
|
|
131
|
+
* the model sees that refusal like any other error result.
|
|
132
|
+
*/
|
|
133
|
+
type ErrorSource = 'result' | 'exception' |
|
|
134
|
+
/** Refused by the server's schema validation before the handler ran. */
|
|
135
|
+
'arguments' |
|
|
136
|
+
/** A tool name the server does not have, or has disabled. */
|
|
137
|
+
'unknown_tool' |
|
|
138
|
+
/** A resource address the server has nothing registered for. */
|
|
139
|
+
'unknown_resource' |
|
|
140
|
+
/** A prompt name the server does not have, or has disabled. */
|
|
141
|
+
'unknown_prompt';
|
|
142
|
+
/** What an event is about: a tool call, a resource read, or a prompt got. */
|
|
143
|
+
type CallKind = 'tool' | 'resource' | 'prompt';
|
|
144
|
+
/**
|
|
145
|
+
* A single tool invocation recorded by the SDK.
|
|
146
|
+
*
|
|
147
|
+
* The event carries no server identity on purpose. The ingest API derives that
|
|
148
|
+
* from the API key the batch was sent with, so a caller cannot attribute tool
|
|
149
|
+
* calls to a server it does not own.
|
|
150
|
+
*
|
|
151
|
+
* Parameter values are never part of an event, in any mode.
|
|
152
|
+
*/
|
|
153
|
+
interface ToolCallEvent {
|
|
154
|
+
/**
|
|
155
|
+
* Identifies this call for as long as it takes to reach storage.
|
|
156
|
+
*
|
|
157
|
+
* A batch that times out after the server has already written it gets resent,
|
|
158
|
+
* so ingest needs a way to recognise a replay and drop it. Without this, a
|
|
159
|
+
* flaky network inflates the very numbers the product exists to report.
|
|
160
|
+
*/
|
|
161
|
+
id: string;
|
|
162
|
+
/** Absent for a tool call; `resource` or `prompt` for the others (contract, 3.5). */
|
|
163
|
+
kind?: Exclude<CallKind, 'tool'>;
|
|
164
|
+
/**
|
|
165
|
+
* Name the tool was registered under; for a resource, its registered URI or
|
|
166
|
+
* URI template; for a prompt, its name.
|
|
167
|
+
*/
|
|
168
|
+
toolName: string;
|
|
169
|
+
/** How long the call took, in milliseconds. May be fractional. */
|
|
170
|
+
durationMs: number;
|
|
171
|
+
/** False when the call was refused, the handler threw, or it returned `isError`. */
|
|
172
|
+
success: boolean;
|
|
173
|
+
/** Which failure path this call took. Absent on success. */
|
|
174
|
+
errorSource?: ErrorSource;
|
|
175
|
+
/** Constructor name of a thrown error, for example `TypeError`. Absent otherwise. */
|
|
176
|
+
errorType?: string;
|
|
177
|
+
/** Error message, truncated to a bounded length. Absent on success. */
|
|
178
|
+
errorMessage?: string;
|
|
179
|
+
/** Where the call came from, or `unknown` when nothing identified itself. */
|
|
180
|
+
clientType: ClientType;
|
|
181
|
+
/**
|
|
182
|
+
* The name the client reported, as it reported it.
|
|
183
|
+
*
|
|
184
|
+
* Turns an unrecognised client into a lead rather than a dead end: a
|
|
185
|
+
* dashboard showing forty percent `other` is useless if nobody can find out
|
|
186
|
+
* what `other` was. This is an application's self-description, the MCP
|
|
187
|
+
* equivalent of a User-Agent, and says nothing about whoever is using it.
|
|
188
|
+
*/
|
|
189
|
+
clientName?: string;
|
|
190
|
+
/** The version the client gives itself, as it gives it. */
|
|
191
|
+
clientVersion?: string;
|
|
192
|
+
/**
|
|
193
|
+
* The version of the server that answered: the `serverVersion` setting, or
|
|
194
|
+
* the version the MCP server gives itself.
|
|
195
|
+
*/
|
|
196
|
+
serverVersion?: string;
|
|
197
|
+
/** When the call started, as an ISO 8601 timestamp. */
|
|
198
|
+
timestamp: string;
|
|
199
|
+
/** Version of the mcpspan package that produced the event. */
|
|
200
|
+
sdkVersion: string;
|
|
201
|
+
/**
|
|
202
|
+
* The connection this call arrived on, as a random identifier made by the
|
|
203
|
+
* SDK. Absent for calls recorded through `track` alone, which has no way to
|
|
204
|
+
* see the connection.
|
|
205
|
+
*/
|
|
206
|
+
sessionId?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Parameter names mapped to their types, when a developer opted in.
|
|
209
|
+
*
|
|
210
|
+
* Absent by default, and never contains a value. Seeing that
|
|
211
|
+
* `search_flights` is always called with `destination` and never with
|
|
212
|
+
* `departureDate` tells a developer their tool description is not landing;
|
|
213
|
+
* seeing which destination tells them nothing they needed and puts their
|
|
214
|
+
* users' data somewhere it does not belong.
|
|
215
|
+
*/
|
|
216
|
+
parameters?: Record<string, string>;
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/track.d.ts
|
|
220
|
+
/**
|
|
221
|
+
* Wraps a tool handler so its calls are recorded.
|
|
222
|
+
*
|
|
223
|
+
* The returned function keeps the handler's exact signature, including its
|
|
224
|
+
* `this`, and passes the result back untouched. Whatever the handler is to the
|
|
225
|
+
* code around it, the wrapper has to be indistinguishable - the moment using
|
|
226
|
+
* this changes how a tool behaves, it stops being an observability library and
|
|
227
|
+
* becomes a liability.
|
|
228
|
+
*
|
|
229
|
+
* Synchronous handlers are measured as they return. Asynchronous ones are
|
|
230
|
+
* measured when their promise settles, since the time a tool takes is the time
|
|
231
|
+
* the agent waits for it, not the time spent building the promise.
|
|
232
|
+
*
|
|
233
|
+
* Failure is recognised in both of its MCP forms: a result the tool marked
|
|
234
|
+
* with `isError`, which the specification treats as the ordinary way to report
|
|
235
|
+
* a problem, and a thrown exception, which usually means the handler broke.
|
|
236
|
+
*
|
|
237
|
+
* Arguments are never read. Nothing a caller passes to a tool reaches an
|
|
238
|
+
* event.
|
|
239
|
+
*/
|
|
240
|
+
export declare function track<TArgs extends unknown[], TResult>(toolName: string, handler: (...args: TArgs) => TResult): (...args: TArgs) => TResult;
|
|
241
|
+
/**
|
|
242
|
+
* Keeps a tool out of the numbers entirely.
|
|
243
|
+
*
|
|
244
|
+
* The counterpart to {@link track}: where that one records a handler, this one
|
|
245
|
+
* marks it so that wrapping the server never touches it.
|
|
246
|
+
*
|
|
247
|
+
* ```ts
|
|
248
|
+
* server.registerTool('health_check', schema, exclude(handler));
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* Meant for tools that are called by machinery rather than by an agent - a
|
|
252
|
+
* health check polled every few seconds would outnumber everything a person
|
|
253
|
+
* actually did, and would drag the error rate and response time of the whole
|
|
254
|
+
* server towards its own.
|
|
255
|
+
*
|
|
256
|
+
* Takes no tool name on purpose. A name repeated here could drift from the
|
|
257
|
+
* real one during a rename, and the exclusion would quietly stop applying.
|
|
258
|
+
*
|
|
259
|
+
* Does nothing on its own: without instrumentation there was nothing about to
|
|
260
|
+
* record this handler anyway. The handler is returned exactly as given.
|
|
261
|
+
*/
|
|
262
|
+
export declare function exclude<THandler extends (...args: never[]) => unknown>(handler: THandler): THandler;
|
|
263
|
+
//#endregion
|
|
264
|
+
export type { ClientType, ErrorSource, McpspanConfig, ToolCallEvent };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//#region src/config.d.ts
|
|
2
|
+
interface McpspanConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Key identifying the server these events belong to.
|
|
5
|
+
*
|
|
6
|
+
* Falls back to `MCPSPAN_API_KEY`. Without either, the SDK collects nothing.
|
|
7
|
+
*/
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Base URL of the ingest API.
|
|
11
|
+
*
|
|
12
|
+
* Falls back to `MCPSPAN_ENDPOINT`. There is no default: without either,
|
|
13
|
+
* the SDK collects nothing and says so once.
|
|
14
|
+
*/
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The version to record calls under: a release, a tag, a commit.
|
|
18
|
+
*
|
|
19
|
+
* Falls back to `MCPSPAN_SERVER_VERSION`, then to the version the MCP server
|
|
20
|
+
* gives itself (`new McpServer({ name, version })`), which is usually all
|
|
21
|
+
* that is needed. The dashboard marks where each version began.
|
|
22
|
+
*/
|
|
23
|
+
serverVersion?: string;
|
|
24
|
+
/** Writes delivery diagnostics to stderr. Off by default. */
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Receives diagnostics instead of stderr.
|
|
28
|
+
*
|
|
29
|
+
* For servers with their own logger, so our messages arrive in the same
|
|
30
|
+
* stream and the same format as everything else. Implies `debug`.
|
|
31
|
+
*
|
|
32
|
+
* A callback that throws is ignored: a problem with reporting a problem
|
|
33
|
+
* cannot be allowed to become one.
|
|
34
|
+
*/
|
|
35
|
+
onDiagnostic?: (message: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Sends whatever is queued when the process is about to exit.
|
|
38
|
+
*
|
|
39
|
+
* On by default. Without it the last partly filled batch dies with the
|
|
40
|
+
* process, and on a stdio server that lives as long as one conversation
|
|
41
|
+
* that can be most of a session.
|
|
42
|
+
*
|
|
43
|
+
* The hook runs when Node's event loop empties. It does not keep the
|
|
44
|
+
* process alive, does not intercept signals, and does not interfere with a
|
|
45
|
+
* server's own shutdown handling.
|
|
46
|
+
*/
|
|
47
|
+
flushOnExit?: boolean;
|
|
48
|
+
/** How long a partly filled batch waits before being sent anyway. */
|
|
49
|
+
flushIntervalMs?: number;
|
|
50
|
+
/** Largest number of events in a single request. */
|
|
51
|
+
maxBatchSize?: number;
|
|
52
|
+
/** Largest number of events held while delivery is failing. */
|
|
53
|
+
maxQueueSize?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Records which parameters a tool was called with, by name and type only.
|
|
56
|
+
*
|
|
57
|
+
* Off by default. Values are never read, in this mode or any other: the name
|
|
58
|
+
* of this option is the whole promise. Turning it on tells you that
|
|
59
|
+
* `search_flights` is always called with `destination` and never with
|
|
60
|
+
* `departureDate`, which usually means a tool description is not landing.
|
|
61
|
+
*/
|
|
62
|
+
captureParameterNames?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Starts collecting, or stops if there is nothing to collect with.
|
|
66
|
+
*
|
|
67
|
+
* Calling this again with a different configuration replaces the previous
|
|
68
|
+
* one, sending whatever the old one still held. Calling it again with the same
|
|
69
|
+
* configuration changes nothing. That is the common case, not an edge: an HTTP
|
|
70
|
+
* server that builds a fresh MCP server for every request - the stateless
|
|
71
|
+
* pattern, and how v2 of the official SDK serves the 2026-07-28 protocol -
|
|
72
|
+
* calls instrument() on each of them, and starting over each time would
|
|
73
|
+
* announce the server once per request and throw away the batching.
|
|
74
|
+
*
|
|
75
|
+
* **Never throws.** This runs inside a developer's server during startup, so a
|
|
76
|
+
* mistyped option must not be the reason their server fails to boot. Bad
|
|
77
|
+
* values are reported on stderr and replaced with defaults.
|
|
78
|
+
*/
|
|
79
|
+
export declare function configure(config?: McpspanConfig): void;
|
|
80
|
+
/**
|
|
81
|
+
* Stops collecting and makes a final attempt to deliver what is queued.
|
|
82
|
+
*
|
|
83
|
+
* Worth calling from a server's own shutdown path. Without it the last partly
|
|
84
|
+
* filled batch dies with the process, which on a short-lived stdio server can
|
|
85
|
+
* be most of a session.
|
|
86
|
+
*/
|
|
87
|
+
export declare function shutdown(): Promise<void>;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/instrument.d.ts
|
|
90
|
+
/**
|
|
91
|
+
* Records every tool a server registers from this point on.
|
|
92
|
+
*
|
|
93
|
+
* This is the whole integration:
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const server = new McpServer({ name: 'flights', version: '1.0.0' });
|
|
97
|
+
* instrument(server, { apiKey: process.env.MCPSPAN_API_KEY });
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* Tools registered afterwards are wrapped as they are registered, and tools
|
|
101
|
+
* registered before it are wrapped where they already are, so nothing about
|
|
102
|
+
* how they are declared, or where this line sits, has to change.
|
|
103
|
+
*
|
|
104
|
+
* **Never throws.** An unfamiliar server object, or one with no registration
|
|
105
|
+
* method at all, leaves the server exactly as it was and collects nothing. A
|
|
106
|
+
* telemetry library that can stop somebody's server from starting has failed
|
|
107
|
+
* at the only thing it truly must not do.
|
|
108
|
+
*/
|
|
109
|
+
export declare function instrument<TServer extends object>(server: TServer, config?: McpspanConfig): TServer;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/types.d.ts
|
|
112
|
+
/**
|
|
113
|
+
* Client application a tool call originated from, as far as the transport
|
|
114
|
+
* allows us to tell.
|
|
115
|
+
*
|
|
116
|
+
* Over stdio the client is usually invisible to the server, which yields
|
|
117
|
+
* `unknown`. `other` is different: a client did identify itself, we just do
|
|
118
|
+
* not recognise it.
|
|
119
|
+
*/
|
|
120
|
+
type ClientType = 'claude' | 'claude-code' | 'cursor' | 'chatgpt' | 'mcp-inspector' | 'other' | 'unknown';
|
|
121
|
+
/**
|
|
122
|
+
* How a failed tool call announced itself.
|
|
123
|
+
*
|
|
124
|
+
* MCP asks tools to report their own failures inside the result, with
|
|
125
|
+
* `isError` set, so that the model can see what went wrong. A thrown exception
|
|
126
|
+
* is the deviation from that, and usually means the handler crashed rather
|
|
127
|
+
* than failing on purpose. Keeping the two apart lets a developer tell a
|
|
128
|
+
* handled business error from a bug.
|
|
129
|
+
*
|
|
130
|
+
* The other two never reach a handler. The server refuses them itself, and
|
|
131
|
+
* the model sees that refusal like any other error result.
|
|
132
|
+
*/
|
|
133
|
+
type ErrorSource = 'result' | 'exception' |
|
|
134
|
+
/** Refused by the server's schema validation before the handler ran. */
|
|
135
|
+
'arguments' |
|
|
136
|
+
/** A tool name the server does not have, or has disabled. */
|
|
137
|
+
'unknown_tool' |
|
|
138
|
+
/** A resource address the server has nothing registered for. */
|
|
139
|
+
'unknown_resource' |
|
|
140
|
+
/** A prompt name the server does not have, or has disabled. */
|
|
141
|
+
'unknown_prompt';
|
|
142
|
+
/** What an event is about: a tool call, a resource read, or a prompt got. */
|
|
143
|
+
type CallKind = 'tool' | 'resource' | 'prompt';
|
|
144
|
+
/**
|
|
145
|
+
* A single tool invocation recorded by the SDK.
|
|
146
|
+
*
|
|
147
|
+
* The event carries no server identity on purpose. The ingest API derives that
|
|
148
|
+
* from the API key the batch was sent with, so a caller cannot attribute tool
|
|
149
|
+
* calls to a server it does not own.
|
|
150
|
+
*
|
|
151
|
+
* Parameter values are never part of an event, in any mode.
|
|
152
|
+
*/
|
|
153
|
+
interface ToolCallEvent {
|
|
154
|
+
/**
|
|
155
|
+
* Identifies this call for as long as it takes to reach storage.
|
|
156
|
+
*
|
|
157
|
+
* A batch that times out after the server has already written it gets resent,
|
|
158
|
+
* so ingest needs a way to recognise a replay and drop it. Without this, a
|
|
159
|
+
* flaky network inflates the very numbers the product exists to report.
|
|
160
|
+
*/
|
|
161
|
+
id: string;
|
|
162
|
+
/** Absent for a tool call; `resource` or `prompt` for the others (contract, 3.5). */
|
|
163
|
+
kind?: Exclude<CallKind, 'tool'>;
|
|
164
|
+
/**
|
|
165
|
+
* Name the tool was registered under; for a resource, its registered URI or
|
|
166
|
+
* URI template; for a prompt, its name.
|
|
167
|
+
*/
|
|
168
|
+
toolName: string;
|
|
169
|
+
/** How long the call took, in milliseconds. May be fractional. */
|
|
170
|
+
durationMs: number;
|
|
171
|
+
/** False when the call was refused, the handler threw, or it returned `isError`. */
|
|
172
|
+
success: boolean;
|
|
173
|
+
/** Which failure path this call took. Absent on success. */
|
|
174
|
+
errorSource?: ErrorSource;
|
|
175
|
+
/** Constructor name of a thrown error, for example `TypeError`. Absent otherwise. */
|
|
176
|
+
errorType?: string;
|
|
177
|
+
/** Error message, truncated to a bounded length. Absent on success. */
|
|
178
|
+
errorMessage?: string;
|
|
179
|
+
/** Where the call came from, or `unknown` when nothing identified itself. */
|
|
180
|
+
clientType: ClientType;
|
|
181
|
+
/**
|
|
182
|
+
* The name the client reported, as it reported it.
|
|
183
|
+
*
|
|
184
|
+
* Turns an unrecognised client into a lead rather than a dead end: a
|
|
185
|
+
* dashboard showing forty percent `other` is useless if nobody can find out
|
|
186
|
+
* what `other` was. This is an application's self-description, the MCP
|
|
187
|
+
* equivalent of a User-Agent, and says nothing about whoever is using it.
|
|
188
|
+
*/
|
|
189
|
+
clientName?: string;
|
|
190
|
+
/** The version the client gives itself, as it gives it. */
|
|
191
|
+
clientVersion?: string;
|
|
192
|
+
/**
|
|
193
|
+
* The version of the server that answered: the `serverVersion` setting, or
|
|
194
|
+
* the version the MCP server gives itself.
|
|
195
|
+
*/
|
|
196
|
+
serverVersion?: string;
|
|
197
|
+
/** When the call started, as an ISO 8601 timestamp. */
|
|
198
|
+
timestamp: string;
|
|
199
|
+
/** Version of the mcpspan package that produced the event. */
|
|
200
|
+
sdkVersion: string;
|
|
201
|
+
/**
|
|
202
|
+
* The connection this call arrived on, as a random identifier made by the
|
|
203
|
+
* SDK. Absent for calls recorded through `track` alone, which has no way to
|
|
204
|
+
* see the connection.
|
|
205
|
+
*/
|
|
206
|
+
sessionId?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Parameter names mapped to their types, when a developer opted in.
|
|
209
|
+
*
|
|
210
|
+
* Absent by default, and never contains a value. Seeing that
|
|
211
|
+
* `search_flights` is always called with `destination` and never with
|
|
212
|
+
* `departureDate` tells a developer their tool description is not landing;
|
|
213
|
+
* seeing which destination tells them nothing they needed and puts their
|
|
214
|
+
* users' data somewhere it does not belong.
|
|
215
|
+
*/
|
|
216
|
+
parameters?: Record<string, string>;
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/track.d.ts
|
|
220
|
+
/**
|
|
221
|
+
* Wraps a tool handler so its calls are recorded.
|
|
222
|
+
*
|
|
223
|
+
* The returned function keeps the handler's exact signature, including its
|
|
224
|
+
* `this`, and passes the result back untouched. Whatever the handler is to the
|
|
225
|
+
* code around it, the wrapper has to be indistinguishable - the moment using
|
|
226
|
+
* this changes how a tool behaves, it stops being an observability library and
|
|
227
|
+
* becomes a liability.
|
|
228
|
+
*
|
|
229
|
+
* Synchronous handlers are measured as they return. Asynchronous ones are
|
|
230
|
+
* measured when their promise settles, since the time a tool takes is the time
|
|
231
|
+
* the agent waits for it, not the time spent building the promise.
|
|
232
|
+
*
|
|
233
|
+
* Failure is recognised in both of its MCP forms: a result the tool marked
|
|
234
|
+
* with `isError`, which the specification treats as the ordinary way to report
|
|
235
|
+
* a problem, and a thrown exception, which usually means the handler broke.
|
|
236
|
+
*
|
|
237
|
+
* Arguments are never read. Nothing a caller passes to a tool reaches an
|
|
238
|
+
* event.
|
|
239
|
+
*/
|
|
240
|
+
export declare function track<TArgs extends unknown[], TResult>(toolName: string, handler: (...args: TArgs) => TResult): (...args: TArgs) => TResult;
|
|
241
|
+
/**
|
|
242
|
+
* Keeps a tool out of the numbers entirely.
|
|
243
|
+
*
|
|
244
|
+
* The counterpart to {@link track}: where that one records a handler, this one
|
|
245
|
+
* marks it so that wrapping the server never touches it.
|
|
246
|
+
*
|
|
247
|
+
* ```ts
|
|
248
|
+
* server.registerTool('health_check', schema, exclude(handler));
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* Meant for tools that are called by machinery rather than by an agent - a
|
|
252
|
+
* health check polled every few seconds would outnumber everything a person
|
|
253
|
+
* actually did, and would drag the error rate and response time of the whole
|
|
254
|
+
* server towards its own.
|
|
255
|
+
*
|
|
256
|
+
* Takes no tool name on purpose. A name repeated here could drift from the
|
|
257
|
+
* real one during a rename, and the exclusion would quietly stop applying.
|
|
258
|
+
*
|
|
259
|
+
* Does nothing on its own: without instrumentation there was nothing about to
|
|
260
|
+
* record this handler anyway. The handler is returned exactly as given.
|
|
261
|
+
*/
|
|
262
|
+
export declare function exclude<THandler extends (...args: never[]) => unknown>(handler: THandler): THandler;
|
|
263
|
+
//#endregion
|
|
264
|
+
export type { ClientType, ErrorSource, McpspanConfig, ToolCallEvent };
|