Skip to content

Commit 1ae03e5

Browse files
committed
ref: keep files under 300 code lines
1 parent 972d76d commit 1ae03e5

File tree

6 files changed

+323
-308
lines changed

6 files changed

+323
-308
lines changed
Lines changed: 3 additions & 303 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,24 @@
11
/**
2-
* Attribute extraction and building functions for MCP server instrumentation
2+
* Core attribute extraction and building functions for MCP server instrumentation
33
*/
44

55
import { isURLObjectRelative, parseStringToURLObject } from '../../utils/url';
66
import {
7-
CLIENT_ADDRESS_ATTRIBUTE,
8-
CLIENT_PORT_ATTRIBUTE,
97
MCP_LOGGING_DATA_TYPE_ATTRIBUTE,
108
MCP_LOGGING_LEVEL_ATTRIBUTE,
119
MCP_LOGGING_LOGGER_ATTRIBUTE,
1210
MCP_LOGGING_MESSAGE_ATTRIBUTE,
13-
MCP_PROMPT_RESULT_DESCRIPTION_ATTRIBUTE,
14-
MCP_PROMPT_RESULT_MESSAGE_COUNT_ATTRIBUTE,
15-
MCP_PROTOCOL_VERSION_ATTRIBUTE,
1611
MCP_REQUEST_ID_ATTRIBUTE,
1712
MCP_RESOURCE_URI_ATTRIBUTE,
18-
MCP_SERVER_NAME_ATTRIBUTE,
19-
MCP_SERVER_TITLE_ATTRIBUTE,
20-
MCP_SERVER_VERSION_ATTRIBUTE,
21-
MCP_SESSION_ID_ATTRIBUTE,
22-
MCP_TOOL_RESULT_CONTENT_COUNT_ATTRIBUTE,
23-
MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE,
24-
MCP_TRANSPORT_ATTRIBUTE,
25-
NETWORK_PROTOCOL_VERSION_ATTRIBUTE,
26-
NETWORK_TRANSPORT_ATTRIBUTE,
2713
} from './attributes';
2814
import { extractTargetInfo, getRequestArguments } from './methodConfig';
29-
import {
30-
getClientInfoForTransport,
31-
getProtocolVersionForTransport,
32-
getSessionDataForTransport,
33-
} from './sessionManagement';
3415
import type {
35-
ExtraHandlerData,
3616
JsonRpcNotification,
3717
JsonRpcRequest,
3818
McpSpanType,
39-
MCPTransport,
40-
PartyInfo,
41-
SessionData,
4219
} from './types';
4320

44-
/**
45-
* Extracts transport types based on transport constructor name
46-
* @param transport - MCP transport instance
47-
* @returns Transport type mapping for span attributes
48-
*/
49-
export function getTransportTypes(transport: MCPTransport): { mcpTransport: string; networkTransport: string } {
50-
const transportName = transport.constructor?.name?.toLowerCase() || '';
5121

52-
if (transportName.includes('stdio')) {
53-
return { mcpTransport: 'stdio', networkTransport: 'pipe' };
54-
}
55-
56-
if (transportName.includes('streamablehttp') || transportName.includes('streamable')) {
57-
return { mcpTransport: 'http', networkTransport: 'tcp' };
58-
}
59-
60-
if (transportName.includes('sse')) {
61-
return { mcpTransport: 'sse', networkTransport: 'tcp' };
62-
}
63-
64-
return { mcpTransport: 'unknown', networkTransport: 'unknown' };
65-
}
6622

