1+ import  {  isURLObjectRelative ,  parseStringToURLObject  }  from  '../../utils/url' ; 
2+ import  {  METHOD_CONFIGS  }  from  './config' ; 
3+ import  type  {  ExtraHandlerData  }  from  './types' ; 
4+ 
5+ /** 
6+  * Extracts target info from method and params based on method type 
7+  */ 
8+ export  function  extractTargetInfo ( 
9+   method : string , 
10+   params : Record < string ,  unknown > , 
11+ ) : { 
12+   target ?: string ; 
13+   attributes : Record < string ,  string > ; 
14+ }  { 
15+   const  config  =  METHOD_CONFIGS [ method  as  keyof  typeof  METHOD_CONFIGS ] ; 
16+   if  ( ! config )  { 
17+     return  {  attributes : { }  } ; 
18+   } 
19+ 
20+   const  target  = 
21+     config . targetField  &&  typeof  params ?. [ config . targetField ]  ===  'string' 
22+       ? ( params [ config . targetField ]  as  string ) 
23+       : undefined ; 
24+ 
25+   return  { 
26+     target, 
27+     attributes : target  &&  config . targetAttribute  ? {  [ config . targetAttribute ] : target  }  : { } , 
28+   } ; 
29+ } 
30+ 
31+ /** 
32+  * Extracts request arguments based on method type 
33+  */ 
34+ export  function  getRequestArguments ( method : string ,  params : Record < string ,  unknown > ) : Record < string ,  string >  { 
35+   const  args : Record < string ,  string >  =  { } ; 
36+   const  config  =  METHOD_CONFIGS [ method  as  keyof  typeof  METHOD_CONFIGS ] ; 
37+ 
38+   if  ( ! config )  { 
39+     return  args ; 
40+   } 
41+ 
42+   // Capture arguments from the configured field 
43+   if  ( config . captureArguments  &&  config . argumentsField  &&  params ?. [ config . argumentsField ] )  { 
44+     const  argumentsObj  =  params [ config . argumentsField ] ; 
45+     if  ( typeof  argumentsObj  ===  'object'  &&  argumentsObj  !==  null )  { 
46+       for  ( const  [ key ,  value ]  of  Object . entries ( argumentsObj  as  Record < string ,  unknown > ) )  { 
47+         args [ `mcp.request.argument.${ key . toLowerCase ( ) }  ]  =  JSON . stringify ( value ) ; 
48+       } 
49+     } 
50+   } 
51+ 
52+   // Capture specific fields as arguments 
53+   if  ( config . captureUri  &&  params ?. uri )  { 
54+     args [ 'mcp.request.argument.uri' ]  =  JSON . stringify ( params . uri ) ; 
55+   } 
56+ 
57+   if  ( config . captureName  &&  params ?. name )  { 
58+     args [ 'mcp.request.argument.name' ]  =  JSON . stringify ( params . name ) ; 
59+   } 
60+ 
61+   return  args ; 
62+ } 
63+ 
64+ /** 
65+  * Extracts additional attributes for specific notification types 
66+  */ 
67+ export  function  getNotificationAttributes ( method : string ,  params : Record < string ,  unknown > ) : Record < string ,  string  |  number >  { 
68+   const  attributes : Record < string ,  string  |  number >  =  { } ; 
69+ 
70+   switch  ( method )  { 
71+     case  'notifications/cancelled' :
72+       if  ( params ?. requestId )  { 
73+         attributes [ 'mcp.cancelled.request_id' ]  =  String ( params . requestId ) ; 
74+       } 
75+       if  ( params ?. reason )  { 
76+         attributes [ 'mcp.cancelled.reason' ]  =  String ( params . reason ) ; 
77+       } 
78+       break ; 
79+ 
80+     case  'notifications/message' :
81+       if  ( params ?. level )  { 
82+         attributes [ 'mcp.logging.level' ]  =  String ( params . level ) ; 
83+       } 
84+       if  ( params ?. logger )  { 
85+         attributes [ 'mcp.logging.logger' ]  =  String ( params . logger ) ; 
86+       } 
87+       if  ( params ?. data  !==  undefined )  { 
88+         attributes [ 'mcp.logging.data_type' ]  =  typeof  params . data ; 
89+         // Store the actual message content 
90+         if  ( typeof  params . data  ===  'string' )  { 
91+           attributes [ 'mcp.logging.message' ]  =  params . data ; 
92+         }  else  { 
93+           attributes [ 'mcp.logging.message' ]  =  JSON . stringify ( params . data ) ; 
94+         } 
95+       } 
96+       break ; 
97+ 
98+     case  'notifications/progress' :
99+       if  ( params ?. progressToken )  { 
100+         attributes [ 'mcp.progress.token' ]  =  String ( params . progressToken ) ; 
101+       } 
102+       if  ( typeof  params ?. progress  ===  'number' )  { 
103+         attributes [ 'mcp.progress.current' ]  =  params . progress ; 
104+       } 
105+       if  ( typeof  params ?. total  ===  'number' )  { 
106+         attributes [ 'mcp.progress.total' ]  =  params . total ; 
107+         if  ( typeof  params ?. progress  ===  'number' )  { 
108+           attributes [ 'mcp.progress.percentage' ]  =  ( params . progress  /  params . total )  *  100 ; 
109+         } 
110+       } 
111+       if  ( params ?. message )  { 
112+         attributes [ 'mcp.progress.message' ]  =  String ( params . message ) ; 
113+       } 
114+       break ; 
115+ 
116+     case  'notifications/resources/updated' :
117+       if  ( params ?. uri )  { 
118+         attributes [ 'mcp.resource.uri' ]  =  String ( params . uri ) ; 
119+         // Extract protocol from URI 
120+         const  urlObject  =  parseStringToURLObject ( String ( params . uri ) ) ; 
121+         if  ( urlObject  &&  ! isURLObjectRelative ( urlObject ) )  { 
122+           attributes [ 'mcp.resource.protocol' ]  =  urlObject . protocol . replace ( ':' ,  '' ) ; 
123+         } 
124+       } 
125+       break ; 
126+ 
127+     case  'notifications/initialized' :
128+       attributes [ 'mcp.lifecycle.phase' ]  =  'initialization_complete' ; 
129+       attributes [ 'mcp.protocol.ready' ]  =  1 ; 
130+       break ; 
131+   } 
132+ 
133+   return  attributes ; 
134+ } 
135+ 
136+ /** 
137+  * Extracts attributes from tool call results for tracking 
138+  * Captures actual content for debugging and monitoring 
139+  * 
140+  * @param  method The MCP method name (should be 'tools/call') 
141+  * @param  result The raw CallToolResult object returned by the tool handler 
142+  */ 
143+ export  function  extractToolResultAttributes ( 
144+   method : string , 
145+   result : unknown , 
146+ ) : Record < string ,  string  |  number  |  boolean >  { 
147+   const  attributes : Record < string ,  string  |  number  |  boolean >  =  { } ; 
148+ 
149+   // Only process tool call results 
150+   if  ( method  !==  'tools/call'  ||  ! result  ||  typeof  result  !==  'object' )  { 
151+     return  attributes ; 
152+   } 
153+ 
154+   // The result is the raw CallToolResult object from the tool handler 
155+   const  toolResult  =  result  as  { 
156+     content ?: Array < {  type ?: string ;  text ?: string ;  [ key : string ] : unknown  } > ; 
157+     structuredContent ?: Record < string ,  unknown > ; 
158+     isError ?: boolean ; 
159+   } ; 
160+ 
161+   // Track if result is an error 
162+   if  ( toolResult . isError  !==  undefined )  { 
163+     attributes [ 'mcp.tool.result.is_error' ]  =  toolResult . isError ; 
164+   } 
165+ 
166+   // Track content metadata and actual content 
167+   if  ( toolResult . content  &&  Array . isArray ( toolResult . content ) )  { 
168+     attributes [ 'mcp.tool.result.content_count' ]  =  toolResult . content . length ; 
169+ 
170+     // Track content types 
171+     const  types  =  toolResult . content . map ( c  =>  c . type ) . filter ( ( type ) : type  is string  =>  typeof  type  ===  'string' ) ; 
172+ 
173+     if  ( types . length  >  0 )  { 
174+       attributes [ 'mcp.tool.result.content_types' ]  =  types . join ( ',' ) ; 
175+     } 
176+ 
177+     // Track actual content - serialize the full content array 
178+     try  { 
179+       attributes [ 'mcp.tool.result.content' ]  =  JSON . stringify ( toolResult . content ) ; 
180+     }  catch  ( error )  { 
181+       // If serialization fails, store a fallback message 
182+       attributes [ 'mcp.tool.result.content' ]  =  '[Content serialization failed]' ; 
183+     } 
184+   } 
185+ 
186+   // Track structured content if exists 
187+   if  ( toolResult . structuredContent  !==  undefined )  { 
188+     attributes [ 'mcp.tool.result.has_structured_content' ]  =  true ; 
189+ 
190+     // Track actual structured content 
191+     try  { 
192+       attributes [ 'mcp.tool.result.structured_content' ]  =  JSON . stringify ( toolResult . structuredContent ) ; 
193+     }  catch  ( error )  { 
194+       // If serialization fails, store a fallback message 
195+       attributes [ 'mcp.tool.result.structured_content' ]  =  '[Structured content serialization failed]' ; 
196+     } 
197+   } 
198+ 
199+   return  attributes ; 
200+ } 
201+ 
202+ /** 
203+  * Extracts arguments from handler parameters for handler-level instrumentation 
204+  */ 
205+ export  function  extractHandlerArguments ( handlerType : string ,  args : unknown [ ] ) : Record < string ,  string >  { 
206+   const  arguments_ : Record < string ,  string >  =  { } ; 
207+   
208+   // Find the first argument that is not the extra object 
209+   const  firstArg  =  args . find ( arg  =>  
210+     arg  &&  
211+     typeof  arg  ===  'object'  &&  
212+     ! ( 'requestId'  in  arg ) 
213+   ) ; 
214+   
215+   if  ( ! firstArg )  { 
216+     return  arguments_ ; 
217+   } 
218+ 
219+   if  ( handlerType  ===  'tool'  ||  handlerType  ===  'prompt' )  { 
220+     // For tools and prompts, first arg contains the arguments 
221+     if  ( typeof  firstArg  ===  'object'  &&  firstArg  !==  null )  { 
222+       for  ( const  [ key ,  value ]  of  Object . entries ( firstArg  as  Record < string ,  unknown > ) )  { 
223+         arguments_ [ `mcp.request.argument.${ key . toLowerCase ( ) }  ]  =  typeof  value  ===  'string'  ? value  : JSON . stringify ( value ) ; 
224+       } 
225+     } 
226+   }  else  if  ( handlerType  ===  'resource' )  { 
227+     // For resources, we might have URI and variables 
228+     // First argument is usually the URI (resource name) 
229+     // Second argument might be variables for template expansion 
230+     const  uriArg  =  args [ 0 ] ; 
231+     if  ( typeof  uriArg  ===  'string'  ||  uriArg  instanceof  URL )  { 
232+       arguments_ [ 'mcp.request.argument.uri' ]  =  JSON . stringify ( uriArg . toString ( ) ) ; 
233+     } 
234+     
235+     // Check if second argument is variables (not the extra object) 
236+     const  secondArg  =  args [ 1 ] ; 
237+     if  ( secondArg  &&  typeof  secondArg  ===  'object'  &&  ! ( 'requestId'  in  secondArg ) )  { 
238+       for  ( const  [ key ,  value ]  of  Object . entries ( secondArg  as  Record < string ,  unknown > ) )  { 
239+         arguments_ [ `mcp.request.argument.${ key . toLowerCase ( ) }  ]  =  typeof  value  ===  'string'  ? value  : JSON . stringify ( value ) ; 
240+       } 
241+     } 
242+   } 
243+ 
244+   return  arguments_ ; 
245+ } 
246+ 
247+ /** 
248+  * Extracts client connection information 
249+  */ 
250+ export  function  extractClientInfo ( extra : ExtraHandlerData ) : { 
251+   address ?: string ; 
252+   port ?: number ; 
253+ }  { 
254+   return  { 
255+     address :
256+       extra ?. requestInfo ?. remoteAddress  || 
257+       extra ?. clientAddress  || 
258+       extra ?. request ?. ip  || 
259+       extra ?. request ?. connection ?. remoteAddress , 
260+     port : extra ?. requestInfo ?. remotePort  ||  extra ?. clientPort  ||  extra ?. request ?. connection ?. remotePort , 
261+   } ; 
262+ }  
0 commit comments