Sanity MCP Server
by jlmelis
- src
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { createClient } from "@sanity/client";
// Initialize Sanity client
const sanityClient = createClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: process.env.SANITY_DATASET,
apiVersion: "2025-03-05",
useCdn: false,
token: process.env.SANITY_TOKEN
});
// Create MCP server
const server = new McpServer({
name: "Sanity-MCP",
version: "1.0.0"
});
// Tool to create documents
server.tool("create-document",
{
type: z.string(),
content: z.record(z.any())
},
async ({ type, content }) => {
try {
// Add unique keys to block content if present
const processedContent = processBlockContent(content);
const result = await sanityClient.create({
_type: type,
...processedContent
});
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
isError: true
};
}
}
);
// Helper function to process block content
function processBlockContent(content: Record<string, any>): Record<string, any> {
if (content.body && Array.isArray(content.body)) {
return {
...content,
body: content.body.map((block: any, index: number) => ({
...block,
_key: block._key || `auto-${index}-${Date.now()}`
}))
};
}
return content;
}
// Tool to edit documents
server.tool("edit-document",
{
id: z.string(),
content: z.record(z.any())
},
async ({ id, content }) => {
try {
// Process block content if present
const processedContent = processBlockContent(content);
const result = await sanityClient
.patch(id)
.set(processedContent)
.commit();
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
isError: true
};
}
}
);
// Tool to list documents by type
server.tool("list-documents",
{
type: z.string(),
limit: z.number().optional().default(10)
},
async ({ type, limit }) => {
try {
const query = `*[_type == "${type}"] | order(_createdAt desc) [0...${limit}]`;
const documents = await sanityClient.fetch(query);
return {
content: [{
type: "text",
text: JSON.stringify(documents, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
isError: true
};
}
}
);
// Tool to get schema from an example document
server.tool("get-schema",
{
type: z.string()
},
async ({ type }) => {
try {
// Get first document of the specified type
const query = `*[_type == "${type}"][0]`;
const document = await sanityClient.fetch(query);
if (!document) {
return {
content: [{
type: "text",
text: `No documents found for type ${type}`
}],
isError: true
};
}
// Create schema template from document
const schema = Object.keys(document).reduce((acc, key) => {
// Skip system fields
if (key.startsWith('_')) return acc;
const value = document[key];
acc[key] = {
type: getValueType(value),
required: true
};
return acc;
}, {} as Record<string, any>);
return {
content: [{
type: "text",
text: JSON.stringify(schema, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}],
isError: true
};
}
}
);
// Helper to determine value type
function getValueType(value: any): string {
if (Array.isArray(value)) {
return 'array';
}
if (typeof value === 'object' && value !== null) {
return 'object';
}
return typeof value;
}
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);