6723
/**
6824
* Extracts additional attributes for specific notification types
@@ -140,154 +96,7 @@ export function getNotificationAttributes(
14096
return attributes;
14197
}
14298

143-
/**
144-
* Extracts and validates PartyInfo from an unknown object
145-
* @param obj - Unknown object that might contain party info
146-
* @returns Validated PartyInfo object with only string properties
147-
*/
148-
function extractPartyInfo(obj: unknown): PartyInfo {
149-
const partyInfo: PartyInfo = {};
15099

151-
if (obj && typeof obj === 'object' && obj !== null) {
152-
const source = obj as Record<string, unknown>;
153-
if (typeof source.name === 'string') partyInfo.name = source.name;
154-
if (typeof source.title === 'string') partyInfo.title = source.title;
155-
if (typeof source.version === 'string') partyInfo.version = source.version;
156-
}
157-
158-
return partyInfo;
159-
}
160-
161-
/**
162-
* Extracts session data from "initialize" requests
163-
* @param request - JSON-RPC "initialize" request containing client info and protocol version
164-
* @returns Session data extracted from request parameters including protocol version and client info
165-
*/
166-
export function extractSessionDataFromInitializeRequest(request: JsonRpcRequest): SessionData {
167-
const sessionData: SessionData = {};
168-
if (request.params && typeof request.params === 'object' && request.params !== null) {
169-
const params = request.params as Record<string, unknown>;
170-
if (typeof params.protocolVersion === 'string') {
171-
sessionData.protocolVersion = params.protocolVersion;
172-
}
173-
if (params.clientInfo) {
174-
sessionData.clientInfo = extractPartyInfo(params.clientInfo);
175-
}
176-
}
177-
return sessionData;
178-
}
179-
180-
/**
181-
* Extracts session data from "initialize" response
182-
* @param result - "initialize" response result containing server info and protocol version
183-
* @returns Partial session data extracted from response including protocol version and server info
184-
*/
185-
export function extractSessionDataFromInitializeResponse(result: unknown): Partial<SessionData> {
186-
const sessionData: Partial<SessionData> = {};
187-
if (result && typeof result === 'object') {
188-
const resultObj = result as Record<string, unknown>;
189-
if (typeof resultObj.protocolVersion === 'string') sessionData.protocolVersion = resultObj.protocolVersion;
190-
if (resultObj.serverInfo) {
191-
sessionData.serverInfo = extractPartyInfo(resultObj.serverInfo);
192-
}
193-
}
194-
return sessionData;
195-
}
196-
197-
/**
198-
* Build client attributes from stored client info
199-
* @param transport - MCP transport instance
200-
* @returns Client attributes for span instrumentation
201-
*/
202-
export function getClientAttributes(transport: MCPTransport): Record<string, string> {
203-
const clientInfo = getClientInfoForTransport(transport);
204-
const attributes: Record<string, string> = {};
205-
206-
if (clientInfo?.name) {
207-
attributes['mcp.client.name'] = clientInfo.name;
208-
}
209-
if (clientInfo?.title) {
210-
attributes['mcp.client.title'] = clientInfo.title;
211-
}
212-
if (clientInfo?.version) {
213-
attributes['mcp.client.version'] = clientInfo.version;
214-
}
215-
216-
return attributes;
217-
}
218-
219-
/**
220-
* Build server attributes from stored server info
221-
* @param transport - MCP transport instance
222-
* @returns Server attributes for span instrumentation
223-
*/
224-
export function getServerAttributes(transport: MCPTransport): Record<string, string> {
225-
const serverInfo = getSessionDataForTransport(transport)?.serverInfo;
226-
const attributes: Record<string, string> = {};
227-
228-
if (serverInfo?.name) {
229-
attributes[MCP_SERVER_NAME_ATTRIBUTE] = serverInfo.name;
230-
}
231-
if (serverInfo?.title) {
232-
attributes[MCP_SERVER_TITLE_ATTRIBUTE] = serverInfo.title;
233-
}
234-
if (serverInfo?.version) {
235-
attributes[MCP_SERVER_VERSION_ATTRIBUTE] = serverInfo.version;
236-
}
237-
238-
return attributes;
239-
}
240-
241-
/**
242-
* Extracts client connection info from extra handler data
243-
* @param extra - Extra handler data containing connection info
244-
* @returns Client address and port information
245-
*/
246-
export function extractClientInfo(extra: ExtraHandlerData): {
247-
address?: string;
248-
port?: number;
249-
} {
250-
return {
251-
address:
252-
extra?.requestInfo?.remoteAddress ||
253-
extra?.clientAddress ||
254-
extra?.request?.ip ||
255-
extra?.request?.connection?.remoteAddress,
256-
port: extra?.requestInfo?.remotePort || extra?.clientPort || extra?.request?.connection?.remotePort,
257-
};
258-
}
259-
260-
/**
261-
* Build transport and network attributes
262-
* @param transport - MCP transport instance
263-
* @param extra - Optional extra handler data
264-
* @returns Transport attributes for span instrumentation
265-
*/
266-
export function buildTransportAttributes(
267-
transport: MCPTransport,
268-
extra?: ExtraHandlerData,
269-
): Record<string, string | number> {
270-
const sessionId = transport.sessionId;
271-
const clientInfo = extra ? extractClientInfo(extra) : {};
272-
const { mcpTransport, networkTransport } = getTransportTypes(transport);
273-
const clientAttributes = getClientAttributes(transport);
274-
const serverAttributes = getServerAttributes(transport);
275-
const protocolVersion = getProtocolVersionForTransport(transport);
276-
277-
const attributes = {
278-
...(sessionId && { [MCP_SESSION_ID_ATTRIBUTE]: sessionId }),
279-
...(clientInfo.address && { [CLIENT_ADDRESS_ATTRIBUTE]: clientInfo.address }),
280-
...(clientInfo.port && { [CLIENT_PORT_ATTRIBUTE]: clientInfo.port }),
281-
[MCP_TRANSPORT_ATTRIBUTE]: mcpTransport,
282-
[NETWORK_TRANSPORT_ATTRIBUTE]: networkTransport,
283-
[NETWORK_PROTOCOL_VERSION_ATTRIBUTE]: '2.0',
284-
...(protocolVersion && { [MCP_PROTOCOL_VERSION_ATTRIBUTE]: protocolVersion }),
285-
...clientAttributes,
286-
...serverAttributes,
287-
};
288-
289-
return attributes;
290-
}
291100

