Sanity MCP

Live

API KEY

CONTENT MANAGEMENT

Design

Structured content documents, dataset queries, and schema-validated fields your agent needs to read and write live in Sanity. Sanity MCP gives your content agent per-user API access, credentials vaulted, scoped, never in the prompt.

  • Acts as the user: Each editor's Sanity token is scoped to their dataset permissions and project roles.
  • Credentials stay vaulted: AES-256, resolved at request time, never in LLM context.
  • Scoped before every call: User permissions enforced. 90-day audit trail on every document read and write.
Sanity MCP
agent · Acme Q3
Run
Find all blog posts published in the last 30 days missing an SEO description and add a generated one.
S
sanity_query
87ms
Content agent
14 posts found missing SEO descriptions. Generated and patched all 14 — descriptions written from post body, avg 148 characters, within 160-char limit.
Sources: Sanity blog dataset, post body text
sanitymcp
14
18:29
Message Claude...

Tools your content agent reaches for on Sanity, scoped per editor.

CALL ANY TOOL
API token scoped per editor. Every GROQ query and document mutation attributed to the authorizing user.
sanity_query
Query dataset
Execute a GROQ query against a Sanity dataset to retrieve documents, filter by type, and project specific fields.
Parameters
Name
Type
Required
Description
query
string
Required
GROQ query string (e.g. *[_type == 'post'] | order(publishedAt desc))
params
object
Optional
GROQ query parameter bindings
sanity_document_get
Get document
sanity_document_patch
Patch document
sanity_document_create
Create document
sanity_assets_upload
Upload asset
Build your Agent
Drop the toolkit in, point it at the authorized editor, and your agent can query and update structured content in Sanity from the first run.
import { ScalekitClient } from "@scalekit-sdk/node";
import { DynamicStructuredTool } from "@langchain/core/tools";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { z } from "zod";

const sk = new ScalekitClient(envUrl, clientId, clientSecret);

const { tools } = await sk.tools.listScopedTools("user_123", {
filter: { connectionNames: ["contentfulmcp"], toolNames: ["contentful_entries_search", "contentful_entry_get", "contentful_entry_publish"] },
pageSize: 100,
});

const lcTools = tools.map((t) => new DynamicStructuredTool({
name: t.tool.definition.name,
description: t.tool.definition.description,
schema: z.object({}).passthrough(),
func: async (args) => {
const { data } = await sk.tools.executeTool({
toolName: t.tool.definition.name,
identifier: "user_123",
params: args,
});
return JSON.stringify(data);
},
}));

const agent = createReactAgent({ llm, tools: lcTools });
import { ScalekitClient } from "@scalekit-sdk/node";
import OpenAI from "openai";

const sk = new ScalekitClient(envUrl, clientId, clientSecret);
const openai = new OpenAI();

const { tools } = await sk.tools.listScopedTools("user_123", {
filter: { connectionNames: ["contentfulmcp"], toolNames: ["contentful_entries_search", "contentful_entry_get", "contentful_entry_publish"] },
pageSize: 100,
});

const llmTools = tools.map((t) => ({
type: "function",
function: {
name: t.tool.definition.name,
description: t.tool.definition.description,
parameters: t.tool.definition.input_schema,
},
}));

const resp = await openai.responses.create({
model: "gpt-4o", input: prompt, tools: llmTools,
});
import { ScalekitClient } from "@scalekit-sdk/node";
import Anthropic from "@anthropic-ai/sdk";

const sk = new ScalekitClient(envUrl, clientId, clientSecret);
const anthropic = new Anthropic();

const { tools } = await sk.tools.listScopedTools("user_123", {
filter: { connectionNames: ["contentfulmcp"], toolNames: ["contentful_entries_search", "contentful_entry_get", "contentful_entry_publish"] },
pageSize: 100,
});

const llmTools = tools.map((t) => ({
name: t.tool.definition.name,
description: t.tool.definition.description,
input_schema: t.tool.definition.input_schema,
}));

const msg = await anthropic.messages.create({
model: "claude-sonnet-4-6", max_tokens: 1024,
tools: llmTools,
messages: [{ role: "user", content: prompt }],
});
import { Agent } from "@google/adk/agents";
import {
MCPToolset, StreamableHTTPConnectionParams,
} from "@google/adk/tools/mcp";

const toolset = new MCPToolset({
connectionParams: new StreamableHTTPConnectionParams({
url: "https://mcp.scalekit.com/contentfulmcp",
headers: { Authorization: `Bearer ${userScopedToken}` },
}),
});

