Sanity MCP Server
README.md
# š® Sanity MCP Server
**The self-hosted Sanity MCP that Sanity deprecated.** Full CRUD, atomic transactions, reference tracking, and tools the official server doesn't have.
[](https://www.npmjs.com/package/@purple-horizons/sanity-mcp)
[](https://opensource.org/licenses/MIT)
---
## Why This Exists
Sanity's official MCP server (`@sanity/mcp-server`) is **archived**. They want you to use their hosted solution at `mcp.sanity.io` with OAuth.
That's fine if you want:
- OAuth flows for every AI tool
- Dependency on Sanity's infrastructure
- No offline or air-gapped usage
- Whatever tools they decide to expose
**This MCP gives you:**
- š Simple token auth ā one env var, done
- š Self-hosted ā runs anywhere, no external dependencies
- š ļø More tools ā reference tracking, diff, history, bulk ops
- ā” Works offline ā no OAuth dance, no hosted service required
---
## Quick Start
```bash
npx @purple-horizons/sanity-mcp
```
Or install globally:
```bash
npm install -g @purple-horizons/sanity-mcp
```
### MCP Configuration
Add to your Claude Desktop, Cursor, or VS Code config:
```json
{
"mcpServers": {
"sanity": {
"command": "npx",
"args": ["@purple-horizons/sanity-mcp"],
"env": {
"SANITY_PROJECT_ID": "your-project-id",
"SANITY_DATASET": "production",
"SANITY_TOKEN": "sk-your-token"
}
}
}
}
```
---
## Tools
### š Read Operations
| Tool | Description |
|------|-------------|
| `sanity_query` | Execute any GROQ query |
| `sanity_get_document` | Fetch a single document by ID |
| `sanity_list_documents` | List documents by type with pagination |
| `sanity_search` | Full-text search across content |
| `sanity_get_types` | Discover all document types |
| `sanity_get_type_info` | Get schema info for a type |
| `sanity_count` | Count documents matching a filter |
### āļø Write Operations
| Tool | Description |
|------|-------------|
| `sanity_create` | Create a new document |
| `sanity_update` | Replace an entire document |
| `sanity_patch` | Partially update specific fields |
| `sanity_delete` | Delete a document |
| `sanity_publish` | Publish a draft |
| `sanity_unpublish` | Move published to draft |
### š Unique Tools (Not in Sanity's Official MCP)
| Tool | Description |
|------|-------------|
| `sanity_references` | **Find all documents referencing a given doc** ā essential before deleting |
| `sanity_diff` | **Compare two documents** ā draft vs published, or any two docs |
| `sanity_history` | **Get revision history** ā see who changed what |
| `sanity_bulk` | **Atomic batch operations** ā all succeed or all fail |
| `sanity_draft_status` | **Check publish state** ā draft, published, or both |
---
## What Makes This Better
### 1. Reference Tracking
Before you delete that image asset, you probably want to know what's using it:
```
sanity_references id="image-abc123"
ā Shows all 47 blog posts using that image
```
Sanity's official MCP doesn't have this. You'd find out the hard way.
### 2. Document Diffing
Content editor made changes. What changed?
```
sanity_diff idA="drafts.post-xyz" idB="post-xyz"
ā Shows exactly which fields differ
```
See the diff before you publish. Or compare any two documents.
### 3. Atomic Bulk Operations
Update 50 documents and they all need to succeed together? One transaction:
```
sanity_bulk operations=[
{ "patch": { "id": "post-1", "set": { "featured": true }}},
{ "patch": { "id": "post-2", "set": { "featured": false }}},
...
]
ā All or nothing. No partial states.
```
With `dryRun: true`, validate before executing.
### 4. Draft Status at a Glance
Is there a draft? Is it published? Both?
```
sanity_draft_status id="post-abc123"
ā { status: "both", hasUnpublishedChanges: true }
```
No more manually checking `drafts.{id}` vs `{id}`.
---
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `SANITY_PROJECT_ID` | ā
| ā | Your Sanity project ID |
| `SANITY_DATASET` | ā | `production` | Dataset name |
| `SANITY_TOKEN` | ā | ā | API token (required for writes) |
| `SANITY_API_VERSION` | ā | `2024-01-20` | API version |
### Getting Your Token
1. Go to [sanity.io/manage](https://sanity.io/manage)
2. Select your project ā API ā Tokens
3. Add new token with Editor or higher permissions
4. Copy and set as `SANITY_TOKEN`
---
## GROQ Examples
```groq
// All posts, newest first
*[_type == "post"] | order(_createdAt desc)
// Specific post by slug
*[_type == "post" && slug.current == "hello-world"][0]
// Posts with expanded author
*[_type == "post"]{
title,
slug,
"author": author->name,
"category": category->title
}
// Count by category
{
"total": count(*[_type == "post"]),
"published": count(*[_type == "post" && !(_id in path("drafts.**"))])
}
// Full-text search
*[_type == "post" && title match "AI*"]
```
---
## Development
```bash
# Clone
git clone https://github.com/Purple-Horizons/sanity-mcp.git
cd sanity-mcp
# Install
npm install
# Build
npm run build
# Test
npm test
# Run locally
npm run dev
```
---
## Comparison
| Feature | This MCP | Sanity Official |
|---------|----------|-----------------|
| Self-hosted | ā
| ā (archived) |
| Simple token auth | ā
| OAuth only |
| Works offline | ā
| ā |
| Reference tracking | ā
| ā |
| Document diff | ā
| ā |
| Bulk transactions | ā
| ā |
| Draft status | ā
| ā |
| Revision history | ā
| ā |
| Schema discovery | ā
| ā
|
| Full CRUD | ā
| ā
|
| GROQ queries | ā
| ā
|
| Release management | ā | ā
|
| Semantic search | ā | ā
|
**tl;dr:** We're better for self-hosting, developer tooling, and content operations. They're better if you need releases and semantic search with embeddings.
---
## License
MIT Ā© [Purple Horizons](https://purplehorizons.io)
---
## Links
- [Sanity GROQ Docs](https://www.sanity.io/docs/groq)
- [MCP Protocol](https://modelcontextprotocol.io)
- [Purple Horizons](https://purplehorizons.io)
- [Report Issues](https://github.com/Purple-Horizons/sanity-mcp/issues)
TDQS
A3.7/5.0
Scored across 18 tools
Disambiguation5/5
Every tool has a clearly distinct purpose: create, update, patch, delete, publish, unpublish, query, search, list, get, diff, history, references, etc. No two tools overlap in functionality.
Naming Consistency5/5
All tools follow a consistent 'sanity_verb_noun' pattern using snake_case, e.g., sanity_create, sanity_publish, sanity_list_documents. No mixed conventions.
Tool Count4/5
18 tools is slightly above the ideal 3-15 range, but the scope of a CMS (CRUD, publishing, search, history, diffs) justifies the count. Still reasonable.
Completeness5/5
The toolset covers the full document lifecycle: CRUD, patch, publish/unpublish, query, search, history, diffs, references, bulk operations, and type introspection. No obvious gaps.
Maintenance
ActivityInactive
ResponsivenessNo issues