@upstash/context7-mcp
Version:
MCP server for Context7
243 lines (242 loc) • 9.88 kB
JavaScript
import { metrics, trace } from "@opentelemetry/api";
import { markCurrentMcpOperationError, markCurrentMcpToolOutcome } from "./mcp-operation-scope.js";
const METER_NAME = "io.github.upstash.context7.mcp";
const DURATION_BUCKETS_SECONDS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
const TIMEOUT_ERROR_CODES = new Set([
"ETIMEDOUT",
"UND_ERR_BODY_TIMEOUT",
"UND_ERR_CONNECT_TIMEOUT",
"UND_ERR_HEADERS_TIMEOUT",
]);
const NETWORK_ERROR_CODES = new Set([
"EAI_AGAIN",
"ECONNREFUSED",
"ECONNRESET",
"EHOSTUNREACH",
"ENETUNREACH",
"ENOTFOUND",
"EPIPE",
]);
function createInstruments() {
const meter = metrics.getMeter(METER_NAME);
return {
upstreamRequests: meter.createCounter("context7.mcp.upstream.requests", {
description: "Number of requests made to Context7 dependencies",
unit: "{request}",
}),
upstreamRequestDuration: meter.createHistogram("context7.mcp.upstream.request.duration", {
description: "Duration of requests made to Context7 dependencies",
unit: "s",
advice: { explicitBucketBoundaries: DURATION_BUCKETS_SECONDS },
}),
activeUpstreamRequests: meter.createUpDownCounter("context7.mcp.upstream.requests.active", {
description: "Number of requests to Context7 dependencies currently in flight",
unit: "{request}",
}),
authenticationAttempts: meter.createCounter("context7.mcp.authentication.attempts", {
description: "Number of hosted MCP authentication decisions",
unit: "{attempt}",
}),
authenticationEvents: meter.createCounter("context7.mcp.authentication.events", {
description: "Number of hosted MCP authentication lifecycle events",
unit: "{event}",
}),
selfCancellingBatches: meter.createCounter("context7.mcp.batches.self_cancelling", {
description: "JSON-RPC batches in which a request was cancelled by the same batch",
unit: "{batch}",
}),
authenticationDuration: meter.createHistogram("context7.mcp.authentication.duration", {
description: "Duration of hosted MCP authentication decisions",
unit: "s",
advice: { explicitBucketBoundaries: DURATION_BUCKETS_SECONDS },
}),
activeAuthentications: meter.createUpDownCounter("context7.mcp.authentication.active", {
description: "Number of hosted MCP requests currently authenticating",
unit: "{request}",
}),
};
}
let instruments;
function getInstruments() {
instruments ??= createInstruments();
return instruments;
}
function elapsedSeconds(startedAt) {
return (performance.now() - startedAt) / 1_000;
}
function statusClass(statusCode) {
if (statusCode >= 100 && statusCode <= 599) {
return `${Math.floor(statusCode / 100)}xx`;
}
return "unknown";
}
export function classifyUpstreamError(error, abortSignal, fallback = "network_error") {
let timeout = false;
let cancelled = false;
let networkError = false;
const rootCount = abortSignal?.aborted ? 2 : 1;
for (let rootIndex = 0; rootIndex < rootCount; rootIndex += 1) {
let value = rootIndex === 0 ? error : abortSignal?.reason;
for (let depth = 0; value && typeof value === "object" && depth < 8; depth += 1) {
const current = value;
const name = current.name;
const code = current.code;
if (name === "TimeoutError" || (typeof code === "string" && TIMEOUT_ERROR_CODES.has(code))) {
timeout = true;
}
else if (name === "AbortError" || code === "ABORT_ERR") {
cancelled = true;
}
else if (typeof code === "string" &&
(code.startsWith("UND_ERR_") || NETWORK_ERROR_CODES.has(code))) {
networkError = true;
}
value = current.cause;
}
}
if (timeout)
return "timeout";
if (cancelled || abortSignal?.aborted)
return "cancelled";
if (networkError)
return "network_error";
return fallback;
}
export function recordToolCallOutcome(outcome) {
if (outcome === "error")
markCurrentMcpOperationError();
markCurrentMcpToolOutcome(outcome);
}
export async function observeUpstreamRequest(operationName, request, consumeResponse, options = {}) {
const { activeUpstreamRequests, upstreamRequestDuration, upstreamRequests } = getInstruments();
const activeAttributes = { "context7.upstream.operation": operationName };
const startedAt = performance.now();
let outcome = "network_error";
let responseStatus;
let responseStatusClass = "none";
activeUpstreamRequests.add(1, activeAttributes);
try {
let response;
try {
response = await request();
}
catch (error) {
outcome = classifyUpstreamError(error, options.abortSignal);
throw error;
}
responseStatus = response.status;
responseStatusClass = statusClass(response.status);
outcome = response.ok ? "success" : "http_error";
try {
return await consumeResponse(response);
}
catch (error) {
outcome = classifyUpstreamError(error, options.abortSignal, "response_error");
throw error;
}
}
finally {
const attributes = {
...activeAttributes,
"http.response.status_code_class": responseStatusClass,
"context7.upstream.outcome": outcome,
};
if (responseStatus !== undefined) {
attributes["http.response.status_code"] = responseStatus;
}
activeUpstreamRequests.add(-1, activeAttributes);
upstreamRequests.add(1, attributes);
upstreamRequestDuration.record(elapsedSeconds(startedAt), attributes);
}
}
function flushableProvider(provider) {
let current = provider;
for (let depth = 0; current && typeof current === "object" && depth < 4; depth += 1) {
if ("forceFlush" in current && typeof current.forceFlush === "function") {
return current;
}
if (!("getDelegate" in current) || typeof current.getDelegate !== "function")
return undefined;
const delegate = current.getDelegate();
if (delegate === current)
return undefined;
current = delegate;
}
return undefined;
}
export async function forceFlushTelemetry() {
const providers = [
flushableProvider(metrics.getMeterProvider()),
flushableProvider(trace.getTracerProvider()),
].filter((provider) => provider !== undefined);
const results = await Promise.allSettled(providers.map((provider) => provider.forceFlush.call(provider)));
const failures = results
.filter((result) => result.status === "rejected")
.map((result) => result.reason);
if (failures.length > 0) {
throw new AggregateError(failures, "OpenTelemetry providers failed to flush during shutdown");
}
}
export async function observeAuthentication(options, operation) {
const { activeAuthentications, authenticationAttempts, authenticationDuration, authenticationEvents, } = getInstruments();
const activeAttributes = { "context7.mcp.route": options.route };
const startedAt = performance.now();
let outcome = "error";
activeAuthentications.add(1, activeAttributes);
try {
const observed = await operation();
outcome = observed.outcome;
const attributes = {
...activeAttributes,
"context7.authentication.enforcement": options.enforcementMode,
"context7.authentication.event": observed.event,
"context7.authentication.method": observed.method,
"context7.authentication.outcome": outcome,
};
authenticationEvents.add(1, attributes);
return observed;
}
finally {
const attributes = {
...activeAttributes,
"context7.authentication.enforcement": options.enforcementMode,
"context7.authentication.outcome": outcome,
};
activeAuthentications.add(-1, activeAttributes);
authenticationAttempts.add(1, attributes);
authenticationDuration.record(elapsedSeconds(startedAt), attributes);
}
}
const MAX_BATCH_CLIENT_NAMES = 50;
const batchClientNames = new Set();
/**
* Counts a batch that cancelled its own request, by the product token of the
* caller's User-Agent. The header is caller-controlled, so the label set is
* capped per process; later names are counted as "other".
*/
export function recordSelfCancellingBatch(userAgent) {
let client = /^[\w.-]{1,40}/.exec(userAgent ?? "")?.[0].toLowerCase() ?? "unknown";
if (!batchClientNames.has(client)) {
if (batchClientNames.size >= MAX_BATCH_CLIENT_NAMES)
client = "other";
else
batchClientNames.add(client);
}
getInstruments().selfCancellingBatches.add(1, { "user_agent.name": client });
}
export function recordAuthenticationEvent(observation) {
const attributes = {
"context7.authentication.enforcement": observation.enforcementMode,
"context7.authentication.event": observation.event,
"context7.authentication.method": observation.method,
"context7.mcp.route": observation.route,
};
if (observation.outcome) {
attributes["context7.authentication.outcome"] = observation.outcome;
}
if (observation.oauthClientRegistration) {
attributes["context7.authentication.oauth_client_registration"] =
observation.oauthClientRegistration;
}
getInstruments().authenticationEvents.add(1, attributes);
}