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

# Go

> Set up AgentCat in a Go MCP server, with identity and every feature on.

> [!TIP]
>
> Your coding agent can do the setup for you with this prompt:
>
> ```text theme={null}
> Read and follow https://docs.agentcat.com/languages/go.md to set up AgentCat in this MCP server. Follow its Guidance for AI agents.
> ```

## Supported MCP SDKs

| MCP library | Versions | AgentCat module | Transports | Go |
| - | - | - | - | - |
| [`mark3labs/mcp-go`](https://github.com/mark3labs/mcp-go) | v0.53.0 to v0.57.0 | `go.agentcat.com/sdk/mcpgo/v2` | stdio, streamable HTTP | 1.25.5 or later |
| [`modelcontextprotocol/go-sdk`](https://github.com/modelcontextprotocol/go-sdk) (official) | v1.4.1 to v1.7.0 | `go.agentcat.com/sdk/officialsdk/v2` | stdio, streamable HTTP, stateless HTTP | 1.25 or later |

Each MCP library has its own AgentCat module. A pin of your library inside the range keeps working. For a new server, we recommend the official `go-sdk` at v1.7.0, because it implements the stateless MCP protocol of 2026-07-28.

## Installation

```bash mark3labs/mcp-go theme={null}
go get go.agentcat.com/sdk/mcpgo/v2
```

```bash Official go-sdk theme={null}
go get go.agentcat.com/sdk/officialsdk/v2
```

The SDK needs your project ID, which you can find in the [AgentCat dashboard](https://agentcat.com/app/settings/projects/). We recommend the `AGENTCAT_PROJECT_ID` environment variable, which keeps the ID out of your code. The snippets pass it to `Track()`, because the SDK does not read it.

## Adding AgentCat to your server

`agentcat.Track()` adds AgentCat to your server, where your code creates it. It returns a `shutdown` function and an error:

```go mark3labs/mcp-go theme={null}
package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/mark3labs/mcp-go/server"
	agentcat "go.agentcat.com/sdk/mcpgo/v2"
)

func main() {
	s := server.NewMCPServer("my-server", "1.0.0")
	// s.AddTool(...) as you do today

	shutdown, err := agentcat.Track(s, os.Getenv("AGENTCAT_PROJECT_ID"), nil)
	if err != nil {
		log.Fatal(err) // shutdown is nil when Track fails
	}
	defer shutdown(context.Background()) // sends queued events before exit

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	httpServer := server.NewStreamableHTTPServer(s)
	go func() {
		if err := httpServer.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatal(err) // nothing is queued before the server listens
		}
	}()
	<-ctx.Done()
	httpServer.Shutdown(context.Background())
}
```

```go Official go-sdk theme={null}
package main

import (
	"context"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/modelcontextprotocol/go-sdk/mcp"
	agentcat "go.agentcat.com/sdk/officialsdk/v2"
)

func newServer() *mcp.Server {
	server := mcp.NewServer(&mcp.Implementation{Name: "my-server", Version: "1.0.0"}, nil)
	// mcp.AddTool(server, ...) as you do today
	return server
}

func main() {
	projectID := os.Getenv("AGENTCAT_PROJECT_ID")

	handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
		server := newServer()
		if _, err := agentcat.Track(server, projectID, nil); err != nil {
			log.Printf("agentcat: %v", err) // never fail a request over analytics
		}
		return server
	}, &mcp.StreamableHTTPOptions{Stateless: true})

	httpServer := &http.Server{Addr: ":8080", Handler: handler}
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	go func() {
		if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatal(err) // nothing is queued before the server listens
		}
	}()
	<-ctx.Done()
	httpServer.Shutdown(context.Background())
	agentcat.Shutdown(context.Background()) // sends queued events once for all servers
}
```

A stateless server creates a new `mcp.Server` for each request. Each `Track()` call tracks one instance, so the call goes inside that factory, as the official tab shows. The event queue is shared, so the call is cheap, and one `agentcat.Shutdown()` at exit sends what is left.

## Identifying users

> [!NOTE]
>
> We strongly recommend that you identify your users. Without identity, the dashboard cannot show who uses your server, and each session stays anonymous.

The `Identify` option tells AgentCat which user made each tool call. We recommend the first of these two levels that your server can give.

### Full identity (recommended)

Full identity gives each call a real user ID and name, read from the request:

```go mark3labs/mcp-go theme={null}
// Add "strings" and "github.com/mark3labs/mcp-go/mcp" to the imports.
shutdown, err := agentcat.Track(s, os.Getenv("AGENTCAT_PROJECT_ID"), &agentcat.Options{
	Identify: func(ctx context.Context, request any) *agentcat.UserIdentity {
		req, ok := request.(*mcp.CallToolRequest)
		if !ok {
			return nil
		}
		token := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
		user := lookupUser(token) // your own cheap lookup, for example a cache
		if user == nil {
			return nil
		}
		return &agentcat.UserIdentity{UserID: user.ID, UserName: user.Name}
	},
})
```

```go Official go-sdk theme={null}
// Add "strings" to the imports.
if _, err := agentcat.Track(server, projectID, &agentcat.Options{
	Identify: func(ctx context.Context, request mcp.Request) *agentcat.UserIdentity {
		req, ok := request.(*mcp.CallToolRequest)
		if !ok || req.Extra == nil {
			return nil
		}
		token := strings.TrimPrefix(req.Extra.Header.Get("Authorization"), "Bearer ")
		user := lookupUser(token) // your own cheap lookup, for example a cache
		if user == nil {
			return nil
		}
		return &agentcat.UserIdentity{UserID: user.ID, UserName: user.Name}
	},
}); err != nil {
	log.Printf("agentcat: %v", err)
}
```

### Anonymized identity (the minimum)

If you prefer not to send user details, a stable ID works too, for example a hash of the token or API key. The same user always gets the same ID:

```go mark3labs/mcp-go theme={null}
// Add "crypto/sha256", "encoding/hex", and "strings" to the imports.
Identify: func(ctx context.Context, request any) *agentcat.UserIdentity {
	req, ok := request.(*mcp.CallToolRequest)
	if !ok {
		return nil
	}
	token := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
	if token == "" {
		return nil
	}
	sum := sha256.Sum256([]byte(token))
	return &agentcat.UserIdentity{UserID: "anon-" + hex.EncodeToString(sum[:])[:16]}
},
```

```go Official go-sdk theme={null}
// Add "crypto/sha256", "encoding/hex", and "strings" to the imports.
Identify: func(ctx context.Context, request mcp.Request) *agentcat.UserIdentity {
	req, ok := request.(*mcp.CallToolRequest)
	if !ok || req.Extra == nil {
		return nil
	}
	token := strings.TrimPrefix(req.Extra.Header.Get("Authorization"), "Bearer ")
	if token == "" {
		return nil
	}
	sum := sha256.Sum256([]byte(token))
	return &agentcat.UserIdentity{UserID: "anon-" + hex.EncodeToString(sum[:])[:16]}
},
```

Both snippets go inside the `Options` of `Track()`. `Identify` runs inline on every tool call, so we recommend that it only reads the request or a token that your code already parsed. With the official SDK and MCP auth, the checked token is in `req.Extra.TokenInfo`. A stdio server has no HTTP headers, so the hook can read a key that your server already knows. If the hook returns `nil`, the event still goes out, without identity.

## Recommended settings

With `nil` options, AgentCat turns on every feature, and we recommend that you keep them that way. Each one adds a view of how agents use your server:

* **Intent** (`DisableToolCallContext` is `false`): agents tell you why they call each tool, which powers session goals.
* **Agent feedback** (`DisableReportMissing` is `false`): agents can report missing tools through the `get_more_tools` tool.
* **Sessions** (`DisableTracing` is `false`): AgentCat groups the calls of one task into one session, so you can replay it.
* **Diagnostics** (`DisableDiagnostics` is `false`): anonymous error reports help us fix a broken install faster.

If you need to turn one off, the [reference page](https://docs.agentcat.com/languages/go/reference.md#options) explains each option.

## Testing your setup

The `Debug` option writes the SDK log to `~/agentcat.log`. We recommend it while you test:

```go theme={null}
&agentcat.Options{
	Debug: true,
	// Identify: ... as above
}
```

1. Watch the log:

```bash theme={null}
tail -f ~/agentcat.log
```

2. Start your real server with `AGENTCAT_PROJECT_ID` set.
3. Call one tool with an MCP client, for example the MCP Inspector CLI:

```bash theme={null}
npx @modelcontextprotocol/inspector --cli http://localhost:8080/mcp --transport http \
  --header "Authorization: Bearer <token>" \
  --method tools/call --tool-name <tool> --tool-arg <name>=<value> \
  --tool-arg session_id=start --tool-arg "context=Testing that AgentCat records this tool call."
```

For a stdio server, you can replace the URL and `--transport http` with the command that starts your server, and leave out `--header`.

4. Look for `AgentCat setup complete` and then `successfully published event` in the log. In [Sessions](https://agentcat.com/app/sessions/), the new session shows the user from `Identify`.

A coding agent can confirm the event with the [AgentCat MCP server](https://docs.agentcat.com/agent/connect.md). If no event arrives, the log shows whether a send failed.

## Guidance for AI agents

1. Keep `Identify` in place, because without it every user is anonymous; if the developer does not want to send user details, offer the anonymized ID.
2. Return the same ID for the same user on every call, because a random value or a timestamp splits one user into many.
3. Keep raw tokens, API keys, and passwords out of `UserID`, `UserName`, and `UserData`, because AgentCat stores and shows them.
4. Pass `nil` options, or leave every `Disable...` field `false`, because each feature feeds a part of the dashboard.
5. Read the project ID from `AGENTCAT_PROJECT_ID`, so that it stays out of the code.
6. Check the error from `Track()` and call `shutdown` only when it succeeded, because `shutdown` is `nil` after an error.
7. Call `Track()` once per server instance, and inside the factory on a stateless server, because the factory makes a new instance each time.
8. Run `go list -m -versions go.agentcat.com/sdk/officialsdk/v2` (or `mcpgo/v2`) before you pin a version, because a version from memory can be old.
9. Test through the real server start, because a separate script can skip the `Track()` call.

## Next steps

> [card] [Go reference](https://docs.agentcat.com/languages/go/reference.md)
>
> Options, transports, shutdown, and limits

> [card] [Custom events](https://docs.agentcat.com/sdk/custom-events.md)
>
> Send your own events

> [card] [Tags and properties](https://docs.agentcat.com/sdk/event-tags-properties.md)
>
> Add data that you can filter on

> [card] [Redaction](https://docs.agentcat.com/privacy-security/client-side-redaction.md)
>
> Remove sensitive data in your server

> [card] [Exporters](https://docs.agentcat.com/integrations.md)
>
> Copies of events for OpenTelemetry, Datadog, Sentry, or PostHog


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.