> ## Documentation Index
> Fetch the complete documentation index at: https://docs.armature.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Go

> Instrument a Go MCP server with mcp-analytics-go

The Go SDK instruments servers built with [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) (v0.49–v0.56) or the official [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) (v1.6+). Go 1.25+.

## Install

<CodeGroup>
  ```bash mark3labs/mcp-go theme={null}
  go get github.com/armature-tech/mcp-analytics-go/armatureanalytics@latest
  ```

  ```bash Official go-sdk theme={null}
  go get github.com/armature-tech/mcp-analytics-go/armatureanalytics/official@latest
  ```
</CodeGroup>

## Set your API key

```bash theme={null}
ANALYTICS_INGEST_API_KEY=<your API key>
```

Create a key in the dashboard, then add it to your deployment secrets. If the key is missing, the SDK quietly no-ops — your server keeps working, and no data is sent.

## Instrument your server

<Tabs>
  <Tab title="mark3labs/mcp-go">
    Create the server with `NewMCPServer` and register tools with `InstrumentTool`:

    ```go main.go theme={null}
    package main

    import (
    	"context"
    	"fmt"
    	"log"
    	"time"

    	"github.com/armature-tech/mcp-analytics-go/armatureanalytics"
    	"github.com/mark3labs/mcp-go/mcp"
    	"github.com/mark3labs/mcp-go/server"
    )

    func main() {
    	s, shutdown := armatureanalytics.NewMCPServer(
    		"Customer MCP", "1.0.0",
    		server.WithToolCapabilities(true),
    	)
    	defer func() {
    		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    		defer cancel()
    		_ = shutdown(ctx)
    	}()

    	armatureanalytics.InstrumentTool(s,
    		mcp.NewTool("lookup_customer",
    			mcp.WithString("customer_id", mcp.Required()),
    		),
    		func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    			id := req.GetArguments()["customer_id"]
    			return mcp.NewToolResultText(fmt.Sprintf("customer %v is active", id)), nil
    		},
    	)

    	if err := server.ServeStdio(s); err != nil {
    		log.Fatal(err)
    	}
    }
    ```
  </Tab>

  <Tab title="Official go-sdk">
    ```go main.go theme={null}
    package main

    import (
    	"context"
    	"time"

    	"github.com/armature-tech/mcp-analytics-go/armatureanalytics/official"
    	"github.com/modelcontextprotocol/go-sdk/mcp"
    )

    type LookupInput struct {
    	CustomerID string `json:"customer_id"`
    }

    type LookupOutput struct {
    	Active bool `json:"active"`
    }

    func main() {
    	s, shutdown := official.NewMCPServer(
    		&mcp.Implementation{Name: "Customer MCP", Version: "1.0.0"}, nil,
    	)
    	defer func() {
    		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    		defer cancel()
    		_ = shutdown(ctx)
    	}()

    	official.InstrumentTool(s,
    		&mcp.Tool{Name: "lookup_customer", Description: "Look up a customer"},
    		func(ctx context.Context, req *mcp.CallToolRequest, input LookupInput) (*mcp.CallToolResult, LookupOutput, error) {
    			return nil, LookupOutput{Active: true}, nil
    		},
    	)

    	// serve over stdio or HTTP as usual
    }
    ```

    The typed `InstrumentTool` derives the input schema from your input struct automatically.
  </Tab>
</Tabs>

## Existing servers

Attach a recorder to a server you already construct:

```go theme={null}
config := armatureanalytics.EnvConfig()
config.Disabled = config.APIKey == ""

rec, err := armatureanalytics.NewRecorder(config)
if err != nil {
	return err
}

s := server.NewMCPServer("Customer MCP", "1.0.0",
	server.WithToolCapabilities(true),
	server.WithHooks(rec.Hooks()),
)
// on shutdown: rec.Close(ctx)
```

For the official SDK, use `official.NewRecorder(config)` followed by `rec.Install(server)`.

<Note>
  Running on serverless or stateless HTTP? See [Stateless & serverless servers](/sdks/stateless-servers) and set `Delivery: armatureanalytics.DeliveryAwait`.
</Note>

## Configuration

```go theme={null}
config := armatureanalytics.EnvConfig()
config.OnError = func(err error, batch armatureanalytics.Batch) {
	log.Printf("analytics delivery failed: %v", err)
}

s, shutdown := armatureanalytics.NewMCPServerWithConfig("Customer MCP", "1.0.0", config)
```

| Field         | Default                                              | Description                                                                                                                 |
| ------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `APIKey`      | `ANALYTICS_INGEST_API_KEY` env var (via `EnvConfig`) | API key used to authenticate ingestion.                                                                                     |
| `EndpointURL` | Armature ingest endpoint                             | Override with `ANALYTICS_INGEST_URL` or this field.                                                                         |
| `Disabled`    | `false`                                              | Set `true` to disable capture entirely.                                                                                     |
| `Delivery`    | `DeliveryBackground`                                 | `DeliveryBackground` posts on a goroutine off the request path; `DeliveryAwait` sends before returning (use in serverless). |
| `Timeout`     | `5 * time.Second`                                    | Delivery timeout. Higher than other SDKs because delivery runs off the request path.                                        |
| `ActorSeed`   | `Authorization` header                               | Function used to attribute calls to a user. See [Identifying users](/sdks/identifying-users).                               |
| `Emit`        | Network delivery                                     | Replace delivery with your own function — useful in tests. Makes `APIKey` optional.                                         |
| `OnError`     | `nil` (silent)                                       | Called when delivery fails. Set this in production so ingest failures are visible.                                          |

<Warning>
  `OnError` is silent by default in Go. Set it in production — otherwise an invalid key or network failure drops events with no signal.
</Warning>

## Flush on shutdown

`NewMCPServer` returns a `Shutdown` function that drains pending deliveries — call it with a timeout on exit (as in the quickstart above). With a manual recorder, call `rec.Close(ctx)`.

## What's not captured

The Go SDK records `initialize` and `tools/call`. Prompts, resources, and OAuth flows are not captured.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Actor always shows as anonymous behind auth middleware">
    With the official SDK adapter, install the analytics middleware **before** your auth middleware if your `ActorSeed` reads values that auth injects into the context.
  </Accordion>

  <Accordion title="No data arrives">
    Check `ANALYTICS_INGEST_API_KEY` is set (missing keys disable delivery silently by design) and set `Config.OnError` to surface failures.
  </Accordion>
</AccordionGroup>
