> ## 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.

# Identifying Users

> How to use AgentCat to identify your user's sessions.

AgentCat allows you to identify users and associate their sessions with specific user information. This lets you to track usage patterns, understand user behavior, and provide personalized experiences.

User identification happens automatically when a tool is invoked. In stateful mode (the default), this happens once on the first tool call and persists for the session. In [stateless mode](/sdk/stateless-servers), it runs on every request since there is no persistent session.

## Basic Implementation

To identify users, provide an `identify` callback function when tracking your MCP server. The simplest approach is to pull the user ID directly from the tool call arguments:

<CodeGroup>
  ```typescript TypeScript theme={null}
  agentcat.track(mcpServer, "proj_abc123xyz", {
    identify: async (request, extra) => {
      const userId = request.params?.arguments?.userId;
      if (!userId) return null;

      return {
        userId,
        userName: request.params?.arguments?.userName
      };
    }
  });

  ```

  ```python Python theme={null}
  import agentcat
  from agentcat import UserIdentity

  def identify_user(request, context):
      user_id = request.params.arguments.get('userId')
      if not user_id:
          return None

      return UserIdentity(
          user_id=user_id,
          user_name=request.params.arguments.get('userName')
      )

  agentcat.track(server, "proj_abc123xyz",
               agentcat.AgentCatOptions(identify=identify_user))
  ```

  ```go Go theme={null}
  import (
      "github.com/modelcontextprotocol/go-sdk/mcp"
      mcpcat "github.com/mcpcat/mcpcat-go-sdk/officialsdk"
      // For mark3labs/mcp-go: mcpcat "github.com/mcpcat/mcpcat-go-sdk/mcpgo"
  )

  mcpcat.Track(s, "proj_abc123xyz", &mcpcat.Options{
      Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
          args := req.GetArguments()
          userID, _ := args["userId"].(string)
          if userID == "" {
              return nil
          }
          userName, _ := args["userName"].(string)
          return &mcpcat.UserIdentity{
              UserID:   userID,
              UserName: userName,
          }
      },
  })
  ```
</CodeGroup>

## User Identity Object

The identify function should return a `UserIdentity` object with the following structure:

<CodeGroup>
  ```typescript TypeScript theme={null}
  interface UserIdentity {
    userId: string;                    // Required: Unique identifier
    userName?: string;                 // Optional: Display name
    userData?: Record<string, any>;    // Optional: Additional metadata
  }
  ```

  ```python Python theme={null}
  from agentcat import UserIdentity

  UserIdentity(
      user_id="user123",                # Required: Unique identifier
      user_name="John Doe",             # Optional: Display name
      user_data={                       # Optional: Additional metadata
          "plan": "premium",
          "company": "Acme Corp"
      }
  )
  ```

  ```go Go theme={null}
  mcpcat.UserIdentity{
      UserID:   "user123",              // Required: Unique identifier
      UserName: "John Doe",             // Optional: Display name
      UserData: map[string]any{         // Optional: Additional metadata
          "plan":    "premium",
          "company": "Acme Corp",
      },
  }
  ```
</CodeGroup>

## Common Patterns

### Token-Based Authentication

Extract user information from authentication tokens:

<CodeGroup>
  ```typescript TypeScript theme={null}
  agentcat.track(mcpServer, "proj_abc123xyz", {
    identify: async (request, extra) => {
      // Get token from request arguments or auth info
      const token = request.params?.arguments?.token ||
                    extra.authInfo?.token;

      // Validate token and fetch user
      const user = await validateTokenAndGetUser(token);

      return {
        userId: user.id,
        userName: user.email,
        userData: {
          role: user.role,
          organization: user.orgId
        }
      };
    }
  });

  ```

  ```python Python theme={null}
  from mcp.server.auth.middleware.auth_context import get_access_token

  def identify_from_token(request, context):
      # Try to get token from MCP auth context first
      access_token = get_access_token()
      if access_token:
          token = access_token.token
      else:
          # Fall back to request arguments
          token = request.params.arguments.get('token')

      # Validate token and fetch user
      user = validate_token_and_get_user(token)

      return UserIdentity(
          user_id=user.id,
          user_name=user.email,
          user_data={
              'role': user.role,
              'organization': user.org_id
          }
      )

  agentcat.track(server, "proj_abc123xyz",
               agentcat.AgentCatOptions(identify=identify_from_token))
  ```

  ```go Go theme={null}
  mcpcat.Track(s, "proj_abc123xyz", &mcpcat.Options{
      Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
          // Get token from request arguments or context
          args := req.GetArguments()
          token, _ := args["token"].(string)

          // Validate token and fetch user
          user, err := validateTokenAndGetUser(token)
          if err != nil {
              return nil
          }

          return &mcpcat.UserIdentity{
              UserID:   user.ID,
              UserName: user.Email,
              UserData: map[string]any{
                  "role":         user.Role,
                  "organization": user.OrgID,
              },
          }
      },
  })
  ```
</CodeGroup>

### API Key Identification

Identify users based on API keys:

<CodeGroup>
  ```typescript TypeScript theme={null}
  agentcat.track(mcpServer, "proj_abc123xyz", {
    identify: async (request, extra) => {
      const apiKey = request.params?.arguments?.apiKey;

      const account = await getAccountByApiKey(apiKey);

      return {
        userId: account.id,
        userName: account.organizationName,
        userData: {
          tier: account.tier,
          monthlyUsage: account.currentUsage,
          region: account.region
        }
      };
    }
  });

  ```

  ```python Python theme={null}
  def identify_from_api_key(request, context):
      api_key = request.params.arguments.get('apiKey')

      account = get_account_by_api_key(api_key)

      return UserIdentity(
          user_id=account.id,
          user_name=account.organization_name,
          user_data={
              'tier': account.tier,
              'monthly_usage': account.current_usage,
              'region': account.region
          }
      )

  agentcat.track(server, "proj_abc123xyz",
               agentcat.AgentCatOptions(identify=identify_from_api_key))
  ```

  ```go Go theme={null}
  mcpcat.Track(s, "proj_abc123xyz", &mcpcat.Options{
      Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
          args := req.GetArguments()
          apiKey, _ := args["apiKey"].(string)

          account := getAccountByApiKey(apiKey)

          return &mcpcat.UserIdentity{
              UserID:   account.ID,
              UserName: account.OrganizationName,
              UserData: map[string]any{
                  "tier":         account.Tier,
                  "monthlyUsage": account.CurrentUsage,
                  "region":       account.Region,
              },
          }
      },
  })
  ```
</CodeGroup>

### MCP Authentication

For servers using MCP's built-in authentication:

<CodeGroup>
  ```typescript TypeScript theme={null}
  agentcat.track(mcpServer, "proj_abc123xyz", {
    identify: async (request, extra) => {
      // Access auth token from extra parameter
      const authToken = extra.authInfo?.token;

      if (!authToken) {
        return null; // Anonymous session
      }

      // Use the MCP auth token to identify user
      const user = await getUserFromMCPToken(authToken);

      return {
        userId: user.id,
        userName: user.email,
        userData: {
          authProvider: 'mcp',
          scopes: extra.authInfo?.scopes
        }
      };
    }
  });

  ```

  ```python Python theme={null}
  from mcp.server.auth.middleware.auth_context import get_access_token

  def identify_from_mcp_auth(request, context):
      # Get the MCP access token
      access_token = get_access_token()

      if not access_token:
          return None  # Anonymous session

      # Use the MCP auth token to identify user
      user = get_user_from_mcp_token(access_token.token)

      return UserIdentity(
          user_id=user.id,
          user_name=user.email,
          user_data={
              'auth_provider': 'mcp',
              'scopes': access_token.scopes
          }
      )

  agentcat.track(server, "proj_abc123xyz",
               agentcat.AgentCatOptions(identify=identify_from_mcp_auth))
  ```

  ```go Go theme={null}
  mcpcat.Track(s, "proj_abc123xyz", &mcpcat.Options{
      Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
          // Extract auth token from context
          authToken := getAuthTokenFromContext(ctx)
          if authToken == "" {
              return nil // Anonymous session
          }

          // Use the auth token to identify user
          user := getUserFromToken(authToken)

          return &mcpcat.UserIdentity{
              UserID:   user.ID,
              UserName: user.Email,
              UserData: map[string]any{
                  "authProvider": "mcp",
              },
          }
      },
  })
  ```