const agent = new Agent({
name: "agent", model: "gemini-2.0-flash",
tools: await toolset.getTools(),
});
Try these prompts
Paste any prompt into your content agent to start querying and updating structured content in Sanity MCP.
Search & recall
Copy the prompt
Copied
List all published blog posts in the [dataset] dataset from the last 30 days.
Copy the prompt
Copied
Get the full document for [document ID] including all references.
Copy the prompt
Copied
Run GROQ query: *[_type == "product" && inStock == true]
Create & update
Copy the prompt
Copied
Create a new blog post draft with title: [title] and body: [content].
Copy the prompt
Copied
Update the [field name] field on document [ID] to [new value].
Copy the prompt
Copied
Publish all documents of type [type] that are in draft status.
SEE HOW AUTH WORKS
Editors authorize Sanity once. Their API token stays vaulted, every mutation runs under their identity, and every change is logged.
1
Authorize
Your user connects
Sanity MCP
once. We tie it to their identity and the meetings they approved — no shared bot account, no org-wide access
Who:
user ‘A’
when:
Once per user
access:
Limited to user
2
Store
Their
Sanity MCP
token lives in a vault scoped to them. User A's meetings are never reachable by an agent acting for user B, even on the same connection
vault:
encrypted
scope:
per-user
tokens:
auto-refreshed
3
Resolve
When your agent calls a
Sanity MCP
tool, we fetch the right token server-side. It never touches your agent, never appears in the LLM context, never shows up in your logs
speed:
~40ms
check:
before every call
seen by:
nobody
4
Audit
Every
Sanity MCP
tool call is logged — who triggered it, which meeting was fetched, what came back. 90 days of history, tied to the user who authorized it
history:
90 days
export:
SIEM-ready
logged:
every call
Test other agents
Same per-user auth pattern across other CMS and content management connectors.
No items found.
Why Scalekit
Secure your agent's access. Connectors ship in minutes
One vault for every CMS connector. Sanity today, Webflow and Notion tomorrow.
01.
Shared tokens break per-user analytics
A shared token looks fine in a demo. In production every call looks like a service account. Scalekit resolves the real user credential so attribution, audit, and scope stay accurate.
// shared token
 audit → bot_service_account
 user_filter → broken

 // scalekit
 audit → user_abc
 scope → enforced ✓
02.
Authentication is not authorization
03.
Multi-tenancy is architectural
04.
Sanity MCP today. Others tomorrow.
“Our agents act across Salesforce, Gong, Google Drive, and more, on behalf of every customer. Scalekit behind the scenes meant we can keep adding tools without ever rebuilding how credentials or tool calling work.”
Venu Madhav Kattagoni
Head of Engineering / Von
FAQs
Frequently Asked Questions
Does the agent use a shared Sanity token or per-editor tokens?
Per-editor tokens. Each team member provisions their own Sanity API token and Scalekit vaults it under their identity. Document mutations are attributed to that editor, not a shared bot credential.
Where is the Sanity API token stored?
In Scalekit's AES-256 vault, namespaced per tenant. Keys resolve at request time and never appear in prompts, logs, or LLM completions.
Can I limit the agent to read-only Sanity access?
Yes. Use a read-only Sanity API token and/or listScopedTools to allow GROQ queries and document retrieval but block mutations for users who should not write to the dataset.
What happens when an editor's Sanity token is revoked?
The next tool call fails closed for that user. Other editors in the team remain unaffected. Revocation is logged with a timestamp.
Can the agent publish to Sanity and trigger a Webflow or Vercel deploy in one workflow?
Yes. A single agent can patch Sanity documents and trigger a Webflow CMS publish or a Vercel deploy hook in one workflow. Each connector resolves under the same editor identity with its own vaulted credential.
Start in your coding agent
Up and running in one command
Install the Scalekit skill in your editor of choice. Connector, auth, tools, prompt, all wired up
Claude Code REPL
/plugin marketplace add scalekit-inc/claude-code-authstack
/plugin install agentkit@scalekit-auth-stack
Cursor Code REPL
# ~/.cursor/mcp.json
{
""mcpServers"": {
""sanitymcp"": {
""url"": ""https://mcp.scalekit.com/sanitymcp"",
""headers"": { ""Authorization"": ""Bearer $SCALEKIT_TOKEN"" }
}
}
}
Codex Code REPL
# ~/.codex/config.toml
[mcp_servers.sanitymcp]
url = ""https://mcp.scalekit.com/sanitymcp""
auth_env = ""SCALEKIT_TOKEN""
Copilot Code REPL
# .vscode/mcp.json
{
""servers"": {
""sanitymcp"": {
""url"": ""https://mcp.scalekit.com/sanitymcp"",
""type"": ""http""
}
}
}