> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Connect Sanity Context with LangChain

Connect Sanity Context to a Python or TypeScript agent using LangChain and its MCP adapters.

[LangChain](https://docs.langchain.com) is a framework for building LLM agents in Python and TypeScript. This guide shows you how to connect a LangChain agent to [Sanity Context](https://www.sanity.io/docs/context/introduction) with LangChain's MCP adapters: [langchain-mcp-adapters](https://github.com/langchain-ai/langchain-mcp-adapters) for Python and [@langchain/mcp-adapters](https://www.npmjs.com/package/@langchain/mcp-adapters) for TypeScript. The adapters convert MCP tools to LangChain tools. You'll build a script that loads your schema or Knowledge Base outline into the system prompt, connects to your Context MCP endpoint, and answers a question about your content.

The same code works whether the endpoint uses Knowledge Base sources, a dataset source, or both. With Knowledge Base sources only, the tools are `knowledge_base_search` and `knowledge_base_read` instead of the dataset source tools, and the initial context is the Knowledge Base outline instead of your schema. With both source types, the endpoint serves the dataset source tools and the Knowledge Base source tools, and its initial context contains both the schema overview and the Knowledge Base outline. See [Context MCP source types](https://www.sanity.io/docs/context/source-types).

## Prerequisites

To follow this guide, you need:

- **Context MCP endpoint URL**: Shown on the MCP endpoint in the Context app in the Sanity Dashboard. To create an MCP endpoint, see [Configure a Context MCP endpoint](https://www.sanity.io/docs/context/configure-mcp), or expand **Create the endpoint with the Sanity CLI (optional)** at the end of this section.
- **Organization API token**: Create one in [Manage](https://www.sanity.io/manage/org/api/tokens) under **API > Tokens** at the organization level. Under **Organization permissions**, select **Context** with the **Viewer** role. Project tokens are refused.
- **Anthropic API key**: `ChatAnthropic` reads it from `ANTHROPIC_API_KEY`.
- **Python 3.10 or later** for Python, or **Node.js 22.18 or later** for TypeScript. Node.js 22.18 and later run `.ts` files directly.

You can create a Knowledge Base and an MCP endpoint from the Sanity CLI instead of the Dashboard. If you haven't signed in, run `npx sanity@latest login`. You still create the organization token in Manage. Replace `ORGANIZATION_ID`, `PROJECT_ID`, and `KNOWLEDGE_BASE_ID` with your own values:

**npm**

```shell
# Create a Knowledge Base. The command prints its ID.
npx sanity@latest context create --organization ORGANIZATION_ID --title "Support" --description "Answers to customer support questions"

# Add sources: a dataset query, files, or a website to crawl (--url)
npx sanity@latest context sources create KNOWLEDGE_BASE_ID --sanity-project-id PROJECT_ID --sanity-dataset-id production --query '*[_type == "article"]'
npx sanity@latest context sources create KNOWLEDGE_BASE_ID --file ./returns-policy.pdf

# Build it, then review anything the build flagged
npx sanity@latest context build KNOWLEDGE_BASE_ID --watch
npx sanity@latest context issues list KNOWLEDGE_BASE_ID --status open

# Create the MCP endpoint and check that it's ready
npx sanity@latest context mcp create support --title "Support" --organization ORGANIZATION_ID --knowledge-base KNOWLEDGE_BASE_ID
npx sanity@latest context mcp status support --organization ORGANIZATION_ID
```

**pnpm**

```shell
# Create a Knowledge Base. The command prints its ID.
pnpm dlx sanity@latest context create --organization ORGANIZATION_ID --title "Support" --description "Answers to customer support questions"

# Add sources: a dataset query, files, or a website to crawl (--url)
pnpm dlx sanity@latest context sources create KNOWLEDGE_BASE_ID --sanity-project-id PROJECT_ID --sanity-dataset-id production --query '*[_type == "article"]'
pnpm dlx sanity@latest context sources create KNOWLEDGE_BASE_ID --file ./returns-policy.pdf

# Build it, then review anything the build flagged
pnpm dlx sanity@latest context build KNOWLEDGE_BASE_ID --watch
pnpm dlx sanity@latest context issues list KNOWLEDGE_BASE_ID --status open

# Create the MCP endpoint and check that it's ready
pnpm dlx sanity@latest context mcp create support --title "Support" --organization ORGANIZATION_ID --knowledge-base KNOWLEDGE_BASE_ID
pnpm dlx sanity@latest context mcp status support --organization ORGANIZATION_ID
```

**yarn**

```shell
# Create a Knowledge Base. The command prints its ID.
yarn dlx sanity@latest context create --organization ORGANIZATION_ID --title "Support" --description "Answers to customer support questions"

# Add sources: a dataset query, files, or a website to crawl (--url)
yarn dlx sanity@latest context sources create KNOWLEDGE_BASE_ID --sanity-project-id PROJECT_ID --sanity-dataset-id production --query '*[_type == "article"]'
yarn dlx sanity@latest context sources create KNOWLEDGE_BASE_ID --file ./returns-policy.pdf

# Build it, then review anything the build flagged
yarn dlx sanity@latest context build KNOWLEDGE_BASE_ID --watch
yarn dlx sanity@latest context issues list KNOWLEDGE_BASE_ID --status open

# Create the MCP endpoint and check that it's ready
yarn dlx sanity@latest context mcp create support --title "Support" --organization ORGANIZATION_ID --knowledge-base KNOWLEDGE_BASE_ID
yarn dlx sanity@latest context mcp status support --organization ORGANIZATION_ID
```

**bun**

```shell
# Create a Knowledge Base. The command prints its ID.
bunx sanity@latest context create --organization ORGANIZATION_ID --title "Support" --description "Answers to customer support questions"

# Add sources: a dataset query, files, or a website to crawl (--url)
bunx sanity@latest context sources create KNOWLEDGE_BASE_ID --sanity-project-id PROJECT_ID --sanity-dataset-id production --query '*[_type == "article"]'
bunx sanity@latest context sources create KNOWLEDGE_BASE_ID --file ./returns-policy.pdf

# Build it, then review anything the build flagged
bunx sanity@latest context build KNOWLEDGE_BASE_ID --watch
bunx sanity@latest context issues list KNOWLEDGE_BASE_ID --status open

# Create the MCP endpoint and check that it's ready
bunx sanity@latest context mcp create support --title "Support" --organization ORGANIZATION_ID --knowledge-base KNOWLEDGE_BASE_ID
bunx sanity@latest context mcp status support --organization ORGANIZATION_ID
```

`mcp create` prints the endpoint URL to use as `SANITY_CONTEXT_MCP_URL`. To serve a live dataset through a dataset source instead, replace `--knowledge-base KNOWLEDGE_BASE_ID` with `-p PROJECT_ID -d production`. See the [Context CLI command reference](https://www.sanity.io/docs/cli-reference/cli-context) for every flag.



## Install dependencies

In a new project directory, install LangChain, the MCP adapters, and the Anthropic provider for your language:

#### TypeScript

**npm**

```shell
npm install langchain@^1 @langchain/mcp-adapters@^2 @langchain/anthropic@^1 @langchain/core@^1
```

**pnpm**

```shell
pnpm add langchain@^1 @langchain/mcp-adapters@^2 @langchain/anthropic@^1 @langchain/core@^1
```

**yarn**

```shell
yarn add langchain@^1 @langchain/mcp-adapters@^2 @langchain/anthropic@^1 @langchain/core@^1
```

**bun**

```shell
bun add langchain@^1 @langchain/mcp-adapters@^2 @langchain/anthropic@^1 @langchain/core@^1
```

The example uses version 2 of `@langchain/mcp-adapters`, which includes its own MCP client, so you don't install the MCP SDK separately. Set the module type in `package.json` so Node.js loads `agent.ts` as an ES module:

**CLI**

```sh
npm pkg set type=module
```

If you type-check with `tsc`, set `"skipLibCheck": true` in `tsconfig.json`. Recent `@langchain/anthropic` releases fail library checks against newer `@anthropic-ai/sdk` types.



#### Python

**CLI**

```sh
python3 -m venv .venv
source .venv/bin/activate
pip install langchain langchain-mcp-adapters langchain-anthropic httpx python-dotenv
```



## Set environment variables

Create a `.env` file next to `agent.ts` or `agent.py`. In TypeScript, Node.js reads it with `--env-file`; in Python, `load_dotenv()` reads it. Replace each placeholder with your own value:

**.env**

```sh
SANITY_CONTEXT_MCP_URL=YOUR_MCP_ENDPOINT_URL
SANITY_ORGANIZATION_TOKEN=YOUR_ORGANIZATION_TOKEN
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
```

## Create the agent

Create `agent.ts` or `agent.py` next to `.env`. The script works in three stages:

1. **Fetch initial context** from the `/initial-context` HTTP endpoint and add it to the system prompt. The agent starts with a compressed schema or Knowledge Base outline, which saves a tool call on every conversation. For details, see [Inline initial context into your system prompt](https://www.sanity.io/docs/context/inline-initial-context).
2. **Connect to the MCP endpoint and load its tools**, authenticating with your organization API token. `MCPAdapter` (TypeScript) or `MultiServerMCPClient` (Python) converts the MCP tools to LangChain tools. The script removes the `initial_context` tool, since its content is already in the system prompt.
3. **Create the agent and call the model** with `createAgent` (TypeScript) or `create_agent` (Python). The agent calls tools as it explores your content, then writes its answer.

#### TypeScript

**agent.ts**

```typescript
import {createAgent} from 'langchain'
import {ChatAnthropic} from '@langchain/anthropic'
import {MCPAdapter} from '@langchain/mcp-adapters'

const MCP_URL = process.env.SANITY_CONTEXT_MCP_URL
const API_TOKEN = process.env.SANITY_ORGANIZATION_TOKEN
if (!MCP_URL || !API_TOKEN) {
  throw new Error('Set SANITY_CONTEXT_MCP_URL and SANITY_ORGANIZATION_TOKEN')
}
const headers = {Authorization: `Bearer ${API_TOKEN}`}

// 1. Fetch the initial context and inline it in the system prompt.
async function fetchInitialContext(): Promise<string | null> {
  const url = new URL(MCP_URL!)
  url.pathname = `${url.pathname.replace(/\/$/, '')}/initial-context`
  try {
    const res = await fetch(url, {headers})
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return await res.text()
  } catch (err) {
    console.warn(`Could not fetch initial context: ${err}`)
    return null
  }
}

const initialContext = await fetchInitialContext()

let systemPrompt =
  'You are a helpful assistant that answers questions using the Sanity Context tools.'
if (initialContext) {
  systemPrompt += `\n\n# Data reference\n\n${initialContext}`
}

// 2. Connect to the Sanity Context MCP server over Streamable HTTP.
const mcpClient = new MCPAdapter({
  // Keep the server's own tool names (groq_query, not sanity_groq_query).
  prefixToolNameWithServerName: false,
  servers: {
    sanity: {
      transport: 'http',
      url: MCP_URL,
      headers,
    },
  },
})

try {
  // Drop initial_context only if its content is already in the prompt.
  const tools = (await mcpClient.listTools()).filter(
    (t) => !(initialContext && t.name === 'initial_context'),
  )

  // 3. Create the agent and call the model.
  const agent = createAgent({
    model: new ChatAnthropic({model: 'claude-sonnet-5-5'}),
    tools,
    systemPrompt,
  })

  const result = await agent.invoke({
    messages: [{role: 'user', content: process.argv[2] ?? 'What content do we have?'}],
  })
  console.log(result.messages.at(-1)?.text)
} finally {
  await mcpClient.close()
}
```

A few details differ in LangChain.js:

- `MCPAdapter` replaces `MultiServerMCPClient`, which is now a deprecated alias, and `listTools()` replaces `getTools()`.
- Version 2 of `@langchain/mcp-adapters` prefixes tool names with the server key by default (`sanity_groq_query`). The example sets `prefixToolNameWithServerName: false` so the names match the server's and the `initial_context` filter works. If you keep the prefix, filter on `sanity_initial_context` instead.
- If the initial context fetch fails, the TypeScript example logs a warning and keeps the `initial_context` tool, so the model can still load the schema or outline itself. The Python example stops with an error instead.

Run it with a question. Node.js reads the `.env` file with `--env-file`:

**CLI**

```sh
node --env-file=.env agent.ts "Do you price match?"
```

The agent calls the Context MCP tools to explore your content, then prints its answer to the terminal. If the run fails, match the message against the list in [Troubleshoot common errors](https://www.sanity.io/docs/context/connect-langchain).

### Remember the conversation with LangGraph

`createAgent` runs on LangGraph, so a LangGraph checkpointer gives the agent memory across turns. `@langchain/langgraph` is already installed as a dependency of `langchain`. Add it to your own dependencies, since the example imports it directly:

**npm**

```shell
npm install @langchain/langgraph
```

**pnpm**

```shell
pnpm add @langchain/langgraph
```

**yarn**

```shell
yarn add @langchain/langgraph
```

**bun**

```shell
bun add @langchain/langgraph
```

Then replace the agent and the `invoke` call in the `try` block:

**agent.ts**

```typescript
import {MemorySaver} from '@langchain/langgraph'

// ...connect and filter tools as in the full example, then:
const agent = createAgent({
  model: new ChatAnthropic({model: 'claude-sonnet-5-5'}),
  tools,
  systemPrompt,
  // Keeps each conversation's messages, keyed by thread_id
  checkpointer: new MemorySaver(),
})

const config = {configurable: {thread_id: 'support-session-1'}}
for (const question of ['What content do we have?', 'Tell me more about the first one.']) {
  const result = await agent.invoke({messages: [{role: 'user', content: question}]}, config)
  console.log(`> ${question}\n${result.messages.at(-1)?.text}\n`)
}
```

The second question works because the checkpointer carries the first answer forward on the same `thread_id`. `MemorySaver` keeps state in memory; use a persistent checkpointer, such as Postgres, in production.

To use the agent inside a larger graph, add `agent.graph` as a node, for example `new StateGraph(MessagesAnnotation).addNode('sanity', agent.graph)`. Put the checkpointer on the parent graph, not on the agent.



#### Python

**agent.py**

```python
import asyncio
import os
from urllib.parse import urlparse, urlunparse

import httpx
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic
from langchain_mcp_adapters.client import MultiServerMCPClient

load_dotenv()

MCP_URL = os.environ["SANITY_CONTEXT_MCP_URL"]
API_TOKEN = os.environ["SANITY_ORGANIZATION_TOKEN"]


async def main():
    # 1. Fetch initial context over HTTP
    parsed = urlparse(MCP_URL)
    initial_context_url = urlunparse(
        parsed._replace(path=parsed.path.rstrip("/") + "/initial-context")
    )

    async with httpx.AsyncClient() as http:
        resp = await http.get(
            initial_context_url,
            headers={"Authorization": f"Bearer {API_TOKEN}"},
        )
        resp.raise_for_status()
        initial_context = resp.text

    # 2. Connect to Sanity Context MCP and load tools
    mcp_client = MultiServerMCPClient({
        "sanity": {
            "transport": "http",
            "url": MCP_URL,
            "headers": {"Authorization": f"Bearer {API_TOKEN}"},
        },
    })
    tools = [t for t in await mcp_client.get_tools() if t.name != "initial_context"]

    # 3. Create the agent and call the LLM
    system_prompt = (
        "You are a helpful assistant.\n\n"
        "# Data reference\n\n"
        "Use this to understand what's available and write better queries.\n\n"
        + initial_context
    )

    llm = ChatAnthropic(model="claude-sonnet-5-5")
    agent = create_agent(llm, tools, system_prompt=system_prompt)

    result = await agent.ainvoke(
        {"messages": [("user", "What content do we have?")]}
    )
    print(result["messages"][-1].text)


asyncio.run(main())
```

Run the script with the virtual environment active. `load_dotenv()` reads the `.env` file:

**CLI**

```sh
python agent.py
```

The agent calls the Context MCP tools to explore your content, then prints its answer to the terminal. If the run fails, match the message against the list in [Troubleshoot common errors](https://www.sanity.io/docs/context/connect-langchain).



## Troubleshoot common errors

These are the most common errors and how to fix them:

#### TypeScript

- `[MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///…/agent.ts is not specified and it doesn't parse as CommonJS.`: The agent still runs, but `package.json` doesn't set the module type. Run `npm pkg set type=module`. Editors and `tsc` report the same cause as `error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level`.
- `Error: Set SANITY_CONTEXT_MCP_URL and SANITY_ORGANIZATION_TOKEN`: The environment variables aren't loaded. Run with `--env-file=.env`.
- `MCPClientError: Authentication failed for HTTP server "sanity"`, after `Could not fetch initial context: Error: HTTP 401`: The token is missing, invalid, or belongs to another organization.
- 403 from the MCP connection with JSON-RPC error `-32007` on an endpoint with a dataset source, or `-32006` on an endpoint with only Knowledge Base sources, or the same 403 from the `/initial-context` fetch with code `contextGrantRequired` or `knowledgeBaseAccessDenied`. The messages are "Serving a dataset requires an organization API token with Context access ('sanity.knowledge-base.read'). Create one under your organization's API settings in Manage." (dataset source) and "No access to knowledge base '<knowledge base ID>'. Requires one of: sanity.knowledge-base.read." (Knowledge Base sources only; `<knowledge base ID>` is a placeholder for the Knowledge Base's ID): On an endpoint with a dataset source, the token isn't an organization API token with Context Viewer permissions. On an endpoint with only Knowledge Base sources, the token has no read access to the Knowledge Base the message names. Project tokens are refused however broad their permissions. Create an organization token as described in [Prerequisites](https://www.sanity.io/docs/context/connect-langchain).
- `Error: Anthropic API key not found`: Add `ANTHROPIC_API_KEY` to the `.env` file. `ChatAnthropic` throws when it's created, before the agent runs.
- A Zod error about `unrecognized_keys` from `MCPAdapter`: The config has an option from version 1, such as `useStandardContentBlocks`. Remove it.
- `Failed to put checkpoint ... missing a required "thread_id"`: Pass `{configurable: {thread_id}}` when the agent has a checkpointer.



#### Python

- `KeyError: 'SANITY_CONTEXT_MCP_URL'`: The `.env` file is missing, or isn't in the directory you run the script from. `load_dotenv()` returns without error when it finds no file.
- `httpx.HTTPStatusError: Client error '401 Unauthorized'` raised by `resp.raise_for_status()`: The value in `SANITY_ORGANIZATION_TOKEN` isn't a valid organization API token.
- `langchain_anthropic.chat_models.AnthropicConnectionError: Connection error.`, caused by `Illegal header value b'Bearer '`: `ANTHROPIC_API_KEY` isn't set, so the request goes out with an empty authorization header. Set it in the `.env` file. `ChatAnthropic` constructs without a key, so the error appears on the first model call, after the client's retries.
- `httpx.HTTPStatusError: Client error '403 Forbidden'` from the `/initial-context` fetch, with code `contextGrantRequired` in the response body: The token isn't an organization API token with Context Viewer permissions. Project tokens are refused however broad their permissions. Create an organization token as described in [Prerequisites](https://www.sanity.io/docs/context/connect-langchain). A 403 that names a Knowledge Base means the token can't read a Knowledge Base the endpoint serves.



## Next steps

- [Sanity Context patterns and best practices](https://www.sanity.io/docs/context/best-practices): Production patterns for public assistants, personalized agents, and multi-backend setups.
- [Inline initial context into your system prompt](https://www.sanity.io/docs/context/inline-initial-context): Control what the inlined payload contains and keep it fresh in long-running agents.
- [Add insights to Sanity Context](https://www.sanity.io/docs/context/insights): Track and analyze agent conversations.
- [AI shopping assistant walkthrough](https://www.sanity.io/sanity-context-ecommerce): A full reference implementation using Next.js and the Vercel AI SDK.

