@anton.andrusenko/shopify-mcp-admin 2.1.2 → 2.3.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 +400 -18
- package/dist/{chunk-QXLLD2A7.js → chunk-5QMYOO4B.js} +20 -4
- package/dist/chunk-EQUN4XCH.js +257 -0
- package/dist/{chunk-UXI33LQD.js → chunk-JU5IFCVJ.js} +1 -1
- package/dist/chunk-RBXQOPVF.js +14743 -0
- package/dist/dashboard/assets/index-ClITn1me.css +1 -0
- package/dist/dashboard/assets/index-Cvo1L2xM.js +126 -0
- package/dist/dashboard/assets/index-Cvo1L2xM.js.map +1 -0
- package/dist/dashboard/index.html +6 -3
- package/dist/dashboard/mcp-icon.svg +34 -0
- package/dist/index.js +6678 -16909
- package/dist/mcp-auth-CWOWKID3.js +24 -0
- package/dist/{security-XP6MXK5B.js → security-44M6F2QU.js} +17 -3
- package/dist/{store-FM7HCQVW.js → store-JK2ZU6DR.js} +2 -2
- package/dist/tools-BCI3Z2AW.js +82 -0
- package/package.json +13 -2
- package/dist/chunk-DCQTHXKI.js +0 -124
- package/dist/dashboard/assets/index-BSh6M640.js +0 -107
- package/dist/dashboard/assets/index-BSh6M640.js.map +0 -1
- package/dist/dashboard/assets/index-DLoVESj2.css +0 -1
- package/dist/mcp-auth-2WVQELV5.js +0 -16
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_AUTH_ERRORS,
|
|
3
|
+
createJsonRpcError,
|
|
4
|
+
createMcpAuthMiddleware,
|
|
5
|
+
getWwwAuthenticateHeader,
|
|
6
|
+
isOAuthAccessToken,
|
|
7
|
+
parseBearerToken,
|
|
8
|
+
validateMcpApiKey,
|
|
9
|
+
validateMcpBearerToken,
|
|
10
|
+
validateOAuthAccessToken
|
|
11
|
+
} from "./chunk-EQUN4XCH.js";
|
|
12
|
+
import "./chunk-5QMYOO4B.js";
|
|
13
|
+
import "./chunk-EGGOXEIC.js";
|
|
14
|
+
export {
|
|
15
|
+
MCP_AUTH_ERRORS,
|
|
16
|
+
createJsonRpcError,
|
|
17
|
+
createMcpAuthMiddleware,
|
|
18
|
+
getWwwAuthenticateHeader,
|
|
19
|
+
isOAuthAccessToken,
|
|
20
|
+
parseBearerToken,
|
|
21
|
+
validateMcpApiKey,
|
|
22
|
+
validateMcpBearerToken,
|
|
23
|
+
validateOAuthAccessToken
|
|
24
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5QMYOO4B.js";
|
|
4
4
|
import "./chunk-EGGOXEIC.js";
|
|
5
5
|
|
|
6
6
|
// src/middleware/security.ts
|
|
@@ -12,7 +12,12 @@ var DEFAULT_SECURITY_OPTIONS = {
|
|
|
12
12
|
enableSecurityHeaders: true
|
|
13
13
|
};
|
|
14
14
|
var CORS_ALLOWED_METHODS = ["GET", "POST", "DELETE", "OPTIONS"];
|
|
15
|
-
var CORS_ALLOWED_HEADERS = [
|
|
15
|
+
var CORS_ALLOWED_HEADERS = [
|
|
16
|
+
"Content-Type",
|
|
17
|
+
"Authorization",
|
|
18
|
+
"Mcp-Session-Id",
|
|
19
|
+
"MCP-Protocol-Version"
|
|
20
|
+
];
|
|
16
21
|
var CORS_MAX_AGE = 86400;
|
|
17
22
|
var SECURITY_HEADERS = {
|
|
18
23
|
"X-Frame-Options": "DENY",
|
|
@@ -51,7 +56,16 @@ function createCorsMiddleware(options) {
|
|
|
51
56
|
// Allowed HTTP methods
|
|
52
57
|
methods: CORS_ALLOWED_METHODS,
|
|
53
58
|
// Allowed request headers
|
|
54
|
-
|
|
59
|
+
//
|
|
60
|
+
// IMPORTANT: For browser-based MCP clients (Claude custom connectors), the client may
|
|
61
|
+
// include additional non-simple headers (e.g. tracing or client metadata). If we
|
|
62
|
+
// hardcode allowedHeaders, CORS preflight can fail and the connector will remain
|
|
63
|
+
// "Disconnected" even though OAuth succeeds.
|
|
64
|
+
//
|
|
65
|
+
// The `cors` package will, by default, reflect `Access-Control-Request-Headers` from
|
|
66
|
+
// the preflight request when `allowedHeaders` is not explicitly set.
|
|
67
|
+
//
|
|
68
|
+
// We keep CORS_ALLOWED_HEADERS exported for documentation/tests, but do not enforce it.
|
|
55
69
|
// Headers exposed to browser JavaScript
|
|
56
70
|
exposedHeaders: opts.exposedHeaders,
|
|
57
71
|
// Allow credentials (cookies, authorization headers)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GID_PATTERN,
|
|
3
|
+
LOCALE_CODE_PATTERN,
|
|
4
|
+
articleIdSchema,
|
|
5
|
+
blogIdSchema,
|
|
6
|
+
clearRegisteredTools,
|
|
7
|
+
collectionIdSchema,
|
|
8
|
+
convertZodToJsonSchema,
|
|
9
|
+
createHandlerWithContext,
|
|
10
|
+
deriveDefaultAnnotations,
|
|
11
|
+
disableTool,
|
|
12
|
+
enableTool,
|
|
13
|
+
getAllRegisteredToolNames,
|
|
14
|
+
getRegisteredTools,
|
|
15
|
+
getToolByName,
|
|
16
|
+
getToolCount,
|
|
17
|
+
getToolNames,
|
|
18
|
+
gidSchema,
|
|
19
|
+
imageIdSchema,
|
|
20
|
+
inventoryItemIdSchema,
|
|
21
|
+
isToolEnabled,
|
|
22
|
+
isToolRegistered,
|
|
23
|
+
isValidToolName,
|
|
24
|
+
localeCodeSchema,
|
|
25
|
+
locationIdSchema,
|
|
26
|
+
marketIdSchema,
|
|
27
|
+
pageIdSchema,
|
|
28
|
+
productIdSchema,
|
|
29
|
+
redirectIdSchema,
|
|
30
|
+
registerAllTools,
|
|
31
|
+
registerContextAwareTool,
|
|
32
|
+
registerTool,
|
|
33
|
+
resetToolEnabledState,
|
|
34
|
+
setToolListChangedCallback,
|
|
35
|
+
validateInput,
|
|
36
|
+
validateToolName,
|
|
37
|
+
variantIdSchema,
|
|
38
|
+
webPresenceIdSchema,
|
|
39
|
+
wrapToolHandler
|
|
40
|
+
} from "./chunk-RBXQOPVF.js";
|
|
41
|
+
import "./chunk-5QMYOO4B.js";
|
|
42
|
+
import "./chunk-EGGOXEIC.js";
|
|
43
|
+
export {
|
|
44
|
+
GID_PATTERN,
|
|
45
|
+
LOCALE_CODE_PATTERN,
|
|
46
|
+
articleIdSchema,
|
|
47
|
+
blogIdSchema,
|
|
48
|
+
clearRegisteredTools,
|
|
49
|
+
collectionIdSchema,
|
|
50
|
+
convertZodToJsonSchema,
|
|
51
|
+
createHandlerWithContext,
|
|
52
|
+
deriveDefaultAnnotations,
|
|
53
|
+
disableTool,
|
|
54
|
+
enableTool,
|
|
55
|
+
getAllRegisteredToolNames,
|
|
56
|
+
getRegisteredTools,
|
|
57
|
+
getToolByName,
|
|
58
|
+
getToolCount,
|
|
59
|
+
getToolNames,
|
|
60
|
+
gidSchema,
|
|
61
|
+
imageIdSchema,
|
|
62
|
+
inventoryItemIdSchema,
|
|
63
|
+
isToolEnabled,
|
|
64
|
+
isToolRegistered,
|
|
65
|
+
isValidToolName,
|
|
66
|
+
localeCodeSchema,
|
|
67
|
+
locationIdSchema,
|
|
68
|
+
marketIdSchema,
|
|
69
|
+
pageIdSchema,
|
|
70
|
+
productIdSchema,
|
|
71
|
+
redirectIdSchema,
|
|
72
|
+
registerAllTools,
|
|
73
|
+
registerContextAwareTool,
|
|
74
|
+
registerTool,
|
|
75
|
+
resetToolEnabledState,
|
|
76
|
+
setToolListChangedCallback,
|
|
77
|
+
validateInput,
|
|
78
|
+
validateToolName,
|
|
79
|
+
variantIdSchema,
|
|
80
|
+
webPresenceIdSchema,
|
|
81
|
+
wrapToolHandler
|
|
82
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anton.andrusenko/shopify-mcp-admin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "MCP server for Shopify Admin API - enables AI agents to manage Shopify stores with 79 tools for products, inventory, collections, content, SEO, metafields, markets & translations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"test": "vitest --run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
|
+
"test:react": "vitest --run --config vitest.react.config.ts",
|
|
25
26
|
"test:coverage": "vitest run --coverage",
|
|
26
27
|
"test:integration": "vitest run tests/integration/*.test.ts --testTimeout=60000",
|
|
27
28
|
"test:e2e": "playwright test",
|
|
@@ -35,7 +36,9 @@
|
|
|
35
36
|
"ci": "npm run lint && npm run typecheck && npm run test && npm run build",
|
|
36
37
|
"version:patch": "npm version patch --no-git-tag-version",
|
|
37
38
|
"version:minor": "npm version minor --no-git-tag-version",
|
|
38
|
-
"version:major": "npm version major --no-git-tag-version"
|
|
39
|
+
"version:major": "npm version major --no-git-tag-version",
|
|
40
|
+
"seed:oauth": "npx tsx scripts/seed-oauth-test-data.ts",
|
|
41
|
+
"db:studio": "npx prisma studio"
|
|
39
42
|
},
|
|
40
43
|
"keywords": [
|
|
41
44
|
"shopify",
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
83
86
|
"@radix-ui/react-hover-card": "^1.1.15",
|
|
84
87
|
"@radix-ui/react-label": "^2.1.8",
|
|
88
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
85
89
|
"@radix-ui/react-progress": "^1.1.8",
|
|
86
90
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
87
91
|
"@radix-ui/react-select": "^2.2.6",
|
|
@@ -94,18 +98,21 @@
|
|
|
94
98
|
"@sentry/node": "^10.29.0",
|
|
95
99
|
"@shopify/shopify-api": "^11.14.1",
|
|
96
100
|
"@tanstack/react-query": "^5.90.11",
|
|
101
|
+
"@tanstack/react-table": "^8.21.3",
|
|
97
102
|
"bcrypt": "^5.1.1",
|
|
98
103
|
"class-variance-authority": "^0.7.1",
|
|
99
104
|
"clsx": "^2.1.1",
|
|
100
105
|
"cmdk": "^1.1.1",
|
|
101
106
|
"cookie-parser": "^1.4.7",
|
|
102
107
|
"cors": "^2.8.5",
|
|
108
|
+
"date-fns": "^4.1.0",
|
|
103
109
|
"express": "^5.1.0",
|
|
104
110
|
"express-rate-limit": "^8.2.1",
|
|
105
111
|
"lucide-react": "^0.555.0",
|
|
106
112
|
"pino": "^9.14.0",
|
|
107
113
|
"prom-client": "^15.1.3",
|
|
108
114
|
"react": "^19.2.1",
|
|
115
|
+
"react-day-picker": "^9.13.0",
|
|
109
116
|
"react-dom": "^19.2.1",
|
|
110
117
|
"react-hook-form": "^7.67.0",
|
|
111
118
|
"react-router-dom": "^7.10.0",
|
|
@@ -120,6 +127,9 @@
|
|
|
120
127
|
"@biomejs/biome": "^1.9.4",
|
|
121
128
|
"@playwright/test": "^1.57.0",
|
|
122
129
|
"@tailwindcss/vite": "^4.1.17",
|
|
130
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
131
|
+
"@testing-library/react": "^16.3.1",
|
|
132
|
+
"@testing-library/user-event": "^14.6.1",
|
|
123
133
|
"@types/bcrypt": "^5.0.2",
|
|
124
134
|
"@types/cookie-parser": "^1.4.10",
|
|
125
135
|
"@types/cors": "^2.8.19",
|
|
@@ -131,6 +141,7 @@
|
|
|
131
141
|
"@vitejs/plugin-react": "^5.1.1",
|
|
132
142
|
"@vitest/coverage-v8": "^3.2.4",
|
|
133
143
|
"autoprefixer": "^10.4.22",
|
|
144
|
+
"jsdom": "^27.3.0",
|
|
134
145
|
"pino-pretty": "^11.3.0",
|
|
135
146
|
"postcss": "^8.5.6",
|
|
136
147
|
"prisma": "^6.0.0",
|
package/dist/chunk-DCQTHXKI.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
log
|
|
3
|
-
} from "./chunk-QXLLD2A7.js";
|
|
4
|
-
|
|
5
|
-
// src/middleware/mcp-auth.ts
|
|
6
|
-
var MCP_AUTH_ERROR_CODES = {
|
|
7
|
-
AUTH_REQUIRED: -32001,
|
|
8
|
-
// Missing authentication
|
|
9
|
-
AUTH_INVALID: -32002,
|
|
10
|
-
// Invalid API key format or key
|
|
11
|
-
AUTH_REVOKED: -32003,
|
|
12
|
-
// API key has been revoked
|
|
13
|
-
AUTH_FORBIDDEN: -32004
|
|
14
|
-
// Tenant suspended or no access
|
|
15
|
-
};
|
|
16
|
-
function createJsonRpcError(code, message, hint) {
|
|
17
|
-
const error = {
|
|
18
|
-
jsonrpc: "2.0",
|
|
19
|
-
error: {
|
|
20
|
-
code,
|
|
21
|
-
message
|
|
22
|
-
},
|
|
23
|
-
id: null
|
|
24
|
-
};
|
|
25
|
-
if (hint) {
|
|
26
|
-
error.error.data = { hint };
|
|
27
|
-
}
|
|
28
|
-
return error;
|
|
29
|
-
}
|
|
30
|
-
function parseBearerToken(authHeader) {
|
|
31
|
-
if (!authHeader) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
const parts = authHeader.split(" ");
|
|
35
|
-
if (parts.length !== 2 || parts[0].toLowerCase() !== "bearer") {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
const token = parts[1];
|
|
39
|
-
if (!token || token.trim() === "") {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
return token;
|
|
43
|
-
}
|
|
44
|
-
async function validateMcpApiKey(authHeader, apiKeyService, prisma) {
|
|
45
|
-
const token = parseBearerToken(authHeader);
|
|
46
|
-
if (!token) {
|
|
47
|
-
return {
|
|
48
|
-
valid: false,
|
|
49
|
-
error: "Authentication required",
|
|
50
|
-
httpStatus: 401
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const validationResult = await apiKeyService.validate(token);
|
|
54
|
-
if (!validationResult.valid) {
|
|
55
|
-
const isRevoked = validationResult.error?.includes("revoked");
|
|
56
|
-
return {
|
|
57
|
-
valid: false,
|
|
58
|
-
error: validationResult.error || "Invalid API key",
|
|
59
|
-
httpStatus: isRevoked ? 403 : 401
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
const tenant = validationResult.tenant;
|
|
63
|
-
const tenantShops = await prisma.tenantShop.findMany({
|
|
64
|
-
where: {
|
|
65
|
-
tenantId: tenant.tenantId,
|
|
66
|
-
uninstalledAt: null
|
|
67
|
-
// Only active shops
|
|
68
|
-
},
|
|
69
|
-
select: {
|
|
70
|
-
shopDomain: true,
|
|
71
|
-
scopes: true
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
const allowedShops = tenantShops.map((shop) => shop.shopDomain);
|
|
75
|
-
const mcpTenantContext = {
|
|
76
|
-
tenantId: tenant.tenantId,
|
|
77
|
-
email: tenant.email,
|
|
78
|
-
defaultShop: validationResult.shop,
|
|
79
|
-
allowedShops
|
|
80
|
-
};
|
|
81
|
-
return {
|
|
82
|
-
valid: true,
|
|
83
|
-
tenant: mcpTenantContext
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function createMcpAuthMiddleware(options) {
|
|
87
|
-
const { apiKeyService, prisma, isRemote } = options;
|
|
88
|
-
return async (req, res, next) => {
|
|
89
|
-
if (!isRemote) {
|
|
90
|
-
log.debug("[mcp-auth] Local mode: skipping API key validation");
|
|
91
|
-
next();
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const authResult = await validateMcpApiKey(req.headers.authorization, apiKeyService, prisma);
|
|
95
|
-
if (!authResult.valid) {
|
|
96
|
-
log.debug(`[mcp-auth] Auth failed: ${authResult.error}`);
|
|
97
|
-
let errorCode;
|
|
98
|
-
let hint;
|
|
99
|
-
if (authResult.httpStatus === 401) {
|
|
100
|
-
errorCode = authResult.error === "Authentication required" ? MCP_AUTH_ERROR_CODES.AUTH_REQUIRED : MCP_AUTH_ERROR_CODES.AUTH_INVALID;
|
|
101
|
-
hint = "Include Authorization: Bearer sk_live_xxx header";
|
|
102
|
-
} else {
|
|
103
|
-
errorCode = authResult.error?.includes("revoked") ? MCP_AUTH_ERROR_CODES.AUTH_REVOKED : MCP_AUTH_ERROR_CODES.AUTH_FORBIDDEN;
|
|
104
|
-
hint = "API key has been revoked or tenant is inactive";
|
|
105
|
-
}
|
|
106
|
-
res.status(authResult.httpStatus || 401).json(createJsonRpcError(errorCode, authResult.error || "Authentication failed", hint));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
req.mcpTenantContext = authResult.tenant;
|
|
110
|
-
log.debug(
|
|
111
|
-
`[mcp-auth] Auth successful for tenant: ${authResult.tenant?.tenantId?.substring(0, 8)}...`
|
|
112
|
-
);
|
|
113
|
-
next();
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
var MCP_AUTH_ERRORS = MCP_AUTH_ERROR_CODES;
|
|
117
|
-
|
|
118
|
-
export {
|
|
119
|
-
createJsonRpcError,
|
|
120
|
-
parseBearerToken,
|
|
121
|
-
validateMcpApiKey,
|
|
122
|
-
createMcpAuthMiddleware,
|
|
123
|
-
MCP_AUTH_ERRORS
|
|
124
|
-
};
|