shopify-graphql-extension-mcp 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/broker.js +134 -0
- package/dist/index.js +148 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# shopify-graphql-extension-mcp
|
|
2
|
+
|
|
3
|
+
MCP server that runs Shopify Admin GraphQL through the Shopify GraphQL API Client
|
|
4
|
+
Chrome extension's browser session. Any MCP client (Claude Code, Cursor, Claude
|
|
5
|
+
Desktop, etc.) can query or mutate any Shopify store you have open in the browser
|
|
6
|
+
— no API tokens, no app install. Pairs with the Chrome extension (hence the name).
|
|
7
|
+
|
|
8
|
+
## How it works
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
MCP client ──stdio MCP──► this server ──ws://127.0.0.1:6789──► extension service worker
|
|
12
|
+
│ chrome.tabs.sendMessage
|
|
13
|
+
▼
|
|
14
|
+
content script → store Admin API
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
A Chrome MV3 service worker can't listen on a port, only dial out — so the server
|
|
18
|
+
is the WebSocket host and the extension connects to it. Runs on plain Node (no
|
|
19
|
+
Bun) so it ships to npm and launches with `npx`.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Install the extension first (from the Chrome Web Store), then add the MCP server.
|
|
24
|
+
|
|
25
|
+
Claude Code:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude mcp add shopify-admin -- npx -y shopify-graphql-extension-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Other MCP clients (Cursor, Claude Desktop, …):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"shopify-admin": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "shopify-graphql-extension-mcp"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Keep an `admin.shopify.com` tab open; the extension reconnects to the server
|
|
45
|
+
automatically and the extension's MCP tab shows the live status.
|
|
46
|
+
|
|
47
|
+
## Local development
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd mcp-bridge
|
|
51
|
+
bun install
|
|
52
|
+
bun run build # bundle to dist/index.js for publishing
|
|
53
|
+
node dist/index.js # or: bun run src/index.ts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The wire contract is shared from `../chrome-extension/src/lib/bridge-protocol.ts`
|
|
57
|
+
(single source of truth, inlined into the bundle at build time, reusable by the
|
|
58
|
+
cloud relay later).
|
|
59
|
+
|
|
60
|
+
Requires `jq` (used by the mutation approval hook below).
|
|
61
|
+
|
|
62
|
+
## Tools
|
|
63
|
+
|
|
64
|
+
- `list_stores` — the stores currently open in the browser (handle + domain).
|
|
65
|
+
- `run_graphql` — run a read-only QUERY. Mutations are rejected with a pointer to
|
|
66
|
+
the mutation tool. Args: `query`, `variables?`, `store?` (handle or domain;
|
|
67
|
+
required only when more than one store is open), `apiVersion?`.
|
|
68
|
+
- `run_graphql_mutation` — run a MUTATION (write). Same args. Reads are rejected.
|
|
69
|
+
This split lets the approval gate target writes only.
|
|
70
|
+
|
|
71
|
+
## Mutation approval
|
|
72
|
+
|
|
73
|
+
Writes are gated in the MCP client, not the bridge, so they survive
|
|
74
|
+
`--dangerously-skip-permissions`:
|
|
75
|
+
|
|
76
|
+
1. An `ask` rule in `~/.claude/settings.json`:
|
|
77
|
+
`"permissions": { "ask": ["mcp__shopify-admin__run_graphql_mutation"] }`.
|
|
78
|
+
2. A `PreToolUse` hook (`hooks/mutation-ask.sh`) that summarizes the mutation
|
|
79
|
+
into a readable prompt and forces the `ask`. Wire it in settings.json with a
|
|
80
|
+
matcher of `mcp__shopify-admin__run_graphql_mutation`.
|
|
81
|
+
|
|
82
|
+
Hook changes load at Claude Code startup, so restart after editing them.
|
|
83
|
+
|
|
84
|
+
## Subscription gate
|
|
85
|
+
|
|
86
|
+
Every tool call checks the user's subscription (trial or active) via the
|
|
87
|
+
extension. Non-subscribers get a message with a checkout link.
|
|
88
|
+
|
|
89
|
+
## Auth (bridge ↔ extension)
|
|
90
|
+
|
|
91
|
+
The WS server accepts a connection only if it presents `?token=<SHOPIFY_BRIDGE_TOKEN>`
|
|
92
|
+
(when that env var is set), otherwise it requires a `chrome-extension://` origin.
|
|
93
|
+
To use a token: start the bridge with `SHOPIFY_BRIDGE_TOKEN=<secret>` and set the
|
|
94
|
+
same value as `bridgeToken` in the extension's `chrome.storage.local`.
|
|
95
|
+
|
|
96
|
+
## Config
|
|
97
|
+
|
|
98
|
+
- `SHOPIFY_BRIDGE_PORT` — bridge WS port (default `6789`). If you change it, also
|
|
99
|
+
set `bridgePort` in the extension's `chrome.storage.local`.
|
|
100
|
+
- `SHOPIFY_BRIDGE_TOKEN` — optional shared secret (see Auth above).
|
|
101
|
+
|
|
102
|
+
## Notes / current limits
|
|
103
|
+
|
|
104
|
+
- One bridge owns the port. On startup the bridge evicts any stale process
|
|
105
|
+
holding the port, so the newest bridge (the one your MCP client talks to)
|
|
106
|
+
always wins.
|
|
107
|
+
- Works only while Chrome is open with a logged-in Shopify session in a tab.
|
package/dist/broker.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/broker.ts
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { WebSocketServer } from "ws";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
// ../chrome-extension/src/lib/bridge-protocol.ts
|
|
9
|
+
var BRIDGE_DEFAULT_PORT = 6789;
|
|
10
|
+
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
// src/broker.ts
|
|
13
|
+
var PORT = Number(process.env.SHOPIFY_BRIDGE_PORT ?? BRIDGE_DEFAULT_PORT);
|
|
14
|
+
var TOKEN = process.env.SHOPIFY_BRIDGE_TOKEN ?? "";
|
|
15
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
16
|
+
var log = (...args) => console.error("[broker]", ...args);
|
|
17
|
+
var extensionSocket = null;
|
|
18
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
19
|
+
function extensionAuthorized(req) {
|
|
20
|
+
if (TOKEN) {
|
|
21
|
+
return new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("token") === TOKEN;
|
|
22
|
+
}
|
|
23
|
+
return (req.headers.origin ?? "").startsWith("chrome-extension://");
|
|
24
|
+
}
|
|
25
|
+
var wss = new WebSocketServer({ noServer: true });
|
|
26
|
+
var httpServer = createServer((_req, res) => {
|
|
27
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
28
|
+
res.end("Shopify MCP broker");
|
|
29
|
+
});
|
|
30
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
31
|
+
const role = new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("role");
|
|
32
|
+
if (role === "mcp") {
|
|
33
|
+
wss.handleUpgrade(req, socket, head, (ws) => onMcpConnect(ws));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!extensionAuthorized(req)) {
|
|
37
|
+
log("rejected extension connection (origin:", req.headers.origin ?? "(none)", ")");
|
|
38
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
39
|
+
socket.destroy();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
wss.handleUpgrade(req, socket, head, (ws) => onExtensionConnect(ws));
|
|
43
|
+
});
|
|
44
|
+
function onExtensionConnect(ws) {
|
|
45
|
+
if (extensionSocket && extensionSocket !== ws) {
|
|
46
|
+
try {
|
|
47
|
+
extensionSocket.close();
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
extensionSocket = ws;
|
|
52
|
+
ws.send(JSON.stringify({ kind: "hello", role: "broker", protocolVersion: BRIDGE_PROTOCOL_VERSION }));
|
|
53
|
+
log("extension connected");
|
|
54
|
+
ws.on("message", (data) => {
|
|
55
|
+
let frame;
|
|
56
|
+
try {
|
|
57
|
+
frame = JSON.parse(String(data));
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (frame.kind !== "response") return;
|
|
62
|
+
const entry = inflight.get(frame.id);
|
|
63
|
+
if (!entry) return;
|
|
64
|
+
clearTimeout(entry.timer);
|
|
65
|
+
inflight.delete(frame.id);
|
|
66
|
+
try {
|
|
67
|
+
entry.mcp.send(JSON.stringify({ ...frame, id: entry.originalId }));
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
ws.on("close", () => {
|
|
72
|
+
if (extensionSocket === ws) {
|
|
73
|
+
extensionSocket = null;
|
|
74
|
+
failInflight((e) => true, "Extension disconnected while a request was in flight.");
|
|
75
|
+
}
|
|
76
|
+
log("extension disconnected");
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function onMcpConnect(ws) {
|
|
80
|
+
log("mcp client connected");
|
|
81
|
+
ws.on("message", (data) => {
|
|
82
|
+
let frame;
|
|
83
|
+
try {
|
|
84
|
+
frame = JSON.parse(String(data));
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (frame.kind === "ping" || frame.kind === "hello") return;
|
|
89
|
+
if (frame.kind !== "request") return;
|
|
90
|
+
if (!extensionSocket) {
|
|
91
|
+
ws.send(JSON.stringify({
|
|
92
|
+
kind: "response",
|
|
93
|
+
id: frame.id,
|
|
94
|
+
ok: false,
|
|
95
|
+
error: "The Shopify extension is not connected. Open Chrome with the extension installed and at least one admin.shopify.com tab open."
|
|
96
|
+
}));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const brokerId = randomUUID();
|
|
100
|
+
const timer = setTimeout(() => {
|
|
101
|
+
inflight.delete(brokerId);
|
|
102
|
+
try {
|
|
103
|
+
ws.send(JSON.stringify({ kind: "response", id: frame.id, ok: false, error: `Timed out after ${REQUEST_TIMEOUT_MS}ms waiting for the extension.` }));
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}, REQUEST_TIMEOUT_MS);
|
|
107
|
+
inflight.set(brokerId, { mcp: ws, originalId: frame.id, timer });
|
|
108
|
+
extensionSocket.send(JSON.stringify({ kind: "request", id: brokerId, method: frame.method, params: frame.params }));
|
|
109
|
+
});
|
|
110
|
+
ws.on("close", () => {
|
|
111
|
+
failInflight((e) => e.mcp === ws, "MCP client disconnected.");
|
|
112
|
+
log("mcp client disconnected");
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function failInflight(match, reason) {
|
|
116
|
+
for (const [id, e] of inflight) {
|
|
117
|
+
if (!match(e)) continue;
|
|
118
|
+
clearTimeout(e.timer);
|
|
119
|
+
inflight.delete(id);
|
|
120
|
+
try {
|
|
121
|
+
e.mcp.send(JSON.stringify({ kind: "response", id: e.originalId, ok: false, error: reason }));
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
httpServer.on("error", (err) => {
|
|
127
|
+
if (err.code === "EADDRINUSE") {
|
|
128
|
+
log("another broker already owns the port; exiting");
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
log("fatal server error:", err.message);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
httpServer.listen(PORT, "127.0.0.1", () => log(`broker listening on ws://127.0.0.1:${PORT}`));
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import WebSocket from "ws";
|
|
12
|
+
|
|
13
|
+
// ../chrome-extension/src/lib/bridge-protocol.ts
|
|
14
|
+
var BRIDGE_DEFAULT_PORT = 6789;
|
|
15
|
+
function isMutation(query) {
|
|
16
|
+
const stripped = query.replace(/"""[\s\S]*?"""/g, '""').replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/\/\*[\s\S]*?\*\//g, " ").replace(/#[^\n]*/g, " ").trim();
|
|
17
|
+
return /^mutation\b/i.test(stripped);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var PORT = Number(process.env.SHOPIFY_BRIDGE_PORT ?? BRIDGE_DEFAULT_PORT);
|
|
22
|
+
var TOKEN = process.env.SHOPIFY_BRIDGE_TOKEN ?? "";
|
|
23
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
24
|
+
var log = (...args) => console.error("[mcp]", ...args);
|
|
25
|
+
var BROKER_URL = `ws://127.0.0.1:${PORT}/?role=mcp${TOKEN ? `&token=${encodeURIComponent(TOKEN)}` : ""}`;
|
|
26
|
+
var BROKER_SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "broker.js");
|
|
27
|
+
var socket = null;
|
|
28
|
+
var connecting = false;
|
|
29
|
+
var spawnedThisCycle = false;
|
|
30
|
+
var pending = /* @__PURE__ */ new Map();
|
|
31
|
+
function spawnBroker() {
|
|
32
|
+
if (spawnedThisCycle) return;
|
|
33
|
+
spawnedThisCycle = true;
|
|
34
|
+
try {
|
|
35
|
+
const child = spawn(process.execPath, [BROKER_SCRIPT], { detached: true, stdio: "ignore" });
|
|
36
|
+
child.unref();
|
|
37
|
+
log("spawned broker");
|
|
38
|
+
} catch (err) {
|
|
39
|
+
log("failed to spawn broker:", err instanceof Error ? err.message : String(err));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function connect() {
|
|
43
|
+
if (connecting || socket?.readyState === WebSocket.OPEN || socket?.readyState === WebSocket.CONNECTING) return;
|
|
44
|
+
connecting = true;
|
|
45
|
+
const ws = new WebSocket(BROKER_URL);
|
|
46
|
+
socket = ws;
|
|
47
|
+
ws.on("open", () => {
|
|
48
|
+
connecting = false;
|
|
49
|
+
spawnedThisCycle = false;
|
|
50
|
+
log("connected to broker");
|
|
51
|
+
});
|
|
52
|
+
ws.on("message", (data) => {
|
|
53
|
+
let frame;
|
|
54
|
+
try {
|
|
55
|
+
frame = JSON.parse(String(data));
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (frame.kind !== "response") return;
|
|
60
|
+
const p = pending.get(frame.id);
|
|
61
|
+
if (!p) return;
|
|
62
|
+
clearTimeout(p.timer);
|
|
63
|
+
pending.delete(frame.id);
|
|
64
|
+
if (frame.ok) p.resolve(frame.result);
|
|
65
|
+
else p.reject(new Error(frame.error ?? "Unknown bridge error"));
|
|
66
|
+
});
|
|
67
|
+
ws.on("error", () => {
|
|
68
|
+
spawnBroker();
|
|
69
|
+
});
|
|
70
|
+
ws.on("close", () => {
|
|
71
|
+
connecting = false;
|
|
72
|
+
if (socket === ws) socket = null;
|
|
73
|
+
spawnedThisCycle = false;
|
|
74
|
+
setTimeout(connect, 300 + Math.floor(Math.random() * 400));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
connect();
|
|
78
|
+
function callBroker(method, params) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
if (socket?.readyState !== WebSocket.OPEN) {
|
|
81
|
+
connect();
|
|
82
|
+
reject(new Error("Connecting to the bridge. Make sure Chrome is open with the extension installed and an admin.shopify.com tab open, then try again."));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const id = randomUUID();
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
pending.delete(id);
|
|
88
|
+
reject(new Error(`Timed out after ${REQUEST_TIMEOUT_MS}ms waiting for the extension.`));
|
|
89
|
+
}, REQUEST_TIMEOUT_MS);
|
|
90
|
+
pending.set(id, { resolve, reject, timer });
|
|
91
|
+
socket.send(JSON.stringify({ kind: "request", id, method, params }));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
var server = new McpServer({ name: "shopify-admin", version: "2.0.0" });
|
|
95
|
+
server.tool(
|
|
96
|
+
"list_stores",
|
|
97
|
+
"List the Shopify stores currently open in the browser (admin.shopify.com tabs). Returns each store's handle and myshopify domain. Pass a handle or domain as the `store` argument to run_graphql when more than one store is open.",
|
|
98
|
+
{},
|
|
99
|
+
async () => {
|
|
100
|
+
const stores = await callBroker("list_stores", {});
|
|
101
|
+
if (!stores || stores.length === 0) {
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: "No Shopify admin tabs are open. Open admin.shopify.com in Chrome for the store you want to use." }],
|
|
104
|
+
isError: true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { content: [{ type: "text", text: JSON.stringify(stores, null, 2) }] };
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
var graphqlArgs = {
|
|
111
|
+
query: z.string().describe("The GraphQL operation."),
|
|
112
|
+
variables: z.record(z.any()).optional().describe("GraphQL variables, as an object."),
|
|
113
|
+
store: z.string().optional().describe("Store handle or myshopify domain. Required only when more than one store is open."),
|
|
114
|
+
apiVersion: z.string().optional().describe('Admin API version, e.g. "2026-04". Defaults to the extension default.')
|
|
115
|
+
};
|
|
116
|
+
async function forwardGraphql(args) {
|
|
117
|
+
try {
|
|
118
|
+
const data = await callBroker("run_graphql", args);
|
|
119
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
server.tool(
|
|
125
|
+
"run_graphql",
|
|
126
|
+
"Run a read-only GraphQL QUERY against a Shopify store's Admin API using the merchant's browser session \u2014 no API token required. Use this for queries only. For mutations (create/update/delete), use run_graphql_mutation, which asks the user for approval first. The store must be open in a browser tab. Returns the raw GraphQL JSON ({ data, errors }). The first 50 calls (queries + mutations combined) are free; after that the tool returns a 'Free tier limit reached' error with a link to subscribe \u2014 relay that upgrade step to the user.",
|
|
127
|
+
graphqlArgs,
|
|
128
|
+
async ({ query, variables, store, apiVersion }) => {
|
|
129
|
+
if (isMutation(query)) {
|
|
130
|
+
return { content: [{ type: "text", text: "This is a mutation (write). Use the run_graphql_mutation tool instead \u2014 it requires the user to approve the write." }], isError: true };
|
|
131
|
+
}
|
|
132
|
+
return forwardGraphql({ query, variables, store, apiVersion });
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
server.tool(
|
|
136
|
+
"run_graphql_mutation",
|
|
137
|
+
"Run a GraphQL MUTATION (create/update/delete) against a Shopify store's Admin API using the merchant's browser session \u2014 no API token required. This writes to live store data, so the user is asked to approve each call. Use run_graphql for read-only queries. The store must be open in a browser tab. Returns the raw GraphQL JSON ({ data, errors }). The first 50 calls (queries + mutations combined) are free; after that the tool returns a 'Free tier limit reached' error with a link to subscribe \u2014 relay that upgrade step to the user.",
|
|
138
|
+
graphqlArgs,
|
|
139
|
+
async ({ query, variables, store, apiVersion }) => {
|
|
140
|
+
if (!isMutation(query)) {
|
|
141
|
+
return { content: [{ type: "text", text: "This is not a mutation. Use the run_graphql tool for read-only queries." }], isError: true };
|
|
142
|
+
}
|
|
143
|
+
return forwardGraphql({ query, variables, store, apiVersion });
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
var transport = new StdioServerTransport();
|
|
147
|
+
await server.connect(transport);
|
|
148
|
+
log("MCP server ready on stdio");
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shopify-graphql-extension-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server that runs Shopify Admin GraphQL through the Shopify GraphQL API Client Chrome extension's browser session — no API tokens.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shopify-graphql-extension-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "esbuild src/index.ts src/broker.ts --bundle --packages=external --platform=node --format=esm --outdir=dist --banner:js='#!/usr/bin/env node'",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"start": "node dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"shopify",
|
|
24
|
+
"graphql",
|
|
25
|
+
"shopify-admin-api",
|
|
26
|
+
"chrome-extension"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
31
|
+
"ws": "^8.18.0",
|
|
32
|
+
"zod": "^3.23.8"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22",
|
|
36
|
+
"@types/ws": "^8.5.12",
|
|
37
|
+
"esbuild": "^0.24.0"
|
|
38
|
+
}
|
|
39
|
+
}
|