292101
/**
293102
* Build type-specific attributes based on message type
@@ -315,114 +124,5 @@ export function buildTypeSpecificAttributes(
315124
return getNotificationAttributes(message.method, params || {});
316125
}
317126

318-
/**
319-
* Build attributes for tool result content items
320-
* @param content - Array of content items from tool result
321-
* @returns Attributes extracted from each content item including type, text, mime type, URI, and resource info
322-
*/
323-
function buildAllContentItemAttributes(content: unknown[]): Record<string, string | number> {
324-
const attributes: Record<string, string | number> = {
325-
[MCP_TOOL_RESULT_CONTENT_COUNT_ATTRIBUTE]: content.length,
326-
};
327-
328-
for (const [i, item] of content.entries()) {
329-
if (typeof item !== 'object' || item === null) continue;
330-
331-
const contentItem = item as Record<string, unknown>;
332-
const prefix = content.length === 1 ? 'mcp.tool.result' : `mcp.tool.result.${i}`;
333-
334-
const safeSet = (key: string, value: unknown): void => {
335-
if (typeof value === 'string') attributes[`${prefix}.${key}`] = value;
336-
};
337-
338-
safeSet('content_type', contentItem.type);
339-
safeSet('mime_type', contentItem.mimeType);
340-
safeSet('uri', contentItem.uri);
341-
safeSet('name', contentItem.name);
342-
343-
if (typeof contentItem.text === 'string') {
344-
const text = contentItem.text;
345-
const maxLength = 500;
346-
attributes[`${prefix}.content`] = text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
347-
}
348-
349-
if (typeof contentItem.data === 'string') {
350-
attributes[`${prefix}.data_size`] = contentItem.data.length;
351-
}
352-
353-
const resource = contentItem.resource;
354-
if (typeof resource === 'object' && resource !== null) {
355-
const res = resource as Record<string, unknown>;
356-
safeSet('resource_uri', res.uri);
357-
safeSet('resource_mime_type', res.mimeType);
358-
}
359-
}
360-
361-
return attributes;
362-
}
363-
364-
/**
365-
* Extract tool result attributes for span instrumentation
366-
* @param result - Tool execution result
367-
* @returns Attributes extracted from tool result content
368-
*/
369-
export function extractToolResultAttributes(result: unknown): Record<string, string | number | boolean> {
370-
let attributes: Record<string, string | number | boolean> = {};
371-
if (typeof result !== 'object' || result === null) return attributes;
372-
373-
const resultObj = result as Record<string, unknown>;
374-
if (typeof resultObj.isError === 'boolean') {
375-
attributes[MCP_TOOL_RESULT_IS_ERROR_ATTRIBUTE] = resultObj.isError;
376-
}
377-
if (Array.isArray(resultObj.content)) {
378-
attributes = { ...attributes, ...buildAllContentItemAttributes(resultObj.content) };
379-
}
380-
return attributes;
381-
}
382-
383-
/**
384-
* Extract prompt result attributes for span instrumentation
385-
* @param result - Prompt execution result
386-
* @returns Attributes extracted from prompt result
387-
*/
388-
export function extractPromptResultAttributes(result: unknown): Record<string, string | number | boolean> {
389-
const attributes: Record<string, string | number | boolean> = {};
390-
if (typeof result !== 'object' || result === null) return attributes;
391-
392-
const resultObj = result as Record<string, unknown>;
393-
394-
if (typeof resultObj.description === 'string')
395-
attributes[MCP_PROMPT_RESULT_DESCRIPTION_ATTRIBUTE] = resultObj.description;
396-
397-
if (Array.isArray(resultObj.messages)) {
398-
attributes[MCP_PROMPT_RESULT_MESSAGE_COUNT_ATTRIBUTE] = resultObj.messages.length;
399-
400-
// Extract attributes for each message
401-
const messages = resultObj.messages;
402-
for (const [i, message] of messages.entries()) {
403-
if (typeof message !== 'object' || message === null) continue;
404-
405-
const messageObj = message as Record<string, unknown>;
406-
const prefix = messages.length === 1 ? 'mcp.prompt.result' : `mcp.prompt.result.${i}`;
407-
408-
const safeSet = (key: string, value: unknown): void => {
409-
if (typeof value === 'string') {
410-
const attrName = messages.length === 1 ? `${prefix}.message_${key}` : `${prefix}.${key}`;
411-
attributes[attrName] = value;
412-
}
413-
};
414-
415-
safeSet('role', messageObj.role);
416-
417-
if (typeof messageObj.content === 'object' && messageObj.content !== null) {
418-
const content = messageObj.content as Record<string, unknown>;
419-
if (typeof content.text === 'string') {
420-
const attrName = messages.length === 1 ? `${prefix}.message_content` : `${prefix}.content`;
421-
attributes[attrName] = content.text;
422-
}
423-
}
424-
}
425-
}
426-
427-
return attributes;
428-
}
127+
// Re-export buildTransportAttributes for spans.ts
128+
export { buildTransportAttributes } from './sessionExtraction';

packages/core/src/integrations/mcp-server/correlation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import { getClient } from '../../currentScopes';
1010
import { SPAN_STATUS_ERROR } from '../../tracing';
1111
import type { Span } from '../../types-hoist/span';
12-
import { extractPromptResultAttributes, extractToolResultAttributes } from './attributeExtraction';
1312
import { filterMcpPiiFromSpanData } from './piiFiltering';
13+
import { extractPromptResultAttributes, extractToolResultAttributes } from './resultExtraction';
1414
import type { MCPTransport, RequestId, RequestSpanMapValue } from './types';
1515

1616
/**

0 commit comments

Comments
 (0)