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.
sanitymcp__get_ui_context
Get ui context
Get the current UI context including the active document and workspace in Sanity Studio.
Parameters
Name
Type
Required
Description
resource
object
Required
No description.
documentId
string
Optional
Sanity document ID (e.g. drafts.abc123 for drafts, or bare ID for published).
documentType
string
Optional
Schema type name of the document.
maxRefDepth
integer
Optional
Maximum reference depth to follow when resolving document references.
workspaceName
string
Optional
Sanity workspace name. Defaults to the default workspace.
sanitymcp_add_cors_origin
Add cors origin
sanitymcp_cors_origins_delete
Delete cors origins
sanitymcp_cors_origins_list
List cors origins
sanitymcp_create_dataset
Create dataset
sanitymcp_create_documents
Create documents
sanitymcp_create_documents_from_json
Create documents from json
sanitymcp_create_project
Create project
sanitymcp_create_release
Create release
sanitymcp_dataset_assets_upload
Upload dataset assets
sanitymcp_deploy_schema
Deploy schema
sanitymcp_discard_drafts
Discard drafts
sanitymcp_get_document
Get document
sanitymcp_get_sanity_rules
Get sanity rules
sanitymcp_give_sanity_feedback
Give sanity feedback
sanitymcp_list_datasets
List datasets
sanitymcp_list_organizations
List organizations
sanitymcp_list_projects
List projects
sanitymcp_list_sanity_rules
List sanity rules
sanitymcp_patch_document_from_json
Patch document from json
sanitymcp_patch_document_from_markdown
Patch document from markdown
sanitymcp_publish_documents
Publish documents
sanitymcp_query_documents
Query documents
sanitymcp_read_docs
Read docs
sanitymcp_run_sanity_cli
Run sanity cli
sanitymcp_search_docs
Search docs
sanitymcp_transform_image
Transform image
sanitymcp_update_dataset
Update dataset
sanitymcp_version_discard
Version discard
sanitymcp_version_unpublish_document
Version unpublish document

For more tools, view docs.

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.
Python · LlamaIndex
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.
GTM and RevOps Teams
Deal room sync
Pulls opportunity context from Salesforce, captures key decisions from Slack, and syncs a running summary to the deal room doc in Google Drive, but only when the deal actually changed.
People Ops and HR teams
Performance review collector
Collects review feedback from Airtable and Google Forms scoped to each manager's direct reports, writes per-employee summaries to Notion, and DMs the manager a Slack digest.
People Ops and HR teams
Offer letter routing agent
Drafts the offer in PandaDoc, blocks on the hiring manager's approval in Slack, then emails the candidate their e-signature link. Every call runs as the recruiter who triggered it, never a shared HR bot.
Support and Ops Teams
Support ticket automation agent
Fetches new Zendesk tickets, drafts a reply from Notion knowledge base articles, digests what it cannot answer to Slack, and archives the rest, acting as the support agent rather than a shared API key.
Test other agents
Same per-user auth pattern across other CMS and content management connectors.
GTM
Deal room sync agent
Pull opportunity context from Salesforce, capture decisions from Slack, and keep the Google Drive deal room doc current.
PEOPLE OPS
Performance review collector agent
Collect review feedback from Airtable and Google Forms per manager, summarise each report in Notion, and DM the digest in Slack.
PEOPLE OPS
Offer letter routing agent
Draft the offer in PandaDoc, gate it on hiring manager approval in Slack, then email the candidate their signature link.
SUPPORT
Support ticket automation (Google ADK)
Fetch, annotate, and archive Zendesk tickets with Notion context, digesting anything it cannot answer to Slack.
Why Scalekit
Secure your agent's access. Connectors ship in minutes
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""
}
}
}