@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.
@@ -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-QXLLD2A7.js";
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 = ["Content-Type", "Authorization", "Mcp-Session-Id"];
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
- allowedHeaders: CORS_ALLOWED_HEADERS,
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)
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  SessionStore,
3
3
  sessionStore
4
- } from "./chunk-UXI33LQD.js";
5
- import "./chunk-QXLLD2A7.js";
4
+ } from "./chunk-JU5IFCVJ.js";
5
+ import "./chunk-5QMYOO4B.js";
6
6
  import "./chunk-EGGOXEIC.js";
7
7
  export {
8
8
  SessionStore,
@@ -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.1.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",
@@ -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
- };