Announcing CIMD support for MCP Client registration
Learn more

Sanity MCP vs Sanity API for AI Agents (2026)

TL;DR

  • Sanity's hosted MCP server at mcp.sanity.io is the only Sanity surface with a self-serve, standards-based OAuth flow. The direct HTTP API authenticates with bearer tokens only; its OAuth2 grant is documented as experimental, requires Sanity to provision your client credentials manually, and issues no refresh tokens.
  • This inverts the usual MCP tradeoff. For Sanity, MCP is the path that gives you per-user identity, and the direct API is the path that pushes you toward a shared service-account credential.
  • The MCP surface stops at the record and schema layer. Webhooks, asset upload, the Live Content API, document history, export, and the text-generating Agent Actions endpoints (generate, transform, translate, prompt) are HTTP-API only.
  • The MCP surface also reaches further than most content agents should. It can create projects, create and modify datasets, add CORS origins, deploy schemas and Studios, and run Sanity CLI commands. Sanity's own tool reference describes project creation as initializing the project with a dataset and API tokens.
  • Content Lake rate limits are enforced per client IP address per second, not per token. A multi-tenant agent fleet egressing from one set of IPs shares a single bucket across every customer.
  • Scalekit's Sanity MCP connector handles the OAuth flow, per-user token storage, and rotation, and Virtual MCP servers let you cut those tools down to the handful your agent should actually hold.

Your agent needs to read and write Sanity. Sanity ships a hosted MCP server at mcp.sanity.io and a full HTTP API across Content Lake, the Management API, and Agent Actions. They are not two views of the same thing. They cover different surface area, they authenticate differently, and the difference matters most in exactly the case most teams are building for: a multi-tenant product where each customer has their own Sanity project. Here is how to pick.

What Sanity MCP and the Sanity API actually are

Two objects are being compared, plus a third that agent builders keep confusing with the first. Establishing what each one is takes about a minute, and it saves an afternoon of chasing the wrong integration.

Sanity MCP

Sanity hosts its MCP server on its own infrastructure at https://mcp.sanity.io. The server speaks the standard Model Context Protocol (MCP) over HTTP and works with any compliant client. Authentication is OAuth by default, with the option to pass an API token in an Authorization: Bearer header instead, in which case tool calls run with that token's role and permissions rather than the signed-in user's.

The older local server, @sanity/mcp-server, is deprecated and its repository is archived. For new agent work, the remote server is the only relevant object.

Where the hosted server earns its keep

The MCP tools cover the part of Sanity that is genuinely annoying to hand-roll: drafts, versions, and content releases. create_version, patch_documents, and publish_documents encode Sanity's rule that published content is never mutated directly, so your agent gets correct draft-and-release behavior without you writing it. Responses also paginate against the client's context budget, and the server fetches Sanity's current agent rules on demand instead of relying on a stale rules file in your repo.

The Sanity HTTP API

Every Sanity API request is pinned to a date-based version in the URL path, as in https://<projectId>.api.sanity.io/v2026-07-28/data/query/production. That pinning is the API path's headline property: you choose when behavior changes, and removed versions fail loudly with a 410 rather than drifting underneath you.

The surface is wide. Content Lake covers query, mutation, actions, assets, export, history, listen, live, and backups. GROQ-powered webhooks fire on document create, update, and delete with GROQ filters and custom projections. The Management API covers projects, roles, and the Access API for robot tokens. Agent Actions exposes generate, transform, translate, prompt, and a schema-aware patch, currently on the experimental vX version.

The surface that is not either of these

Sanity also ships Agent Context, backed by a separate hosted Context MCP endpoint. It gives agents structured, read-only access to a configured slice of a dataset, in GROQ mode or Knowledge Base mode, and it cannot write. If you are building a customer-facing assistant that answers from your content, that is the right object. If you are building an agent that edits content, it is not, and the rest of this article is about the two surfaces that can write.

Comparing them where it matters for agents

The capability gap runs in both directions here, which is unusual. The MCP server is missing things a production content pipeline needs, and it also exposes things a content agent has no business holding.

What your agent can actually do

Sanity's tool reference currently documents 37 tools on the hosted server. Scalekit's connector catalog lists 45 for the same connector, including JSON and Markdown variants of the create and patch tools. Sanity notes directly in its docs that the available tool set may vary as it ships server updates, which is worth holding onto for the versioning discussion below.

