shopify-store-mcp 1.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.
Files changed (88) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +172 -0
  3. package/dist/config.d.ts +10 -0
  4. package/dist/config.js +65 -0
  5. package/dist/db.d.ts +19 -0
  6. package/dist/db.js +161 -0
  7. package/dist/errors.d.ts +36 -0
  8. package/dist/errors.js +93 -0
  9. package/dist/graphql/admin/common/collections.d.ts +8 -0
  10. package/dist/graphql/admin/common/collections.js +44 -0
  11. package/dist/graphql/admin/common/customers.d.ts +13 -0
  12. package/dist/graphql/admin/common/customers.js +112 -0
  13. package/dist/graphql/admin/common/orders.d.ts +13 -0
  14. package/dist/graphql/admin/common/orders.js +142 -0
  15. package/dist/graphql/admin/common/products.d.ts +23 -0
  16. package/dist/graphql/admin/common/products.js +159 -0
  17. package/dist/graphql/admin/common/shop.d.ts +7 -0
  18. package/dist/graphql/admin/common/shop.js +38 -0
  19. package/dist/graphql/admin/index.d.ts +15 -0
  20. package/dist/graphql/admin/index.js +18 -0
  21. package/dist/graphql/admin/specialized/bulk.d.ts +33 -0
  22. package/dist/graphql/admin/specialized/bulk.js +132 -0
  23. package/dist/graphql/admin/specialized/files.d.ts +22 -0
  24. package/dist/graphql/admin/specialized/files.js +170 -0
  25. package/dist/graphql/admin/specialized/inventory.d.ts +13 -0
  26. package/dist/graphql/admin/specialized/inventory.js +78 -0
  27. package/dist/graphql/admin/specialized/metafields.d.ts +22 -0
  28. package/dist/graphql/admin/specialized/metafields.js +100 -0
  29. package/dist/graphql/admin/specialized/metaobjects.d.ts +36 -0
  30. package/dist/graphql/admin/specialized/metaobjects.js +239 -0
  31. package/dist/graphql/admin/specialized/search.d.ts +21 -0
  32. package/dist/graphql/admin/specialized/search.js +100 -0
  33. package/dist/graphql/collections.d.ts +1 -0
  34. package/dist/graphql/collections.js +37 -0
  35. package/dist/graphql/customers.d.ts +2 -0
  36. package/dist/graphql/customers.js +98 -0
  37. package/dist/graphql/inventory.d.ts +2 -0
  38. package/dist/graphql/inventory.js +67 -0
  39. package/dist/graphql/metafields.d.ts +2 -0
  40. package/dist/graphql/metafields.js +43 -0
  41. package/dist/graphql/orders.d.ts +2 -0
  42. package/dist/graphql/orders.js +116 -0
  43. package/dist/graphql/products.d.ts +4 -0
  44. package/dist/graphql/products.js +140 -0
  45. package/dist/graphql/shop.d.ts +1 -0
  46. package/dist/graphql/shop.js +32 -0
  47. package/dist/graphql/storefront/common/cart.d.ts +23 -0
  48. package/dist/graphql/storefront/common/cart.js +210 -0
  49. package/dist/graphql/storefront/common/collections.d.ts +11 -0
  50. package/dist/graphql/storefront/common/collections.js +114 -0
  51. package/dist/graphql/storefront/common/products.d.ts +14 -0
  52. package/dist/graphql/storefront/common/products.js +155 -0
  53. package/dist/graphql/storefront/index.d.ts +7 -0
  54. package/dist/graphql/storefront/index.js +8 -0
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +97 -0
  57. package/dist/logger.d.ts +58 -0
  58. package/dist/logger.js +165 -0
  59. package/dist/prompts/index.d.ts +2 -0
  60. package/dist/prompts/index.js +169 -0
  61. package/dist/queue.d.ts +73 -0
  62. package/dist/queue.js +120 -0
  63. package/dist/resources/index.d.ts +3 -0
  64. package/dist/resources/index.js +180 -0
  65. package/dist/shopify-client.d.ts +16 -0
  66. package/dist/shopify-client.js +39 -0
  67. package/dist/tools/graphql.d.ts +3 -0
  68. package/dist/tools/graphql.js +41 -0
  69. package/dist/tools/index.d.ts +3 -0
  70. package/dist/tools/index.js +23 -0
  71. package/dist/tools/infrastructure.d.ts +6 -0
  72. package/dist/tools/infrastructure.js +215 -0
  73. package/dist/tools/shop.d.ts +3 -0
  74. package/dist/tools/shop.js +28 -0
  75. package/dist/tools/smart-bulk.d.ts +7 -0
  76. package/dist/tools/smart-bulk.js +286 -0
  77. package/dist/tools/smart-files.d.ts +7 -0
  78. package/dist/tools/smart-files.js +169 -0
  79. package/dist/tools/smart-metaobjects.d.ts +7 -0
  80. package/dist/tools/smart-metaobjects.js +186 -0
  81. package/dist/tools/smart-schema.d.ts +7 -0
  82. package/dist/tools/smart-schema.js +138 -0
  83. package/dist/types.d.ts +19 -0
  84. package/dist/types.js +2 -0
  85. package/dist/utils/polling.d.ts +53 -0
  86. package/dist/utils/polling.js +77 -0
  87. package/package.json +83 -0
  88. package/prisma/schema.prisma +82 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Smart schema discovery tool
