@@ -184,11 +184,45 @@ func (s *stdioSession) Start(ctx context.Context) error {
184184
185185// readInputStream reads requests/notifications from MCP clients through stdin
186186func (s * stdioSession ) readInputStream (ctx context.Context ) error {
187+ sessionStart := time .Now ()
188+
189+ // Define attributes for session metrics
190+ // Note: mcp.protocol.version is added dynamically after protocol negotiation
191+ sessionAttrs := []attribute.KeyValue {
192+ attribute .String ("network.transport" , "pipe" ),
193+ attribute .String ("network.protocol.name" , "stdio" ),
194+ }
195+
196+ s .server .instrumentation .McpActiveSessions .Add (ctx , 1 , metric .WithAttributes (sessionAttrs ... ))
197+
198+ var err error
199+ defer func () {
200+ // Build full attributes including mcp.protocol.version if negotiated
201+ fullAttrs := sessionAttrs
202+ if s .protocol != "" {
203+ fullAttrs = append (fullAttrs , attribute .String ("mcp.protocol.version" , s .protocol ))
204+ }
205+
206+ // Decrement active sessions counter
207+ s .server .instrumentation .McpActiveSessions .Add (ctx , - 1 , metric .WithAttributes (fullAttrs ... ))
208+
209+ // Record session duration
210+ sessionDuration := time .Since (sessionStart ).Seconds ()
211+ durationAttrs := make ([]attribute.KeyValue , len (fullAttrs ))
212+ copy (durationAttrs , fullAttrs )
213+ if err != nil && err != io .EOF {
214+ durationAttrs = append (durationAttrs , attribute .String ("error.type" , err .Error ()))
215+ }
216+ s .server .instrumentation .McpSessionDuration .Record (ctx , sessionDuration , metric .WithAttributes (durationAttrs ... ))
217+ }()
218+
187219 for {
188- if err : = ctx .Err (); err != nil {
220+ if err = ctx .Err (); err != nil {
189221 return err
190222 }
191- line , err := s .readLine (ctx )
223+
224+ var line string
225+ line , err = s .readLine (ctx )
192226 if err != nil {
193227 if err == io .EOF {
194228 return nil
@@ -206,7 +240,9 @@ func (s *stdioSession) readInputStream(ctx context.Context) error {
206240 )
207241 defer span .End ()
208242
209- v , res , err := processMcpMessage (msgCtx , []byte (line ), s .server , s .protocol , "" , "" , nil , "" )
243+ var v string
244+ var res any
245+ v , res , err = processMcpMessage (msgCtx , []byte (line ), s .server , s .protocol , "" , "" , nil , "" )
210246 if err != nil {
211247 // errors during the processing of message will generate a valid MCP Error response.
212248 // server can continue to run.
@@ -309,6 +345,7 @@ func mcpRouter(s *Server) (chi.Router, error) {
309345
310346// sseHandler handles sse initialization and message.
311347func sseHandler (s * Server , w http.ResponseWriter , r * http.Request ) {
348+ sessionStart := time .Now ()
312349 ctx , span := s .instrumentation .Tracer .Start (r .Context (), "toolbox/server/mcp/sse" ,
313350 trace .WithSpanKind (trace .SpanKindServer ),
314351 )
@@ -325,23 +362,34 @@ func sseHandler(s *Server, w http.ResponseWriter, r *http.Request) {
325362 w .Header ().Set ("Connection" , "keep-alive" )
326363 w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
327364
365+ // Define attributes for session metrics
366+ networkProtocolVersion := fmt .Sprintf ("%d.%d" , r .ProtoMajor , r .ProtoMinor )
367+ sessionAttrs := []attribute.KeyValue {
368+ attribute .String ("network.transport" , "tcp" ),
369+ attribute .String ("network.protocol.name" , "http" ),
370+ attribute .String ("network.protocol.version" , networkProtocolVersion ),
371+ attribute .String ("mcp.protocol.version" , "2024-11-05" ),
372+ attribute .String ("toolset.name" , toolsetName ),
373+ }
374+
375+ // Increment active sessions counter
376+ s .instrumentation .McpActiveSessions .Add (ctx , 1 , metric .WithAttributes (sessionAttrs ... ))
377+
328378 var err error
329379 defer func () {
380+ // Decrement active sessions counter
381+ s .instrumentation .McpActiveSessions .Add (ctx , - 1 , metric .WithAttributes (sessionAttrs ... ))
382+
383+ // Record session duration
384+ sessionDuration := time .Since (sessionStart ).Seconds ()
385+ durationAttrs := make ([]attribute.KeyValue , len (sessionAttrs ))
386+ copy (durationAttrs , sessionAttrs )
330387 if err != nil {
331388 span .SetStatus (codes .Error , err .Error ())
389+ durationAttrs = append (durationAttrs , attribute .String ("error.type" , err .Error ()))
332390 }
391+ s .instrumentation .McpSessionDuration .Record (ctx , sessionDuration , metric .WithAttributes (durationAttrs ... ))
333392 span .End ()
334- status := "success"
335- if err != nil {
336- status = "error"
337- }
338- s .instrumentation .McpSse .Add (
339- r .Context (),
340- 1 ,
341- metric .WithAttributes (attribute .String ("toolbox.toolset.name" , toolsetName )),
342- metric .WithAttributes (attribute .String ("toolbox.sse.sessionId" , sessionId )),
343- metric .WithAttributes (attribute .String ("toolbox.operation.status" , status )),
344- )
345393 }()
346394
347395 flusher , ok := w .(http.Flusher )
@@ -474,17 +522,6 @@ func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
474522 span .SetStatus (codes .Error , err .Error ())
475523 }
476524 span .End ()
477-
478- status := "success"
479- if err != nil {
480- status = "error"
481- }
482- s .instrumentation .McpPost .Add (
483- r .Context (),
484- 1 ,
485- metric .WithAttributes (attribute .String ("toolbox.sse.sessionId" , sessionId )),
486- metric .WithAttributes (attribute .String ("toolbox.operation.status" , status )),
487- )
488525 }()
489526
490527 networkProtocolVersion := fmt .Sprintf ("%d.%d" , r .ProtoMajor , r .ProtoMinor )
@@ -540,6 +577,8 @@ func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
540577
541578// processMcpMessage process the messages received from clients
542579func processMcpMessage (ctx context.Context , body []byte , s * Server , protocolVersion string , toolsetName string , promptsetName string , header http.Header , networkProtocolVersion string ) (string , any , error ) {
580+ operationStart := time .Now ()
581+
543582 logger , err := util .LoggerFromContext (ctx )
544583 if err != nil {
545584 return "" , jsonrpc .NewError ("" , jsonrpc .INTERNAL_ERROR , err .Error (), nil ), err
@@ -590,6 +629,44 @@ func processMcpMessage(ctx context.Context, body []byte, s *Server, protocolVers
590629 networkProtocolName = "http"
591630 }
592631
632+ var metricErrorType string
633+ genAIAttrs := & util.GenAIMetricAttrs {
634+ NetworkProtocolName : networkProtocolName ,
635+ NetworkProtocolVersion : networkProtocolVersion ,
636+ }
637+ ctx = util .WithGenAIMetricAttrs (ctx , genAIAttrs )
638+
639+ // Record operation duration metric on function exit
640+ defer func () {
641+ operationDuration := time .Since (operationStart ).Seconds ()
642+ durationAttrs := []attribute.KeyValue {
643+ attribute .String ("mcp.method.name" , baseMessage .Method ),
644+ attribute .String ("network.transport" , networkTransport ),
645+ attribute .String ("network.protocol.name" , networkProtocolName ),
646+ attribute .String ("toolset.name" , toolsetName ),
647+ }
648+ if protocolVersion != "" {
649+ durationAttrs = append (durationAttrs , attribute .String ("mcp.protocol.version" , protocolVersion ))
650+ }
651+ if networkProtocolVersion != "" {
652+ durationAttrs = append (durationAttrs , attribute .String ("network.protocol.version" , networkProtocolVersion ))
653+ }
654+ // Add gen_ai attributes populated by method handlers
655+ if genAIAttrs .OperationName != "" {
656+ durationAttrs = append (durationAttrs , attribute .String ("gen_ai.operation.name" , genAIAttrs .OperationName ))
657+ }
658+ if genAIAttrs .ToolName != "" {
659+ durationAttrs = append (durationAttrs , attribute .String ("gen_ai.tool.name" , genAIAttrs .ToolName ))
660+ }
661+ if genAIAttrs .PromptName != "" {
662+ durationAttrs = append (durationAttrs , attribute .String ("gen_ai.prompt.name" , genAIAttrs .PromptName ))
663+ }
664+ if metricErrorType != "" {
665+ durationAttrs = append (durationAttrs , attribute .String ("error.type" , metricErrorType ))
666+ }
667+ s .instrumentation .McpOperationDuration .Record (ctx , operationDuration , metric .WithAttributes (durationAttrs ... ))
668+ }()
669+
593670 // Set required semantic attributes for span according to OTEL MCP semcov
594671 // ref: https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/#server
595672 span .SetAttributes (
@@ -625,14 +702,18 @@ func processMcpMessage(ctx context.Context, body []byte, s *Server, protocolVers
625702 return "" , nil , err
626703 }
627704
705+ // Add instrumentation to context for use in method handlers
706+ ctx = util .WithInstrumentation (ctx , s .instrumentation )
707+
628708 // Process the method
629709 switch baseMessage .Method {
630710 case mcputil .INITIALIZE :
631711 result , version , err := mcp .InitializeResponse (ctx , baseMessage .Id , body , s .version )
632712 if err != nil {
633713 span .SetStatus (codes .Error , err .Error ())
634714 if rpcErr , ok := result .(jsonrpc.JSONRPCError ); ok {
635- span .SetAttributes (attribute .String ("error.type" , rpcErr .Error .String ()))
715+ metricErrorType = rpcErr .Error .String ()
716+ span .SetAttributes (attribute .String ("error.type" , metricErrorType ))
636717 }
637718 return "" , result , err
638719 }
@@ -643,25 +724,28 @@ func processMcpMessage(ctx context.Context, body []byte, s *Server, protocolVers
643724 if ! ok {
644725 err := fmt .Errorf ("toolset does not exist" )
645726 rpcErr := jsonrpc .NewError (baseMessage .Id , jsonrpc .INVALID_REQUEST , err .Error (), nil )
727+ metricErrorType = rpcErr .Error .String ()
646728 span .SetStatus (codes .Error , err .Error ())
647- span .SetAttributes (attribute .String ("error.type" , rpcErr . Error . String () ))
729+ span .SetAttributes (attribute .String ("error.type" , metricErrorType ))
648730 return "" , rpcErr , err
649731 }
650732 promptset , ok := s .ResourceMgr .GetPromptset (promptsetName )
651733 if ! ok {
652734 err := fmt .Errorf ("promptset does not exist" )
653735 rpcErr := jsonrpc .NewError (baseMessage .Id , jsonrpc .INVALID_REQUEST , err .Error (), nil )
736+ metricErrorType = rpcErr .Error .String ()
654737 span .SetStatus (codes .Error , err .Error ())
655- span .SetAttributes (attribute .String ("error.type" , rpcErr . Error . String () ))
738+ span .SetAttributes (attribute .String ("error.type" , metricErrorType ))
656739 return "" , rpcErr , err
657740 }
658741 result , err := mcp .ProcessMethod (ctx , protocolVersion , baseMessage .Id , baseMessage .Method , toolset , promptset , s .ResourceMgr , body , header )
659742 if err != nil {
660743 span .SetStatus (codes .Error , err .Error ())
661744 // Set error.type based on JSON-RPC error code
662745 if rpcErr , ok := result .(jsonrpc.JSONRPCError ); ok {
746+ metricErrorType = rpcErr .Error .String ()
663747 span .SetAttributes (attribute .Int ("jsonrpc.error.code" , rpcErr .Error .Code ))
664- span .SetAttributes (attribute .String ("error.type" , rpcErr . Error . String () ))
748+ span .SetAttributes (attribute .String ("error.type" , metricErrorType ))
665749 }
666750 }
667751 return "" , result , err
0 commit comments