-
Notifications
You must be signed in to change notification settings - Fork 491
feat(contrib/mcp-go) Make simpler way to include mcp-go tracer #4115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
BenchmarksBenchmark execution time: 2025-11-12 19:58:48 Comparing candidate commit e8a5c51 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics. |
e307540 to
f056d4c
Compare
880547f to
0507b87
Compare
46a9a29 to
a922b07
Compare
4ed9165 to
3f1c7fb
Compare
e83aa44 to
1fe77b4
Compare
3f1c7fb to
469570e
Compare
|
1fe77b4 to
e8a5c51
Compare
469570e to
c704875
Compare
|
I don't think this is a good idea. Reflection is not pretty in a library, on top of that we have no real way of ensuring that the If we want to make it simpler we can change the existings funcs to be usable directly as
func WithToolHandlerMiddleware() server.ServerOption {
return server.WithToolHandlerMiddleware(func(next server.ToolHandlerFunc) server.ToolHandlerFunc {
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
toolSpan, ctx := llmobs.StartToolSpan(ctx, request.Params.Name, llmobs.WithIntegration(string(instrumentation.PackageMark3LabsMCPGo)))
result, err := next(ctx, request)
inputJSON, marshalErr := json.Marshal(request)
if marshalErr != nil {
instr.Logger().Warn("mcp-go: failed to marshal tool request: %v", marshalErr)
}
var outputText string
if result != nil {
resultJSON, marshalErr := json.Marshal(result)
if marshalErr != nil {
instr.Logger().Warn("mcp-go: failed to marshal tool result: %v", marshalErr)
}
outputText = string(resultJSON)
}
toolSpan.AnnotateTextIO(string(inputJSON), outputText)
if err != nil {
toolSpan.Finish(llmobs.WithError(err))
} else {
toolSpan.Finish()
}
return result, err
}
})
}
func WithHooks(hooks *server.Hooks) server.ServerOption {
if hooks == nil {
hooks = new(server.Hooks)
}
AddServerHooks(hooks)
return server.WithHooks(hooks)
}Making it transparent to the user if they already have hooks in place. They would simple need to change it to the following: // If they have hooks:
srv := server.NewMCPServer("my-server", "1.0.0",
mcpgotrace.WithHooks(hooks),
mcpgotrace.WithToolHandlerMiddleware(),
)
// if they don't have hooks
srv := server.NewMCPServer("my-server", "1.0.0",
mcpgotrace.WithHooks(nil),
mcpgotrace.WithToolHandlerMiddleware(),
)cc @rarguelloF what do you think ? |
|
I like that. I thought about it but wanted to see if I count abstract away hooks/middleware details. But I think you're right and it's a decent middle ground. |
|
For future reference, We try to avoid it in general, as some customers work in really constrained environments, like serverless: #3762 |

What does this PR do?
Provides an easier way to add tracing to an mcp server that does not leak implementation details (use of hooks, middleware).
Unfortunately this does use some reflection, which is a bit messy. I can understand pushback on this.
Motivation
I was bothered by how the abstractions were leaked, and put the burden on the user to understand them. Also, I'm thinking about how to write an orchestrion patch, and this will make it easier.
Reviewer's Checklist
./scripts/lint.shlocally.Unsure? Have a question? Request a review!