3
+ * Combines metafield definitions and metaobject definitions in one call
4
+ */
5
+ import { z } from "zod";
6
+ import { formatSuccessResponse, formatGraphQLErrors, formatErrorResponse, } from "../errors.js";
7
+ import { GET_METAFIELD_DEFINITIONS, GET_METAOBJECT_DEFINITIONS, } from "../graphql/admin/index.js";
8
+ import { enqueue } from "../queue.js";
9
+ import { logOperation } from "../logger.js";
10
+ // Valid metafield owner types
11
+ const METAFIELD_OWNER_TYPES = [
12
+ "PRODUCT",
13
+ "PRODUCTVARIANT",
14
+ "COLLECTION",
15
+ "CUSTOMER",
16
+ "ORDER",
17
+ "DRAFTORDER",
18
+ "SHOP",
19
+ "COMPANY",
20
+ "LOCATION",
21
+ "MARKET",
22
+ "PAGE",
23
+ "BLOG",
24
+ "ARTICLE",
25
+ "DISCOUNT",
26
+ ];
27
+ export function registerSmartSchemaTools(server, client, storeDomain) {
28
+ server.registerTool("schema_discover", {
29
+ title: "Discover Store Schema",
30
+ description: "Discover custom schema in the store: metafield definitions and metaobject types. " +
31
+ "This is a SMART tool that combines multiple discovery queries in one call. " +
32
+ "Use this to understand what custom fields and data types are available in the store " +
33
+ "before working with metafields or metaobjects.",
34
+ inputSchema: {
35
+ includeMetafields: z
36
+ .boolean()
37
+ .default(true)
38
+ .describe("Include metafield definitions in the response"),
39
+ includeMetaobjects: z
40
+ .boolean()
41
+ .default(true)
42
+ .describe("Include metaobject definitions in the response"),
43
+ metafieldOwnerTypes: z
44
+ .array(z.enum(METAFIELD_OWNER_TYPES))
45
+ .optional()
46
+ .describe("Filter metafield definitions by owner type(s). " +
47
+ "If not provided, fetches definitions for PRODUCT, CUSTOMER, ORDER, and SHOP."),
48
+ },
49
+ annotations: {
50
+ readOnlyHint: true,
51
+ destructiveHint: false,
52
+ idempotentHint: true,
53
+ openWorldHint: false,
54
+ },
55
+ }, async ({ includeMetafields, includeMetaobjects, metafieldOwnerTypes }) => {
56
+ const startTime = Date.now();
57
+ try {
58
+ const result = {};
59
+ // Fetch metafield definitions
60
+ if (includeMetafields) {
61
+ const ownerTypes = metafieldOwnerTypes || ["PRODUCT", "CUSTOMER", "ORDER", "SHOP"];
62
+ const metafieldDefs = {};
63
+ for (const ownerType of ownerTypes) {
64
+ const response = await enqueue(() => client.request(GET_METAFIELD_DEFINITIONS, {
65
+ variables: {
66
+ ownerType,
67
+ first: 100,
68
+ },
69
+ }));
70
+ if (response.errors) {
71
+ console.error(`[schema_discover] Error fetching metafield definitions for ${ownerType}:`, response.errors);
72
+ continue;
73
+ }
74
+ const data = response.data;
75
+ metafieldDefs[ownerType] = data.metafieldDefinitions.edges.map((edge) => edge.node);
76
+ }
77
+ result.metafieldDefinitions = metafieldDefs;
78
+ }
79
+ // Fetch metaobject definitions
80
+ if (includeMetaobjects) {
81
+ const response = await enqueue(() => client.request(GET_METAOBJECT_DEFINITIONS, {
82
+ variables: { first: 100 },
83
+ }));
84
+ if (response.errors) {
85
+ await logOperation({
86
+ storeDomain,
87
+ toolName: "schema_discover",
88
+ query: GET_METAOBJECT_DEFINITIONS,
89
+ variables: {},
90
+ response,
91
+ success: false,
92
+ errorMessage: "GraphQL errors fetching metaobject definitions",
93
+ durationMs: Date.now() - startTime,
94
+ });
95
+ return formatGraphQLErrors(response);
96
+ }
97
+ const data = response.data;
98
+ result.metaobjectDefinitions = data.metaobjectDefinitions.edges.map((edge) => edge.node);
99
+ }
100
+ // Build summary
101
+ const summary = [];
102
+ if (result.metafieldDefinitions) {
103
+ const totalDefs = Object.values(result.metafieldDefinitions).reduce((sum, defs) => sum + defs.length, 0);
104
+ summary.push(`${totalDefs} metafield definitions across ${Object.keys(result.metafieldDefinitions).length} owner types`);
105
+ }
106
+ if (result.metaobjectDefinitions) {
107
+ summary.push(`${result.metaobjectDefinitions.length} metaobject types`);
108
+ }
109
+ await logOperation({
110
+ storeDomain,
111
+ toolName: "schema_discover",
112
+ query: "schema_discover",
113
+ variables: { includeMetafields, includeMetaobjects, metafieldOwnerTypes },
114
+ response: { summary },
115
+ success: true,
116
+ durationMs: Date.now() - startTime,
117
+ });
118
+ return formatSuccessResponse({
119
+ success: true,
120
+ summary: summary.join(", "),
121
+ ...result,
122
+ });
123
+ }
124
+ catch (error) {
125
+ await logOperation({
126
+ storeDomain,
127
+ toolName: "schema_discover",
128
+ query: "schema_discover",
129
+ variables: { includeMetafields, includeMetaobjects, metafieldOwnerTypes },
130
+ success: false,
131
+ errorMessage: error instanceof Error ? error.message : String(error),
132
+ durationMs: Date.now() - startTime,
133
+ });
134
+ return formatErrorResponse(error);
135
+ }
136
+ });
137
+ }
138
+ //# sourceMappingURL=smart-schema.js.map
@@ -0,0 +1,19 @@
1
+ export interface ToolResponse {
2
+ content: Array<{
3
+ type: "text";
4
+ text: string;
5
+ }>;
6
+ isError?: boolean;
7
+ }
8
+ export interface PageInfo {
9
+ hasNextPage: boolean;
10
+ endCursor: string | null;
11
+ }
12
+ export interface MoneyV2 {
13
+ amount: string;
14
+ currencyCode: string;
15
+ }
16
+ export interface UserError {
17
+ field: string[];
18
+ message: string;
19
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Polling utility for async Shopify operations
3
+ * Used by smart tools to wait for bulk operations, file uploads, etc.
4
+ */
5
+ export interface PollResult<T> {
6
+ done: boolean;
7
+ result?: T;
8
+ error?: string;
9
+ }
10
+ export interface PollOptions {
11
+ /** Time between checks in milliseconds */
12
+ intervalMs: number;
13
+ /** Maximum time to wait in milliseconds */
14
+ timeoutMs: number;
15
+ /** Optional callback on each poll iteration */
16
+ onPoll?: (elapsed: number) => void;
17
+ }
18
+ /**
19
+ * Poll until a condition is met or timeout
20
+ *
21
+ * @param checkFn Function that checks the condition and returns result
22
+ * @param options Polling configuration
23
+ * @returns The result when done
24
+ * @throws Error if timeout or error returned from checkFn
25
+ *
26
+ * @example
27
+ * const result = await pollUntil(
28
+ * async () => {
29
+ * const file = await getFile(id);
30
+ * if (file.status === 'READY') return { done: true, result: file };
31
+ * if (file.status === 'FAILED') return { done: true, error: 'Upload failed' };
32
+ * return { done: false };
33
+ * },
34
+ * { intervalMs: 2000, timeoutMs: 30000 }
35
+ * );
36
+ */
37
+ export declare function pollUntil<T>(checkFn: () => Promise<PollResult<T>>, options: PollOptions): Promise<T>;
38
+ /**
39
+ * Sleep for a specified duration
40
+ */
41
+ export declare function sleep(ms: number): Promise<void>;
42
+ /**
43
+ * Retry a function with exponential backoff
44
+ *
45
+ * @param fn Function to retry
46
+ * @param options Retry configuration
47
+ */
48
+ export declare function retryWithBackoff<T>(fn: () => Promise<T>, options: {
49
+ maxRetries: number;
50
+ initialDelayMs: number;
51
+ maxDelayMs: number;
52
+ shouldRetry?: (error: unknown) => boolean;
53
+ }): Promise<T>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Polling utility for async Shopify operations
3
+ * Used by smart tools to wait for bulk operations, file uploads, etc.
4
+ */
5
+ /**
6
+ * Poll until a condition is met or timeout
7
+ *
8
+ * @param checkFn Function that checks the condition and returns result
9
+ * @param options Polling configuration
10
+ * @returns The result when done
11
+ * @throws Error if timeout or error returned from checkFn
12
+ *
13
+ * @example
14
+ * const result = await pollUntil(
15
+ * async () => {
16
+ * const file = await getFile(id);
17
+ * if (file.status === 'READY') return { done: true, result: file };
18
+ * if (file.status === 'FAILED') return { done: true, error: 'Upload failed' };
19
+ * return { done: false };
20
+ * },
21
+ * { intervalMs: 2000, timeoutMs: 30000 }
22
+ * );
23
+ */
24
+ export async function pollUntil(checkFn, options) {
25
+ const start = Date.now();
26
+ while (Date.now() - start < options.timeoutMs) {
27
+ const { done, result, error } = await checkFn();
28
+ if (error) {
29
+ throw new Error(error);
30
+ }
31
+ if (done) {
32
+ return result;
33
+ }
34
+ // Call optional progress callback
35
+ if (options.onPoll) {
36
+ options.onPoll(Date.now() - start);
37
+ }
38
+ // Wait before next check
39
+ await sleep(options.intervalMs);
40
+ }
41
+ throw new Error(`Polling timeout after ${options.timeoutMs}ms`);
42
+ }
43
+ /**
44
+ * Sleep for a specified duration
45
+ */
46
+ export function sleep(ms) {
47
+ return new Promise((resolve) => setTimeout(resolve, ms));
48
+ }
49
+ /**
50
+ * Retry a function with exponential backoff
51
+ *
52
+ * @param fn Function to retry
53
+ * @param options Retry configuration
54
+ */
55
+ export async function retryWithBackoff(fn, options) {
56
+ let lastError;
57
+ let delay = options.initialDelayMs;
58
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
59
+ try {
60
+ return await fn();
61
+ }
62
+ catch (error) {
63
+ lastError = error;
64
+ // Check if we should retry this error
65
+ if (options.shouldRetry && !options.shouldRetry(error)) {
66
+ throw error;
67
+ }
68
+ // Don't sleep after the last attempt
69
+ if (attempt < options.maxRetries) {
70
+ await sleep(delay);
71
+ delay = Math.min(delay * 2, options.maxDelayMs);
72
+ }
73
+ }
74
+ }
75
+ throw lastError;
76
+ }
77
+ //# sourceMappingURL=polling.js.map
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "shopify-store-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Shopify store operations via Admin and Storefront APIs",
5
+ "author": "Brahim Benzarti",
6
+ "license": "ISC",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "bin": {
10
+ "shopify-store-mcp": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist/**/*.js",
14
+ "dist/**/*.d.ts",
15
+ "prisma/schema.prisma",
16
+ "LICENSE",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "mcp",
21
+ "modelcontextprotocol",
22
+ "shopify",
23
+ "admin-api",
24
+ "graphql",
25
+ "ai",
26
+ "claude",
27
+ "cursor"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/ibrahimbenzarti/shopify-store-mcp.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/ibrahimbenzarti/shopify-store-mcp/issues"
35
+ },
36
+ "homepage": "https://github.com/ibrahimbenzarti/shopify-store-mcp#readme",
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "scripts": {
41
+ "build": "prisma generate && tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
42
+ "dev": "tsc --watch",
43
+ "start": "node dist/index.js",
44
+ "inspect": "npm run build && npx @modelcontextprotocol/inspector dist/index.js",
45
+ "typecheck": "tsc --noEmit",
46
+ "graphql-codegen": "graphql-codegen -p=adminApi && graphql-codegen -p=storefrontApi",
47
+ "graphql-codegen:admin": "graphql-codegen -p=adminApi",
48
+ "graphql-codegen:storefront": "graphql-codegen -p=storefrontApi",
49
+ "graphql-codegen:watch": "graphql-codegen --watch",
50
+ "db:generate": "prisma generate",
51
+ "db:push": "prisma db push",
52
+ "db:studio": "prisma studio",
53
+ "prepublishOnly": "npm run build"
54
+ },
55
+ "dependencies": {
56
+ "@modelcontextprotocol/sdk": "^1.11.0",
57
+ "@prisma/client": "^6.3.0",
58
+ "@shopify/admin-api-client": "^1.1.0",
59
+ "@shopify/storefront-api-client": "^1.0.0",
60
+ "dotenv": "^16.4.5",
61
+ "p-queue": "^8.0.1",
62
+ "zod": "^3.23.0"
63
+ },
64
+ "devDependencies": {
65
+ "@graphql-codegen/cli": "^5.0.0",
66
+ "@shopify/api-codegen-preset": "^1.1.1",
67
+ "@types/node": "^22.0.0",
68
+ "graphql": "^16.9.0",
69
+ "graphql-config": "^5.1.0",
70
+ "prisma": "^6.3.0",
71
+ "typescript": "^5.6.0"
72
+ },
73
+ "resolutions": {
74
+ "@graphql-tools/url-loader": "8.0.16",
75
+ "@graphql-codegen/client-preset": "4.7.0",
76
+ "@graphql-codegen/typescript-operations": "4.5.0"
77
+ },
78
+ "overrides": {
79
+ "@graphql-tools/url-loader": "8.0.16",
80
+ "@graphql-codegen/client-preset": "4.7.0",
81
+ "@graphql-codegen/typescript-operations": "4.5.0"
82
+ }
83
+ }
@@ -0,0 +1,82 @@
1
+ // Shopify Store MCP - Prisma Schema
2
+ // SQLite database for operation logging, configuration, and background jobs
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "sqlite"
10
+ url = env("DATABASE_URL")
11
+ }
12
+
13
+ // ============================================================================
14
+ // STORE CONFIGURATION
15
+ // ============================================================================
16
+
17
+ // Store configuration per shop (rate limit tier, settings)
18
+ model StoreConfig {
19
+ id String @id @default(uuid())
20
+ storeDomain String @unique
21
+ tier String @default("STANDARD") // STANDARD, ADVANCED, PLUS, ENTERPRISE
22
+ autoDetected Boolean @default(false)
23
+ shopName String?
24
+ shopPlan String?
25
+ createdAt DateTime @default(now())
26
+ updatedAt DateTime @updatedAt
27
+ }
28
+
29
+ // ============================================================================
30
+ // OPERATION LOGGING
31
+ // ============================================================================
32
+
33
+ // Logs every GraphQL operation for debugging and history
34
+ model OperationLog {
35
+ id String @id @default(uuid())
36
+ storeDomain String
37
+ sessionId String // MCP session identifier
38
+ toolName String // Tool that was called
39
+ operationType String // "query" | "mutation"
40
+ query String // GraphQL query (may be truncated)
41
+ variables String? // JSON string of variables
42
+ response String? // JSON string of response (may be truncated)
43
+ success Boolean
44
+ errorMessage String?
45
+ durationMs Int
46
+ createdAt DateTime @default(now())
47
+
48
+ @@index([storeDomain])
49
+ @@index([storeDomain, createdAt])
50
+ @@index([toolName])
51
+ @@index([sessionId])
52
+ @@index([createdAt])
53
+ }
54
+
55
+ // ============================================================================
56
+ // BACKGROUND JOBS
57
+ // ============================================================================
58
+
59
+ // Tracks long-running async operations (bulk export/import, file uploads)
60
+ model BackgroundJob {
61
+ id String @id @default(uuid())
62
+ storeDomain String
63
+ sessionId String // MCP session that started the job
64
+ bulkOperationId String? // Shopify bulk operation GID if applicable
65
+ type String // BULK_EXPORT, BULK_IMPORT, FILE_UPLOAD
66
+ name String // Human-readable job name
67
+ status String @default("PENDING") // PENDING, RUNNING, COMPLETED, FAILED, CANCELLED
68
+ progress Float @default(0) // Progress percentage (0-100)
69
+ total Int @default(0) // Total items to process
70
+ processed Int @default(0) // Items processed so far
71
+ data String? // JSON - additional job metadata
72
+ result String? // JSON - job results
73
+ error String? // Error message if failed
74
+ createdAt DateTime @default(now())
75
+ updatedAt DateTime @updatedAt
76
+
77
+ @@index([storeDomain])
78
+ @@index([storeDomain, status])
79
+ @@index([bulkOperationId])
80
+ @@index([status])
81
+ @@index([sessionId])
82
+ }