Skip to content

Commit aea6854

Browse files
tizmagikclaude
andcommitted
Add README for @shopify/mcp package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 92d5a6d commit aea6854

7 files changed

Lines changed: 108 additions & 66 deletions

File tree

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,20 @@
229229
]
230230
}
231231
},
232+
"packages/mcp": {
233+
"entry": [
234+
"**/src/index.ts!"
235+
],
236+
"project": "**/*.ts!",
237+
"ignoreDependencies": [
238+
"zod-to-json-schema"
239+
],
240+
"vite": {
241+
"config": [
242+
"vite.config.ts"
243+
]
244+
}
245+
},
232246
"packages/plugin-did-you-mean": {
233247
"entry": [
234248
"**/{commands,hooks}/**/*.ts!",

packages/cli-kit/src/public/node/mcp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export async function requestDeviceCode(): Promise<DeviceCodeResponse> {
2828
const fqdn = await identityFqdn()
2929
const identityClientId = clientId()
3030
const scopes = allDefaultScopes()
31-
const params = `client_id=${identityClientId}&scope=${scopes.join(' ')}`
31+
const params = new URLSearchParams({client_id: identityClientId, scope: scopes.join(' ')}).toString()
3232
const url = `https://${fqdn}/oauth/device_authorization`
3333

3434
const response = await shopifyFetch(url, {

packages/mcp/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# @shopify/mcp
2+
3+
MCP server for the Shopify Admin API. Connects AI coding agents (Claude, Cursor, etc.) to your Shopify store via the [Model Context Protocol](https://modelcontextprotocol.io).
4+
5+
## Setup
6+
7+
```bash
8+
claude mcp add shopify -- npx -y -p @shopify/mcp
9+
```
10+
11+
Optionally set a default store so you don't have to pass it with every request:
12+
13+
```bash
14+
export SHOPIFY_FLAG_STORE=my-store.myshopify.com
15+
```
16+
17+
## Tools
18+
19+
### `shopify_auth_login`
20+
21+
Authenticate with a Shopify store. Returns a URL the user must visit to complete login via device auth. After approval, subsequent `shopify_graphql` calls will use the session automatically.
22+
23+
| Parameter | Type | Required | Description |
24+
|-----------|------|----------|-------------|
25+
| `store` | string | No | Store domain. Defaults to `SHOPIFY_FLAG_STORE` env var. |
26+
27+
### `shopify_graphql`
28+
29+
Execute a GraphQL query or mutation against the Shopify Admin API. Uses the latest supported API version.
30+
31+
| Parameter | Type | Required | Description |
32+
|-----------|------|----------|-------------|
33+
| `query` | string | Yes | GraphQL query or mutation string |
34+
| `variables` | object | No | GraphQL variables |
35+
| `store` | string | No | Store domain override. Defaults to `SHOPIFY_FLAG_STORE` env var. |
36+
| `allowMutations` | boolean | No | Must be `true` to execute mutations. Safety measure to prevent unintended changes. |
37+
38+
## Example
39+
40+
```
41+
Agent: "List my products"
42+
43+
→ shopify_auth_login(store: "my-store.myshopify.com")
44+
← "Open this URL to authenticate: https://accounts.shopify.com/activate?user_code=ABCD-EFGH"
45+
46+
[user approves in browser]
47+
48+
→ shopify_graphql(query: "{ products(first: 5) { edges { node { title } } } }")
49+
← { "products": { "edges": [{ "node": { "title": "T-Shirt" } }, ...] } }
50+
```

packages/mcp/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@
4848
]
4949
},
5050
"dependencies": {
51-
"@modelcontextprotocol/sdk": "1.27.1",
51+
"@modelcontextprotocol/sdk": "1.22.0",
5252
"@shopify/cli-kit": "3.91.0",
53-
"zod": "3.25.76"
53+
"zod": "3.24.1",
54+
"zod-to-json-schema": "3.24.5"
5455
},
5556
"devDependencies": {
5657
"@vitest/coverage-istanbul": "^3.1.4"

packages/mcp/src/tools/graphql.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,25 @@ describe('handleGraphql', () => {
140140
expect(result.content[0]!.text).toContain('allowMutations')
141141
})
142142

143+
test('detects mutations after a query in multi-operation document', async () => {
144+
process.env.SHOPIFY_FLAG_STORE = 'test.myshopify.com'
145+
const sm = createMockSessionManager()
146+
const result = await handleGraphql(sm, {
147+
query: 'query { shop { name } }\nmutation { productDelete(input: {id: "1"}) { deletedProductId } }',
148+
allowMutations: false,
149+
})
150+
expect(result.isError).toBe(true)
151+
expect(result.content[0]!.text).toContain('allowMutations')
152+
})
153+
143154
test('mutation pattern detection', () => {
144-
const pattern = /^\s*mutation[\s({]/i
155+
const pattern = /(?:^|\n)\s*mutation[\s({]/i
145156
expect(pattern.test('mutation { shop { name } }')).toBe(true)
146157
expect(pattern.test(' mutation CreateProduct($input: ProductInput!) { }')).toBe(true)
147158
expect(pattern.test('mutation(')).toBe(true)
148159
expect(pattern.test('MUTATION { }')).toBe(true)
149160
expect(pattern.test('Mutation { }')).toBe(true)
161+
expect(pattern.test('query { shop { name } }\nmutation { }')).toBe(true)
150162
expect(pattern.test('query { shop { name } }')).toBe(false)
151163
expect(pattern.test('{ shop { name } }')).toBe(false)
152164
expect(pattern.test('query GetMutationResults { }')).toBe(false)

packages/mcp/src/tools/graphql.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
55
import {z} from 'zod'
66
import {adminRequest} from '@shopify/cli-kit/node/api/admin'
77

8-
const MUTATION_PATTERN = /^\s*mutation[\s({]/i
8+
const MUTATION_PATTERN = /(?:^|\n)\s*mutation[\s({]/i
99

1010
export async function handleGraphql(
1111
sessionManager: SessionManager,
@@ -19,7 +19,7 @@ export async function handleGraphql(
1919
}
2020
}
2121

22-
const stripped = params.query.replace(/^\s*(#[^\n]*\n)*/g, '')
22+
const stripped = params.query.replace(/#[^\n]*/g, '')
2323
if (MUTATION_PATTERN.test(stripped) && !params.allowMutations) {
2424
return {
2525
content: [{type: 'text', text: 'Error: Mutations require allowMutations: true. This is a safety measure to prevent unintended changes.'}],

pnpm-lock.yaml

Lines changed: 25 additions & 60 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)