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 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 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, 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 or build a Knowledge Base.
- Organization API token: Create one in Manage 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. 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.
Create the endpoint with the Sanity CLI (optional)
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:
# 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
# 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
# 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
# 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 for every flag.
Install dependencies
Create a project folder and install eve, the AI SDK and its Anthropic provider:
npm install eve ai@^7 @ai-sdk/anthropic@^4
pnpm add eve ai@^7 @ai-sdk/anthropic@^4
yarn add eve ai@^7 @ai-sdk/anthropic@^4
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:
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:
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:
agent/
agent.ts model and settings
instructions.ts instructions, with the initial context
connections/
sanity.ts the Sanity Context MCP endpointSet the model, and turn off eve's default shell, file and web tools so the agent answers only from Sanity:
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.
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:
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
npx eve devpnpm dlx eve dev
yarn dlx eve dev
bunx eve devThis 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 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:
| 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 and Context MCP 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: falseinagent/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 metadataorFailed 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. SetmodelContextWindowTokensinagent/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)withcontextGrantRequired("Serving a dataset requires an organization API token with Context access"), orknowledgeBaseAccessDenied("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.wheneve devstarts, orSet SANITY_CONTEXT_MCP_URL and SANITY_ORGANIZATION_TOKEN.on the first turn: The environment variables aren't loaded. Check that the.env.localfile 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: Production patterns for public assistants, personalized agents, and multi-backend setups.
- Add insights to Sanity Context: Track what your agent's conversations ask and where answers fall short.
- 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: The same agent as a single script, without eve.