1. Conduid
  2. Developer Tools
  3. Go SDK
MCP server · Developer Tools

Go SDK

Production observability for MCP servers. Traces, metrics, and logs — wired up in two lines.

54Fair

Scored yesterday · breakdown

About Go SDK

Go SDK is an MCP server published by last9 in the Developer Tools category: production observability for MCP servers. Traces, metrics, and logs — wired up in two lines.

The repository has 4 stars and 0 forks, with the last commit yesterday. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/last9/mcp-go-sdk

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about Go SDK

Powered by Claude · Grounded in docs

I know everything about Go SDK. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 6 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • !License declaredNo license file. Legally you have no right to use it in a product.
  • ✓Tests presentA test suite exists in the repository.
  • ✓Dependencies pinnedA lockfile pins dependency versions.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
  • !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.

Releases

v0.1.5v0.1.5 · 8 Oct 2026Changelog 57bb3bac3236fd01dbb9efdb577a60b99a0cef2c Fix MCP tool turn correlation using request context (#58) d0b15cca9e5359c8cf24b4bc3bf3a75dabab3ca4 Merge remote-tracking branch 'origin/master' into bug/active-sessions-gauge-leak…
v0.1.4v0.1.4 · 14 Aug 2026Changelog 11e5cfd8e2b487da695d6c2aadb9050503dfcabb fix: isolate anonymous stateless MCP clients (#30)
v0.1.3v0.1.3 · 29 Jul 2026Changelog 6a8d55213d6814b3ab7a954159e553009a324b82 chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp (#16) 7d65864b56a6ac3b5e0772d235dcd414badae68e chore(deps): bump…
v0.1.2v0.1.2 · 24 Apr 2026Changelog c458f18b81923e544a19f72ec67cfc9ab98948e9 fix(logs): start initialize span before session create for trace_id in logs
v0.1.1v0.1.1 · 24 Apr 2026Changelog 129f68a35144ac0a0eb7df755b591d1760afd661 feat: comprehensive OTel observability for Go MCP servers d60e088716a66621cbb80ca3b1157fb78f358501 fix(logs): thread context through logger calls for trace/span ID correlation…

README

mcp-go-sdk

MCP servers are black boxes. You ship one, an LLM starts calling it, and you have no idea what's happening inside — which tools fire, how long they take, what fails, why. That's not acceptable in production.

This library fixes it. Wrap your server or client with two lines and get full OpenTelemetry observability: traces, metrics, structured logs — all correlated, all standard.

server, err := mcp.NewServer("my-server", "1.0.0")
client, err := mcp.NewClient("my-agent", "1.0.0")

Set OTEL_EXPORTER_OTLP_ENDPOINT and you're done. No configuration files, no boilerplate, no instrumentation scattered across your handlers.

Installation

go get github.com/last9/mcp-go-sdk

The thirty-second version

package main

import (
    "context"
    "log"

    sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
    mcp "github.com/last9/mcp-go-sdk/mcp"
)

type SearchArgs struct {
    Query string `json:"query"`
    Limit int    `json:"limit,omitempty"`
}

func main() {
    server, err := mcp.NewServer("search-server", "1.0.0")
    if err != nil {
        log.Fatal(err)
    }
    defer server.Shutdown(context.Background())

    tool := &sdkmcp.Tool{
        Name:        "search",
        Description: "Search the knowledge base",
    }

    mcp.RegisterInstrumentedTool(server, tool, func(ctx context.Context, req *sdkmcp.CallToolRequest, args SearchArgs) (*sdkmcp.CallToolResult, any, error) {
        // your logic here — untouched, unmodified
        return &sdkmcp.CallToolResult{
            Content: []sdkmcp.Content{&sdkmcp.TextContent{Text: "results..."}},
        }, nil, nil
    })

    if err := server.Serve(context.Background(), &sdkmcp.StdioTransport{}); err != nil {
        log.Fatal(err)
    }
}

Your business logic stays exactly as it was. The instrumentation happens around it, not inside it.

What you actually get

Every tools/call, resources/read, prompts/get, and sampling/createMessage produces a span named after the operation — mcp tools/call search, mcp resources/read, and so on — with attributes that follow the OpenTelemetry GenAI semantic conventions:

gen_ai.system            = mcp
gen_ai.operation.name    = tools/call
gen_ai.tool.name         = search
mcp.tool.name            = search
mcp.server.transport     = stdio
mcp.client.name          = claude-desktop
mcp.operation.status     = success | error

These aren't made-up attribute names. They're the emerging standard for AI observability, which means your traces compose with whatever else you're already shipping to your backend.

Query correlation

When Claude calls three tools in a single reasoning turn, those are three separate RPC calls arriving at your server. Without correlation, you get three unrelated traces. That's useless.

Tool calls preserve the remote parent from a valid W3C traceparent HTTP header, falling back to _meta.traceparent (with optional _meta.tracestate). Their mcp.turn.id is the validated trace ID. Without trace context, _meta["last9/turn-id"] or _meta["x-codex-turn-metadata"].turn_id supplies a correlation attribute; it does not create a shared parent or trace. Calls without either remain independent. Clients must send turn context on each call; session identity and tools/list do not define turn boundaries.

Metrics

Metric Type Description
mcp.tool.calls.total Counter Tool invocations
mcp.tool.call.duration.seconds Histogram Tool latency
mcp.tool.errors.total Counter Tool failures
mcp.resource.reads.total Counter Resource reads
mcp.resource.read.duration.seconds Histogram Resource read latency
mcp.prompt.gets.total Counter Prompt fetches
mcp.prompt.get.duration.seconds Histogram Prompt fetch latency
mcp.sampling.creates.total Counter Sampling calls
mcp.sampling.create.duration.seconds Histogram Sampling latency
mcp.server.request.duration.seconds Histogram All operations
mcp.active.sessions Gauge Clients connected through the initialize handshake right now. Drops when the client disconnects, the idle session expires, or the server shuts down.

Flushed every 10 seconds. Histogram buckets are set to sensible defaults for LLM workloads — not the generic OTel defaults that make P99 charts useless.

Logs

Every log line emitted through log/slog automatically carries trace_id and span_id pulled from context. When a tool fails, you don't grep through logs trying to figure out which trace it belonged to. You click the span, open the correlated logs, and you're there.

Environment variables

# Where to send everything (one endpoint if your collector handles all signals)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

# Or per-signal if you need it
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs

# Who you are
OTEL_SERVICE_NAME=my-mcp-server
OTEL_SERVICE_VERSION=1.0.0
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production

Standard OTel conventions throughout. No proprietary configuration.

Options

The defaults are good. You shouldn't need most of these. But they're here when you do.

server, err := mcp.NewServerWithOptions("my-server", "1.0.0",
    // Strip arguments and URIs from spans — right call for PII-sensitive deployments
    mcp.WithDisableArgCapture(),
    mcp.WithDisableResourceCapture(),
    mcp.WithDisablePromptCapture(),
    mcp.WithDisableSamplingCapture(),

    // Skip entire operation families you don't use: no spans or metrics,
    // requests still reach your handlers
    mcp.WithDisableResources(),
    mcp.WithDisablePrompts(),
    mcp.WithDisableSampling(),

    // Your app already initialized OTel — don't let us clobber it
    mcp.WithSkipProviderInit(),

    // How long before we consider a session idle
    mcp.WithSessionTimeout(15 * time.Minute),

    // Minimum severity that gets exported to OTel Logs
    mcp.WithLogLevel(slog.LevelWarn),
)

WithSkipProviderInit deserves a call-out. If your application already calls otel.SetTracerProvider, use this option. Without it we register our own global providers and you end up with two pipelines fighting each other. With it, we pick up yours and everything goes through one place.

If one process creates more than one server or client without this option, the first to start registers the global providers. The others still export their own spans, metrics and logs through their own providers; they just don't replace the globals. If the first one shuts down while others are still running, its providers are flushed but kept alive, so anything using the globals keeps exporting, and they are shut down when the last instance stops.

Outbound HTTP

Your tools probably make HTTP calls. Those calls should be children of the tool span, not invisible gaps in your trace. Pass the context and wrap the client:

func handleSearch(ctx context.Context, req *sdkmcp.CallToolRequest, args SearchArgs) (*sdkmcp.CallToolResult, any, error) {
    client := mcp.WithHTTPTracing(&http.Client{Timeout: 10 * time.Second})

    httpReq, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/search", nil)
    resp, err := client.Do(httpReq)
    // ...
}

The full call chain — LLM reasoning turn → tool call → HTTP request — appears as one connected trace. That's what you need to find real latency bottlenecks.

Client instrumentation

Building an agent or orchestrator that calls MCP servers? Instrument the client the same way:

client, err := mcp.NewClient("my-agent", "1.0.0")
if err != nil {
    log.Fatal(err)
}
defer client.Shutdown(context.Background())

session, err := client.Connect(ctx, &sdkmcp.StdioTransport{}, nil)
if err != nil {
    log.Fatal(err)
}

// Every call through the session is automatically traced
result, err := session.CallTool(ctx, &sdkmcp.CallToolParams{
    Name:      "search",
    Arguments: args,
})

Client spans measure total round-trip latency — not just server processing time. Server name and version are populated automatically from the MCP initialize handshake. The same options apply:

client, err := mcp.NewClientWithOptions("my-agent", "1.0.0",
    mcp.WithDisableArgCapture(),
    mcp.WithSkipProviderInit(),
)

If your app already has OTel

Common case for mature services. Hand off your existing providers and we use them:

otel.SetTracerProvider(yourTracerProvider)
otel.SetMeterProvider(yourMeterProvider)

server, err := mcp.NewServerWithOptions("my-server", "1.0.0",
    mcp.WithSkipProviderInit(),
)

No duplicate pipelines. No surprise exporter registrations. Your observability stack stays under your control.

Claude Desktop setup

Point your server binary at a collector by setting env vars in the MCP config (~/Library/Application Support/Claude/claude_desktop_config.json):

{
    "mcpServers": {
        "my-server": {
            "command": "/path/to/your/binary",
            "env": {
                "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4318",
                "OTEL_SERVICE_NAME": "my-mcp-server"
            }
        }
    }
}

Examples

  • stdio server — calculator, data processor, random generator, outbound HTTP
  • HTTP server — streamable HTTP transport with weather, news, and database tools

License

MIT

README fetched from the repository 6 days ago. The original is authoritative.

Questions

About Go SDK

How do I install Go SDK?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Go SDK safe to use with an AI agent?

Its trust score is 54 out of 100 (fair). It passes 4 of the 6 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Go SDK still maintained?

Yes — the latest release is v0.1.5 (8 Oct 2026), and the last commit was yesterday. The repository has 4 stars.