@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
183 lines (159 loc) • 6.07 kB
JavaScript
import { getIsolationScope, withIsolationScope } from '../../currentScopes.js';
import { fill } from '../../utils/object.js';
import { startInactiveSpan, withActiveSpan } from '../../tracing/trace.js';
import { extractSessionDataFromInitializeRequest, extractSessionDataFromInitializeResponse } from './attributeExtraction.js';
import { storeSpanForRequest, completeSpanWithResults, cleanupPendingSpansForTransport } from './correlation.js';
import { captureError } from './errorCapture.js';
import { storeSessionDataForTransport, updateSessionDataForTransport, cleanupSessionDataForTransport } from './sessionManagement.js';
import { buildMcpServerSpanConfig, createMcpNotificationSpan, createMcpOutgoingNotificationSpan } from './spans.js';
import { isJsonRpcRequest, isJsonRpcNotification, isJsonRpcResponse } from './validation.js';
/**
* Transport layer instrumentation for MCP server
*
* Handles message interception and response correlation.
* @see https://modelcontextprotocol.io/specification/2025-06-18/basic/transports
*/
/**
* Wraps transport.onmessage to create spans for incoming messages.
* For "initialize" requests, extracts and stores client info and protocol version
* in the session data for the transport.
* @param transport - MCP transport instance to wrap
*/
function wrapTransportOnMessage(transport) {
if (transport.onmessage) {
fill(transport, 'onmessage', originalOnMessage => {
return function ( message, extra) {
if (isJsonRpcRequest(message)) {
if (message.method === 'initialize') {
try {
const sessionData = extractSessionDataFromInitializeRequest(message);
storeSessionDataForTransport(this, sessionData);
} catch {
// noop
}
}
const isolationScope = getIsolationScope().clone();
return withIsolationScope(isolationScope, () => {
const spanConfig = buildMcpServerSpanConfig(message, this, extra );
const span = startInactiveSpan(spanConfig);
storeSpanForRequest(this, message.id, span, message.method);
return withActiveSpan(span, () => {
return (originalOnMessage ).call(this, message, extra);
});
});
}
if (isJsonRpcNotification(message)) {
return createMcpNotificationSpan(message, this, extra , () => {
return (originalOnMessage ).call(this, message, extra);
});
}
return (originalOnMessage ).call(this, message, extra);
};
});
}
}
/**
* Wraps transport.send to handle outgoing messages and response correlation.
* For "initialize" responses, extracts and stores protocol version and server info
* in the session data for the transport.
* @param transport - MCP transport instance to wrap
*/
function wrapTransportSend(transport) {
if (transport.send) {
fill(transport, 'send', originalSend => {
return async function ( ...args) {
const [message] = args;
if (isJsonRpcNotification(message)) {
return createMcpOutgoingNotificationSpan(message, this, () => {
return (originalSend ).call(this, ...args);
});
}
if (isJsonRpcResponse(message)) {
if (message.id !== null && message.id !== undefined) {
if (message.error) {
captureJsonRpcErrorResponse(message.error);
}
if (message.result && typeof message.result === 'object') {
const result = message.result ;
if (result.protocolVersion || result.serverInfo) {
try {
const serverData = extractSessionDataFromInitializeResponse(message.result);
updateSessionDataForTransport(this, serverData);
} catch {
// noop
}
}
}
completeSpanWithResults(this, message.id, message.result);
}
}
return (originalSend ).call(this, ...args);
};
});
}
}
/**
* Wraps transport.onclose to clean up pending spans for this transport only
* @param transport - MCP transport instance to wrap
*/
function wrapTransportOnClose(transport) {
if (transport.onclose) {
fill(transport, 'onclose', originalOnClose => {
return function ( ...args) {
cleanupPendingSpansForTransport(this);
cleanupSessionDataForTransport(this);
return (originalOnClose ).call(this, ...args);
};
});
}
}
/**
* Wraps transport error handlers to capture connection errors
* @param transport - MCP transport instance to wrap
*/
function wrapTransportError(transport) {
if (transport.onerror) {
fill(transport, 'onerror', (originalOnError) => {
return function ( error) {
captureTransportError(error);
return originalOnError.call(this, error);
};
});
}
}
/**
* Captures JSON-RPC error responses for server-side errors.
* @see https://www.jsonrpc.org/specification#error_object
* @internal
* @param errorResponse - JSON-RPC error response
*/
function captureJsonRpcErrorResponse(errorResponse) {
try {
if (errorResponse && typeof errorResponse === 'object' && 'code' in errorResponse && 'message' in errorResponse) {
const jsonRpcError = errorResponse ;
const isServerError =
jsonRpcError.code === -32603 || (jsonRpcError.code >= -32099 && jsonRpcError.code <= -32000);
if (isServerError) {
const error = new Error(jsonRpcError.message);
error.name = `JsonRpcError_${jsonRpcError.code}`;
captureError(error, 'protocol');
}
}
} catch {
// noop
}
}
/**
* Captures transport connection errors
* @internal
* @param error - Transport error
*/
function captureTransportError(error) {
try {
captureError(error, 'transport');
} catch {
// noop
}
}
export { wrapTransportError, wrapTransportOnClose, wrapTransportOnMessage, wrapTransportSend };
//# sourceMappingURL=transport.js.map