|
1 | 1 | /**
|
2 |
| - * Attribute extraction and building functions for MCP server instrumentation |
| 2 | + * Core attribute extraction and building functions for MCP server instrumentation |
3 | 3 | */
|
4 | 4 |
|
5 | 5 | import { isURLObjectRelative, parseStringToURLObject } from '../../utils/url';
|
6 | 6 | import {
|
7 |
| - CLIENT_ADDRESS_ATTRIBUTE, |
8 |
| - CLIENT_PORT_ATTRIBUTE, |
9 | 7 | MCP_LOGGING_DATA_TYPE_ATTRIBUTE,
|
10 | 8 | MCP_LOGGING_LEVEL_ATTRIBUTE,
|
11 | 9 | MCP_LOGGING_LOGGER_ATTRIBUTE,
|
12 | 10 | MCP_LOGGING_MESSAGE_ATTRIBUTE,
|
13 |
| - MCP_PROMPT_RESULT_DESCRIPTION_ATTRIBUTE, |
14 |
| - MCP_PROMPT_RESULT_MESSAGE_COUNT_ATTRIBUTE, |
15 |
| - MCP_PROTOCOL_VERSION_ATTRIBUTE, |
16 | 11 | MCP_REQUEST_ID_ATTRIBUTE,
|
17 | 12 | 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, |
27 | 13 | } from './attributes';
|
28 | 14 | import { extractTargetInfo, getRequestArguments } from './methodConfig';
|
29 |
| -import { |
30 |
| - getClientInfoForTransport, |
31 |
| - getProtocolVersionForTransport, |
32 |
| - getSessionDataForTransport, |
33 |
| -} from './sessionManagement'; |
34 | 15 | import type {
|
35 |
| - ExtraHandlerData, |
36 | 16 | JsonRpcNotification,
|
37 | 17 | JsonRpcRequest,
|
38 | 18 | McpSpanType,
|
39 |
| - MCPTransport, |
40 |
| - PartyInfo, |
41 |
| - SessionData, |
42 | 19 | } from './types';
|
43 | 20 |
|
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() || ''; |
51 | 21 |
|
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 |
| -} |
66 | 22 |
|
67 | 23 | /**
|
68 | 24 | * Extracts additional attributes for specific notification types
|
@@ -140,154 +96,7 @@ export function getNotificationAttributes(
|
140 | 96 | return attributes;
|
141 | 97 | }
|
142 | 98 |
|
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 = {}; |
150 | 99 |
|
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 |
| -} |
291 | 100 |
|
292 | 101 | /**
|
293 | 102 | * Build type-specific attributes based on message type
|
@@ -315,114 +124,5 @@ export function buildTypeSpecificAttributes(
|
315 | 124 | return getNotificationAttributes(message.method, params || {});
|
316 | 125 | }
|
317 | 126 |
|
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'; |
0 commit comments