mcpotel

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2025 License: MIT Imports: 3 Imported by: 0

README

OpenTelemetry instrumentation for modelcontextprotocol/go-sdk

This library provides middleware to provide observability of MCP servers and clients using OpenTelemetry. Currently only trace propagation is implemented, full traces between when making tool calls that themselves generate traces such as for Gen AI SDKs.

Installation

Add github.com/curioswitch/mcp-go-sdk-otel to your go.mod.

go get github.com/curioswitch/mcp-go-sdk-otel@latest

Usage

Add middleware to clients and servers to enable instrumentation. While typical usage requires adding sending middleware to clients and receiving middleware to servers, it is possible to have invocations in the other direction and can make sense to add both.

server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
server.AddReceivingMiddleware(mcpotel.ReceivingMiddleware())
server.AddSendingMiddleware(mcpotel.SendingMiddleware())

client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
client.AddSendingMiddleware(mcpotel.SendingMiddleware())
server.AddReceivingMiddleware(mcpotel.ReceivingMiddleware())

See the package docs for more information.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReceivingMiddleware

func ReceivingMiddleware() mcp.Middleware

ReceivingMiddleware returns middleware to extract OpenTelemetry context from received messages.

While receiving middleware is most commonly utilized on the server, it can also be useful on the client in case the server calls functions exposed by the client.

func main() {
	// Initialize your server and/or client
	// ...

	server.AddReceivingMiddleware(mcpotel.ReceivingMiddleware())
	client.AddReceivingMiddleware(mcpotel.ReceivingMiddleware())
}
Example
ctx := context.Background()

otel.SetTextMapPropagator(propagation.TraceContext{})
clientTransport, serverTransport := mcp.NewInMemoryTransports()

echoTraceIDs := func(ctx context.Context, _ *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	spanCtx := trace.SpanContextFromContext(ctx)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			&mcp.TextContent{Text: fmt.Sprintf("%s/%s", spanCtx.TraceID().String(), spanCtx.SpanID().String())},
		},
	}, nil
}

server := mcp.NewServer(&mcp.Implementation{Name: "receiver", Version: "v0.0.1"}, nil)
server.AddReceivingMiddleware(ReceivingMiddleware())
server.AddTool(
	&mcp.Tool{
		Name:        "echo",
		Description: "Echo trace IDs",
		InputSchema: &jsonschema.Schema{
			Type: "object",
		},
	},
	echoTraceIDs,
)

serverSession, err := server.Connect(ctx, serverTransport, nil)
if err != nil {
	log.Fatal(err)
}

client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
clientSession, err := client.Connect(ctx, clientTransport, nil)
if err != nil {
	log.Fatal(err)
}

res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{
	Name: "echo",
	Meta: map[string]any{
		"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(res.Content[0].(*mcp.TextContent).Text)
Output:

0af7651916cd43dd8448eb211c80319c/b7ad6b7169203331

func SendingMiddleware

func SendingMiddleware() mcp.Middleware

SendingMiddleware returns middleware to inject OpenTelemetry context into sent messages.

While sending middleware is most commonly utilized on the client, it can also be useful on the server in case the server calls functions exposed by the client.

func main() {
	// Initialize your server and/or client
	// ...

	client.AddSendingMiddleware(mcpotel.SendingMiddleware())
	server.AddSendingMiddleware(mcpotel.SendingMiddleware())
}
Example
ctx := context.Background()

otel.SetTextMapPropagator(propagation.TraceContext{})
clientTransport, serverTransport := mcp.NewInMemoryTransports()

echoTraceIDs := func(_ context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	traceparent := ""
	if meta := req.GetParams().GetMeta(); meta != nil {
		if s, ok := meta["traceparent"].(string); ok {
			traceparent = s
		}
	}
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			&mcp.TextContent{Text: traceparent},
		},
	}, nil
}

server := mcp.NewServer(&mcp.Implementation{Name: "receiver", Version: "v0.0.1"}, nil)
server.AddTool(
	&mcp.Tool{
		Name:        "echo",
		Description: "Echo traceparent",
		InputSchema: &jsonschema.Schema{
			Type: "object",
		},
	},
	echoTraceIDs,
)

serverSession, err := server.Connect(ctx, serverTransport, nil)
if err != nil {
	log.Fatal(err)
}

client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
client.AddSendingMiddleware(SendingMiddleware())
clientSession, err := client.Connect(ctx, clientTransport, nil)
if err != nil {
	log.Fatal(err)
}

traceID, _ := trace.TraceIDFromHex("0af7651916cd43dd8448eb211c80319c")
spanID, _ := trace.SpanIDFromHex("b7ad6b7169203331")
spanCtx := trace.NewSpanContext(trace.SpanContextConfig{
	TraceID:    traceID,
	SpanID:     spanID,
	TraceFlags: trace.FlagsSampled,
})
ctx = trace.ContextWithSpanContext(ctx, spanCtx)

res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{
	Name: "echo",
})
if err != nil {
	log.Fatal(err)
}

fmt.Println(res.Content[0].(*mcp.TextContent).Text)
Output:

00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL