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

# Claude Agent SDK

> Hooks and MCP server that give Anthropic's Claude Agent SDK persistent memory, in Python and TypeScript.

Add persistent memory to an Anthropic Claude Agent in two ways: hooks for zero-friction automatic memory (the model never sees the plumbing), and an MCP server that exposes `synap_search` and `synap_remember` as explicit tools the model can call.

<Note>
  Runtime: Node.js 18+ only. The JS SDK wraps the Python SDK as a subprocess, so it needs Python 3.11+ on the host and does NOT run on Edge / Cloudflare Workers / Bun / Deno / Lambda-Node-only. On Next.js, pin the route to runtime = "nodejs". (The Python package has no such constraint.)
</Note>

## Overview

This guide shows how to add Synap to a Claude Agent SDK application to build agents that:

* Inject relevant memories before every turn, automatically, with no tool calls
* Record every completed turn back to Synap so memory grows with use
* Expose explicit search/store tools the model can call mid-conversation when it needs to

The integration is available in both Python and TypeScript and ships three exports:

| Export                    | Language            | Purpose                                                          |
| ------------------------- | ------------------- | ---------------------------------------------------------------- |
| `create_synap_hooks`      | Python + TypeScript | Hooks for automatic context injection and turn recording         |
| `create_synap_mcp_server` | Python + TypeScript | MCP server exposing `synap_search` and `synap_remember` as tools |
| `build_synap_tools`       | TypeScript only     | Raw tool definitions for manual composition                      |

<Warning>
  The TypeScript package wraps the JavaScript SDK, which wraps the Python SDK as a subprocess. **The TypeScript install requires a Python 3.11+ runtime on the host.** Edge Runtime, Cloudflare Workers, Bun, Deno Deploy, and AWS Lambda Node-only runtimes are not supported. See [Installation → JavaScript / TypeScript SDK](/setup/installation#javascript-typescript-sdk). The Python install has no such constraint.
</Warning>

## Setup

<CodeGroup>
  ```bash Python (pip) theme={null}
  pip install maximem-synap-claude-agent
  ```

  ```bash Python (uv) theme={null}
  uv add maximem-synap-claude-agent
  # pip-compatible (existing venv): uv pip install maximem-synap-claude-agent
  ```

  ```bash TypeScript theme={null}
  npm install @maximem/synap-claude-agent @anthropic-ai/claude-agent-sdk zod
  ```
</CodeGroup>

<Note>
  In Python the pip package is `maximem-synap-claude-agent`, but the import drops the `maximem-` prefix and uses underscores: `from synap_claude_agent import ...`. In TypeScript the import name matches the npm package: `import { createSynapHooks } from "@maximem/synap-claude-agent"`.
</Note>

Configure your API key. Generate one from the [Synap Dashboard](https://synap.maximem.ai).

```bash .env theme={null}
SYNAP_API_KEY=synap_your_key_here
ANTHROPIC_API_KEY=your-anthropic-api-key
```

Initialize the SDK once at application startup:

<CodeGroup>
  ```python Python theme={null}
  from maximem_synap import MaximemSynapSDK

  sdk = MaximemSynapSDK()
  await sdk.initialize()
  ```

  ```typescript TypeScript theme={null}
  import { createClient } from "@maximem/synap-js-sdk";

  const sdk = createClient();
  await sdk.init();
  ```
</CodeGroup>

See [SDK Initialization](/sdk/initialization) for the full lifecycle and configuration options.

## Basic integration

The smallest useful integration uses hooks: memory injection and turn recording happen automatically, with no changes to the model's tool surface:

<CodeGroup>
  ```python Python theme={null}
  # pip install maximem-synap-claude-agent
  import asyncio
  import uuid
  from maximem_synap import MaximemSynapSDK
  from claude_agent_sdk import query, ClaudeAgentOptions
  from synap_claude_agent import create_synap_hooks

  sdk = MaximemSynapSDK()
  await sdk.initialize()

  # If you pass conversation_id explicitly it must be a valid UUID;
  # omit it to let Synap auto-generate one.
  hooks = create_synap_hooks(
      sdk=sdk,
      user_id="alice",
      customer_id="acme",                  # optional, required for B2B instances
      conversation_id=str(uuid.uuid4()),   # optional; auto-generated if omitted
  )

  async def main():
      async for message in query(
          prompt="What did I tell you about my trial account?",
          options=ClaudeAgentOptions(hooks=hooks),
      ):
          print(message)

  asyncio.run(main())
  ```

  ```typescript TypeScript theme={null}
  // npm install @maximem/synap-claude-agent @anthropic-ai/claude-agent-sdk zod
  import { query } from "@anthropic-ai/claude-agent-sdk";
  import { createClient } from "@maximem/synap-js-sdk";
  import { createSynapHooks } from "@maximem/synap-claude-agent";

  const sdk = createClient();
  await sdk.init();

  const hooks = createSynapHooks({
    sdk,
    userId: "alice",
    customerId: "acme",                      // optional
    conversationId: crypto.randomUUID(),     // optional; must be a valid UUID if passed
  });

  for await (const message of query({
    prompt: "What did I tell you about my trial account?",
    options: { hooks },
  })) {
    console.log(message);
  }
  ```
</CodeGroup>

The hooks intercept the agent's lifecycle: `before_query` fetches Synap context and prepends it as a system message, and `after_turn` ingests the completed user/assistant exchange. **Context fetch failures degrade gracefully**: empty context is injected and the error is logged. **Turn ingestion failures surface explicitly** so silent data loss is impossible.

For explicit memory control (the model decides when to search or store), layer in the MCP server (see below).

***

## Core concepts

### Hooks: automatic memory

`create_synap_hooks` returns a dict of hook callbacks that the Claude Agent SDK invokes around each turn:

| Hook           | Behavior                                                                          |
| -------------- | --------------------------------------------------------------------------------- |
| `before_query` | Fetches Synap context for the incoming prompt and prepends it as a system message |
| `after_turn`   | Ingests the full user + assistant turn back into Synap                            |

```python theme={null}
import uuid

hooks = create_synap_hooks(
    sdk=sdk,
    user_id="alice",
    customer_id="acme",
    conversation_id=str(uuid.uuid4()),
)
```

The model never sees the hook plumbing: there are no tools added, no schemas to learn. This is the right primitive for production agents where memory should be omnipresent.

### MCP server: explicit memory tools

When you want the *model* to decide when to query or store memories, register the Synap MCP server. It exposes two tools:

* **`synap_search`**: search memories by natural-language query
* **`synap_remember`**: store a new memory

<CodeGroup>
  ```python Python theme={null}
  from claude_agent_sdk import query, ClaudeAgentOptions
  from synap_claude_agent import create_synap_hooks, create_synap_mcp_server

  hooks = create_synap_hooks(sdk=sdk, user_id="alice")
  mcp_server = create_synap_mcp_server(sdk=sdk, user_id="alice")

  async for message in query(
      prompt="Search your memory for anything about my project deadlines.",
      options=ClaudeAgentOptions(
          hooks=hooks,
          mcp_servers={"synap": mcp_server},
      ),
  ):
      print(message)
  ```

  ```typescript TypeScript theme={null}
  import { query } from "@anthropic-ai/claude-agent-sdk";
  import { createSynapHooks, createSynapMcpServer } from "@maximem/synap-claude-agent";

  const hooks = createSynapHooks({ sdk, userId: "alice" });
  const mcpServer = createSynapMcpServer({ sdk, userId: "alice" });

  for await (const message of query({
    prompt: "Search your memory for anything about my project deadlines.",
    options: {
      hooks,
      mcpServers: { synap: mcpServer },
    },
  })) {
    console.log(message);
  }
  ```
</CodeGroup>

The MCP server can be used alone, or alongside hooks for layered memory (automatic context plus on-demand search/store).

### Raw tool definitions (TypeScript)

For TypeScript users who want to compose tools manually rather than going through MCP, `buildSynapTools` returns raw Anthropic tool definitions:

```typescript theme={null}
import { buildSynapTools } from "@maximem/synap-claude-agent";

const tools = buildSynapTools({ sdk, userId: "alice", customerId: "acme" });
// tools = [synapSearchTool, synapRememberTool]
```

Pass them directly into the agent's tool list. The Python integration uses MCP exclusively for explicit tooling, so this export is TypeScript-only.

***

## Complete example: agent with hooks + MCP server

The pattern below combines both primitives. Hooks provide automatic memory on every turn, and the MCP server gives the model the option to dig deeper when it decides recall is needed:

<CodeGroup>
  ```python Python theme={null}
  from claude_agent_sdk import query, ClaudeAgentOptions
  from synap_claude_agent import create_synap_hooks, create_synap_mcp_server


  async def handle_query(sdk, user_id: str, prompt: str, customer_id: str | None = None) -> str:
      hooks = create_synap_hooks(sdk=sdk, user_id=user_id, customer_id=customer_id)
      mcp = create_synap_mcp_server(sdk=sdk, user_id=user_id, customer_id=customer_id)

      full = []
      async for message in query(
          prompt=prompt,
          options=ClaudeAgentOptions(
              hooks=hooks,
              mcp_servers={"synap": mcp},
          ),
      ):
          full.append(message)
      return "\n".join(str(m) for m in full)


  # Usage
  reply = await handle_query(sdk, user_id="alice", prompt="What plan am I on?", customer_id="acme")
  ```

  ```typescript TypeScript theme={null}
  import { query } from "@anthropic-ai/claude-agent-sdk";
  import { createSynapHooks, createSynapMcpServer } from "@maximem/synap-claude-agent";

  async function handleQuery(sdk, userId: string, prompt: string, customerId?: string) {
    const hooks = createSynapHooks({ sdk, userId, customerId });
    const mcpServer = createSynapMcpServer({ sdk, userId, customerId });

    const out: string[] = [];
    for await (const message of query({
      prompt,
      options: { hooks, mcpServers: { synap: mcpServer } },
    })) {
      out.push(String(message));
    }
    return out.join("\n");
  }

  // Usage
  const reply = await handleQuery(sdk, "alice", "What plan am I on?", "acme");
  ```
</CodeGroup>

Three things to notice in this pattern:

1. **Hooks and MCP server cover different needs.** Hooks are silent and always-on; MCP tools are explicit and model-driven.
2. **They compose.** Use both: the model sees relevant context every turn AND can query for more when needed.
3. **Scope is per-request.** Each `handle_query` invocation creates its own hooks and MCP server scoped to the right user.

***

## Advanced patterns

### Hooks vs. MCP server

|                   | Hooks                                           | MCP server                                            |
| ----------------- | ----------------------------------------------- | ----------------------------------------------------- |
| Context injection | Automatic, every turn                           | On-demand via tool call                               |
| Memory storage    | Automatic, every turn                           | On-demand via tool call                               |
| Model awareness   | Model doesn't see the tools                     | Model can decide when to search/store                 |
| Best for          | Production agents where memory is always needed | Research agents where explicit memory control matters |

Use both together for maximum coverage: hooks handle the always-on path, MCP tools handle the explicit path.

### Multi-tenant scoping

All three exports accept the standard scoping triple: `user_id` (required), optional `customer_id`, optional `conversation_id`. `customer_id` is required on B2B Synap instances and ignored on single-tenant ones. See [Memory Scopes](/concepts/memory-scopes).

```python theme={null}
hooks = create_synap_hooks(sdk=sdk, user_id="alice", customer_id="acme")
```

For multi-tenant services, construct hooks/MCP per request rather than caching them globally.

### Failure semantics

The integration follows the Synap-wide contract:

* **`before_query` (hooks) degrades gracefully**: empty context on failure, error logged.
* **`after_turn` (hooks) surfaces failures**: turn ingestion raises `SynapIntegrationError`.
* **`synap_search` (MCP) degrades gracefully**: returns `[]` on failure.
* **`synap_remember` (MCP) surfaces failures**: raises `SynapIntegrationError`.

This is by design: read failures shouldn't break a user-facing turn, but silent write failures would let the memory drift away from reality.

***

## Going further

* [Patterns overview](/patterns/overview): reusable memory patterns across frameworks.
* [Cookbook overview](/cookbook/overview): end-to-end worked examples.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Vercel AI SDK" icon="triangle" href="/integrations/vercel-ai-sdk">
    Middleware for any Vercel AI SDK model.
  </Card>

  <Card title="Mastra" icon="layer-group" href="/integrations/mastra">
    `SynapMemory` for Mastra.
  </Card>

  <Card title="Context Fetch" icon="search" href="/sdk/context-fetch">
    The retrieval API behind hooks and `synap_search`: modes, scopes, and response shapes.
  </Card>

  <Card title="Memory Scopes" icon="layer-group" href="/concepts/memory-scopes">
    How `user_id`, `customer_id`, and `conversation_id` interact across reads.
  </Card>
</CardGroup>
