@@ -31,57 +31,76 @@ func appendTracingHooks(hooks *server.Hooks) {
3131 hooks .AddOnError (tracingHooks .onError )
3232}
3333
34- var toolHandlerMiddleware = func (next server.ToolHandlerFunc ) server.ToolHandlerFunc {
35- return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
36- startOpts := []llmobs.StartSpanOption {
37- llmobs .WithIntegration (string (instrumentation .PackageMark3LabsMCPGo )),
38- }
39- if session := server .ClientSessionFromContext (ctx ); session != nil {
40- startOpts = append (startOpts , llmobs .WithSessionID (session .SessionID ()))
41- }
42- toolSpan , ctx := llmobs .StartToolSpan (ctx , request .Params .Name , startOpts ... )
34+ // redactedOutput is the placeholder substituted for the tool result body
35+ // when TracingConfig.RedactToolOutput is set.
36+ const redactedOutput = "[REDACTED]"
37+
38+ // newToolHandlerMiddleware returns the tool-call middleware. When
39+ // redactOutput is true, the tool result body sent to LLMObs is replaced
40+ // with redactedOutput; result.IsError is still honored for span error status.
41+ func newToolHandlerMiddleware (redactOutput bool ) func (server.ToolHandlerFunc ) server.ToolHandlerFunc {
42+ return func (next server.ToolHandlerFunc ) server.ToolHandlerFunc {
43+ return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
44+ startOpts := []llmobs.StartSpanOption {
45+ llmobs .WithIntegration (string (instrumentation .PackageMark3LabsMCPGo )),
46+ }
47+ if session := server .ClientSessionFromContext (ctx ); session != nil {
48+ startOpts = append (startOpts , llmobs .WithSessionID (session .SessionID ()))
49+ }
50+ toolSpan , ctx := llmobs .StartToolSpan (ctx , request .Params .Name , startOpts ... )
4351
44- var result * mcp.CallToolResult
45- var err error
52+ var result * mcp.CallToolResult
53+ var err error
4654
47- defer func () {
48- inputJSON , marshalErr := json .Marshal (request )
49- if marshalErr != nil {
50- instr .Logger ().Warn ("mcp-go: failed to marshal tool request: %v" , marshalErr )
51- }
52- var outputText string
53- if result != nil {
54- resultJSON , marshalErr := json .Marshal (result )
55+ defer func () {
56+ inputJSON , marshalErr := json .Marshal (request )
5557 if marshalErr != nil {
56- instr .Logger ().Warn ("mcp-go: failed to marshal tool result : %v" , marshalErr )
58+ instr .Logger ().Warn ("mcp-go: failed to marshal tool request : %v" , marshalErr )
5759 }
58- outputText = string (resultJSON )
59- }
60+ outputText := toolOutputForLLMObs (result , redactOutput )
61+
62+ toolSpan .Annotate (llmobs .WithAnnotatedTags (map [string ]string {
63+ instrmcp .MCPToolTag : request .Params .Name ,
64+ instrmcp .MCPToolKindTag : "server" ,
65+ instrmcp .MCPMethodTag : request .Method ,
66+ }))
67+ tagWithSessionID (ctx , toolSpan )
68+ toolSpan .AnnotateTextIO (string (inputJSON ), outputText )
69+
70+ // There are two ways a tool can express an error:
71+ // 1. It can return a Go error.
72+ // 2. It can return IsError: true as part of the tool result.
73+ if err != nil {
74+ toolSpan .Finish (llmobs .WithError (err ))
75+ } else if result != nil && result .IsError {
76+ toolSpan .Finish (llmobs .WithError (errors .New ("tool resulted in an error" )))
77+ } else {
78+ toolSpan .Finish ()
79+ }
80+ }()
6081
61- toolSpan .Annotate (llmobs .WithAnnotatedTags (map [string ]string {
62- instrmcp .MCPToolTag : request .Params .Name ,
63- instrmcp .MCPToolKindTag : "server" ,
64- instrmcp .MCPMethodTag : request .Method ,
65- }))
66- tagWithSessionID (ctx , toolSpan )
67- toolSpan .AnnotateTextIO (string (inputJSON ), outputText )
68-
69- // There are two ways a tool can express an error:
70- // 1. It can return a Go error.
71- // 2. It can return IsError: true as part of the tool result.
72- if err != nil {
73- toolSpan .Finish (llmobs .WithError (err ))
74- } else if result != nil && result .IsError {
75- toolSpan .Finish (llmobs .WithError (errors .New ("tool resulted in an error" )))
76- } else {
77- toolSpan .Finish ()
78- }
79- }()
82+ result , err = next (ctx , request )
8083
81- result , err = next (ctx , request )
84+ return result , err
85+ }
86+ }
87+ }
8288
83- return result , err
89+ // toolOutputForLLMObs marshals the tool result for the LLMObs span output
90+ // annotation, or returns the redaction placeholder when output redaction is
91+ // enabled. A nil result is reported as the empty string.
92+ func toolOutputForLLMObs (result * mcp.CallToolResult , redact bool ) string {
93+ if result == nil {
94+ return ""
95+ }
96+ if redact {
97+ return redactedOutput
98+ }
99+ resultJSON , marshalErr := json .Marshal (result )
100+ if marshalErr != nil {
101+ instr .Logger ().Warn ("mcp-go: failed to marshal tool result: %v" , marshalErr )
84102 }
103+ return string (resultJSON )
85104}
86105
87106func newHooks () * hooks {
0 commit comments