@upstash/context7-mcp 4.0.7 → 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,466 @@
1
+ import { classifyInboundRequest, isJSONRPCErrorResponse, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResultResponse, SUBSCRIPTION_ID_META_KEY, } from "@modelcontextprotocol/server";
2
+ import { metrics } from "@opentelemetry/api";
3
+ const INSTRUMENTATION_NAME = "io.github.upstash.context7.mcp";
4
+ const SUBSCRIPTION_DURATION_BUCKETS_SECONDS = [
5
+ 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300,
6
+ ];
7
+ function createInstruments() {
8
+ const meter = metrics.getMeter(INSTRUMENTATION_NAME);
9
+ return {
10
+ activeSubscriptions: meter.createUpDownCounter("context7.mcp.subscriptions.active", {
11
+ description: "Number of accepted MCP subscriptions currently open",
12
+ unit: "{subscription}",
13
+ }),
14
+ subscriptionDuration: meter.createHistogram("context7.mcp.subscription.duration", {
15
+ description: "Duration of an accepted MCP subscription",
16
+ unit: "s",
17
+ advice: { explicitBucketBoundaries: SUBSCRIPTION_DURATION_BUCKETS_SECONDS },
18
+ }),
19
+ };
20
+ }
21
+ let instruments;
22
+ function getInstruments() {
23
+ instruments ??= createInstruments();
24
+ return instruments;
25
+ }
26
+ function asRecord(value) {
27
+ return value && typeof value === "object" && !Array.isArray(value)
28
+ ? value
29
+ : undefined;
30
+ }
31
+ function elapsedSeconds(startedAt) {
32
+ return (performance.now() - startedAt) / 1_000;
33
+ }
34
+ export class SubscriptionLifecycle {
35
+ onFinish;
36
+ abortSignal;
37
+ metricAttributes;
38
+ finished = false;
39
+ startedAt = performance.now();
40
+ constructor(observation, onFinish) {
41
+ this.onFinish = onFinish;
42
+ this.abortSignal = observation.abortSignal;
43
+ this.metricAttributes = {
44
+ "context7.mcp.route": observation.route,
45
+ "network.transport": observation.networkTransport,
46
+ ...(observation.protocolVersion
47
+ ? { "mcp.protocol.version": observation.protocolVersion }
48
+ : {}),
49
+ };
50
+ getInstruments().activeSubscriptions.add(1, this.metricAttributes);
51
+ this.abortSignal?.addEventListener("abort", this.handleAbort, { once: true });
52
+ }
53
+ handleAbort = () => {
54
+ this.finish("cancelled");
55
+ };
56
+ finish(outcome) {
57
+ if (this.finished)
58
+ return;
59
+ this.finished = true;
60
+ this.abortSignal?.removeEventListener("abort", this.handleAbort);
61
+ const { activeSubscriptions, subscriptionDuration } = getInstruments();
62
+ activeSubscriptions.add(-1, this.metricAttributes);
63
+ subscriptionDuration.record(elapsedSeconds(this.startedAt), {
64
+ ...this.metricAttributes,
65
+ "context7.mcp.subscription.outcome": outcome,
66
+ });
67
+ this.onFinish?.();
68
+ }
69
+ }
70
+ export function subscriptionAcknowledgementId(message) {
71
+ if (!isJSONRPCNotification(message) ||
72
+ message.method !== "notifications/subscriptions/acknowledged") {
73
+ return undefined;
74
+ }
75
+ const subscriptionId = asRecord(asRecord(message.params)?._meta)?.[SUBSCRIPTION_ID_META_KEY];
76
+ return typeof subscriptionId === "string" || typeof subscriptionId === "number"
77
+ ? subscriptionId
78
+ : undefined;
79
+ }
80
+ export function completedSubscriptionId(message) {
81
+ if (!isJSONRPCResultResponse(message))
82
+ return undefined;
83
+ const result = asRecord(message.result);
84
+ if (result?.resultType !== "complete")
85
+ return undefined;
86
+ const subscriptionId = asRecord(result._meta)?.[SUBSCRIPTION_ID_META_KEY];
87
+ return typeof subscriptionId === "string" || typeof subscriptionId === "number"
88
+ ? subscriptionId
89
+ : undefined;
90
+ }
91
+ function cancellationRequestId(message) {
92
+ if (!isJSONRPCNotification(message) || message.method !== "notifications/cancelled") {
93
+ return undefined;
94
+ }
95
+ const requestId = asRecord(message.params)?.requestId;
96
+ return typeof requestId === "string" || typeof requestId === "number" ? requestId : undefined;
97
+ }
98
+ /** Owns the entry-handled listen/cancel state that never reaches an MCP server transport. */
99
+ export class StdioSubscriptionTelemetry {
100
+ startOperation;
101
+ modernProtocolVersion;
102
+ connectionClosed = false;
103
+ states = new Map();
104
+ constructor(startOperation, modernProtocolVersion) {
105
+ this.startOperation = startOperation;
106
+ this.modernProtocolVersion = modernProtocolVersion;
107
+ }
108
+ receive(message, extra, handler, protocolVersion) {
109
+ const observation = this.observation(protocolVersion);
110
+ if (protocolVersion === this.modernProtocolVersion &&
111
+ isJSONRPCRequest(message) &&
112
+ message.method === "subscriptions/listen") {
113
+ const operation = this.startOperation(message, observation);
114
+ const state = this.stateFor(message.id);
115
+ const attempt = { cancelRequested: false, operation, phase: "pending" };
116
+ state.attempts.push(attempt);
117
+ try {
118
+ operation.run(() => handler(message, extra));
119
+ }
120
+ catch (error) {
121
+ operation.fail("handler_error", error);
122
+ this.removeAttempt(message.id, state, attempt);
123
+ throw error;
124
+ }
125
+ return true;
126
+ }
127
+ const cancelledId = cancellationRequestId(message);
128
+ const state = cancelledId === undefined ? undefined : this.states.get(cancelledId);
129
+ const pendingAttempt = state?.attempts.at(-1);
130
+ const activeSubscription = state?.subscription !== undefined &&
131
+ !state.subscription.acknowledgementWritePending &&
132
+ !state.subscription.terminalWritePending;
133
+ if (protocolVersion !== this.modernProtocolVersion ||
134
+ cancelledId === undefined ||
135
+ (!pendingAttempt && !activeSubscription)) {
136
+ return false;
137
+ }
138
+ const operation = this.startOperation(message, observation);
139
+ try {
140
+ operation.run(() => handler(message, extra));
141
+ if (pendingAttempt) {
142
+ pendingAttempt.cancelRequested = true;
143
+ }
144
+ else if (state?.subscription && activeSubscription) {
145
+ state.subscription.lifecycle.finish("cancelled");
146
+ state.subscription = undefined;
147
+ this.prune(cancelledId, state);
148
+ }
149
+ operation.finish();
150
+ }
151
+ catch (error) {
152
+ operation.fail("handler_error", error);
153
+ throw error;
154
+ }
155
+ return true;
156
+ }
157
+ async send(message, options, send, protocolVersion) {
158
+ const acknowledgementId = subscriptionAcknowledgementId(message);
159
+ const rejectionId = isJSONRPCErrorResponse(message) ? message.id : undefined;
160
+ const attemptId = acknowledgementId ?? rejectionId;
161
+ const state = attemptId === undefined ? undefined : this.states.get(attemptId);
162
+ const pendingAttempt = state?.attempts[0];
163
+ const attempt = pendingAttempt?.phase === "pending" ? pendingAttempt : undefined;
164
+ const completionId = completedSubscriptionId(message);
165
+ const completionState = completionId === undefined ? undefined : this.states.get(completionId);
166
+ const completing = completionState?.subscription && !completionState.subscription.terminalWritePending
167
+ ? completionState.subscription
168
+ : undefined;
169
+ let acknowledged;
170
+ if (attempt && acknowledgementId !== undefined && state) {
171
+ state.subscription?.lifecycle.finish("replaced");
172
+ acknowledged = {
173
+ acknowledgementWritePending: true,
174
+ lifecycle: new SubscriptionLifecycle(this.observation(protocolVersion)),
175
+ terminalWritePending: false,
176
+ };
177
+ state.subscription = acknowledged;
178
+ attempt.phase = "acknowledging";
179
+ }
180
+ else if (attempt && rejectionId !== undefined && state) {
181
+ attempt.operation.applyResponse(message);
182
+ attempt.phase = "rejecting";
183
+ }
184
+ if (completing && completionState) {
185
+ completing.terminalWritePending = true;
186
+ }
187
+ try {
188
+ await send(message, options);
189
+ if (attempt && acknowledgementId !== undefined && state && acknowledged) {
190
+ this.settleAcknowledgement(acknowledgementId, state, attempt, acknowledged, false);
191
+ }
192
+ else if (attempt && rejectionId !== undefined && state) {
193
+ this.settleRejection(rejectionId, state, attempt);
194
+ }
195
+ if (completionId !== undefined && completionState && completing) {
196
+ completing.lifecycle.finish("completed");
197
+ if (completionState.subscription === completing) {
198
+ completionState.subscription = undefined;
199
+ }
200
+ this.prune(completionId, completionState);
201
+ }
202
+ }
203
+ catch (error) {
204
+ attempt?.operation.fail("transport_error", error);
205
+ if (attempt && acknowledgementId !== undefined && state && acknowledged) {
206
+ this.settleAcknowledgement(acknowledgementId, state, attempt, acknowledged, true);
207
+ }
208
+ else if (attempt && rejectionId !== undefined && state) {
209
+ this.settleRejection(rejectionId, state, attempt);
210
+ }
211
+ if (completionId !== undefined && completionState && completing) {
212
+ completing.lifecycle.finish("transport_error");
213
+ if (completionState.subscription === completing) {
214
+ completionState.subscription = undefined;
215
+ }
216
+ this.prune(completionId, completionState);
217
+ }
218
+ throw error;
219
+ }
220
+ finally {
221
+ attempt?.operation.finish();
222
+ }
223
+ }
224
+ close(outcome, includeSending = false) {
225
+ this.connectionClosed = true;
226
+ for (const [requestId, state] of this.states) {
227
+ state.attempts = state.attempts.filter((attempt) => {
228
+ if (!includeSending && attempt.phase !== "pending")
229
+ return true;
230
+ attempt.operation.fail(outcome);
231
+ return false;
232
+ });
233
+ const subscription = state.subscription;
234
+ if (subscription &&
235
+ (includeSending ||
236
+ (!subscription.acknowledgementWritePending && !subscription.terminalWritePending))) {
237
+ subscription.lifecycle.finish(outcome);
238
+ state.subscription = undefined;
239
+ }
240
+ this.prune(requestId, state);
241
+ }
242
+ }
243
+ settleAcknowledgement(requestId, state, attempt, subscription, sendFailed) {
244
+ this.removeAttempt(requestId, state, attempt);
245
+ if (state.subscription !== subscription)
246
+ return;
247
+ subscription.acknowledgementWritePending = false;
248
+ if (subscription.terminalWritePending) {
249
+ this.prune(requestId, state);
250
+ return;
251
+ }
252
+ if (attempt.cancelRequested) {
253
+ subscription.lifecycle.finish("cancelled");
254
+ state.subscription = undefined;
255
+ }
256
+ else if (this.connectionClosed) {
257
+ subscription.lifecycle.finish(sendFailed ? "transport_error" : "connection_closed");
258
+ state.subscription = undefined;
259
+ }
260
+ // The SDK retains an accepted subscription even when only the ACK write
261
+ // fails, so it must continue consuming capacity until cancel/close.
262
+ this.prune(requestId, state);
263
+ }
264
+ settleRejection(requestId, state, attempt) {
265
+ this.removeAttempt(requestId, state, attempt);
266
+ // The queued cancellation is processed after this rejected listen. It can
267
+ // only cancel a pre-existing subscription; otherwise it is a no-op.
268
+ if (attempt.cancelRequested &&
269
+ state.subscription &&
270
+ !state.subscription.acknowledgementWritePending &&
271
+ !state.subscription.terminalWritePending) {
272
+ state.subscription.lifecycle.finish("cancelled");
273
+ state.subscription = undefined;
274
+ }
275
+ this.prune(requestId, state);
276
+ }
277
+ removeAttempt(requestId, state, attempt) {
278
+ const index = state.attempts.findIndex((candidate) => candidate.operation === attempt.operation);
279
+ if (index !== -1)
280
+ state.attempts.splice(index, 1);
281
+ this.prune(requestId, state);
282
+ }
283
+ stateFor(requestId) {
284
+ let state = this.states.get(requestId);
285
+ if (!state) {
286
+ state = { attempts: [] };
287
+ this.states.set(requestId, state);
288
+ }
289
+ return state;
290
+ }
291
+ prune(requestId, state) {
292
+ if (state.attempts.length === 0 &&
293
+ !state.subscription &&
294
+ this.states.get(requestId) === state) {
295
+ this.states.delete(requestId);
296
+ }
297
+ }
298
+ observation(protocolVersion) {
299
+ return { networkTransport: "pipe", protocolVersion, route: "stdio" };
300
+ }
301
+ }
302
+ export function mcpRouteFromUrl(url) {
303
+ const pathname = new URL(url).pathname.replace(/\/+$/, "").toLowerCase();
304
+ return pathname === "/mcp/oauth" ? "oauth" : "anonymous";
305
+ }
306
+ async function modernListenRequest(request, options) {
307
+ let body = options?.parsedBody;
308
+ if (body === undefined) {
309
+ // Avoid cloning and decoding every request. Modern clients identify this
310
+ // method in the standard header, and Node adapters pass parsedBody anyway.
311
+ if (request.headers.get("mcp-method") !== "subscriptions/listen")
312
+ return undefined;
313
+ try {
314
+ body = await request.clone().json();
315
+ }
316
+ catch {
317
+ return undefined;
318
+ }
319
+ }
320
+ if (!isJSONRPCRequest(body) || body.method !== "subscriptions/listen")
321
+ return undefined;
322
+ const classified = classifyInboundRequest({
323
+ body,
324
+ httpMethod: request.method,
325
+ mcpMethodHeader: request.headers.get("mcp-method") ?? undefined,
326
+ mcpNameHeader: request.headers.get("mcp-name") ?? undefined,
327
+ protocolVersionHeader: request.headers.get("mcp-protocol-version") ?? undefined,
328
+ });
329
+ if (classified.kind !== "modern" ||
330
+ classified.messageKind !== "request" ||
331
+ classified.message.method !== "subscriptions/listen") {
332
+ return undefined;
333
+ }
334
+ return {
335
+ message: classified.message,
336
+ protocolVersion: classified.classification.revision,
337
+ };
338
+ }
339
+ function isSubscriptionStream(response) {
340
+ return (response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream") === true);
341
+ }
342
+ function wrapSubscriptionStream(response, lifecycle, abortSignal) {
343
+ const source = response.body;
344
+ if (!source) {
345
+ lifecycle.finish("transport_error");
346
+ return response;
347
+ }
348
+ const reader = source.getReader();
349
+ const observed = new ReadableStream({
350
+ async pull(controller) {
351
+ try {
352
+ const chunk = await reader.read();
353
+ if (chunk.done) {
354
+ lifecycle.finish("completed");
355
+ controller.close();
356
+ return;
357
+ }
358
+ controller.enqueue(chunk.value);
359
+ }
360
+ catch (error) {
361
+ lifecycle.finish(abortSignal.aborted ? "cancelled" : "transport_error");
362
+ controller.error(error);
363
+ }
364
+ },
365
+ async cancel(reason) {
366
+ lifecycle.finish("cancelled");
367
+ await reader.cancel(reason);
368
+ },
369
+ });
370
+ return new Response(observed, {
371
+ headers: response.headers,
372
+ status: response.status,
373
+ statusText: response.statusText,
374
+ });
375
+ }
376
+ /**
377
+ * Observes only the MCP v2 listen route that createMcpHandler serves before it
378
+ * connects a server transport. All other HTTP traffic delegates untouched, so
379
+ * Envoy remains the owner of generic inbound HTTP telemetry.
380
+ */
381
+ export function instrumentMcpHttpHandler(handler, startOperation) {
382
+ const subscriptions = new Set();
383
+ const finishSubscriptions = (outcome) => {
384
+ for (const subscription of subscriptions)
385
+ subscription.finish(outcome);
386
+ };
387
+ return {
388
+ bus: handler.bus,
389
+ notify: handler.notify,
390
+ close: async () => {
391
+ try {
392
+ await handler.close();
393
+ finishSubscriptions("completed");
394
+ }
395
+ catch (error) {
396
+ finishSubscriptions("transport_error");
397
+ throw error;
398
+ }
399
+ },
400
+ fetch: async (request, options) => {
401
+ const listen = await modernListenRequest(request, options);
402
+ if (!listen)
403
+ return handler.fetch(request, options);
404
+ const observation = {
405
+ abortSignal: request.signal,
406
+ networkProtocol: "http",
407
+ networkTransport: "tcp",
408
+ protocolVersion: listen.protocolVersion,
409
+ route: mcpRouteFromUrl(request.url),
410
+ };
411
+ const operation = startOperation(listen.message, observation);
412
+ const abortOperation = () => operation.fail("cancelled");
413
+ request.signal.addEventListener("abort", abortOperation, { once: true });
414
+ if (request.signal.aborted)
415
+ abortOperation();
416
+ try {
417
+ const response = await operation.run(() => handler.fetch(request, options));
418
+ if (!isSubscriptionStream(response)) {
419
+ try {
420
+ const message = await response.clone().json();
421
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
422
+ operation.applyResponse(message);
423
+ operation.finish();
424
+ }
425
+ else if (response.status >= 500) {
426
+ operation.fail(`http_${response.status}`);
427
+ }
428
+ else {
429
+ operation.finish();
430
+ }
431
+ }
432
+ catch {
433
+ if (response.status >= 500)
434
+ operation.fail(`http_${response.status}`);
435
+ else
436
+ operation.finish();
437
+ }
438
+ return response;
439
+ }
440
+ // The SDK enqueues the mandatory acknowledgement before resolving
441
+ // fetch. The semantic operation therefore ends here; only the custom
442
+ // subscription lifecycle remains active for the SSE stream lifetime.
443
+ operation.finish();
444
+ let lifecycle;
445
+ lifecycle = new SubscriptionLifecycle(observation, () => subscriptions.delete(lifecycle));
446
+ subscriptions.add(lifecycle);
447
+ if (request.signal.aborted)
448
+ lifecycle.finish("cancelled");
449
+ try {
450
+ return wrapSubscriptionStream(response, lifecycle, request.signal);
451
+ }
452
+ catch (error) {
453
+ lifecycle.finish("transport_error");
454
+ throw error;
455
+ }
456
+ }
457
+ catch (error) {
458
+ operation.fail(request.signal.aborted ? "cancelled" : "handler_error", error);
459
+ throw error;
460
+ }
461
+ finally {
462
+ request.signal.removeEventListener("abort", abortOperation);
463
+ }
464
+ },
465
+ };
466
+ }