> 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 Vercel eve

Connect Sanity Context to an agent built with eve, Vercel's framework for agents, using an MCP connection.

Vercel's [eve](https://eve.dev) is a framework for agents. An eve agent is a folder of files: a model and settings, instructions, and connections to outside services. This guide shows you how to connect an eve agent to [Sanity Context](https://www.sanity.io/docs/context/introduction) through an MCP (Model Context Protocol) connection, with the endpoint's initial context in the agent's instructions. The same code works whether the endpoint uses Knowledge Base sources, a dataset source, or both.

## 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. If you don't have an endpoint with content yet, a quick start creates one: [query a dataset](https://www.sanity.io/docs/context/quickstart) or [build a Knowledge Base](https://www.sanity.io/docs/context/quickstart-knowledge-base).
- **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 don't work.
- **Anthropic API key**: The AI SDK's Anthropic provider reads it from `ANTHROPIC_API_KEY`. Get one from the [Claude Console](https://platform.claude.com/settings/keys). To use another model provider, swap the provider package and model.
- **Node.js 24 or later**: Required by eve. The eve CLI exits on earlier versions.

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

Create a project folder and install eve, the AI SDK and its Anthropic provider:

**npm**

```shell
npm install eve ai@^7 @ai-sdk/anthropic@^4
```

**pnpm**

```shell
pnpm add eve ai@^7 @ai-sdk/anthropic@^4
```

**yarn**

```shell
yarn add eve ai@^7 @ai-sdk/anthropic@^4
```

**bun**

```shell
bun add eve ai@^7 @ai-sdk/anthropic@^4
```

Keep `ai` on v7, because eve declares it as a peer dependency at `^7`. Projects built with eve use ES modules and need Node.js 24 or later. Record both in `package.json`:

**CLI**

```sh
npm pkg set type=module "engines.node=>=24"
```

To start from eve's own scaffold instead, run `npx eve@latest init my-agent`. It writes a new agent, installs its dependencies, and opens the terminal UI. Then, in the `my-agent` folder, install the Anthropic provider, set the environment variables, delete `agent/instructions.md`, and add the three files from the Create the agent section. An eve agent can't have both `instructions.md` and `instructions.ts`.

## Set environment variables

Create a `.env.local` file in the project folder. `eve dev` loads it automatically. Replace each placeholder with your own value:

**.env.local**

```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

The eve framework finds the agent by its file layout. Create three files in an `agent/` folder:

```text
agent/
  agent.ts                model and settings
  instructions.ts         instructions, with the initial context
  connections/
    sanity.ts             the Sanity Context MCP endpoint
```

Set the model, and turn off eve's default shell, file and web tools so the agent answers only from Sanity:

**agent/agent.ts**

```typescript
import {anthropic} from '@ai-sdk/anthropic'
import {defineAgent} from 'eve'

export default defineAgent({
  model: anthropic('claude-sonnet-5-5'),
  // eve looks up the context window in AI Gateway's model catalog. Setting it keeps the build
  // working when the catalog doesn't list your model or can't be reached.
  modelContextWindowTokens: 200_000,
  // Answer from Sanity only: no shell, file or web tools. In one test with them on (Claude
  // routed through OpenRouter), every call to Sanity Context's tools failed.
  defaultTools: false,
})
```

Fetch the initial context and add it to the instructions. It's the schema overview for a dataset source, the Knowledge Base outline for Knowledge Base sources, or both, plus the endpoint's instructions if it has any, so the agent writes accurate queries from the first turn. Before each model call, eve resolves `defineDynamic` instructions, and `ctx.memo` keyed on the session ID keeps the first successful fetch for the session. If Sanity Context rejects the token, the function throws, so the turn fails instead of answering without your content. If the request or response fails in any other way, the turn continues: the instructions leave out the initial context and tell the agent to call the `initial_context` tool, and the next model call fetches again. For details, see [Inline initial context into your system prompt](https://www.sanity.io/docs/context/inline-initial-context).

**agent/instructions.ts**

```typescript
import {defineDynamic, defineInstructions} from 'eve/instructions'

// A failed request or response that lets the turn continue without the initial context.
class InitialContextUnavailable extends Error {}

// Initial context: the schema overview for a dataset source, the Knowledge Base outline
// for Knowledge Base sources, or both.
async function fetchInitialContext() {
  const mcpUrl = process.env.SANITY_CONTEXT_MCP_URL
  const token = process.env.SANITY_ORGANIZATION_TOKEN
  if (!mcpUrl || !token) throw new Error('Set SANITY_CONTEXT_MCP_URL and SANITY_ORGANIZATION_TOKEN.')

  // Append to the path, not the whole URL, so any query parameters survive.
  const url = new URL(mcpUrl)
  url.pathname = `${url.pathname.replace(/\/$/, '')}/initial-context`

  const response = await fetch(url, {headers: {Authorization: `Bearer ${token}`}}).catch((error) => {
    throw new InitialContextUnavailable(`Could not reach Sanity Context: ${error}`)
  })
  if (response.status === 401 || response.status === 403) {
    // A rejected token fails the MCP connection too. Stop instead of answering without your content.
    throw new Error(`Sanity Context rejected the token (${response.status}): ${await response.text()}`)
  }
  if (!response.ok) throw new InitialContextUnavailable(`Could not fetch initial context (${response.status})`)
  return response.text().catch((error) => {
    throw new InitialContextUnavailable(`Could not read initial context: ${error}`)
  })
}

// Instructions are resolved before each model call. memo keeps the first successful fetch for the session.
// A failed fetch saves nothing, so the next model call fetches again.
export default defineDynamic(async (ctx) => {
  const initialContext = await ctx.memo(ctx.session.id, fetchInitialContext).catch((error) => {
    if (!(error instanceof InitialContextUnavailable)) throw error
    console.warn(`${error.message}. The agent will call the initial_context tool instead.`)
    return null
  })
  return defineInstructions({
    content: [
      'You answer questions using the Sanity Context tools.',
      "Look up content before you answer, and don't make up facts.",
      '',
      ...(initialContext
        ? ['# Data reference', '', initialContext]
        : ['Call the initial_context tool before your first lookup to learn what content exists.']),
    ].join('\n'),
  })
})
```

Connect the Sanity Context MCP endpoint with your organization token:

**agent/connections/sanity.ts**

```typescript
import {defineMcpClientConnection} from 'eve/connections'

export default defineMcpClientConnection({
  url: process.env.SANITY_CONTEXT_MCP_URL!,
  // The model reads this to decide when to look here. Describe your own content.
  description: 'Company content in Sanity: products, policies and help articles. Look things up here before answering.',
  auth: {getToken: async () => ({token: process.env.SANITY_ORGANIZATION_TOKEN!})},
})
```

The tool loop runs inside eve: the model finds Sanity Context's tools with `eve__search`, calls them through `eve__tool`, and answers when it has what it needs. The connection's `description` tells the model when to look there, so describe your own content.

## Run the agent

**npm**

```shell
npx eve dev
```

**pnpm**

```shell
pnpm dlx eve dev
```

**yarn**

```shell
yarn dlx eve dev
```

**bun**

```shell
bunx eve dev
```

This opens eve's terminal UI. Ask a question that your content can answer, such as "How long do I have to return something?" for a store with a returns policy. The terminal UI shows the agent's calls to the Sanity Context tools, such as **Sanity: Groq query** or **Sanity: Knowledge base read**, then its answer. With the initial context in the instructions, the agent knows what content exists before it calls a tool.

To call the agent from your own app or tests instead, run `npx eve dev --no-ui` and use eve's HTTP API or the [Client](https://eve.dev/docs/guides/client) from `eve/client`. Local `eve dev` also adds a subagent that can edit the agent's own files, even with `defaultTools: false`. It runs its model through AI Gateway, so with only an Anthropic API key it's offered to the agent but fails if called. To leave it out, run `npx eve dev --no-default-extensions`. Deployed builds don't include it.

## Dataset and Knowledge Base sources

The same code works with an endpoint that serves Knowledge Bases. What changes is what the endpoint returns:

##### Tools and initial context by source type

|  | Dataset source | Knowledge Base source |
| --- | --- | --- |
| Content source | A dataset | One or more Knowledge Bases |
| Initial context | Schema overview | Outline of each Knowledge Base, with its ID |
| Tools | initial_context, schema_explorer, groq_query, array_field_reader | initial_context, knowledge_base_search, knowledge_base_read |

An endpoint can have one dataset source and several Knowledge Base sources. If it has both types, it serves the tools from both columns, and initial context contains both the schema overview and the Knowledge Base outline. See [Context MCP tools](https://www.sanity.io/docs/context/mcp-tools) and [Context MCP source types](https://www.sanity.io/docs/context/source-types).

## Troubleshoot common errors

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

- Every Sanity tool call fails, or the agent says its lookup tool isn't working: If Claude is routed through OpenRouter and eve's default tools are on, set `defaultTools: false` in `agent/agent.ts`. This failure was seen in one test with that setup. With a direct Anthropic API key, the same agent worked with the default tools on.
- The build fails with `does not have known AI Gateway context window metadata` or `Failed to load AI Gateway model metadata`: Your model's context window isn't available, either because AI Gateway's model catalog doesn't list the model or because eve couldn't reach the catalog. Set `modelContextWindowTokens` in `agent/agent.ts`.
- `Sanity Context rejected the token (401)`: The token is missing, invalid, or belongs to another organization. Check the organization ID in the URL too.
- `Sanity Context rejected the token (403)` with `contextGrantRequired` ("Serving a dataset requires an organization API token with Context access"), or `knowledgeBaseAccessDenied` ("No access to knowledge base") on a Knowledge Base endpoint: The token isn't an organization token with Context access. Project tokens are refused. Create an organization token with the Context permission.
- `Expected the connection export "default" from "connections/sanity.ts" to match the public eve shape. The "url" field must be a valid URL.` when `eve dev` starts, or `Set SANITY_CONTEXT_MCP_URL and SANITY_ORGANIZATION_TOKEN.` on the first turn: The environment variables aren't loaded. Check that the `.env.local` file is in the project folder and sets both variables.
- `eve requires Node.js >=24. You are running v22.22.1.`: Upgrade to Node.js 24 or later. The version in the message is the one you're running.

## 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.
- [Add insights to Sanity Context](https://www.sanity.io/docs/context/insights): Track what your agent's conversations ask and where answers fall short.
- [Knowledge Bases](https://www.sanity.io/docs/context/knowledge-bases): Organize your datasets, website, and files into verified facts, each cited to its source. Context flags conflicting sources for your team to resolve before agents see them.
- [Connect Sanity Context with Vercel AI SDK](https://www.sanity.io/docs/context/connect-vercel-ai-sdk): The same agent as a single script, without eve.

