A read-only MCP server that lets Claude (or any MCP client) look up a Shopify store's metafield definitions instead of guessing them.
When you build or migrate a Shopify theme, the metafield structure — every namespace, key, and data type — lives in the Admin. The theme code has to match it exactly. Reference custom.spec_sheet when the store actually defines custom.specs and the Liquid renders blank. There was no way for an AI assistant to see that structure, so it guessed. This server hands it the real list.
Three tools over stdio:
| Tool | What it answers |
|---|---|
list_metafield_definitions |
"What metafields does a PRODUCT (or COLLECTION, CUSTOMER, …) have?" Returns namespace, key, type, name, and description for each. |
get_definition |
"Is it custom.specs or custom.spec_sheet, and what type does it hold?" Resolves one namespace.key to its full shape, including validations. |
diff_definitions |
"What changed between these two stores?" Compares two snapshots and reports what was added, removed, or changed. Built for migrations. |
It reads from the Shopify Admin GraphQL API using a store domain and an Admin API access token. It also ships with a mock mode that runs against a recorded fixture, so you can try it without a store.
This server only sends GraphQL queries. It never sends a mutation. The app it powers requests no write scope, so it cannot create, edit, or delete anything in your store — it can only read metafield definitions. It is safe to point at a live production store.
The token you give it should be a read-scoped Admin API access token (read_products, and the matching read scope for any other owner type you want to inspect). If you give it a token with write scopes, this server still never writes — but a least-privilege token is the right habit.
Requires Node 20 or newer. The server has no runtime dependencies — it speaks JSON-RPC 2.0 on stdin/stdout directly.
npx shopify-metafield-mcp # runs the server (expects env vars, see below)Or clone it:
git clone https://github.com/VelkinaStudio/shopify-metafield-mcp
cd shopify-metafield-mcp
node --test # run the tests
node src/server.js --mock src/mockData.json # run in mock modePut this under mcpServers in your Claude Code config (~/.claude.json, or a .mcp.json in a project):
{
"mcpServers": {
"shopify-metafields": {
"command": "npx",
"args": ["-y", "shopify-metafield-mcp"],
"env": {
"SHOPIFY_STORE": "your-store.myshopify.com",
"SHOPIFY_ADMIN_TOKEN": "shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}SHOPIFY_STORE accepts either your-store or your-store.myshopify.com. Restart Claude Code and the three tools appear.
Mock mode reads a recorded fixture instead of calling Shopify — no token needed:
{
"mcpServers": {
"shopify-metafields": {
"command": "node",
"args": ["src/server.js", "--mock", "src/mockData.json"]
}
}
}You can also pass a fixture by env var: MCP_MOCK_FILE=path/to/file.json.
In the Shopify admin: Settings → Apps and sales channels → Develop apps → Create an app → Configure Admin API scopes. Grant the read scopes you need (read_products, read_customers, …), install the app, and copy the Admin API access token (shpat_…). Keep it out of source control — pass it through the env block above or a secret manager, never commit it.
Returns a sorted list of { namespace, key, type, name, description } plus a count. Owner types: PRODUCT, PRODUCTVARIANT, COLLECTION, CUSTOMER, ORDER, COMPANY, COMPANY_LOCATION, LOCATION, BLOG, ARTICLE, PAGE, SHOP, MARKET, DRAFTORDER, DISCOUNT.
// input — either form works
{ "handle": "custom.specs" }
{ "namespace": "custom", "key": "specs", "ownerType": "PRODUCT" }Returns { found, definition }. definition includes type, typeCategory, description, ownerType, and validations. If the namespace.key does not exist, found is false — that is the answer that tells you why the Liquid was rendering blank.
// input — each snapshot is a definitions array OR a raw Admin API response
{ "before": [ /* … */ ], "after": [ /* … */ ] }Returns { summary, added, removed, changed }. A definition's identity is (ownerType, namespace, key); a "change" means that identity exists in both snapshots but its type, description, name, or validations differ. changed entries carry field-level before/after so you can see exactly what moved.
- Transport is stdio. The server implements
initialize,tools/list,tools/call, andping, and acknowledgesnotifications/initialized. - It negotiates the protocol version the client requests (it supports
2025-11-25and earlier). - All logging goes to stderr. stdout carries only JSON-RPC messages, so the stream stays clean. This is the most common way to break an stdio MCP server, and this one is careful about it.
node --testThe test suite runs entirely against a recorded fixture (test/fixtures/definitions.json). It never makes a live API call. It includes test/stdio.test.js, which spawns the server and pipes a real JSON-RPC sequence through it to confirm the wire protocol.
To watch the bytes go in and out, run the standalone handshake demo:
node scripts/handshake.mjsMIT. See LICENSE.
Built by Velkina — https://velkina.com