-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathcorrelation.ts
More file actions
116 lines (104 loc) · 4.19 KB
/
correlation.ts
File metadata and controls
116 lines (104 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Request-span correlation system for MCP server instrumentation
*
* Handles mapping requestId to span data for correlation with handler execution.
* Uses WeakMap to scope correlation maps per transport instance, preventing
* request ID collisions between different MCP sessions.
*/
import { getClient } from '../../currentScopes';
import { SPAN_STATUS_ERROR } from '../../tracing';
import type { Span } from '../../types-hoist/span';
import { MCP_PROTOCOL_VERSION_ATTRIBUTE } from './attributes';
import { filterMcpPiiFromSpanData } from './piiFiltering';
import { extractPromptResultAttributes, extractToolResultAttributes } from './resultExtraction';
import { buildServerAttributesFromInfo, extractSessionDataFromInitializeResponse } from './sessionExtraction';
import type { MCPTransport, RequestId, RequestSpanMapValue } from './types';
/**
* Transport-scoped correlation system that prevents collisions between different MCP sessions
* @internal Each transport instance gets its own correlation map, eliminating request ID conflicts
*/
const transportToSpanMap = new WeakMap<MCPTransport, Map<RequestId, RequestSpanMapValue>>();
/**
* Gets or creates the span map for a specific transport instance
* @internal
* @param transport - MCP transport instance
* @returns Span map for the transport
*/
function getOrCreateSpanMap(transport: MCPTransport): Map<RequestId, RequestSpanMapValue> {
let spanMap = transportToSpanMap.get(transport);
if (!spanMap) {
spanMap = new Map();
transportToSpanMap.set(transport, spanMap);
}
return spanMap;
}
/**
* Stores span context for later correlation with handler execution
* @param transport - MCP transport instance
* @param requestId - Request identifier
* @param span - Active span to correlate
* @param method - MCP method name
*/
export function storeSpanForRequest(transport: MCPTransport, requestId: RequestId, span: Span, method: string): void {
const spanMap = getOrCreateSpanMap(transport);
spanMap.set(requestId, {
span,
method,
startTime: Date.now(),
});
}
/**
* Completes span with results and cleans up correlation
* @param transport - MCP transport instance
* @param requestId - Request identifier
* @param result - Execution result for attribute extraction
*/
export function completeSpanWithResults(transport: MCPTransport, requestId: RequestId, result: unknown): void {
const spanMap = getOrCreateSpanMap(transport);
const spanData = spanMap.get(requestId);
if (spanData) {
const { span, method } = spanData;
if (method === 'initialize') {
const sessionData = extractSessionDataFromInitializeResponse(result);
const serverAttributes = buildServerAttributesFromInfo(sessionData.serverInfo);
const initAttributes: Record<string, string | number> = {
...serverAttributes,
};
if (sessionData.protocolVersion) {
initAttributes[MCP_PROTOCOL_VERSION_ATTRIBUTE] = sessionData.protocolVersion;
}
span.setAttributes(initAttributes);
} else if (method === 'tools/call') {
const rawToolAttributes = extractToolResultAttributes(result);
const client = getClient();
const sendDefaultPii = Boolean(client?.getOptions().sendDefaultPii);
const toolAttributes = filterMcpPiiFromSpanData(rawToolAttributes, sendDefaultPii);
span.setAttributes(toolAttributes);
} else if (method === 'prompts/get') {
const rawPromptAttributes = extractPromptResultAttributes(result);
const client = getClient();
const sendDefaultPii = Boolean(client?.getOptions().sendDefaultPii);
const promptAttributes = filterMcpPiiFromSpanData(rawPromptAttributes, sendDefaultPii);
span.setAttributes(promptAttributes);
}
span.end();
spanMap.delete(requestId);
}
}
/**
* Cleans up pending spans for a specific transport (when that transport closes)
* @param transport - MCP transport instance
*/
export function cleanupPendingSpansForTransport(transport: MCPTransport): void {
const spanMap = transportToSpanMap.get(transport);
if (spanMap) {
for (const [, spanData] of spanMap) {
spanData.span.setStatus({
code: SPAN_STATUS_ERROR,
message: 'cancelled',
});
spanData.span.end();
}
spanMap.clear();
}
}