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.
@@ -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 };
@@ -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 };