</CodeGroup>

### Error Handling

Handle identification failures gracefully:

<CodeGroup>
  ```typescript TypeScript theme={null}
  agentcat.track(mcpServer, "proj_abc123xyz", {
    identify: async (request, extra) => {
      try {
        const user = await fetchUserInfo(request);
        return {
          userId: user.id,
          userName: user.name
        };
      } catch (error) {
        console.error('User identification failed:', error);
        // Return null to continue with anonymous tracking
        return null;
      }
    }
  });
  ```

  ```python Python theme={null}
  def identify_with_error_handling(request, context):
      try:
          user = fetch_user_info(request)
          return UserIdentity(
              user_id=user.id,
              user_name=user.name
          )
      except Exception as e:
          print(f'User identification failed: {e}')
          # Return None to continue with anonymous tracking
          return None

  agentcat.track(server, "proj_abc123xyz",
               agentcat.AgentCatOptions(identify=identify_with_error_handling))
  ```

  ```go Go theme={null}
  mcpcat.Track(s, "proj_abc123xyz", &mcpcat.Options{
      Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
          user, err := fetchUserInfo(ctx, req)
          if err != nil {
              log.Printf("User identification failed: %v", err)
              // Return nil to continue with anonymous tracking
              return nil
          }
          return &mcpcat.UserIdentity{
              UserID:   user.ID,
              UserName: user.Name,
          }
      },
  })
  ```
</CodeGroup>

## Function Parameters

The identify function receives parameters that vary between the TypeScript, Python, and Go SDKs:

<Note>
  The request structure follows the MCP (Model Context Protocol) specification. The specific types and fields available depend on your SDK:

  * TypeScript: Uses the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
  * Python: Uses the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
  * Go: Uses the [official Go MCP SDK](https://github.com/modelcontextprotocol/go-sdk) or [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go)
</Note>

<CodeGroup>
  ```typescript TypeScript theme={null}
  // TypeScript SDK parameters
  identify: async (request, extra) => {
    // request: MCP request object
    // extra: RequestHandlerExtra object
  }
  ```

  ```python Python theme={null}
  # Python SDK parameters
  def identify(request, context):
      # request: MCP request object
      # context: RequestContext object
      pass
  ```

  ```go Go theme={null}
  // Go SDK parameters
  Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
      // ctx: context.Context — request context with cancellation and deadlines
      // req: *mcp.CallToolRequest — the tool call request with arguments
      return nil
  }
  ```
</CodeGroup>

### request

The MCP request object following the JSON-RPC format:

<CodeGroup>
  ```typescript TypeScript theme={null}
  {
    method: string;              // The tool/method being called (e.g., "tools/call")
    params?: {                   // Optional parameters
      _meta?: {                  // Optional metadata
        progressToken?: string | number;  // Token for progress notifications
      };
      name?: string;             // Tool name being invoked
      arguments?: {              // Tool-specific arguments passed by the client
        // Your custom arguments like userId, apiKey, token, etc. passed into tool calls
        [key: string]: any;
      };
    };
  }
  ```

  ```python Python theme={null}
  # Python request object structure
  request.method           # The tool/method being called (e.g., "tools/call")
  request.params           # Optional parameters object
  request.params.name      # Tool name being invoked
  request.params.arguments # Tool-specific arguments dict
  # Access arguments like:
  request.params.arguments.get('userId')
  request.params.arguments.get('apiKey')
  ```

  ```go Go theme={null}
  // Go request object structure (*mcp.CallToolRequest)
  req.Method()                          // The tool/method being called
  req.Params.Name                       // Tool name being invoked
  req.GetArguments()                    // Tool-specific arguments map
  // Access arguments like:
  args := req.GetArguments()
  userID, _ := args["userId"].(string)
  apiKey, _ := args["apiKey"].(string)
  ```
</CodeGroup>

### TypeScript: extra

The MCP RequestHandlerExtra object providing additional context:

```typescript theme={null}
{
  signal: AbortSignal;         // Abort signal for request cancellation
  authInfo?: AuthInfo;         // Validated access token information
  sessionId?: string;          // Session ID from the transport
  _meta?: RequestMeta;         // Metadata from the original request
  requestId: RequestId;        // JSON-RPC ID of the request (string | number)

  // Functions for sending related messages (not typically used in identify)
  sendNotification: (notification: Notification) => Promise<void>;
  sendRequest: <U>(request: Request, resultSchema: U, options?: RequestOptions) => Promise<z.infer<U>>;
}

```

### Python: context

The MCP RequestContext object providing session and client information:

```python theme={null}
# Python context object
context.session                                   # ServerSession object
context.session.client_params.clientInfo.name     # Client name
context.session.client_params.clientInfo.version  # Client version
context.lifespan_context                          # Application-specific context storage
```

<Note>
  In [stateless mode](/sdk/stateless-servers), `context.session` still exists but represents the per-request context rather than a long-lived session. The `clientInfo` fields are extracted from HTTP headers on each request.
</Note>

<Note>
  **Python Authentication Access**: In Python MCP servers, authentication tokens are accessed via the `get_access_token()` function from `mcp.server.auth.middleware.auth_context`, not through the context parameter. This is different from the TypeScript SDK where auth info is available in the `extra` parameter.
</Note>

### Go: ctx and req

The Go SDK identify function receives `context.Context` and `*mcp.CallToolRequest`:

```go theme={null}
// ctx: standard Go context with cancellation and deadline support
ctx.Done()      // Channel closed when request is cancelled
ctx.Err()       // Returns context.Canceled or context.DeadlineExceeded

// req: the MCP CallToolRequest
req.Params.Name                       // Tool name being invoked
args := req.GetArguments()            // Tool arguments as map[string]any
```

<Note>
  **Go Authentication Access**: In Go MCP servers, authentication information is typically extracted from the `context.Context` parameter. The exact method depends on your authentication middleware and MCP library.
</Note>

## Common Usage Patterns

<CodeGroup>
  ```typescript TypeScript theme={null}
  identify: async (request, extra) => {
    // Access tool arguments
    const userId = request.params?.arguments?.userId;
    const apiKey = request.params?.arguments?.apiKey;

    // Access auth information (if using MCP auth)
    const authToken = extra.authInfo?.token;

    // Tool name being called
    const toolName = request.params?.name;

    // Check if request was cancelled
    if (extra.signal.aborted) {
      return null;
    }

    // Your identification logic here...
  }

  ```

  ```python Python theme={null}
  from mcp.server.auth.middleware.auth_context import get_access_token

  def identify(request, context):
      # Access tool arguments
      user_id = request.params.arguments.get('userId')
      api_key = request.params.arguments.get('apiKey')

      # Access auth token (if using MCP auth)
      access_token = get_access_token()
      if access_token:
          auth_token = access_token.token

      # Tool name being called
      tool_name = request.params.name

      # Access client information from context
      client_name = None
      try:
          client_info = context.session.client_params.clientInfo
          client_name = client_info.name if client_info else None
      except:
          pass

      # Your identification logic here...
  ```

  ```go Go theme={null}
  Identify: func(ctx context.Context, req *mcp.CallToolRequest) *mcpcat.UserIdentity {
      // Access tool arguments
      args := req.GetArguments()
      userID, _ := args["userId"].(string)
      apiKey, _ := args["apiKey"].(string)

      // Tool name being called
      toolName := req.Params.Name

      // Check if request was cancelled
      if ctx.Err() != nil {
          return nil
      }

      // Your identification logic here...
  }
  ```
</CodeGroup>

## Important Considerations

<Note>
  **Stateful servers (default):** The identify function is called **once per session** on the first tool invocation. This minimizes overhead while ensuring consistent user tracking throughout the session.

  **[Stateless servers](/sdk/stateless-servers):** The identify function is called on **every request** since there is no persistent session. Keep your identify function lightweight — avoid expensive API calls or database lookups if possible. Without identification, stateless events cannot be grouped into sessions.
</Note>

### Anonymous Sessions

If you don't provide an identify function or if it returns `null`, sessions will be tracked anonymously. This is useful for:

* Public APIs where user identification isn't required
* Development and testing environments
* Respecting user privacy preferences
