/** * Sentry SDK integration for MCP servers. * * Provides error tracking and performance monitoring for MCP protocol tools * while filtering out normal error responses (isError: true). * * Usage: * import { initSentry } from "./sentry.js"; * * initSentry(process.env.ENVIRONMENT || "development"); */ import * as Sentry from "@sentry/node"; // Profiling integration removed due to type incompatibilities (CF-567) export function initSentry(environment: string = "development"): void { const dsn = process.env.SENTRY_DSN || ""; if (!dsn) { console.error("SENTRY_DSN not set, Sentry disabled"); return; } Sentry.init({ dsn, environment, tracesSampleRate: parseFloat(process.env.SENTRY_TRACE_SAMPLE_RATE || "0.1"), profilesSampleRate: parseFloat( process.env.SENTRY_PROFILE_SAMPLE_RATE || "0.01" ), integrations: [ Sentry.httpIntegration(), Sentry.postgresIntegration(), ], beforeSend(event: Sentry.ErrorEvent, hint: Sentry.EventHint) { // MCP protocol: Don't send normal error responses (isError: true) const originalException = hint.originalException as any; if (originalException?.isError === true) { return null; // This is a normal MCP error response, not an exception } // Strip PII from headers if (event.request?.headers) { const headers = { ...event.request.headers }; delete headers.Authorization; delete headers.Cookie; delete headers["X-API-Key"]; event.request.headers = headers; } // Redact sensitive data in request body if (event.request?.data) { let dataStr = JSON.stringify(event.request.data); // Redact API keys dataStr = dataStr.replace( /(api[_-]?key|token|secret)["']?\s*[:=]\s*["']?[\w-]+/gi, "$1=REDACTED" ); // Redact emails dataStr = dataStr.replace( /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "EMAIL_REDACTED" ); try { event.request.data = JSON.parse(dataStr); } catch { event.request.data = dataStr; } } return event; }, enableLogs: true, beforeSendLog(log) { if (log.level === "debug") return null; return log; }, maxBreadcrumbs: 30, attachStacktrace: true, release: process.env.APP_VERSION || "unknown", sendDefaultPii: false, // Never send PII automatically }); console.log( `✅ Sentry initialized: ${environment} - ${process.env.APP_VERSION || "unknown"}` ); } export function logInfo(msg: string, data?: Record): void { Sentry.logger.info(msg, data); } export function logWarn(msg: string, data?: Record): void { Sentry.logger.warn(msg, data); } export function logError(msg: string, data?: Record): void { Sentry.logger.error(msg, data); } /** * Wrap MCP tool handler with Sentry transaction tracking. * * Usage: * const result = await withSentryTransaction("tool_name", async () => { * return await performToolOperation(); * }); */ export async function withSentryTransaction( toolName: string, handler: () => Promise ): Promise { return Sentry.startSpan( { name: `tool_${toolName}`, op: "mcp.tool" }, async (span: Sentry.Span) => { try { const result = await handler(); span.setStatus({ code: 1, message: "ok" }); // SpanStatusCode.OK return result; } catch (error: unknown) { // Capture exception to Sentry (unless it's a normal MCP error) const err = error as { isError?: boolean }; if (!err.isError) { Sentry.captureException(error); } span.setStatus({ code: 2, message: "error" }); // SpanStatusCode.ERROR throw error; } } ); }