@upstash/context7-mcp 4.0.6 → 4.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,627 @@
1
+ import { BAGGAGE_META_KEY, McpServer, PROTOCOL_VERSION_META_KEY, SUPPORTED_PROTOCOL_VERSIONS, TRACEPARENT_META_KEY, TRACESTATE_META_KEY, isCallToolResult, isJSONRPCErrorResponse, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResultResponse, } from "@modelcontextprotocol/server";
2
+ import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, isSpanContextValid, metrics, propagation, trace, } from "@opentelemetry/api";
3
+ import { MCP_TOOL_NAMES } from "./tool-names.js";
4
+ import { runInMcpOperationScope } from "./mcp-operation-scope.js";
5
+ import { StdioSubscriptionTelemetry, instrumentMcpHttpHandler as instrumentHttpSubscriptions, mcpRouteFromUrl, } from "./mcp-subscription-telemetry.js";
6
+ export { mcpRouteFromUrl } from "./mcp-subscription-telemetry.js";
7
+ const INSTRUMENTATION_NAME = "io.github.upstash.context7.mcp";
8
+ const MCP_DURATION_BUCKETS_SECONDS = [
9
+ 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300,
10
+ ];
11
+ const KNOWN_MCP_METHODS = new Set([
12
+ "completion/complete",
13
+ "elicitation/create",
14
+ "initialize",
15
+ "logging/setLevel",
16
+ "notifications/cancelled",
17
+ "notifications/elicitation/complete",
18
+ "notifications/initialized",
19
+ "notifications/message",
20
+ "notifications/progress",
21
+ "notifications/prompts/list_changed",
22
+ "notifications/resources/list_changed",
23
+ "notifications/resources/updated",
24
+ "notifications/roots/list_changed",
25
+ "notifications/subscriptions/acknowledged",
26
+ "notifications/tasks/status",
27
+ "notifications/tools/list_changed",
28
+ "ping",
29
+ "prompts/get",
30
+ "prompts/list",
31
+ "resources/list",
32
+ "resources/read",
33
+ "resources/subscribe",
34
+ "resources/templates/list",
35
+ "resources/unsubscribe",
36
+ "roots/list",
37
+ "sampling/createMessage",
38
+ "server/discover",
39
+ "subscriptions/listen",
40
+ "tasks/cancel",
41
+ "tasks/get",
42
+ "tasks/list",
43
+ "tasks/result",
44
+ "tools/call",
45
+ "tools/list",
46
+ ]);
47
+ const KNOWN_TOOLS = new Set(MCP_TOOL_NAMES);
48
+ export const MODERN_MCP_PROTOCOL_VERSION = "2026-07-28";
49
+ const KNOWN_PROTOCOL_VERSIONS = new Set([
50
+ ...SUPPORTED_PROTOCOL_VERSIONS,
51
+ MODERN_MCP_PROTOCOL_VERSION,
52
+ ]);
53
+ const EMPTY_TRACE_CARRIER = Object.freeze({});
54
+ const CALLER_FAULT_CODES = new Set([-32700, -32600, -32601, -32602, -32002]);
55
+ function getInstruments() {
56
+ const meter = metrics.getMeter(INSTRUMENTATION_NAME);
57
+ return {
58
+ operationDuration: meter.createHistogram("mcp.server.operation.duration", {
59
+ description: "MCP request or notification duration from receipt until the result or acknowledgement is sent",
60
+ unit: "s",
61
+ advice: { explicitBucketBoundaries: MCP_DURATION_BUCKETS_SECONDS },
62
+ }),
63
+ sessionDuration: meter.createHistogram("mcp.server.session.duration", {
64
+ description: "Duration of a stateful MCP server session",
65
+ unit: "s",
66
+ advice: { explicitBucketBoundaries: MCP_DURATION_BUCKETS_SECONDS },
67
+ }),
68
+ activeOperations: meter.createUpDownCounter("context7.mcp.operations.active", {
69
+ description: "Number of MCP requests and notifications currently being handled",
70
+ unit: "{operation}",
71
+ }),
72
+ };
73
+ }
74
+ let instruments;
75
+ function mcpInstruments() {
76
+ instruments ??= getInstruments();
77
+ return instruments;
78
+ }
79
+ function asRecord(value) {
80
+ return value && typeof value === "object" && !Array.isArray(value)
81
+ ? value
82
+ : undefined;
83
+ }
84
+ export function normalizeMcpMethodName(method) {
85
+ return typeof method === "string" && KNOWN_MCP_METHODS.has(method) ? method : "unknown";
86
+ }
87
+ export function normalizeMcpToolName(tool) {
88
+ return typeof tool === "string" && KNOWN_TOOLS.has(tool) ? tool : "unknown";
89
+ }
90
+ function normalizedTool(message) {
91
+ if (!isJSONRPCRequest(message) || message.method !== "tools/call")
92
+ return undefined;
93
+ const name = asRecord(message.params)?.name;
94
+ return normalizeMcpToolName(name);
95
+ }
96
+ function normalizedProtocolVersion(value) {
97
+ return typeof value === "string" && KNOWN_PROTOCOL_VERSIONS.has(value) ? value : undefined;
98
+ }
99
+ function messageProtocolVersion(message) {
100
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message))
101
+ return undefined;
102
+ const params = asRecord(message.params);
103
+ const metadata = asRecord(params?._meta);
104
+ return (normalizedProtocolVersion(metadata?.[PROTOCOL_VERSION_META_KEY]) ??
105
+ normalizedProtocolVersion(params?.protocolVersion));
106
+ }
107
+ export function mcpTraceCarrier(message) {
108
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message))
109
+ return EMPTY_TRACE_CARRIER;
110
+ const metadata = asRecord(asRecord(message.params)?._meta);
111
+ if (!metadata)
112
+ return EMPTY_TRACE_CARRIER;
113
+ const carrier = {};
114
+ for (const key of [TRACEPARENT_META_KEY, TRACESTATE_META_KEY, BAGGAGE_META_KEY]) {
115
+ const value = metadata[key];
116
+ if (typeof value === "string")
117
+ carrier[key] = value;
118
+ }
119
+ return carrier;
120
+ }
121
+ function ambientLink(parentContext) {
122
+ const ambient = trace.getSpan(context.active())?.spanContext();
123
+ const parent = trace.getSpan(parentContext)?.spanContext();
124
+ if (!ambient || !isSpanContextValid(ambient))
125
+ return undefined;
126
+ if (parent && ambient.traceId === parent.traceId && ambient.spanId === parent.spanId) {
127
+ return undefined;
128
+ }
129
+ return [{ context: ambient }];
130
+ }
131
+ function elapsedSeconds(startedAt) {
132
+ return (performance.now() - startedAt) / 1_000;
133
+ }
134
+ function requestIdAttribute(requestId) {
135
+ return requestId === null ? undefined : String(requestId);
136
+ }
137
+ function startOperation(message, config, transportProtocolVersion) {
138
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message))
139
+ return undefined;
140
+ const method = normalizeMcpMethodName(message.method);
141
+ const tool = normalizedTool(message);
142
+ const protocolVersion = messageProtocolVersion(message) ?? transportProtocolVersion ?? config.protocolVersion;
143
+ const activeAttributes = {
144
+ "context7.mcp.route": config.route,
145
+ "mcp.method.name": method,
146
+ };
147
+ const attributes = {
148
+ ...activeAttributes,
149
+ "network.transport": config.networkTransport,
150
+ };
151
+ if (config.networkProtocol)
152
+ attributes["network.protocol.name"] = config.networkProtocol;
153
+ if (protocolVersion)
154
+ attributes["mcp.protocol.version"] = protocolVersion;
155
+ if (tool) {
156
+ activeAttributes["gen_ai.tool.name"] = tool;
157
+ attributes["gen_ai.tool.name"] = tool;
158
+ attributes["gen_ai.operation.name"] = "execute_tool";
159
+ }
160
+ const requestId = isJSONRPCRequest(message) ? message.id : undefined;
161
+ const parentContext = propagation.extract(ROOT_CONTEXT, mcpTraceCarrier(message));
162
+ const span = trace.getTracer(INSTRUMENTATION_NAME).startSpan(tool ? `${method} ${tool}` : method, {
163
+ attributes,
164
+ kind: SpanKind.SERVER,
165
+ links: ambientLink(parentContext),
166
+ }, parentContext);
167
+ if (requestId !== undefined) {
168
+ const requestIdValue = requestIdAttribute(requestId);
169
+ if (requestIdValue)
170
+ span.setAttribute("jsonrpc.request.id", requestIdValue);
171
+ }
172
+ const operation = {
173
+ activeAttributes,
174
+ attributes,
175
+ context: ROOT_CONTEXT,
176
+ requestId,
177
+ span,
178
+ state: "handling",
179
+ startedAt: performance.now(),
180
+ };
181
+ operation.context = trace.setSpan(parentContext, span);
182
+ mcpInstruments().activeOperations.add(1, activeAttributes);
183
+ return operation;
184
+ }
185
+ function finishOperation(operation) {
186
+ if (operation.state === "finished")
187
+ return;
188
+ operation.state = "finished";
189
+ const attributes = operation.errorType || operation.toolOutcome
190
+ ? { ...operation.attributes }
191
+ : operation.attributes;
192
+ if (operation.errorType)
193
+ attributes["error.type"] = operation.errorType;
194
+ if (operation.toolOutcome) {
195
+ attributes["context7.mcp.tool.outcome"] = operation.toolOutcome;
196
+ operation.span.setAttribute("context7.mcp.tool.outcome", operation.toolOutcome);
197
+ }
198
+ const { activeOperations, operationDuration } = mcpInstruments();
199
+ activeOperations.add(-1, operation.activeAttributes);
200
+ operationDuration.record(elapsedSeconds(operation.startedAt), attributes);
201
+ if (operation.errorType) {
202
+ operation.span.setAttribute("error.type", operation.errorType);
203
+ operation.span.setStatus({
204
+ code: SpanStatusCode.ERROR,
205
+ message: operation.statusDescription,
206
+ });
207
+ }
208
+ operation.span.end();
209
+ }
210
+ function operationIsFinished(operation) {
211
+ return operation.state === "finished";
212
+ }
213
+ function runOperation(operation, handler) {
214
+ return runInMcpOperationScope(operation, () => context.with(operation.context, handler));
215
+ }
216
+ export function classifyServerResponse(message, operationMethod) {
217
+ if (isJSONRPCErrorResponse(message)) {
218
+ return {
219
+ errorType: CALLER_FAULT_CODES.has(message.error.code)
220
+ ? undefined
221
+ : String(message.error.code),
222
+ rpcStatusCode: String(message.error.code),
223
+ statusDescription: message.error.message,
224
+ };
225
+ }
226
+ if (isJSONRPCResultResponse(message) &&
227
+ operationMethod === "tools/call" &&
228
+ isCallToolResult(message.result) &&
229
+ message.result.isError) {
230
+ return { errorType: "tool_error" };
231
+ }
232
+ return {};
233
+ }
234
+ function applyServerResponse(message, operation) {
235
+ const classification = classifyServerResponse(message, operation.attributes["mcp.method.name"]);
236
+ // A JSON-RPC error is the canonical final classification, including caller
237
+ // faults that intentionally clear a provisional server error. Successful
238
+ // envelopes retain an application-level tool_error captured by the tool
239
+ // wrapper when the SDK normalizes the result before transport serialization.
240
+ if (isJSONRPCErrorResponse(message) || classification.errorType) {
241
+ operation.errorType = classification.errorType;
242
+ }
243
+ if (classification.errorType && operation.attributes["mcp.method.name"] === "tools/call") {
244
+ operation.toolOutcome = "error";
245
+ }
246
+ operation.statusDescription = classification.statusDescription;
247
+ if (classification.rpcStatusCode) {
248
+ operation.attributes["rpc.response.status_code"] = classification.rpcStatusCode;
249
+ operation.span.setAttribute("rpc.response.status_code", classification.rpcStatusCode);
250
+ }
251
+ }
252
+ function cancellationRequestId(message) {
253
+ if (!isJSONRPCNotification(message) || message.method !== "notifications/cancelled") {
254
+ return undefined;
255
+ }
256
+ const requestId = asRecord(message.params)?.requestId;
257
+ return typeof requestId === "string" || typeof requestId === "number" ? requestId : undefined;
258
+ }
259
+ function configFromRequestContext(requestContext) {
260
+ const request = requestContext.requestInfo;
261
+ if (!request) {
262
+ return {
263
+ route: "stdio",
264
+ networkTransport: "pipe",
265
+ protocolVersion: requestContext.era === "modern" ? MODERN_MCP_PROTOCOL_VERSION : undefined,
266
+ };
267
+ }
268
+ return {
269
+ abortSignal: request.signal,
270
+ route: mcpRouteFromUrl(request.url),
271
+ networkProtocol: "http",
272
+ networkTransport: "tcp",
273
+ protocolVersion: normalizedProtocolVersion(request.headers.get("mcp-protocol-version")) ??
274
+ (requestContext.era === "modern" ? MODERN_MCP_PROTOCOL_VERSION : undefined),
275
+ };
276
+ }
277
+ function startSubscriptionEntryOperation(message, observation) {
278
+ const operation = startOperation(message, {
279
+ ...observation,
280
+ protocolVersion: normalizedProtocolVersion(observation.protocolVersion),
281
+ });
282
+ if (!operation)
283
+ throw new TypeError("Expected an MCP request or notification");
284
+ return {
285
+ applyResponse(response) {
286
+ if (!operationIsFinished(operation))
287
+ applyServerResponse(response, operation);
288
+ },
289
+ fail(errorType, error) {
290
+ if (operationIsFinished(operation))
291
+ return;
292
+ operation.errorType = errorType;
293
+ operation.statusDescription = error instanceof Error ? error.message : undefined;
294
+ if (error instanceof Error)
295
+ operation.span.recordException(error);
296
+ finishOperation(operation);
297
+ },
298
+ finish() {
299
+ finishOperation(operation);
300
+ },
301
+ run: (callback) => runOperation(operation, callback),
302
+ };
303
+ }
304
+ export function instrumentMcpHttpHandler(handler) {
305
+ return instrumentHttpSubscriptions(handler, startSubscriptionEntryOperation);
306
+ }
307
+ class InstrumentedTransport {
308
+ transport;
309
+ config;
310
+ abortSignal;
311
+ inFlight = new Map();
312
+ messageHandler;
313
+ closeHandler;
314
+ errorHandler;
315
+ protocolVersion;
316
+ constructor(transport, config) {
317
+ this.transport = transport;
318
+ this.config = config;
319
+ this.abortSignal = config.abortSignal;
320
+ this.protocolVersion = config.protocolVersion;
321
+ this.onclose = transport.onclose;
322
+ this.onerror = transport.onerror;
323
+ this.onmessage = transport.onmessage;
324
+ this.abortSignal?.addEventListener("abort", this.handleAbort, { once: true });
325
+ }
326
+ handleAbort = () => {
327
+ this.finishAll("cancelled", true);
328
+ };
329
+ get hasPerRequestStream() {
330
+ return this.transport.hasPerRequestStream;
331
+ }
332
+ get sessionId() {
333
+ return this.transport.sessionId;
334
+ }
335
+ set sessionId(value) {
336
+ this.transport.sessionId = value;
337
+ }
338
+ get onclose() {
339
+ return this.closeHandler;
340
+ }
341
+ set onclose(handler) {
342
+ this.closeHandler = handler;
343
+ this.transport.onclose = () => {
344
+ this.finishAll("connection_closed");
345
+ this.detachAbortHandler();
346
+ handler?.();
347
+ };
348
+ }
349
+ get onerror() {
350
+ return this.errorHandler;
351
+ }
352
+ set onerror(handler) {
353
+ this.errorHandler = handler;
354
+ this.transport.onerror = handler;
355
+ }
356
+ get onmessage() {
357
+ return this.messageHandler;
358
+ }
359
+ set onmessage(handler) {
360
+ this.messageHandler = handler;
361
+ this.transport.onmessage = handler
362
+ ? (message, extra) => this.receive(message, extra, handler)
363
+ : undefined;
364
+ }
365
+ setProtocolVersion = (version) => {
366
+ this.protocolVersion = normalizedProtocolVersion(version);
367
+ this.transport.setProtocolVersion?.(version);
368
+ };
369
+ setSupportedProtocolVersions = (versions) => {
370
+ this.transport.setSupportedProtocolVersions?.(versions);
371
+ };
372
+ async start() {
373
+ await this.transport.start();
374
+ }
375
+ async close() {
376
+ try {
377
+ await this.transport.close();
378
+ }
379
+ finally {
380
+ this.finishAll("connection_closed");
381
+ this.detachAbortHandler();
382
+ }
383
+ }
384
+ async send(message, options) {
385
+ const responseId = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message) ? message.id : undefined;
386
+ const operation = responseId !== undefined && responseId !== null ? this.inFlight.get(responseId) : undefined;
387
+ try {
388
+ if (!operation) {
389
+ await this.transport.send(message, options);
390
+ return;
391
+ }
392
+ applyServerResponse(message, operation);
393
+ operation.state = "sending";
394
+ await this.transport.send(message, options);
395
+ }
396
+ catch (error) {
397
+ if (operation && !operationIsFinished(operation)) {
398
+ operation.errorType = "transport_error";
399
+ operation.statusDescription = error instanceof Error ? error.message : undefined;
400
+ if (error instanceof Error)
401
+ operation.span.recordException(error);
402
+ }
403
+ throw error;
404
+ }
405
+ finally {
406
+ if (operation) {
407
+ this.removeInFlight(operation);
408
+ finishOperation(operation);
409
+ }
410
+ }
411
+ }
412
+ receive(message, extra, handler) {
413
+ const operation = startOperation(message, this.config, this.protocolVersion);
414
+ if (!operation) {
415
+ handler(message, extra);
416
+ return;
417
+ }
418
+ if (operation.requestId !== undefined && operation.requestId !== null) {
419
+ const previous = this.inFlight.get(operation.requestId);
420
+ if (previous) {
421
+ previous.errorType = "duplicate_request_id";
422
+ finishOperation(previous);
423
+ }
424
+ this.inFlight.set(operation.requestId, operation);
425
+ }
426
+ try {
427
+ runOperation(operation, () => handler(message, extra));
428
+ }
429
+ catch (error) {
430
+ operation.errorType = "handler_error";
431
+ if (error instanceof Error)
432
+ operation.span.recordException(error);
433
+ if (operation.requestId !== undefined && operation.requestId !== null) {
434
+ this.inFlight.delete(operation.requestId);
435
+ }
436
+ finishOperation(operation);
437
+ throw error;
438
+ }
439
+ if (isJSONRPCNotification(message)) {
440
+ const cancelledRequestId = cancellationRequestId(message);
441
+ if (cancelledRequestId !== undefined)
442
+ this.cancelOperation(cancelledRequestId);
443
+ finishOperation(operation);
444
+ }
445
+ }
446
+ cancelOperation(requestId) {
447
+ const operation = this.inFlight.get(requestId);
448
+ if (!operation)
449
+ return;
450
+ operation.errorType = "cancelled";
451
+ this.inFlight.delete(requestId);
452
+ finishOperation(operation);
453
+ }
454
+ removeInFlight(operation) {
455
+ const requestId = operation.requestId;
456
+ if (requestId !== undefined &&
457
+ requestId !== null &&
458
+ this.inFlight.get(requestId) === operation) {
459
+ this.inFlight.delete(requestId);
460
+ }
461
+ }
462
+ detachAbortHandler() {
463
+ this.abortSignal?.removeEventListener("abort", this.handleAbort);
464
+ }
465
+ finishAll(errorType, includeSending = false) {
466
+ for (const [requestId, operation] of this.inFlight) {
467
+ // A normal per-request HTTP transport closes its stream from inside send().
468
+ // Let an active send settle so its success or failure remains authoritative.
469
+ if (operation.state === "sending" && !includeSending)
470
+ continue;
471
+ operation.errorType ??= errorType;
472
+ finishOperation(operation);
473
+ this.inFlight.delete(requestId);
474
+ }
475
+ }
476
+ }
477
+ /**
478
+ * Owns session telemetry at the one process-level stdio wire. The SDK may
479
+ * create multiple products while probing protocol eras, but they all share
480
+ * this transport, which also receives the actually negotiated version.
481
+ */
482
+ class InstrumentedStdioTransport {
483
+ transport;
484
+ closeHandler;
485
+ closePromise;
486
+ closeRequested = false;
487
+ errorType;
488
+ errorHandler;
489
+ finished = false;
490
+ messageHandler;
491
+ pendingTerminalError = false;
492
+ protocolVersion;
493
+ startedAt = performance.now();
494
+ subscriptions = new StdioSubscriptionTelemetry(startSubscriptionEntryOperation, MODERN_MCP_PROTOCOL_VERSION);
495
+ constructor(transport) {
496
+ this.transport = transport;
497
+ this.onclose = transport.onclose;
498
+ this.onerror = transport.onerror;
499
+ this.onmessage = transport.onmessage;
500
+ }
501
+ get hasPerRequestStream() {
502
+ return this.transport.hasPerRequestStream;
503
+ }
504
+ get sessionId() {
505
+ return this.transport.sessionId;
506
+ }
507
+ set sessionId(value) {
508
+ this.transport.sessionId = value;
509
+ }
510
+ get onclose() {
511
+ return this.closeHandler;
512
+ }
513
+ set onclose(handler) {
514
+ this.closeHandler = handler;
515
+ this.transport.onclose = () => {
516
+ this.subscriptions.close("connection_closed");
517
+ if (!this.closeRequested) {
518
+ this.finish(this.pendingTerminalError ? "transport_error" : undefined);
519
+ }
520
+ handler?.();
521
+ };
522
+ }
523
+ get onerror() {
524
+ return this.errorHandler;
525
+ }
526
+ set onerror(handler) {
527
+ this.errorHandler = handler;
528
+ this.transport.onerror = (error) => {
529
+ this.pendingTerminalError = true;
530
+ queueMicrotask(() => {
531
+ this.pendingTerminalError = false;
532
+ });
533
+ handler?.(error);
534
+ };
535
+ }
536
+ get onmessage() {
537
+ return this.messageHandler;
538
+ }
539
+ set onmessage(handler) {
540
+ this.messageHandler = handler;
541
+ this.transport.onmessage = handler
542
+ ? (message, extra) => this.receive(message, extra, handler)
543
+ : undefined;
544
+ }
545
+ setProtocolVersion = (version) => {
546
+ this.protocolVersion = normalizedProtocolVersion(version);
547
+ this.transport.setProtocolVersion?.(version);
548
+ };
549
+ setSupportedProtocolVersions = (versions) => {
550
+ this.transport.setSupportedProtocolVersions?.(versions);
551
+ };
552
+ async start() {
553
+ try {
554
+ await this.transport.start();
555
+ }
556
+ catch (error) {
557
+ this.errorType = "transport_error";
558
+ this.finish("transport_error");
559
+ throw error;
560
+ }
561
+ }
562
+ close() {
563
+ if (!this.closePromise) {
564
+ this.closeRequested = true;
565
+ this.closePromise = this.closeTransport();
566
+ }
567
+ return this.closePromise;
568
+ }
569
+ async send(message, options) {
570
+ try {
571
+ await this.subscriptions.send(message, options, (outbound, sendOptions) => this.transport.send(outbound, sendOptions), this.protocolVersion);
572
+ }
573
+ catch (error) {
574
+ this.errorType ??= "transport_error";
575
+ throw error;
576
+ }
577
+ }
578
+ receive(message, extra, handler) {
579
+ this.protocolVersion = messageProtocolVersion(message) ?? this.protocolVersion;
580
+ if (this.subscriptions.receive(message, extra, handler, this.protocolVersion))
581
+ return;
582
+ handler(message, extra);
583
+ }
584
+ async closeTransport() {
585
+ try {
586
+ await this.transport.close();
587
+ this.subscriptions.close("connection_closed", true);
588
+ this.finish();
589
+ }
590
+ catch (error) {
591
+ this.errorType = "transport_error";
592
+ this.subscriptions.close("transport_error", true);
593
+ this.finish("transport_error");
594
+ throw error;
595
+ }
596
+ }
597
+ finish(errorType = this.errorType) {
598
+ if (this.finished)
599
+ return;
600
+ this.finished = true;
601
+ const attributes = { "network.transport": "pipe" };
602
+ if (this.protocolVersion)
603
+ attributes["mcp.protocol.version"] = this.protocolVersion;
604
+ if (errorType)
605
+ attributes["error.type"] = errorType;
606
+ mcpInstruments().sessionDuration.record(elapsedSeconds(this.startedAt), attributes);
607
+ }
608
+ }
609
+ export function instrumentStdioTransport(transport) {
610
+ return new InstrumentedStdioTransport(transport);
611
+ }
612
+ /**
613
+ * High-level MCP server with protocol-aware OpenTelemetry at the SDK transport
614
+ * boundary. This observes individual JSON-RPC operations for HTTP and stdio,
615
+ * including batched messages, instead of treating an HTTP envelope as one MCP
616
+ * operation.
617
+ */
618
+ export class InstrumentedMcpServer extends McpServer {
619
+ requestContext;
620
+ constructor(serverInfo, options, requestContext) {
621
+ super(serverInfo, options);
622
+ this.requestContext = requestContext;
623
+ }
624
+ connect(transport) {
625
+ return super.connect(new InstrumentedTransport(transport, configFromRequestContext(this.requestContext)));
626
+ }
627
+ }
@@ -0,0 +1,63 @@
1
+ const DEFAULT_CLOSE_TIMEOUT_MS = 5_000;
2
+ const DEFAULT_FLUSH_TIMEOUT_MS = 5_000;
3
+ async function withTimeout(operation, timeoutMs, description) {
4
+ let timeout;
5
+ try {
6
+ await Promise.race([
7
+ operation(),
8
+ new Promise((_resolve, reject) => {
9
+ timeout = setTimeout(() => reject(new Error(`${description} exceeded ${timeoutMs}ms`)), timeoutMs);
10
+ }),
11
+ ]);
12
+ }
13
+ finally {
14
+ if (timeout)
15
+ clearTimeout(timeout);
16
+ }
17
+ }
18
+ /**
19
+ * Coordinates one bounded, idempotent process shutdown for HTTP and stdio.
20
+ * Passing an input lifecycle additionally treats stdio EOF as termination.
21
+ */
22
+ export function installProcessShutdown(handle, options = {}) {
23
+ const signals = options.signals ?? process;
24
+ const exit = options.exit ?? ((code) => process.exit(code));
25
+ const closeTimeoutMs = options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
26
+ const flushTimeoutMs = options.flushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS;
27
+ let shutdownPromise;
28
+ const reportError = (error) => {
29
+ try {
30
+ options.onerror?.(error);
31
+ }
32
+ catch {
33
+ // A reporting callback must not prevent shutdown.
34
+ }
35
+ };
36
+ const shutdown = () => {
37
+ shutdownPromise ??= (async () => {
38
+ let exitCode = 0;
39
+ try {
40
+ await withTimeout(() => handle.close(), closeTimeoutMs, "Server close");
41
+ }
42
+ catch (error) {
43
+ exitCode = 1;
44
+ reportError(error);
45
+ }
46
+ try {
47
+ if (options.flush) {
48
+ await withTimeout(options.flush, flushTimeoutMs, "OpenTelemetry flush");
49
+ }
50
+ }
51
+ catch (error) {
52
+ reportError(error);
53
+ }
54
+ exit(exitCode);
55
+ })();
56
+ };
57
+ options.input?.once("end", shutdown);
58
+ options.input?.once("close", shutdown);
59
+ signals.once("SIGHUP", shutdown);
60
+ signals.once("SIGINT", shutdown);
61
+ signals.once("SIGTERM", shutdown);
62
+ return shutdown;
63
+ }
@@ -0,0 +1,14 @@
1
+ export function telemetryIsDisabled(environment = process.env) {
2
+ return environment.OTEL_SDK_DISABLED?.trim().toLowerCase() === "true";
3
+ }
4
+ export function embeddedPrometheusIsEnabled(environment = process.env) {
5
+ if (telemetryIsDisabled(environment))
6
+ return false;
7
+ const configuredExporters = environment.OTEL_METRICS_EXPORTER;
8
+ if (!configuredExporters)
9
+ return true;
10
+ return configuredExporters
11
+ .split(",")
12
+ .map((value) => value.trim().toLowerCase())
13
+ .includes("prometheus");
14
+ }