@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
351 lines (305 loc) • 12.2 kB
JavaScript
import { parseStringToURLObject, isURLObjectRelative } from '../../utils/url.js';
import { MCP_REQUEST_ID_ATTRIBUTE, MCP_PROTOCOL_VERSION_ATTRIBUTE, CLIENT_PORT_ATTRIBUTE, CLIENT_ADDRESS_ATTRIBUTE, MCP_SESSION_ID_ATTRIBUTE, MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE, NETWORK_PROTOCOL_VERSION_ATTRIBUTE, NETWORK_TRANSPORT_ATTRIBUTE, MCP_TRANSPORT_ATTRIBUTE, MCP_TOOL_RESULT_CONTENT_COUNT_ATTRIBUTE, MCP_SERVER_NAME_ATTRIBUTE, MCP_SERVER_TITLE_ATTRIBUTE, MCP_SERVER_VERSION_ATTRIBUTE, MCP_RESOURCE_URI_ATTRIBUTE, MCP_LOGGING_LEVEL_ATTRIBUTE, MCP_LOGGING_LOGGER_ATTRIBUTE, MCP_LOGGING_DATA_TYPE_ATTRIBUTE, MCP_LOGGING_MESSAGE_ATTRIBUTE } from './attributes.js';
import { extractTargetInfo, getRequestArguments } from './methodConfig.js';
import { getProtocolVersionForTransport, getClientInfoForTransport, getSessionDataForTransport } from './sessionManagement.js';
/**
* Attribute extraction and building functions for MCP server instrumentation
*/
/**
* Extracts transport types based on transport constructor name
* @param transport - MCP transport instance
* @returns Transport type mapping for span attributes
*/
function getTransportTypes(transport) {
const transportName = transport.constructor?.name?.toLowerCase() || '';
if (transportName.includes('stdio')) {
return { mcpTransport: 'stdio', networkTransport: 'pipe' };
}
if (transportName.includes('streamablehttp') || transportName.includes('streamable')) {
return { mcpTransport: 'http', networkTransport: 'tcp' };
}
if (transportName.includes('sse')) {
return { mcpTransport: 'sse', networkTransport: 'tcp' };
}
return { mcpTransport: 'unknown', networkTransport: 'unknown' };
}
/**
* Extracts additional attributes for specific notification types
* @param method - Notification method name
* @param params - Notification parameters
* @returns Method-specific attributes for span instrumentation
*/
function getNotificationAttributes(
method,
params,
) {
const attributes = {};
switch (method) {
case 'notifications/cancelled':
if (params?.requestId) {
attributes['mcp.cancelled.request_id'] = String(params.requestId);
}
if (params?.reason) {
attributes['mcp.cancelled.reason'] = String(params.reason);
}
break;
case 'notifications/message':
if (params?.level) {
attributes[MCP_LOGGING_LEVEL_ATTRIBUTE] = String(params.level);
}
if (params?.logger) {
attributes[MCP_LOGGING_LOGGER_ATTRIBUTE] = String(params.logger);
}
if (params?.data !== undefined) {
attributes[MCP_LOGGING_DATA_TYPE_ATTRIBUTE] = typeof params.data;
if (typeof params.data === 'string') {
attributes[MCP_LOGGING_MESSAGE_ATTRIBUTE] = params.data;
} else {
attributes[MCP_LOGGING_MESSAGE_ATTRIBUTE] = JSON.stringify(params.data);
}
}
break;
case 'notifications/progress':
if (params?.progressToken) {
attributes['mcp.progress.token'] = String(params.progressToken);
}
if (typeof params?.progress === 'number') {
attributes['mcp.progress.current'] = params.progress;
}
if (typeof params?.total === 'number') {
attributes['mcp.progress.total'] = params.total;
if (typeof params?.progress === 'number') {
attributes['mcp.progress.percentage'] = (params.progress / params.total) * 100;
}
}
if (params?.message) {
attributes['mcp.progress.message'] = String(params.message);
}
break;
case 'notifications/resources/updated':
if (params?.uri) {
attributes[MCP_RESOURCE_URI_ATTRIBUTE] = String(params.uri);
const urlObject = parseStringToURLObject(String(params.uri));
if (urlObject && !isURLObjectRelative(urlObject)) {
attributes['mcp.resource.protocol'] = urlObject.protocol.replace(':', '');
}
}
break;
case 'notifications/initialized':
attributes['mcp.lifecycle.phase'] = 'initialization_complete';
attributes['mcp.protocol.ready'] = 1;
break;
}
return attributes;
}
/**
* Extracts and validates PartyInfo from an unknown object
* @param obj - Unknown object that might contain party info
* @returns Validated PartyInfo object with only string properties
*/
function extractPartyInfo(obj) {
const partyInfo = {};
if (obj && typeof obj === 'object' && obj !== null) {
const source = obj ;
if (typeof source.name === 'string') partyInfo.name = source.name;
if (typeof source.title === 'string') partyInfo.title = source.title;
if (typeof source.version === 'string') partyInfo.version = source.version;
}
return partyInfo;
}
/**
* Extracts session data from "initialize" requests
* @param request - JSON-RPC "initialize" request containing client info and protocol version
* @returns Session data extracted from request parameters including protocol version and client info
*/
function extractSessionDataFromInitializeRequest(request) {
const sessionData = {};
if (request.params && typeof request.params === 'object' && request.params !== null) {
const params = request.params ;
if (typeof params.protocolVersion === 'string') {
sessionData.protocolVersion = params.protocolVersion;
}
if (params.clientInfo) {
sessionData.clientInfo = extractPartyInfo(params.clientInfo);
}
}
return sessionData;
}
/**
* Extracts session data from "initialize" response
* @param result - "initialize" response result containing server info and protocol version
* @returns Partial session data extracted from response including protocol version and server info
*/
function extractSessionDataFromInitializeResponse(result) {
const sessionData = {};
if (result && typeof result === 'object') {
const resultObj = result ;
if (typeof resultObj.protocolVersion === 'string') sessionData.protocolVersion = resultObj.protocolVersion;
if (resultObj.serverInfo) {
sessionData.serverInfo = extractPartyInfo(resultObj.serverInfo);
}
}
return sessionData;
}
/**
* Build client attributes from stored client info
* @param transport - MCP transport instance
* @returns Client attributes for span instrumentation
*/
function getClientAttributes(transport) {
const clientInfo = getClientInfoForTransport(transport);
const attributes = {};
if (clientInfo?.name) {
attributes['mcp.client.name'] = clientInfo.name;
}
if (clientInfo?.title) {
attributes['mcp.client.title'] = clientInfo.title;
}
if (clientInfo?.version) {
attributes['mcp.client.version'] = clientInfo.version;
}
return attributes;
}
/**
* Build server attributes from stored server info
* @param transport - MCP transport instance
* @returns Server attributes for span instrumentation
*/
function getServerAttributes(transport) {
const serverInfo = getSessionDataForTransport(transport)?.serverInfo;
const attributes = {};
if (serverInfo?.name) {
attributes[MCP_SERVER_NAME_ATTRIBUTE] = serverInfo.name;
}
if (serverInfo?.title) {
attributes[MCP_SERVER_TITLE_ATTRIBUTE] = serverInfo.title;
}
if (serverInfo?.version) {
attributes[MCP_SERVER_VERSION_ATTRIBUTE] = serverInfo.version;
}
return attributes;
}
/**
* Extracts client connection info from extra handler data
* @param extra - Extra handler data containing connection info
* @returns Client address and port information
*/
function extractClientInfo(extra)
{
return {
address:
extra?.requestInfo?.remoteAddress ||
extra?.clientAddress ||
extra?.request?.ip ||
extra?.request?.connection?.remoteAddress,
port: extra?.requestInfo?.remotePort || extra?.clientPort || extra?.request?.connection?.remotePort,
};
}
/**
* Build transport and network attributes
* @param transport - MCP transport instance
* @param extra - Optional extra handler data
* @returns Transport attributes for span instrumentation
*/
function buildTransportAttributes(
transport,
extra,
) {
const sessionId = transport.sessionId;
const clientInfo = extra ? extractClientInfo(extra) : {};
const { mcpTransport, networkTransport } = getTransportTypes(transport);
const clientAttributes = getClientAttributes(transport);
const serverAttributes = getServerAttributes(transport);
const protocolVersion = getProtocolVersionForTransport(transport);
const attributes = {
...(sessionId && { [MCP_SESSION_ID_ATTRIBUTE]: sessionId }),
...(clientInfo.address && { [CLIENT_ADDRESS_ATTRIBUTE]: clientInfo.address }),
...(clientInfo.port && { [CLIENT_PORT_ATTRIBUTE]: clientInfo.port }),
[MCP_TRANSPORT_ATTRIBUTE]: mcpTransport,
[NETWORK_TRANSPORT_ATTRIBUTE]: networkTransport,
[NETWORK_PROTOCOL_VERSION_ATTRIBUTE]: '2.0',
...(protocolVersion && { [MCP_PROTOCOL_VERSION_ATTRIBUTE]: protocolVersion }),
...clientAttributes,
...serverAttributes,
};
return attributes;
}
/**
* Build type-specific attributes based on message type
* @param type - Span type (request or notification)
* @param message - JSON-RPC message
* @param params - Optional parameters for attribute extraction
* @returns Type-specific attributes for span instrumentation
*/
function buildTypeSpecificAttributes(
type,
message,
params,
) {
if (type === 'request') {
const request = message ;
const targetInfo = extractTargetInfo(request.method, params || {});
return {
...(request.id !== undefined && { [MCP_REQUEST_ID_ATTRIBUTE]: String(request.id) }),
...targetInfo.attributes,
...getRequestArguments(request.method, params || {}),
};
}
return getNotificationAttributes(message.method, params || {});
}
/**
* Build attributes for tool result content items
* @param content - Array of content items from tool result
* @returns Attributes extracted from each content item including type, text, mime type, URI, and resource info
*/
function buildAllContentItemAttributes(content) {
const attributes = {
[MCP_TOOL_RESULT_CONTENT_COUNT_ATTRIBUTE]: content.length,
};
for (const [i, item] of content.entries()) {
if (typeof item !== 'object' || item === null) continue;
const contentItem = item ;
const prefix = content.length === 1 ? 'mcp.tool.result' : `mcp.tool.result.${i}`;
const safeSet = (key, value) => {
if (typeof value === 'string') attributes[`${prefix}.${key}`] = value;
};
safeSet('content_type', contentItem.type);
safeSet('mime_type', contentItem.mimeType);
safeSet('uri', contentItem.uri);
safeSet('name', contentItem.name);
if (typeof contentItem.text === 'string') {
const text = contentItem.text;
const maxLength = 500;
attributes[`${prefix}.content`] = text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
}
if (typeof contentItem.data === 'string') {
attributes[`${prefix}.data_size`] = contentItem.data.length;
}
const resource = contentItem.resource;
if (typeof resource === 'object' && resource !== null) {
const res = resource ;
safeSet('resource_uri', res.uri);
safeSet('resource_mime_type', res.mimeType);
}
}
return attributes;
}
/**
* Extract tool result attributes for span instrumentation
* @param result - Tool execution result
* @returns Attributes extracted from tool result content
*/
function extractToolResultAttributes(result) {
let attributes = {};
if (typeof result !== 'object' || result === null) return attributes;
const resultObj = result ;
if (typeof resultObj.isError === 'boolean') {
attributes[MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE] = resultObj.isError;
}
if (Array.isArray(resultObj.content)) {
attributes = { ...attributes, ...buildAllContentItemAttributes(resultObj.content) };
}
return attributes;
}
export { buildTransportAttributes, buildTypeSpecificAttributes, extractClientInfo, extractSessionDataFromInitializeRequest, extractSessionDataFromInitializeResponse, extractToolResultAttributes, getClientAttributes, getNotificationAttributes, getServerAttributes, getTransportTypes };
//# sourceMappingURL=attributeExtraction.js.map