Capability
Sanity MCP
Sanity HTTP API
GROQ queries against a dataset
Yes (query_documents)
Yes
Fetch a document by ID
Yes (get_document)
Yes
Create drafts and patch documents
Yes (create_documents, patch_documents)
Yes
Publish, unpublish, discard drafts
Yes
Yes
Content releases and versions
Yes (create_release, create_version)
Yes
Read and deploy schemas
Yes (get_schema, deploy_schema)
Yes
Semantic search over embeddings indices
Yes (semantic_search)
Yes, via the Embeddings Index API
Create projects, datasets, CORS origins
Yes
Yes, via the Management API
Upload image and file assets
No, returns CLI guidance only
Yes, via the Assets API
GROQ-powered webhooks
No
Yes
Live Content API and listeners
No
Yes
Document history and export
No
Yes
Agent Actions: generate, translate, prompt
No
Yes, on vX
Agent Actions: image generate and transform
Yes
Yes, on vX
Robot token lifecycle (Access API)
No
Yes

Where the MCP surface stops

The absent capabilities cluster around two themes: reacting to change, and moving bytes. If your agent needs to fire when an editor publishes something, you need webhooks or the Live Content API, and neither has an MCP tool. If it needs to put an image into Content Lake, dataset_assets_upload will hand it CLI instructions rather than uploading anything.

The third gap is the text-generating half of Agent Actions. Translating a document into eight locales, generating field content from a schema-aware instruction, or running a targeted prompt call are all HTTP-only. An editorial agent that localizes content is an HTTP-API agent, not an MCP agent, no matter how much of the rest of its work the MCP tools cover.

Where the MCP surface goes too far

The other direction is the one that should worry you more. create_project, create_dataset, update_dataset, add_cors_origin, cors_origins_delete, deploy_schema, deploy_studio, and run_sanity_cli all sit on the same server as query_documents. Sanity's reference describes create_project as creating a project and initializing it with a dataset and API tokens.

Connect a content agent to the full server and it holds every one of those. A prompt injection inside a document body now has a path to adding a CORS origin, deploying a schema, or minting credentials. This is not a Sanity flaw; it is a general-purpose developer and editor server doing what it was designed to do. It is a reason to put something between your agent and that server. For a broader look at these risks, see MCP Security Risks in the Enterprise.

The auth path each one puts you on

This is where Sanity breaks the pattern most MCP comparisons follow. Everywhere else, MCP means OAuth and the direct API means you get your pick of credential types. On Sanity it runs the other way.

MCP: OAuth by default, bearer token by configuration

The hosted server uses OAuth by default and attributes what the agent does to the signed-in Sanity user, so edits land in revision history under a real identity. Scalekit's connector catalog lists the connection type as OAuth 2.1 with Dynamic Client Registration (DCR), which means you are not filing a partner request to get a client.

You can opt out by setting an Authorization header with an API token, and the server then skips OAuth entirely and runs with that token's role. That is the escape hatch for headless MCP use, and it is also the point at which per-user identity disappears. Sanity documents OAuth sessions as typically expiring after about 7 days, with the client expected to prompt for re-authentication.

The API: tokens only, and OAuth is partner-gated

The HTTP API authenticates with bearer tokens: personal tokens, which carry your own role and link changes to you in revision history, and robot tokens, which are project-scoped or organization-scoped service credentials. Personal tokens last a year by default, shorter under SAML SSO. Robot tokens last until deleted unless you set an expiry, which since June 2026 can be a 30, 60, or 90 day preset or a custom date.

The OAuth escape hatch that mostly is not one

Sanity does publish an OAuth2 integration for acting on behalf of a user, and its state matters. The docs mark it experimental, describe it as intended for technology partners, require Sanity to provision your application credentials manually, support only the authorization_code grant, and state that refresh tokens are not supported: renewal means sending the user back through authentication. For a self-serve product onboarding customers this week, that is not a viable per-user credential path.

What this means for multi-tenant agents

Put those two paragraphs together and the decision sharpens. If your agent must act as each individual user in each customer's Sanity project, the MCP server's OAuth flow is the practical way to get there. If you go direct to the HTTP API without a partner arrangement, you are issuing a robot token per customer project, which means every edit your agent makes is attributed to that robot and not to the person who asked for it.

Robot tokens are the correct answer for genuinely headless work: a nightly sync, a migration, a build-time export. They are the wrong answer when an auditor asks which human authorized a publish. Choose deliberately rather than by default, because the Access API will happily let you mint one credential and forget the question. Understanding credential ownership across agent tool-calling patterns helps teams make this choice with their eyes open.

What you own in production

Both paths leave real operational work on your side. The specific work differs, and two of the differences bite harder than teams expect.

Rate limits are per IP, not per tenant

Sanity enforces Content Lake rate limits per client IP address per second: 25 requests per second for mutations (combined across /data/mutate and /data/actions), 25 per second for asset uploads, and 500 per second globally. Concurrency is capped per dataset at 500 queries and 100 mutations.

The per-IP part is the one to internalize. Concurrency scales with your customer count because each customer has their own dataset. The mutation rate limit does not. Fifty tenants running publish-heavy agents out of the same egress IPs share one 25 requests per second bucket, and one customer's bulk operation can push everyone into 429. The official client retries queries with exponential backoff but never retries mutations, so that queue is yours to build. This is one of the harder auth challenges when moving from single-tenant to multi-tenant tool calling.

Versioning is pinned on one path and drifting on the other

The HTTP API is date-versioned in the URL, with X-Sanity-Warning and X-Sanity-Deprecated response headers and a 410 once a version is removed. You pin a static date string, and you move when you choose to move. For a deterministic pipeline where an unexpected schema change is an incident, that is the property you are buying.

MCP tool schemas carry no such contract. Sanity ships server releases with changelogs, and its documentation says the available tool set may vary as updates land. The 37-versus-45 count between Sanity's reference and a connector catalog illustrates the same thing. Your agent picks up capability changes without a redeploy, which is a feature for fast-moving products and a liability for pipelines you expect to behave identically next quarter.

The 45-tool context problem

Every tool definition on an MCP server costs context on every turn. Using Scalekit's published working estimate of roughly 200 tokens per tool, a full 45-tool Sanity connection costs somewhere near 9,000 tokens before the agent reads a single document. At a few thousand runs a day that is a real line item. This is exactly the problem explored in MCP is up to 32× more expensive than CLI.

An editorial agent needs perhaps five of those tools. The gap between five and forty-five is both the token bill and, as covered above, the blast radius.

When to use MCP, when to use the API

Neither path wins outright, and most production Sanity agents end up running both. The split is cleaner than usual because the auth story and the capability story point in the same direction.

Use Sanity MCP when:

  • Your agent acts on behalf of individual editors and you need edits attributed to them in revision history rather than to a shared robot
  • The work is drafts, patches, releases, versions, and publishing, which is the part of Sanity the MCP tools model correctly
  • You are building an interactive assistant in Claude Code, Cursor, or your own chat surface where the user is present to complete the OAuth flow
  • You want Sanity to own tool schemas and pick up new capabilities without a redeploy on your side
  • You are running schema and migration work where get_schema, list_workspace_schemas, and get_sanity_rules save you from feeding context by hand

Use the Sanity HTTP API when:

  • Your agent reacts to content events, which requires GROQ-powered webhooks or the Live Content API
  • It uploads assets, reads document history, or exports a dataset
  • It calls the text-generating Agent Actions endpoints for translation, generation, or schema-aware patching
  • It runs genuinely headless on a schedule with a robot token, where no user session exists to attribute anything to
  • You need version pinning because an unplanned schema change would be a production incident

Building Sanity agents with Scalekit

Scalekit ships Sanity as an MCP connector, which means the OAuth dance, the token vault, and the tool execution all sit behind one interface. For the HTTP-API-only capabilities above, you add a second connector through bring-your-own-connector carrying a robot token, and both live on the same connected-account model.

Connect the user before you do anything else

The first call establishes the connected account for a specific user. Nothing downstream works until that account is ACTIVE, and the connection_name string has to match the connection you configured in the Scalekit dashboard exactly, which is the single most common integration error.

pip install scalekit-sdk-python langchain-openai
import os from scalekit import ScalekitClient scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions # Must match the connection name configured in the Scalekit dashboard CONNECTION_NAME = "sanitymcp" IDENTIFIER = "user_123" account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=IDENTIFIER, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=IDENTIFIER, ) print("Authorize Sanity:", link.link)

Retrieve the tools this user is authorized to call

list_scoped_tools is not a catalog browse. It returns the tools bound to this identifier's connected account, which is the surface that user's Sanity role actually permits. An editor and a developer on the same product get different lists from the same call, and that is the distinction between a per-user agent and a shared-credential one.

from scalekit.v1.tools.tools_pb2 import ScopedToolFilter scoped = scalekit_client.tools.list_scoped_tools( IDENTIFIER, filter=ScopedToolFilter(connection_names=[CONNECTION_NAME]), page_size=50, ) for tool in scoped.tools: print(tool.name)

Run the agent loop with LangChain

The LangChain adapter returns native StructuredTool objects, so the model fills each tool's input schema and you never hand-write Sanity's resource and workspaceName arguments. The full schemas live on the connector's docs page if you need to read them.

from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage tools = actions.langchain.get_tools( identifier=IDENTIFIER, connection_names=[CONNECTION_NAME], page_size=100, ) tool_map = {tool.name: tool for tool in tools} llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ HumanMessage( "Find article drafts updated in the last 7 days and summarize what is still unpublished" ) ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for call in response.tool_calls: result = tool_map[call["name"]].invoke(call["args"]) messages.append(ToolMessage(content=str(result), tool_call_id=call["id"]))

For a deeper look at how LangChain's tool calling model works end to end, see LangChain Tool Calling: How It Works, Where It Stops, and How Scalekit Completes It.

Call a single tool deterministically

Not every step belongs in a reasoning loop. When your pipeline already knows what it wants, execute_tool runs one tool against one user's credentials with no model in the path.

result = actions.execute_tool( tool_name="sanitymcp_list_projects", tool_input={}, connection_name=CONNECTION_NAME, identifier=IDENTIFIER, ) print(result)

Cut 45 tools down to 5 with a Virtual MCP server

This is the answer to both problems raised earlier: the context cost and the fact that a content agent should never see create_project or run_sanity_cli. A Virtual MCP server is a scoped endpoint that declares exactly which connections and which tools are exposed. You create it once per agent role, not once per user.

from datetime import timedelta from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = scalekit_client.actions.mcp.create_config( name="sanity-editorial-agent", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="sanitymcp", tools=[ "sanitymcp_query_documents", "sanitymcp_get_document", "sanitymcp_patch_documents", "sanitymcp_list_releases", "sanitymcp_publish_documents", ], ), ], ) config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url

Mint a session token per run, per user

The server URL is static and safe to share. The session token is what carries user identity, and any request holding it runs as that user. Default expiry is about an hour; create_session_token is also the remint call, because there is no refresh endpoint and a token cannot be extended in place.

token_response = scalekit_client.actions.mcp.create_session_token( mcp_config_id=config_id, identifier="user_123", expiry=timedelta(minutes=30), ) token = token_response.token

Connect the agent with Mastra

Mastra has native MCP support, so it reads the tool list and schemas from the Virtual MCP server directly. Pass the static URL and the user's session token as bearer auth, minted server-side and never placed in client code.

npm install @scalekit-sdk/node @mastra/core @mastra/mcp @ai-sdk/openai
import { Agent } from '@mastra/core/agent'; import { MCPClient } from '@mastra/mcp'; import { openai } from '@ai-sdk/openai'; // Returned from your backend for the authenticated user, not a process-wide secret const { mcpServerUrl, mcpToken } = await getSanitySessionForUser(currentUserId); const mcp = new MCPClient({ servers: { sanity: { url: new URL(mcpServerUrl), requestInit: { headers: { Authorization: `Bearer ${mcpToken}` }, }, }, }, }); const tools = await mcp.getTools(); const agent = new Agent({ name: 'sanity_editorial_agent', instructions: 'You maintain editorial content in Sanity. Never publish a document unless explicitly instructed to.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( 'Summarize the unpublished article drafts in the current release', ); console.log(result.text); await mcp.disconnect();

What the logs give you afterwards

Sanity's revision history tells you a document changed and which identity changed it. It does not tell you which agent run did it, which tool was called with which arguments, or whether the credential was still valid at that moment. Scalekit's agent logs carry the full delegation chain: who authorized the call, which agent ran it, which tool executed, and what came back, queryable in the dashboard and exportable to your SIEM.

That matters most in the failure case. When a publish goes out that nobody expected, the question is whether the agent misbehaved, the model chose wrong, or a stale token silently broke a step. Distinguishing those three from application logs alone is close to impossible if the credential lived outside any observability layer. This is why agent tool observability is worth investing in from the start, not as an afterthought.

The credential problem that exists on both paths

Whichever path you pick, you end up holding one credential per user or per customer project. Fifty customers means fifty credential lifecycles, and Sanity manages exactly none of them for you.

What neither path gives you

The MCP OAuth flow hands you a session per user that Sanity documents as expiring after roughly 7 days. The API path hands you a robot token per project that either never expires or expires on a date you set and then cannot unset. Both need somewhere to live: encrypted at rest, isolated per tenant, never in the agent process, never in a log line, never in an LLM context window.

Both also need a revocation story. When a customer churns or an editor leaves, you have to enumerate every live Sanity credential tied to that identity and kill it. Sanity's Access API gives you the delete endpoint; the inventory, the trigger, and the cleanup are yours. The token type differs between the paths. The infrastructure they demand does not. For a detailed breakdown of what secure token management for AI agents at scale actually requires, that piece covers the full lifecycle.

Where Scalekit fits

Scalekit's Sanity MCP connector runs the OAuth flow, stores each user's credential in an AES-256 encrypted vault isolated per tenant, and refreshes it without your agent ever seeing the token. Credentials never touch the agent runtime. Virtual MCP servers then scope what that credential can reach, which is the piece that makes a multi-tool, multi-tenant Sanity agent something you can put in front of an auditor.

Which one to build against

If your agent acts as individual editors and its job is drafts, patches, releases, and publishing, build against the MCP server. It is the only Sanity surface where per-user OAuth is self-serve, and that identity is worth more than the coverage you give up.

If your agent reacts to content events, moves assets, translates documents, or runs headless on a schedule, build against the HTTP API with a scoped robot token and a pinned version. Accept that the actor in revision history is a robot, and log the human intent on your side.

Most production Sanity agents run both. The question that decides each path is whether a human needs to be the actor of record, and the credential infrastructure underneath does not change either way.

Next steps for Sanity agent builders

Browse the Sanity connector on Scalekit, or the full connector catalog if your agent touches more than one tool. Framework code samples for LangChain and Mastra run end to end, and AgentKit pricing covers tool calling and audit logs on the free tier.

Building something Sanity-shaped and want a second opinion on the auth model? Join the Scalekit Slack community, or talk to an engineer if you need an answer today.

FAQs

What is the main difference between Sanity MCP and the Sanity HTTP API for agents?

Sanity MCP provides a self-serve OAuth 2.1 flow that attributes agent actions to real user identities. The HTTP API uses bearer tokens (personal or robot), with OAuth2 marked experimental and requiring manual provisioning by Sanity. MCP covers drafts, patches, releases, and publishing; the HTTP API covers webhooks, asset upload, document history, export, and text-generating Agent Actions.

Why does Sanity MCP invert the usual MCP vs. API auth tradeoff?

On most platforms, the direct API offers more flexible auth options including OAuth. Sanity is the exception: its hosted MCP server is the only surface with a practical, self-serve per-user OAuth flow. The direct HTTP API's OAuth2 integration is experimental, partner-gated, and issues no refresh tokens, making it unsuitable for self-serve multi-tenant products.

What capabilities does Sanity MCP lack that the HTTP API provides?

Sanity MCP does not support GROQ-powered webhooks, the Live Content API, document history, dataset export, asset uploads, robot token lifecycle management, or the text-generating Agent Actions endpoints (generate, transform, translate, prompt). These are all HTTP API only.

What tools on the Sanity MCP server should content agents avoid holding?

Content agents should not hold tools like create_project, create_dataset, update_dataset, add_cors_origin, cors_origins_delete, deploy_schema, deploy_studio, and run_sanity_cli. These administrative tools dramatically increase blast radius and create a path for prompt injection attacks to cause serious infrastructure changes.

How do Sanity Content Lake rate limits affect multi-tenant agents?

Rate limits are enforced per client IP address per second — not per tenant or per token. Fifty tenants sharing one egress IP share a single 25 mutations/second bucket. One customer's bulk operation can push all others into 429 errors. The official client never retries mutations automatically, so queue management is the developer's responsibility.

What is a Virtual MCP server and why does it matter for Sanity agents?

A Virtual MCP server is a scoped endpoint that exposes only the specific connections and tools you define. For Sanity, it lets you cut a 45-tool server down to the 5 tools a content agent actually needs. This reduces both context token cost and blast radius, and is created once per agent role rather than once per user.

When should a Sanity agent use the MCP server versus the HTTP API?

Use MCP when the agent acts on behalf of individual editors and actions must be attributed to real user identities, or when the work is limited to drafts, patches, releases, and publishing. Use the HTTP API when the agent reacts to content events via webhooks, uploads assets, calls text-generating Agent Actions, runs headlessly on a schedule, or requires version pinning for deterministic pipelines.

How does Scalekit handle credential storage for Sanity MCP integrations?

Scalekit runs the OAuth flow, stores each user's credential in an AES-256 encrypted vault isolated per tenant, and handles token refresh without the agent ever seeing the raw token. Credentials never enter the agent runtime, log lines, or LLM context windows. Virtual MCP servers then enforce which tools each credential can reach.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.