@anton.andrusenko/shopify-mcp-admin 0.2.0 → 0.4.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 +31 -0
- package/dist/index.js +1755 -754
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -22,7 +22,10 @@ var configSchema = z.object({
|
|
|
22
22
|
PORT: z.string().default("3000").transform(Number),
|
|
23
23
|
// Transport selection (AC-2.2.6, AC-2.2.7)
|
|
24
24
|
// Default: stdio for Claude Desktop compatibility
|
|
25
|
-
TRANSPORT: z.enum(["stdio", "http"]).default("stdio")
|
|
25
|
+
TRANSPORT: z.enum(["stdio", "http"]).default("stdio"),
|
|
26
|
+
// Store info cache TTL (milliseconds)
|
|
27
|
+
// Default: 5 minutes (300000ms) - configurable for performance tuning
|
|
28
|
+
STORE_INFO_CACHE_TTL_MS: z.string().optional().default("300000").transform(Number).describe("Cache TTL for store info in milliseconds (default: 5 minutes)")
|
|
26
29
|
}).refine(
|
|
27
30
|
(data) => {
|
|
28
31
|
const hasLegacyAuth = !!data.SHOPIFY_ACCESS_TOKEN;
|
|
@@ -936,7 +939,7 @@ function transformShopResponse(shop) {
|
|
|
936
939
|
async function getStoreInfo() {
|
|
937
940
|
const now = Date.now();
|
|
938
941
|
const config = getConfig();
|
|
939
|
-
const ttl =
|
|
942
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
940
943
|
if (_cachedStoreInfo && now - _cacheTimestamp < ttl) {
|
|
941
944
|
log.debug("Returning cached store info");
|
|
942
945
|
return _cachedStoreInfo;
|
|
@@ -963,6 +966,42 @@ async function getStoreInfo() {
|
|
|
963
966
|
var require2 = createRequire(import.meta.url);
|
|
964
967
|
var packageJson = require2("../package.json");
|
|
965
968
|
var SERVER_NAME = "shopify-mcp-admin";
|
|
969
|
+
var SERVER_INSTRUCTIONS = `This MCP server provides access to a Shopify store's Admin API.
|
|
970
|
+
|
|
971
|
+
KEY CONCEPTS:
|
|
972
|
+
- Products contain variants; each variant has its own price, SKU, and inventory
|
|
973
|
+
- Inventory is tracked per variant AND per location (multi-location stores)
|
|
974
|
+
- Metafields store custom data using namespace/key pairs
|
|
975
|
+
- Collections organize products for navigation and SEO
|
|
976
|
+
- All IDs use Shopify GID format: "gid://shopify/Product/123"
|
|
977
|
+
|
|
978
|
+
RESOURCE TYPES & GID FORMATS:
|
|
979
|
+
- Products: gid://shopify/Product/{id}
|
|
980
|
+
- Variants: gid://shopify/ProductVariant/{id}
|
|
981
|
+
- Collections: gid://shopify/Collection/{id}
|
|
982
|
+
- Images: gid://shopify/MediaImage/{id}
|
|
983
|
+
- Pages: gid://shopify/Page/{id}
|
|
984
|
+
- Blogs: gid://shopify/Blog/{id}
|
|
985
|
+
- Articles: gid://shopify/Article/{id}
|
|
986
|
+
- Redirects: gid://shopify/UrlRedirect/{id}
|
|
987
|
+
- InventoryItems: gid://shopify/InventoryItem/{id}
|
|
988
|
+
- Locations: gid://shopify/Location/{id}
|
|
989
|
+
|
|
990
|
+
BEST PRACTICES:
|
|
991
|
+
- Always verify product/variant IDs using get-product before updates
|
|
992
|
+
- Use list-low-inventory to proactively identify stock issues
|
|
993
|
+
- When updating SEO, consider updating URL redirects
|
|
994
|
+
- For bulk operations, prefer bulk tools when available
|
|
995
|
+
- Use metafields to store custom SEO data (JSON-LD, Open Graph)
|
|
996
|
+
|
|
997
|
+
RATE LIMITS:
|
|
998
|
+
- Shopify GraphQL has cost-based rate limiting (~50 points/sec)
|
|
999
|
+
- Large queries may retry automatically with exponential backoff
|
|
1000
|
+
- Rate limit errors include helpful retry suggestions
|
|
1001
|
+
|
|
1002
|
+
WORKFLOW HINTS:
|
|
1003
|
+
- Tool descriptions include **Prerequisites:** and **Follow-ups:** for multi-step operations
|
|
1004
|
+
- Check tool category and relationships for semantic grouping`;
|
|
966
1005
|
function getServerVersion() {
|
|
967
1006
|
return packageJson.version;
|
|
968
1007
|
}
|
|
@@ -978,9 +1017,13 @@ function createServer() {
|
|
|
978
1017
|
capabilities: {
|
|
979
1018
|
tools: {},
|
|
980
1019
|
// Enable tools capability (AC-2.1.4)
|
|
981
|
-
resources: {}
|
|
1020
|
+
resources: {},
|
|
982
1021
|
// Enable resources capability (AC-2.1.4)
|
|
983
|
-
|
|
1022
|
+
logging: {}
|
|
1023
|
+
// Enable logging capability (AC-9.5.1.2)
|
|
1024
|
+
},
|
|
1025
|
+
instructions: SERVER_INSTRUCTIONS
|
|
1026
|
+
// Provide Shopify context to AI agents (AC-9.5.1.1)
|
|
984
1027
|
}
|
|
985
1028
|
);
|
|
986
1029
|
return server;
|
|
@@ -2334,6 +2377,21 @@ function createSingleTenantContext(client, shopDomain) {
|
|
|
2334
2377
|
|
|
2335
2378
|
// src/tools/registration.ts
|
|
2336
2379
|
var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2380
|
+
function deriveDefaultAnnotations(name) {
|
|
2381
|
+
const isReadOnly = name.startsWith("get-") || name.startsWith("list-");
|
|
2382
|
+
const isDestructive = name.startsWith("delete-");
|
|
2383
|
+
const isCreate = name.startsWith("create-");
|
|
2384
|
+
return {
|
|
2385
|
+
readOnlyHint: isReadOnly,
|
|
2386
|
+
destructiveHint: isDestructive,
|
|
2387
|
+
// create-* is NOT idempotent (each call creates new resource)
|
|
2388
|
+
// delete-* is NOT idempotent (second call fails - resource gone)
|
|
2389
|
+
// update-*, set-*, add-*, remove-*, reorder-* ARE idempotent
|
|
2390
|
+
idempotentHint: !isCreate && !isDestructive,
|
|
2391
|
+
// All our tools interact with Shopify API
|
|
2392
|
+
openWorldHint: true
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2337
2395
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
2338
2396
|
function validateToolName(name) {
|
|
2339
2397
|
if (!name || name.trim() === "") {
|
|
@@ -2393,7 +2451,7 @@ function wrapToolHandler(toolName, schema, handler) {
|
|
|
2393
2451
|
};
|
|
2394
2452
|
}
|
|
2395
2453
|
function registerTool(definition, handler, options = {}) {
|
|
2396
|
-
const { name, title, description, inputSchema:
|
|
2454
|
+
const { name, title, description, inputSchema: inputSchema46, annotations } = definition;
|
|
2397
2455
|
try {
|
|
2398
2456
|
if (!options.skipNameValidation) {
|
|
2399
2457
|
validateToolName(name);
|
|
@@ -2401,15 +2459,22 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2401
2459
|
if (registeredTools.has(name)) {
|
|
2402
2460
|
throw new Error(`Tool "${name}" is already registered. Tool names must be unique.`);
|
|
2403
2461
|
}
|
|
2404
|
-
const jsonSchema = convertZodToJsonSchema(
|
|
2405
|
-
const wrappedHandler = wrapToolHandler(name,
|
|
2462
|
+
const jsonSchema = convertZodToJsonSchema(inputSchema46);
|
|
2463
|
+
const wrappedHandler = wrapToolHandler(name, inputSchema46, handler);
|
|
2464
|
+
const finalAnnotations = {
|
|
2465
|
+
...deriveDefaultAnnotations(name),
|
|
2466
|
+
...annotations,
|
|
2467
|
+
// Use definition title as fallback for annotation title
|
|
2468
|
+
title: annotations?.title ?? title
|
|
2469
|
+
};
|
|
2406
2470
|
const registeredTool = {
|
|
2407
2471
|
name,
|
|
2408
2472
|
title,
|
|
2409
2473
|
description,
|
|
2410
2474
|
inputSchema: jsonSchema,
|
|
2411
|
-
zodSchema:
|
|
2412
|
-
handler: wrappedHandler
|
|
2475
|
+
zodSchema: inputSchema46,
|
|
2476
|
+
handler: wrappedHandler,
|
|
2477
|
+
annotations: finalAnnotations
|
|
2413
2478
|
};
|
|
2414
2479
|
registeredTools.set(name, registeredTool);
|
|
2415
2480
|
log.debug(`Registered tool: ${name}`);
|
|
@@ -2428,7 +2493,8 @@ function getRegisteredTools() {
|
|
|
2428
2493
|
return Array.from(registeredTools.values()).map((tool) => ({
|
|
2429
2494
|
name: tool.name,
|
|
2430
2495
|
description: `${tool.title}: ${tool.description}`,
|
|
2431
|
-
inputSchema: tool.inputSchema
|
|
2496
|
+
inputSchema: tool.inputSchema,
|
|
2497
|
+
annotations: tool.annotations
|
|
2432
2498
|
}));
|
|
2433
2499
|
}
|
|
2434
2500
|
function getToolByName(name) {
|
|
@@ -2448,7 +2514,7 @@ function registerContextAwareTool(definition, handler, options = {}) {
|
|
|
2448
2514
|
}
|
|
2449
2515
|
|
|
2450
2516
|
// src/tools/add-product-image.ts
|
|
2451
|
-
import { z as
|
|
2517
|
+
import { z as z3 } from "zod";
|
|
2452
2518
|
|
|
2453
2519
|
// src/shopify/mutations.ts
|
|
2454
2520
|
var PRODUCT_CREATE_MUTATION = `
|
|
@@ -3088,22 +3154,42 @@ async function addProductImage(productId, input) {
|
|
|
3088
3154
|
};
|
|
3089
3155
|
}
|
|
3090
3156
|
|
|
3157
|
+
// src/tools/validators.ts
|
|
3158
|
+
import { z as z2 } from "zod";
|
|
3159
|
+
var GID_PATTERN = /^gid:\/\/shopify\/[A-Za-z]+\/\d+$/;
|
|
3160
|
+
function gidSchema(resourceType) {
|
|
3161
|
+
const pattern = resourceType ? new RegExp(`^gid://shopify/${resourceType}/\\d+$`) : GID_PATTERN;
|
|
3162
|
+
const example = resourceType ? `gid://shopify/${resourceType}/123456789` : "gid://shopify/Product/123456789";
|
|
3163
|
+
return z2.string().regex(pattern, `Must be a valid Shopify GID (e.g., ${example})`).describe(`Shopify Global ID (format: ${example})`);
|
|
3164
|
+
}
|
|
3165
|
+
var productIdSchema = gidSchema("Product");
|
|
3166
|
+
var variantIdSchema = gidSchema("ProductVariant");
|
|
3167
|
+
var collectionIdSchema = gidSchema("Collection");
|
|
3168
|
+
var imageIdSchema = gidSchema("MediaImage");
|
|
3169
|
+
var pageIdSchema = gidSchema("Page");
|
|
3170
|
+
var blogIdSchema = gidSchema("Blog");
|
|
3171
|
+
var articleIdSchema = gidSchema("Article");
|
|
3172
|
+
var redirectIdSchema = gidSchema("UrlRedirect");
|
|
3173
|
+
var inventoryItemIdSchema = gidSchema("InventoryItem");
|
|
3174
|
+
var locationIdSchema = gidSchema("Location");
|
|
3175
|
+
var marketIdSchema = gidSchema("Market");
|
|
3176
|
+
|
|
3091
3177
|
// src/tools/add-product-image.ts
|
|
3092
|
-
var inputSchema =
|
|
3093
|
-
productId:
|
|
3178
|
+
var inputSchema = z3.object({
|
|
3179
|
+
productId: productIdSchema.describe(
|
|
3094
3180
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
|
|
3095
3181
|
),
|
|
3096
|
-
url:
|
|
3182
|
+
url: z3.string().url().describe(
|
|
3097
3183
|
"Publicly accessible image URL. Shopify will fetch and host the image. Required. Supported formats: JPEG, PNG, GIF, WEBP."
|
|
3098
3184
|
),
|
|
3099
|
-
altText:
|
|
3185
|
+
altText: z3.string().optional().describe(
|
|
3100
3186
|
"Alt text for accessibility and SEO. Describes the image for screen readers. Recommended for better accessibility and search rankings."
|
|
3101
3187
|
)
|
|
3102
3188
|
});
|
|
3103
|
-
var outputSchema =
|
|
3104
|
-
id:
|
|
3105
|
-
url:
|
|
3106
|
-
altText:
|
|
3189
|
+
var outputSchema = z3.object({
|
|
3190
|
+
id: z3.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
|
|
3191
|
+
url: z3.string().describe("Shopify CDN URL for the image"),
|
|
3192
|
+
altText: z3.string().nullable().describe("Alt text for the image")
|
|
3107
3193
|
});
|
|
3108
3194
|
var handleAddProductImage = async (context, params) => {
|
|
3109
3195
|
log.debug(`Adding image to product on shop: ${context.shopDomain}`);
|
|
@@ -3161,6 +3247,14 @@ function registerAddProductImageTool() {
|
|
|
3161
3247
|
relatedTools: ["get-product", "update-product-image"],
|
|
3162
3248
|
prerequisites: ["create-product"],
|
|
3163
3249
|
followUps: ["update-product-image", "reorder-product-images"]
|
|
3250
|
+
},
|
|
3251
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
3252
|
+
annotations: {
|
|
3253
|
+
readOnlyHint: false,
|
|
3254
|
+
destructiveHint: false,
|
|
3255
|
+
idempotentHint: false,
|
|
3256
|
+
// Adding same URL creates duplicate images!
|
|
3257
|
+
openWorldHint: true
|
|
3164
3258
|
}
|
|
3165
3259
|
},
|
|
3166
3260
|
handleAddProductImage
|
|
@@ -3168,7 +3262,7 @@ function registerAddProductImageTool() {
|
|
|
3168
3262
|
}
|
|
3169
3263
|
|
|
3170
3264
|
// src/tools/add-products-to-collection.ts
|
|
3171
|
-
import { z as
|
|
3265
|
+
import { z as z4 } from "zod";
|
|
3172
3266
|
|
|
3173
3267
|
// src/shopify/collections.ts
|
|
3174
3268
|
var GET_COLLECTION_QUERY = `
|
|
@@ -3569,18 +3663,18 @@ async function removeProductsFromCollection(collectionId, productIds) {
|
|
|
3569
3663
|
}
|
|
3570
3664
|
|
|
3571
3665
|
// src/tools/add-products-to-collection.ts
|
|
3572
|
-
var inputSchema2 =
|
|
3573
|
-
collectionId:
|
|
3666
|
+
var inputSchema2 = z4.object({
|
|
3667
|
+
collectionId: collectionIdSchema.describe(
|
|
3574
3668
|
'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
|
|
3575
3669
|
),
|
|
3576
|
-
productIds:
|
|
3670
|
+
productIds: z4.array(productIdSchema).min(1).max(250).describe(
|
|
3577
3671
|
'Array of product GIDs to add (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 250 products per call. Use list-products to find valid product IDs.'
|
|
3578
3672
|
)
|
|
3579
3673
|
});
|
|
3580
|
-
var outputSchema2 =
|
|
3581
|
-
collectionId:
|
|
3582
|
-
productsCount:
|
|
3583
|
-
addedCount:
|
|
3674
|
+
var outputSchema2 = z4.object({
|
|
3675
|
+
collectionId: z4.string().describe("Collection GID"),
|
|
3676
|
+
productsCount: z4.number().describe("Total number of products now in the collection"),
|
|
3677
|
+
addedCount: z4.number().describe("Number of products added in this operation")
|
|
3584
3678
|
});
|
|
3585
3679
|
var handleAddProductsToCollection = async (context, params) => {
|
|
3586
3680
|
log.debug(
|
|
@@ -3624,6 +3718,14 @@ function registerAddProductsToCollectionTool() {
|
|
|
3624
3718
|
relatedTools: ["remove-products-from-collection", "list-products"],
|
|
3625
3719
|
prerequisites: ["create-collection", "list-products"],
|
|
3626
3720
|
followUps: ["get-collection"]
|
|
3721
|
+
},
|
|
3722
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
3723
|
+
annotations: {
|
|
3724
|
+
readOnlyHint: false,
|
|
3725
|
+
destructiveHint: false,
|
|
3726
|
+
idempotentHint: true,
|
|
3727
|
+
// Adding same products is idempotent (no-op)
|
|
3728
|
+
openWorldHint: true
|
|
3627
3729
|
}
|
|
3628
3730
|
},
|
|
3629
3731
|
handleAddProductsToCollection
|
|
@@ -3631,7 +3733,7 @@ function registerAddProductsToCollectionTool() {
|
|
|
3631
3733
|
}
|
|
3632
3734
|
|
|
3633
3735
|
// src/tools/create-article.ts
|
|
3634
|
-
import { z as
|
|
3736
|
+
import { z as z5 } from "zod";
|
|
3635
3737
|
|
|
3636
3738
|
// src/shopify/blogs.ts
|
|
3637
3739
|
var LIST_BLOGS_QUERY = `
|
|
@@ -4137,51 +4239,51 @@ async function deleteArticle(articleId) {
|
|
|
4137
4239
|
}
|
|
4138
4240
|
|
|
4139
4241
|
// src/tools/create-article.ts
|
|
4140
|
-
var inputSchema3 =
|
|
4141
|
-
blogId:
|
|
4242
|
+
var inputSchema3 = z5.object({
|
|
4243
|
+
blogId: blogIdSchema.describe(
|
|
4142
4244
|
'The blog ID to create the article in (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs.'
|
|
4143
4245
|
),
|
|
4144
|
-
title:
|
|
4246
|
+
title: z5.string().min(1).describe(
|
|
4145
4247
|
"The title of the article (required). This will be displayed as the article heading. Example: 'How to Use Our Products', '10 Tips for Beginners'."
|
|
4146
4248
|
),
|
|
4147
|
-
authorName:
|
|
4249
|
+
authorName: z5.string().min(1).describe(
|
|
4148
4250
|
"The name of the article author (required). This is displayed on the article. Example: 'John Doe', 'Marketing Team'."
|
|
4149
4251
|
),
|
|
4150
|
-
body:
|
|
4252
|
+
body: z5.string().optional().describe(
|
|
4151
4253
|
"The HTML body content of the article. Supports HTML markup for formatting. Example: '<h2>Introduction</h2><p>Welcome to our guide...</p>'"
|
|
4152
4254
|
),
|
|
4153
|
-
summary:
|
|
4255
|
+
summary: z5.string().optional().describe(
|
|
4154
4256
|
"A summary or excerpt of the article. Used for previews on blog listing pages. Can include HTML markup."
|
|
4155
4257
|
),
|
|
4156
|
-
tags:
|
|
4258
|
+
tags: z5.array(z5.string()).optional().describe(
|
|
4157
4259
|
"Tags for categorization. Helps organize articles and improve discoverability. Example: ['guide', 'tutorial', 'beginner']."
|
|
4158
4260
|
),
|
|
4159
|
-
image:
|
|
4160
|
-
url:
|
|
4161
|
-
altText:
|
|
4261
|
+
image: z5.object({
|
|
4262
|
+
url: z5.string().url().describe("The URL of the featured image"),
|
|
4263
|
+
altText: z5.string().optional().describe("Alt text for accessibility")
|
|
4162
4264
|
}).optional().describe("Featured image for the article."),
|
|
4163
|
-
isPublished:
|
|
4265
|
+
isPublished: z5.boolean().optional().describe(
|
|
4164
4266
|
"Whether the article should be visible on the storefront. Defaults to false (unpublished). Set to true to make the article immediately visible."
|
|
4165
4267
|
),
|
|
4166
|
-
publishDate:
|
|
4268
|
+
publishDate: z5.string().optional().describe(
|
|
4167
4269
|
"The date and time when the article should become visible (ISO 8601 format). Example: '2024-01-15T10:00:00Z'. If not set and isPublished is true, publishes immediately."
|
|
4168
4270
|
),
|
|
4169
|
-
handle:
|
|
4271
|
+
handle: z5.string().optional().describe(
|
|
4170
4272
|
"The URL handle/slug for the article. If not provided, it will be auto-generated from the title. Example: 'how-to-use-our-products' would create URL /blogs/blog-handle/how-to-use-our-products."
|
|
4171
4273
|
),
|
|
4172
|
-
templateSuffix:
|
|
4274
|
+
templateSuffix: z5.string().optional().describe(
|
|
4173
4275
|
"The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
|
|
4174
4276
|
)
|
|
4175
4277
|
});
|
|
4176
|
-
var outputSchema3 =
|
|
4177
|
-
id:
|
|
4178
|
-
title:
|
|
4179
|
-
handle:
|
|
4180
|
-
blog:
|
|
4181
|
-
id:
|
|
4182
|
-
title:
|
|
4278
|
+
var outputSchema3 = z5.object({
|
|
4279
|
+
id: z5.string().describe('Created article GID (e.g., "gid://shopify/Article/123")'),
|
|
4280
|
+
title: z5.string().describe("Article title"),
|
|
4281
|
+
handle: z5.string().describe("URL handle/slug"),
|
|
4282
|
+
blog: z5.object({
|
|
4283
|
+
id: z5.string().describe("Parent blog GID"),
|
|
4284
|
+
title: z5.string().describe("Parent blog title")
|
|
4183
4285
|
}),
|
|
4184
|
-
isPublished:
|
|
4286
|
+
isPublished: z5.boolean().describe("Whether article is visible on storefront")
|
|
4185
4287
|
});
|
|
4186
4288
|
var handleCreateArticle = async (context, params) => {
|
|
4187
4289
|
log.debug(`Creating article on shop: ${context.shopDomain}`);
|
|
@@ -4237,6 +4339,14 @@ function registerCreateArticleTool() {
|
|
|
4237
4339
|
relatedTools: ["list-blogs", "list-articles", "update-article"],
|
|
4238
4340
|
prerequisites: ["list-blogs"],
|
|
4239
4341
|
followUps: ["list-articles", "update-article"]
|
|
4342
|
+
},
|
|
4343
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4344
|
+
annotations: {
|
|
4345
|
+
readOnlyHint: false,
|
|
4346
|
+
destructiveHint: false,
|
|
4347
|
+
idempotentHint: false,
|
|
4348
|
+
// create-* tools are NOT idempotent
|
|
4349
|
+
openWorldHint: true
|
|
4240
4350
|
}
|
|
4241
4351
|
},
|
|
4242
4352
|
handleCreateArticle
|
|
@@ -4244,26 +4354,26 @@ function registerCreateArticleTool() {
|
|
|
4244
4354
|
}
|
|
4245
4355
|
|
|
4246
4356
|
// src/tools/create-blog.ts
|
|
4247
|
-
import { z as
|
|
4248
|
-
var inputSchema4 =
|
|
4249
|
-
title:
|
|
4357
|
+
import { z as z6 } from "zod";
|
|
4358
|
+
var inputSchema4 = z6.object({
|
|
4359
|
+
title: z6.string().min(1).describe(
|
|
4250
4360
|
"The title of the blog (required). This will be displayed as the blog heading. Example: 'Company News', 'Product Guides', 'Industry Insights'."
|
|
4251
4361
|
),
|
|
4252
|
-
handle:
|
|
4362
|
+
handle: z6.string().optional().describe(
|
|
4253
4363
|
"The URL handle/slug for the blog. If not provided, it will be auto-generated from the title. Example: 'news' would create URL /blogs/news."
|
|
4254
4364
|
),
|
|
4255
|
-
commentPolicy:
|
|
4365
|
+
commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
|
|
4256
4366
|
"Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately. Defaults to MODERATE if not specified."
|
|
4257
4367
|
),
|
|
4258
|
-
templateSuffix:
|
|
4368
|
+
templateSuffix: z6.string().optional().describe(
|
|
4259
4369
|
"The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
|
|
4260
4370
|
)
|
|
4261
4371
|
});
|
|
4262
|
-
var outputSchema4 =
|
|
4263
|
-
id:
|
|
4264
|
-
title:
|
|
4265
|
-
handle:
|
|
4266
|
-
commentPolicy:
|
|
4372
|
+
var outputSchema4 = z6.object({
|
|
4373
|
+
id: z6.string().describe('Created blog GID (e.g., "gid://shopify/Blog/123")'),
|
|
4374
|
+
title: z6.string().describe("Blog title"),
|
|
4375
|
+
handle: z6.string().describe("URL handle/slug"),
|
|
4376
|
+
commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
|
|
4267
4377
|
});
|
|
4268
4378
|
var handleCreateBlog = async (context, params) => {
|
|
4269
4379
|
log.debug(`Creating blog on shop: ${context.shopDomain}`);
|
|
@@ -4305,6 +4415,14 @@ function registerCreateBlogTool() {
|
|
|
4305
4415
|
relationships: {
|
|
4306
4416
|
relatedTools: ["list-blogs", "update-blog", "create-article"],
|
|
4307
4417
|
followUps: ["create-article", "list-blogs"]
|
|
4418
|
+
},
|
|
4419
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4420
|
+
annotations: {
|
|
4421
|
+
readOnlyHint: false,
|
|
4422
|
+
destructiveHint: false,
|
|
4423
|
+
idempotentHint: false,
|
|
4424
|
+
// create-* tools are NOT idempotent
|
|
4425
|
+
openWorldHint: true
|
|
4308
4426
|
}
|
|
4309
4427
|
},
|
|
4310
4428
|
handleCreateBlog
|
|
@@ -4312,20 +4430,20 @@ function registerCreateBlogTool() {
|
|
|
4312
4430
|
}
|
|
4313
4431
|
|
|
4314
4432
|
// src/tools/create-collection.ts
|
|
4315
|
-
import { z as
|
|
4316
|
-
var inputSchema5 =
|
|
4317
|
-
title:
|
|
4318
|
-
handle:
|
|
4433
|
+
import { z as z7 } from "zod";
|
|
4434
|
+
var inputSchema5 = z7.object({
|
|
4435
|
+
title: z7.string().min(1).describe("Collection title (required). This will be displayed to customers."),
|
|
4436
|
+
handle: z7.string().optional().describe(
|
|
4319
4437
|
'URL handle/slug for the collection (e.g., "summer-sale"). Auto-generated from title if not provided.'
|
|
4320
4438
|
),
|
|
4321
|
-
descriptionHtml:
|
|
4439
|
+
descriptionHtml: z7.string().optional().describe(
|
|
4322
4440
|
"Collection description with HTML formatting. Displayed on collection pages in the store."
|
|
4323
4441
|
),
|
|
4324
|
-
seo:
|
|
4325
|
-
title:
|
|
4326
|
-
description:
|
|
4442
|
+
seo: z7.object({
|
|
4443
|
+
title: z7.string().optional().describe("SEO title for search engines"),
|
|
4444
|
+
description: z7.string().optional().describe("SEO meta description for search engines")
|
|
4327
4445
|
}).optional().describe("SEO metadata to optimize collection visibility in search results"),
|
|
4328
|
-
sortOrder:
|
|
4446
|
+
sortOrder: z7.enum([
|
|
4329
4447
|
"ALPHA_ASC",
|
|
4330
4448
|
"ALPHA_DESC",
|
|
4331
4449
|
"BEST_SELLING",
|
|
@@ -4337,18 +4455,18 @@ var inputSchema5 = z6.object({
|
|
|
4337
4455
|
]).optional().describe(
|
|
4338
4456
|
"How products are sorted within the collection. Options: ALPHA_ASC (A-Z), ALPHA_DESC (Z-A), BEST_SELLING, CREATED (oldest first), CREATED_DESC (newest first), MANUAL, PRICE_ASC (low to high), PRICE_DESC (high to low)"
|
|
4339
4457
|
),
|
|
4340
|
-
image:
|
|
4341
|
-
src:
|
|
4342
|
-
altText:
|
|
4458
|
+
image: z7.object({
|
|
4459
|
+
src: z7.string().url().describe("Publicly accessible image URL"),
|
|
4460
|
+
altText: z7.string().optional().describe("Alt text for accessibility and SEO")
|
|
4343
4461
|
}).optional().describe("Collection image displayed on collection pages"),
|
|
4344
|
-
templateSuffix:
|
|
4462
|
+
templateSuffix: z7.string().optional().describe(
|
|
4345
4463
|
'Liquid template suffix for custom collection templates. For example, "featured" uses collection.featured.liquid'
|
|
4346
4464
|
)
|
|
4347
4465
|
});
|
|
4348
|
-
var outputSchema5 =
|
|
4349
|
-
id:
|
|
4350
|
-
title:
|
|
4351
|
-
handle:
|
|
4466
|
+
var outputSchema5 = z7.object({
|
|
4467
|
+
id: z7.string().describe('Created collection GID (e.g., "gid://shopify/Collection/123")'),
|
|
4468
|
+
title: z7.string().describe("Collection title"),
|
|
4469
|
+
handle: z7.string().describe("Collection URL handle")
|
|
4352
4470
|
});
|
|
4353
4471
|
var handleCreateCollection = async (context, params) => {
|
|
4354
4472
|
log.debug(`Creating collection on shop: ${context.shopDomain}`);
|
|
@@ -4387,14 +4505,352 @@ function registerCreateCollectionTool() {
|
|
|
4387
4505
|
relationships: {
|
|
4388
4506
|
relatedTools: ["list-collections", "get-collection"],
|
|
4389
4507
|
followUps: ["add-products-to-collection", "update-collection"]
|
|
4508
|
+
},
|
|
4509
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4510
|
+
annotations: {
|
|
4511
|
+
readOnlyHint: false,
|
|
4512
|
+
destructiveHint: false,
|
|
4513
|
+
idempotentHint: false,
|
|
4514
|
+
// Each call creates a new collection
|
|
4515
|
+
openWorldHint: true
|
|
4390
4516
|
}
|
|
4391
4517
|
},
|
|
4392
4518
|
handleCreateCollection
|
|
4393
4519
|
);
|
|
4394
4520
|
}
|
|
4395
4521
|
|
|
4522
|
+
// src/tools/create-market.ts
|
|
4523
|
+
import { z as z8 } from "zod";
|
|
4524
|
+
|
|
4525
|
+
// src/shopify/markets.ts
|
|
4526
|
+
var LIST_MARKETS_QUERY = `
|
|
4527
|
+
query Markets($first: Int!, $after: String) {
|
|
4528
|
+
markets(first: $first, after: $after) {
|
|
4529
|
+
nodes {
|
|
4530
|
+
id
|
|
4531
|
+
name
|
|
4532
|
+
handle
|
|
4533
|
+
enabled
|
|
4534
|
+
status
|
|
4535
|
+
type
|
|
4536
|
+
currencySettings {
|
|
4537
|
+
baseCurrency { currencyCode }
|
|
4538
|
+
localCurrencies
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
pageInfo {
|
|
4542
|
+
hasNextPage
|
|
4543
|
+
endCursor
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
`;
|
|
4548
|
+
var GET_MARKET_QUERY = `
|
|
4549
|
+
query Market($id: ID!) {
|
|
4550
|
+
market(id: $id) {
|
|
4551
|
+
id
|
|
4552
|
+
name
|
|
4553
|
+
handle
|
|
4554
|
+
enabled
|
|
4555
|
+
status
|
|
4556
|
+
type
|
|
4557
|
+
currencySettings {
|
|
4558
|
+
baseCurrency { currencyCode currencyName }
|
|
4559
|
+
localCurrencies
|
|
4560
|
+
}
|
|
4561
|
+
webPresences(first: 10) {
|
|
4562
|
+
nodes {
|
|
4563
|
+
id
|
|
4564
|
+
defaultLocale { locale name }
|
|
4565
|
+
alternateLocales { locale name }
|
|
4566
|
+
subfolderSuffix
|
|
4567
|
+
domain { host }
|
|
4568
|
+
rootUrls { locale url }
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
`;
|
|
4574
|
+
var MARKET_CREATE_MUTATION = `
|
|
4575
|
+
mutation MarketCreate($input: MarketCreateInput!) {
|
|
4576
|
+
marketCreate(input: $input) {
|
|
4577
|
+
market { id name handle enabled }
|
|
4578
|
+
userErrors { field message }
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
`;
|
|
4582
|
+
var MARKET_UPDATE_MUTATION = `
|
|
4583
|
+
mutation MarketUpdate($id: ID!, $input: MarketUpdateInput!) {
|
|
4584
|
+
marketUpdate(id: $id, input: $input) {
|
|
4585
|
+
market { id name handle enabled }
|
|
4586
|
+
userErrors { field message }
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
`;
|
|
4590
|
+
var MARKET_DELETE_MUTATION = `
|
|
4591
|
+
mutation MarketDelete($id: ID!) {
|
|
4592
|
+
marketDelete(id: $id) {
|
|
4593
|
+
deletedId
|
|
4594
|
+
userErrors { field message }
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
`;
|
|
4598
|
+
async function listMarkets(first = 50, after) {
|
|
4599
|
+
const client = await getShopifyClient();
|
|
4600
|
+
log.debug(`Listing markets: first=${first}, after=${after}`);
|
|
4601
|
+
const response = await withRateLimit(
|
|
4602
|
+
() => client.graphql.request(LIST_MARKETS_QUERY, {
|
|
4603
|
+
variables: { first, after }
|
|
4604
|
+
}),
|
|
4605
|
+
"list-markets"
|
|
4606
|
+
);
|
|
4607
|
+
if (response.errors && response.errors.length > 0) {
|
|
4608
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
4609
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
4610
|
+
}
|
|
4611
|
+
const marketsData = response.data?.markets;
|
|
4612
|
+
if (!marketsData) {
|
|
4613
|
+
throw new Error("No response data from markets query");
|
|
4614
|
+
}
|
|
4615
|
+
const markets = marketsData.nodes.map((node) => ({
|
|
4616
|
+
id: node.id,
|
|
4617
|
+
name: node.name,
|
|
4618
|
+
handle: node.handle,
|
|
4619
|
+
enabled: node.enabled,
|
|
4620
|
+
status: node.status,
|
|
4621
|
+
type: node.type,
|
|
4622
|
+
currencySettings: node.currencySettings ? {
|
|
4623
|
+
baseCurrency: {
|
|
4624
|
+
currencyCode: node.currencySettings.baseCurrency.currencyCode
|
|
4625
|
+
},
|
|
4626
|
+
localCurrencies: node.currencySettings.localCurrencies
|
|
4627
|
+
} : void 0
|
|
4628
|
+
}));
|
|
4629
|
+
log.debug(`Listed ${markets.length} markets`);
|
|
4630
|
+
return {
|
|
4631
|
+
markets,
|
|
4632
|
+
pageInfo: {
|
|
4633
|
+
hasNextPage: marketsData.pageInfo.hasNextPage,
|
|
4634
|
+
endCursor: marketsData.pageInfo.endCursor
|
|
4635
|
+
}
|
|
4636
|
+
};
|
|
4637
|
+
}
|
|
4638
|
+
async function getMarket(marketId) {
|
|
4639
|
+
const client = await getShopifyClient();
|
|
4640
|
+
log.debug(`Fetching market: ${marketId}`);
|
|
4641
|
+
const response = await withRateLimit(
|
|
4642
|
+
() => client.graphql.request(GET_MARKET_QUERY, {
|
|
4643
|
+
variables: { id: marketId }
|
|
4644
|
+
}),
|
|
4645
|
+
"get-market"
|
|
4646
|
+
);
|
|
4647
|
+
if (response.errors && response.errors.length > 0) {
|
|
4648
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
4649
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
4650
|
+
}
|
|
4651
|
+
const market = response.data?.market;
|
|
4652
|
+
if (!market) {
|
|
4653
|
+
log.debug(`Market not found: ${marketId}`);
|
|
4654
|
+
return null;
|
|
4655
|
+
}
|
|
4656
|
+
return {
|
|
4657
|
+
id: market.id,
|
|
4658
|
+
name: market.name,
|
|
4659
|
+
handle: market.handle,
|
|
4660
|
+
enabled: market.enabled,
|
|
4661
|
+
status: market.status,
|
|
4662
|
+
type: market.type,
|
|
4663
|
+
currencySettings: market.currencySettings ? {
|
|
4664
|
+
baseCurrency: {
|
|
4665
|
+
currencyCode: market.currencySettings.baseCurrency.currencyCode,
|
|
4666
|
+
currencyName: market.currencySettings.baseCurrency.currencyName
|
|
4667
|
+
},
|
|
4668
|
+
localCurrencies: market.currencySettings.localCurrencies
|
|
4669
|
+
} : void 0,
|
|
4670
|
+
webPresences: market.webPresences?.nodes.map((wp) => ({
|
|
4671
|
+
id: wp.id,
|
|
4672
|
+
defaultLocale: {
|
|
4673
|
+
locale: wp.defaultLocale.locale,
|
|
4674
|
+
name: wp.defaultLocale.name
|
|
4675
|
+
},
|
|
4676
|
+
alternateLocales: wp.alternateLocales?.map((al) => ({
|
|
4677
|
+
locale: al.locale,
|
|
4678
|
+
name: al.name
|
|
4679
|
+
})),
|
|
4680
|
+
subfolderSuffix: wp.subfolderSuffix ?? void 0,
|
|
4681
|
+
domain: wp.domain ? { host: wp.domain.host } : void 0,
|
|
4682
|
+
rootUrls: wp.rootUrls?.map((ru) => ({
|
|
4683
|
+
locale: ru.locale,
|
|
4684
|
+
url: ru.url
|
|
4685
|
+
}))
|
|
4686
|
+
}))
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
async function createMarket(input) {
|
|
4690
|
+
const client = await getShopifyClient();
|
|
4691
|
+
log.debug(`Creating market: ${input.name}`);
|
|
4692
|
+
const graphqlInput = {
|
|
4693
|
+
name: input.name
|
|
4694
|
+
};
|
|
4695
|
+
if (input.handle !== void 0) {
|
|
4696
|
+
graphqlInput.handle = input.handle;
|
|
4697
|
+
}
|
|
4698
|
+
if (input.regions !== void 0) {
|
|
4699
|
+
graphqlInput.regions = input.regions;
|
|
4700
|
+
}
|
|
4701
|
+
if (input.enabled !== void 0) {
|
|
4702
|
+
graphqlInput.enabled = input.enabled;
|
|
4703
|
+
}
|
|
4704
|
+
const response = await withRateLimit(
|
|
4705
|
+
() => client.graphql.request(MARKET_CREATE_MUTATION, {
|
|
4706
|
+
variables: { input: graphqlInput }
|
|
4707
|
+
}),
|
|
4708
|
+
"create-market"
|
|
4709
|
+
);
|
|
4710
|
+
if (response.errors && response.errors.length > 0) {
|
|
4711
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
4712
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
4713
|
+
}
|
|
4714
|
+
const result = response.data?.marketCreate;
|
|
4715
|
+
if (!result) {
|
|
4716
|
+
throw new Error("No response data from marketCreate mutation");
|
|
4717
|
+
}
|
|
4718
|
+
if (result.userErrors && result.userErrors.length > 0) {
|
|
4719
|
+
throw new ShopifyUserErrorException(result.userErrors);
|
|
4720
|
+
}
|
|
4721
|
+
if (!result.market) {
|
|
4722
|
+
throw new Error("Market creation succeeded but no market was returned");
|
|
4723
|
+
}
|
|
4724
|
+
log.debug(`Created market: ${result.market.id}`);
|
|
4725
|
+
return result.market;
|
|
4726
|
+
}
|
|
4727
|
+
async function updateMarket(marketId, input) {
|
|
4728
|
+
const client = await getShopifyClient();
|
|
4729
|
+
log.debug(`Updating market: ${marketId}`);
|
|
4730
|
+
const graphqlInput = {};
|
|
4731
|
+
if (input.name !== void 0) {
|
|
4732
|
+
graphqlInput.name = input.name;
|
|
4733
|
+
}
|
|
4734
|
+
if (input.handle !== void 0) {
|
|
4735
|
+
graphqlInput.handle = input.handle;
|
|
4736
|
+
}
|
|
4737
|
+
if (input.enabled !== void 0) {
|
|
4738
|
+
graphqlInput.enabled = input.enabled;
|
|
4739
|
+
}
|
|
4740
|
+
const response = await withRateLimit(
|
|
4741
|
+
() => client.graphql.request(MARKET_UPDATE_MUTATION, {
|
|
4742
|
+
variables: { id: marketId, input: graphqlInput }
|
|
4743
|
+
}),
|
|
4744
|
+
"update-market"
|
|
4745
|
+
);
|
|
4746
|
+
if (response.errors && response.errors.length > 0) {
|
|
4747
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
4748
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
4749
|
+
}
|
|
4750
|
+
const result = response.data?.marketUpdate;
|
|
4751
|
+
if (!result) {
|
|
4752
|
+
throw new Error("No response data from marketUpdate mutation");
|
|
4753
|
+
}
|
|
4754
|
+
if (result.userErrors && result.userErrors.length > 0) {
|
|
4755
|
+
throw new ShopifyUserErrorException(result.userErrors);
|
|
4756
|
+
}
|
|
4757
|
+
if (!result.market) {
|
|
4758
|
+
throw new Error("Market update succeeded but no market was returned");
|
|
4759
|
+
}
|
|
4760
|
+
log.debug(`Updated market: ${result.market.id}`);
|
|
4761
|
+
return result.market;
|
|
4762
|
+
}
|
|
4763
|
+
async function deleteMarket(marketId) {
|
|
4764
|
+
const client = await getShopifyClient();
|
|
4765
|
+
log.debug(`Deleting market: ${marketId}`);
|
|
4766
|
+
const response = await withRateLimit(
|
|
4767
|
+
() => client.graphql.request(MARKET_DELETE_MUTATION, {
|
|
4768
|
+
variables: { id: marketId }
|
|
4769
|
+
}),
|
|
4770
|
+
"delete-market"
|
|
4771
|
+
);
|
|
4772
|
+
if (response.errors && response.errors.length > 0) {
|
|
4773
|
+
const errorMessage = response.errors.map((e) => e.message).join(", ");
|
|
4774
|
+
throw new Error(`GraphQL error: ${errorMessage}`);
|
|
4775
|
+
}
|
|
4776
|
+
const result = response.data?.marketDelete;
|
|
4777
|
+
if (!result) {
|
|
4778
|
+
throw new Error("No response data from marketDelete mutation");
|
|
4779
|
+
}
|
|
4780
|
+
if (result.userErrors && result.userErrors.length > 0) {
|
|
4781
|
+
throw new ShopifyUserErrorException(result.userErrors);
|
|
4782
|
+
}
|
|
4783
|
+
if (!result.deletedId) {
|
|
4784
|
+
throw new Error("Market deletion succeeded but no deletedId was returned");
|
|
4785
|
+
}
|
|
4786
|
+
log.debug(`Deleted market: ${result.deletedId}`);
|
|
4787
|
+
return result.deletedId;
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
// src/tools/create-market.ts
|
|
4791
|
+
var inputSchema6 = z8.object({
|
|
4792
|
+
name: z8.string().min(1).describe('Market name (required). Not shown to customers. Example: "Canada", "Europe"'),
|
|
4793
|
+
handle: z8.string().optional().describe(
|
|
4794
|
+
'URL handle/slug for the market. Auto-generated from name if not provided. Example: "ca" for Canada, "eu" for Europe'
|
|
4795
|
+
),
|
|
4796
|
+
countryCodes: z8.array(z8.string().length(2)).optional().describe(
|
|
4797
|
+
'Array of ISO 3166-1 alpha-2 country codes to include in this market. Example: ["CA"] for Canada, ["FR", "DE", "IT"] for multiple European countries'
|
|
4798
|
+
),
|
|
4799
|
+
enabled: z8.boolean().optional().default(true).describe("Whether the market should be enabled. Default: true")
|
|
4800
|
+
});
|
|
4801
|
+
var outputSchema6 = z8.object({
|
|
4802
|
+
id: z8.string().describe("Created market GID"),
|
|
4803
|
+
name: z8.string().describe("Market name"),
|
|
4804
|
+
handle: z8.string().describe("URL handle"),
|
|
4805
|
+
enabled: z8.boolean().describe("Whether the market is enabled")
|
|
4806
|
+
});
|
|
4807
|
+
var handleCreateMarket = async (context, params) => {
|
|
4808
|
+
log.debug(`Creating market "${params.name}" on shop: ${context.shopDomain}`);
|
|
4809
|
+
const input = {
|
|
4810
|
+
name: params.name
|
|
4811
|
+
};
|
|
4812
|
+
if (params.handle !== void 0) {
|
|
4813
|
+
input.handle = params.handle;
|
|
4814
|
+
}
|
|
4815
|
+
if (params.countryCodes !== void 0 && params.countryCodes.length > 0) {
|
|
4816
|
+
input.regions = { countryCodes: params.countryCodes };
|
|
4817
|
+
}
|
|
4818
|
+
if (params.enabled !== void 0) {
|
|
4819
|
+
input.enabled = params.enabled;
|
|
4820
|
+
}
|
|
4821
|
+
const market = await createMarket(input);
|
|
4822
|
+
log.debug(`Created market: ${market.id}`);
|
|
4823
|
+
return market;
|
|
4824
|
+
};
|
|
4825
|
+
function registerCreateMarketTool() {
|
|
4826
|
+
registerContextAwareTool(
|
|
4827
|
+
{
|
|
4828
|
+
name: "create-market",
|
|
4829
|
+
title: "Create Market",
|
|
4830
|
+
description: "Create a new market for regional targeting. Markets group one or more regions (countries) for localized shopping experiences. Provide a name (required) and optionally: handle, country codes, and enabled status. **Typical workflow:** create-market \u2192 create-web-presence (configure SEO URLs) \u2192 enable-shop-locale (add languages). **Prerequisites:** None. Store must have Markets feature enabled on their plan. **Follow-ups:** get-market, create-web-presence, update-market.",
|
|
4831
|
+
inputSchema: inputSchema6,
|
|
4832
|
+
outputSchema: outputSchema6,
|
|
4833
|
+
// AI Agent Optimization
|
|
4834
|
+
category: "market",
|
|
4835
|
+
relationships: {
|
|
4836
|
+
relatedTools: ["list-markets", "get-market"],
|
|
4837
|
+
followUps: ["get-market", "update-market"]
|
|
4838
|
+
},
|
|
4839
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4840
|
+
annotations: {
|
|
4841
|
+
readOnlyHint: false,
|
|
4842
|
+
destructiveHint: false,
|
|
4843
|
+
idempotentHint: false,
|
|
4844
|
+
// Creating markets is not idempotent
|
|
4845
|
+
openWorldHint: true
|
|
4846
|
+
}
|
|
4847
|
+
},
|
|
4848
|
+
handleCreateMarket
|
|
4849
|
+
);
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4396
4852
|
// src/tools/create-page.ts
|
|
4397
|
-
import { z as
|
|
4853
|
+
import { z as z9 } from "zod";
|
|
4398
4854
|
|
|
4399
4855
|
// src/shopify/pages.ts
|
|
4400
4856
|
var PAGE_QUERY = `
|
|
@@ -4676,28 +5132,28 @@ async function deletePage(pageId) {
|
|
|
4676
5132
|
}
|
|
4677
5133
|
|
|
4678
5134
|
// src/tools/create-page.ts
|
|
4679
|
-
var
|
|
4680
|
-
title:
|
|
5135
|
+
var inputSchema7 = z9.object({
|
|
5136
|
+
title: z9.string().min(1).describe(
|
|
4681
5137
|
"The title of the page (required). This will be displayed as the page heading. Example: 'About Us', 'Contact', 'Privacy Policy'."
|
|
4682
5138
|
),
|
|
4683
|
-
body:
|
|
5139
|
+
body: z9.string().optional().describe(
|
|
4684
5140
|
"The HTML body content of the page. Supports HTML markup for formatting. Example: '<h1>About Our Company</h1><p>We are a great company.</p>'"
|
|
4685
5141
|
),
|
|
4686
|
-
handle:
|
|
5142
|
+
handle: z9.string().optional().describe(
|
|
4687
5143
|
"The URL handle/slug for the page. If not provided, it will be auto-generated from the title. Example: 'about-us' would create URL /pages/about-us."
|
|
4688
5144
|
),
|
|
4689
|
-
isPublished:
|
|
5145
|
+
isPublished: z9.boolean().optional().describe(
|
|
4690
5146
|
"Whether the page should be visible on the storefront. Defaults to false (unpublished). Set to true to make the page immediately visible."
|
|
4691
5147
|
),
|
|
4692
|
-
templateSuffix:
|
|
5148
|
+
templateSuffix: z9.string().optional().describe(
|
|
4693
5149
|
"The suffix of the Liquid template used to render the page. For example, 'contact' would use the template 'page.contact.liquid'."
|
|
4694
5150
|
)
|
|
4695
5151
|
});
|
|
4696
|
-
var
|
|
4697
|
-
id:
|
|
4698
|
-
title:
|
|
4699
|
-
handle:
|
|
4700
|
-
isPublished:
|
|
5152
|
+
var outputSchema7 = z9.object({
|
|
5153
|
+
id: z9.string().describe('Created page GID (e.g., "gid://shopify/Page/123")'),
|
|
5154
|
+
title: z9.string().describe("Page title"),
|
|
5155
|
+
handle: z9.string().describe("URL handle/slug"),
|
|
5156
|
+
isPublished: z9.boolean().describe("Whether page is visible on storefront")
|
|
4701
5157
|
});
|
|
4702
5158
|
var handleCreatePage = async (context, params) => {
|
|
4703
5159
|
log.debug(`Creating page on shop: ${context.shopDomain}`);
|
|
@@ -4734,12 +5190,20 @@ function registerCreatePageTool() {
|
|
|
4734
5190
|
name: "create-page",
|
|
4735
5191
|
title: "Create Page",
|
|
4736
5192
|
description: "Create a new online store page. Pages are static content like 'About Us', 'Contact', 'FAQ', or policy pages. Provide a title (required) and optional body content (HTML supported), handle (URL slug), and publish status. New pages are unpublished by default. Useful for: creating informational pages, legal pages, landing pages. **Typical workflow:** create-page \u2192 update-page (to publish). **Prerequisites:** None. **Follow-ups:** get-page, update-page. Returns created page ID, title, handle, and publish status.",
|
|
4737
|
-
inputSchema:
|
|
4738
|
-
outputSchema:
|
|
5193
|
+
inputSchema: inputSchema7,
|
|
5194
|
+
outputSchema: outputSchema7,
|
|
4739
5195
|
category: "content",
|
|
4740
5196
|
relationships: {
|
|
4741
5197
|
relatedTools: ["list-pages", "get-page", "update-page"],
|
|
4742
5198
|
followUps: ["get-page", "update-page"]
|
|
5199
|
+
},
|
|
5200
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5201
|
+
annotations: {
|
|
5202
|
+
readOnlyHint: false,
|
|
5203
|
+
destructiveHint: false,
|
|
5204
|
+
idempotentHint: false,
|
|
5205
|
+
// create-* tools are NOT idempotent
|
|
5206
|
+
openWorldHint: true
|
|
4743
5207
|
}
|
|
4744
5208
|
},
|
|
4745
5209
|
handleCreatePage
|
|
@@ -4747,78 +5211,78 @@ function registerCreatePageTool() {
|
|
|
4747
5211
|
}
|
|
4748
5212
|
|
|
4749
5213
|
// src/tools/create-product.ts
|
|
4750
|
-
import { z as
|
|
4751
|
-
var
|
|
5214
|
+
import { z as z10 } from "zod";
|
|
5215
|
+
var inputSchema8 = z10.object({
|
|
4752
5216
|
// Required field
|
|
4753
|
-
title:
|
|
5217
|
+
title: z10.string().min(1, "title is required").describe('Product title (required). Example: "Premium Cotton T-Shirt"'),
|
|
4754
5218
|
// Optional core fields
|
|
4755
|
-
description:
|
|
4756
|
-
vendor:
|
|
4757
|
-
productType:
|
|
4758
|
-
tags:
|
|
4759
|
-
status:
|
|
5219
|
+
description: z10.string().optional().describe('Product description (HTML supported). Example: "<p>Soft cotton t-shirt.</p>"'),
|
|
5220
|
+
vendor: z10.string().optional().describe('Product vendor/brand name. Example: "Acme Corp"'),
|
|
5221
|
+
productType: z10.string().optional().describe('Product type for categorization. Example: "T-Shirts"'),
|
|
5222
|
+
tags: z10.array(z10.string()).optional().describe('Tags for search and filtering. Example: ["summer", "cotton", "casual"]'),
|
|
5223
|
+
status: z10.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().default("DRAFT").describe(
|
|
4760
5224
|
"Product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden). Default: DRAFT"
|
|
4761
5225
|
),
|
|
4762
5226
|
// SEO metadata
|
|
4763
|
-
seo:
|
|
4764
|
-
title:
|
|
4765
|
-
description:
|
|
5227
|
+
seo: z10.object({
|
|
5228
|
+
title: z10.string().optional().describe("SEO title for search engine results"),
|
|
5229
|
+
description: z10.string().optional().describe("SEO meta description for search engines")
|
|
4766
5230
|
}).optional().describe(
|
|
4767
5231
|
"SEO metadata for search engine optimization. If not provided, Shopify uses product title/description."
|
|
4768
5232
|
),
|
|
4769
5233
|
// Variants
|
|
4770
|
-
variants:
|
|
4771
|
-
|
|
4772
|
-
price:
|
|
4773
|
-
compareAtPrice:
|
|
4774
|
-
sku:
|
|
4775
|
-
barcode:
|
|
4776
|
-
inventoryQuantity:
|
|
4777
|
-
options:
|
|
5234
|
+
variants: z10.array(
|
|
5235
|
+
z10.object({
|
|
5236
|
+
price: z10.string().describe('Variant price as decimal string. Example: "29.99"'),
|
|
5237
|
+
compareAtPrice: z10.string().optional().describe('Original price for sale display. Example: "39.99"'),
|
|
5238
|
+
sku: z10.string().optional().describe('Stock keeping unit. Example: "SHIRT-001-RED-M"'),
|
|
5239
|
+
barcode: z10.string().optional().describe("Barcode (UPC, EAN, etc.)"),
|
|
5240
|
+
inventoryQuantity: z10.number().int().min(0).optional().describe("Initial inventory quantity (note: may require separate inventory API)"),
|
|
5241
|
+
options: z10.array(z10.string()).optional().describe('Variant options. Example: ["Red", "Medium"]')
|
|
4778
5242
|
})
|
|
4779
5243
|
).optional().describe("Product variants. If not provided, a default variant is created."),
|
|
4780
5244
|
// Images
|
|
4781
|
-
images:
|
|
4782
|
-
|
|
4783
|
-
url:
|
|
4784
|
-
altText:
|
|
5245
|
+
images: z10.array(
|
|
5246
|
+
z10.object({
|
|
5247
|
+
url: z10.string().url("Invalid image URL format").describe("Publicly accessible image URL"),
|
|
5248
|
+
altText: z10.string().optional().describe("Alt text for accessibility")
|
|
4785
5249
|
})
|
|
4786
5250
|
).optional().describe("Product images. Shopify fetches images from URLs.")
|
|
4787
5251
|
});
|
|
4788
|
-
var
|
|
4789
|
-
id:
|
|
4790
|
-
title:
|
|
4791
|
-
handle:
|
|
4792
|
-
description:
|
|
4793
|
-
vendor:
|
|
4794
|
-
productType:
|
|
4795
|
-
status:
|
|
4796
|
-
tags:
|
|
4797
|
-
seo:
|
|
4798
|
-
title:
|
|
4799
|
-
description:
|
|
5252
|
+
var outputSchema8 = z10.object({
|
|
5253
|
+
id: z10.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
5254
|
+
title: z10.string().describe("Product title"),
|
|
5255
|
+
handle: z10.string().describe("URL handle/slug"),
|
|
5256
|
+
description: z10.string().nullable().describe("Product description (HTML)"),
|
|
5257
|
+
vendor: z10.string().describe("Vendor/brand name"),
|
|
5258
|
+
productType: z10.string().describe("Product type"),
|
|
5259
|
+
status: z10.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
5260
|
+
tags: z10.array(z10.string()).describe("Product tags"),
|
|
5261
|
+
seo: z10.object({
|
|
5262
|
+
title: z10.string().nullable().describe("SEO title for search engines"),
|
|
5263
|
+
description: z10.string().nullable().describe("SEO description/meta description")
|
|
4800
5264
|
}).describe("SEO metadata for search engine optimization"),
|
|
4801
|
-
createdAt:
|
|
4802
|
-
updatedAt:
|
|
4803
|
-
variants:
|
|
4804
|
-
|
|
4805
|
-
id:
|
|
4806
|
-
title:
|
|
4807
|
-
price:
|
|
4808
|
-
compareAtPrice:
|
|
4809
|
-
sku:
|
|
4810
|
-
barcode:
|
|
4811
|
-
inventoryQuantity:
|
|
5265
|
+
createdAt: z10.string().describe("Creation timestamp (ISO 8601)"),
|
|
5266
|
+
updatedAt: z10.string().describe("Last update timestamp (ISO 8601)"),
|
|
5267
|
+
variants: z10.array(
|
|
5268
|
+
z10.object({
|
|
5269
|
+
id: z10.string().describe("Variant GID"),
|
|
5270
|
+
title: z10.string().describe("Variant title"),
|
|
5271
|
+
price: z10.string().describe("Price"),
|
|
5272
|
+
compareAtPrice: z10.string().nullable().describe("Compare at price"),
|
|
5273
|
+
sku: z10.string().nullable().describe("SKU"),
|
|
5274
|
+
barcode: z10.string().nullable().describe("Barcode"),
|
|
5275
|
+
inventoryQuantity: z10.number().nullable().describe("Inventory quantity")
|
|
4812
5276
|
})
|
|
4813
5277
|
).describe("Product variants"),
|
|
4814
|
-
images:
|
|
4815
|
-
|
|
4816
|
-
id:
|
|
4817
|
-
url:
|
|
4818
|
-
altText:
|
|
5278
|
+
images: z10.array(
|
|
5279
|
+
z10.object({
|
|
5280
|
+
id: z10.string().describe("Image GID"),
|
|
5281
|
+
url: z10.string().describe("Image URL"),
|
|
5282
|
+
altText: z10.string().nullable().describe("Alt text")
|
|
4819
5283
|
})
|
|
4820
5284
|
).describe("Product images"),
|
|
4821
|
-
totalInventory:
|
|
5285
|
+
totalInventory: z10.number().describe("Total inventory across variants")
|
|
4822
5286
|
});
|
|
4823
5287
|
var handleCreateProduct = async (context, params) => {
|
|
4824
5288
|
log.debug(`Creating product on shop: ${context.shopDomain}`);
|
|
@@ -4853,14 +5317,22 @@ function registerCreateProductTool() {
|
|
|
4853
5317
|
name: "create-product",
|
|
4854
5318
|
title: "Create Product",
|
|
4855
5319
|
description: "Create a new product in the Shopify store. Supports setting title, description, vendor, type, tags, status, and SEO metadata. Optionally include variants with pricing/SKU and images with URLs. SEO (seo.title, seo.description) is optional - defaults to product title/description if not set. Returns the created product with ID, handle, SEO, variants, and images. Status defaults to DRAFT if not specified. **Typical workflow:** create-product \u2192 add-product-image \u2192 set-product-metafields \u2192 add-products-to-collection. **Prerequisites:** None. **Follow-ups:** add-product-image, set-product-metafields, add-products-to-collection.",
|
|
4856
|
-
inputSchema:
|
|
4857
|
-
outputSchema:
|
|
5320
|
+
inputSchema: inputSchema8,
|
|
5321
|
+
outputSchema: outputSchema8,
|
|
4858
5322
|
// AI Agent Optimization (Story 5.5)
|
|
4859
5323
|
category: "product",
|
|
4860
5324
|
relationships: {
|
|
4861
5325
|
relatedTools: ["get-product", "update-product", "list-products"],
|
|
4862
5326
|
followUps: ["add-product-image", "set-product-metafields", "add-products-to-collection"],
|
|
4863
5327
|
alternatives: ["update-product"]
|
|
5328
|
+
},
|
|
5329
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5330
|
+
annotations: {
|
|
5331
|
+
readOnlyHint: false,
|
|
5332
|
+
destructiveHint: false,
|
|
5333
|
+
idempotentHint: false,
|
|
5334
|
+
// Each call creates a new product
|
|
5335
|
+
openWorldHint: true
|
|
4864
5336
|
}
|
|
4865
5337
|
},
|
|
4866
5338
|
handleCreateProduct
|
|
@@ -4868,7 +5340,7 @@ function registerCreateProductTool() {
|
|
|
4868
5340
|
}
|
|
4869
5341
|
|
|
4870
5342
|
// src/tools/create-redirect.ts
|
|
4871
|
-
import { z as
|
|
5343
|
+
import { z as z11 } from "zod";
|
|
4872
5344
|
|
|
4873
5345
|
// src/shopify/redirects.ts
|
|
4874
5346
|
var URL_REDIRECT_CREATE_MUTATION = `
|
|
@@ -5016,20 +5488,20 @@ async function deleteRedirect(redirectId) {
|
|
|
5016
5488
|
}
|
|
5017
5489
|
|
|
5018
5490
|
// src/tools/create-redirect.ts
|
|
5019
|
-
var
|
|
5020
|
-
path:
|
|
5491
|
+
var inputSchema9 = z11.object({
|
|
5492
|
+
path: z11.string().min(1).refine((val) => val.startsWith("/"), {
|
|
5021
5493
|
message: "Path must start with '/' (e.g., '/old-product-url')"
|
|
5022
5494
|
}).describe(
|
|
5023
5495
|
"The source path to redirect FROM. Must start with '/'. Example: '/old-product-url' or '/collections/old-collection'."
|
|
5024
5496
|
),
|
|
5025
|
-
target:
|
|
5497
|
+
target: z11.string().min(1).describe(
|
|
5026
5498
|
"The destination URL to redirect TO. Can be relative ('/new-product-url') or absolute ('https://example.com/page')."
|
|
5027
5499
|
)
|
|
5028
5500
|
});
|
|
5029
|
-
var
|
|
5030
|
-
id:
|
|
5031
|
-
path:
|
|
5032
|
-
target:
|
|
5501
|
+
var outputSchema9 = z11.object({
|
|
5502
|
+
id: z11.string().describe('Created redirect GID (e.g., "gid://shopify/UrlRedirect/123")'),
|
|
5503
|
+
path: z11.string().describe("Source path that will redirect"),
|
|
5504
|
+
target: z11.string().describe("Target URL visitors will be redirected to")
|
|
5033
5505
|
});
|
|
5034
5506
|
var handleCreateRedirect = async (context, params) => {
|
|
5035
5507
|
log.debug(`Creating redirect on shop: ${context.shopDomain}`);
|
|
@@ -5063,13 +5535,21 @@ function registerCreateRedirectTool() {
|
|
|
5063
5535
|
name: "create-redirect",
|
|
5064
5536
|
title: "Create URL Redirect",
|
|
5065
5537
|
description: "Create a URL redirect to preserve SEO value when URLs change. Redirects automatically forward visitors and search engines from an old path to a new target URL. Path must start with '/'. Useful for: product URL changes, collection restructuring, page migrations, fixing broken links. **Prerequisites:** None. **Follow-ups:** list-redirects to verify creation.",
|
|
5066
|
-
inputSchema:
|
|
5067
|
-
outputSchema:
|
|
5538
|
+
inputSchema: inputSchema9,
|
|
5539
|
+
outputSchema: outputSchema9,
|
|
5068
5540
|
// AI Agent Optimization (Story 5.5)
|
|
5069
5541
|
category: "seo",
|
|
5070
5542
|
relationships: {
|
|
5071
5543
|
relatedTools: ["list-redirects", "delete-redirect"],
|
|
5072
5544
|
followUps: ["list-redirects"]
|
|
5545
|
+
},
|
|
5546
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5547
|
+
annotations: {
|
|
5548
|
+
readOnlyHint: false,
|
|
5549
|
+
destructiveHint: false,
|
|
5550
|
+
idempotentHint: false,
|
|
5551
|
+
// Each call creates a new redirect (duplicate path fails)
|
|
5552
|
+
openWorldHint: true
|
|
5073
5553
|
}
|
|
5074
5554
|
},
|
|
5075
5555
|
handleCreateRedirect
|
|
@@ -5077,15 +5557,15 @@ function registerCreateRedirectTool() {
|
|
|
5077
5557
|
}
|
|
5078
5558
|
|
|
5079
5559
|
// src/tools/delete-article.ts
|
|
5080
|
-
import { z as
|
|
5081
|
-
var
|
|
5082
|
-
id:
|
|
5560
|
+
import { z as z12 } from "zod";
|
|
5561
|
+
var inputSchema10 = z12.object({
|
|
5562
|
+
id: articleIdSchema.describe(
|
|
5083
5563
|
'The article ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Article/12345". Use list-articles to find article IDs. This action cannot be undone - consider using update-article to unpublish instead.'
|
|
5084
5564
|
)
|
|
5085
5565
|
});
|
|
5086
|
-
var
|
|
5087
|
-
success:
|
|
5088
|
-
deletedArticleId:
|
|
5566
|
+
var outputSchema10 = z12.object({
|
|
5567
|
+
success: z12.boolean().describe("Whether the deletion was successful"),
|
|
5568
|
+
deletedArticleId: z12.string().describe("The ID of the deleted article")
|
|
5089
5569
|
});
|
|
5090
5570
|
var handleDeleteArticle = async (context, params) => {
|
|
5091
5571
|
log.debug(`Deleting article on shop: ${context.shopDomain}`);
|
|
@@ -5119,13 +5599,21 @@ function registerDeleteArticleTool() {
|
|
|
5119
5599
|
name: "delete-article",
|
|
5120
5600
|
title: "Delete Article",
|
|
5121
5601
|
description: "Permanently delete an article by ID. This action cannot be undone. The article will no longer be accessible on the storefront. Use list-articles to find the article ID first. Consider unpublishing instead of deleting if you may need the content later. **Typical workflow:** list-articles \u2192 delete-article. **Prerequisites:** Article ID. **Follow-ups:** None. Returns success confirmation and deleted article ID.",
|
|
5122
|
-
inputSchema:
|
|
5123
|
-
outputSchema:
|
|
5602
|
+
inputSchema: inputSchema10,
|
|
5603
|
+
outputSchema: outputSchema10,
|
|
5124
5604
|
category: "content",
|
|
5125
5605
|
relationships: {
|
|
5126
5606
|
relatedTools: ["list-articles", "update-article"],
|
|
5127
5607
|
prerequisites: ["list-articles"],
|
|
5128
5608
|
alternatives: ["update-article"]
|
|
5609
|
+
},
|
|
5610
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5611
|
+
annotations: {
|
|
5612
|
+
readOnlyHint: false,
|
|
5613
|
+
destructiveHint: true,
|
|
5614
|
+
idempotentHint: false,
|
|
5615
|
+
// delete-* tools are NOT idempotent
|
|
5616
|
+
openWorldHint: true
|
|
5129
5617
|
}
|
|
5130
5618
|
},
|
|
5131
5619
|
handleDeleteArticle
|
|
@@ -5133,15 +5621,15 @@ function registerDeleteArticleTool() {
|
|
|
5133
5621
|
}
|
|
5134
5622
|
|
|
5135
5623
|
// src/tools/delete-blog.ts
|
|
5136
|
-
import { z as
|
|
5137
|
-
var
|
|
5138
|
-
id:
|
|
5624
|
+
import { z as z13 } from "zod";
|
|
5625
|
+
var inputSchema11 = z13.object({
|
|
5626
|
+
id: blogIdSchema.describe(
|
|
5139
5627
|
'The blog ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs. WARNING: Deleting a blog also deletes ALL articles within it.'
|
|
5140
5628
|
)
|
|
5141
5629
|
});
|
|
5142
|
-
var
|
|
5143
|
-
success:
|
|
5144
|
-
deletedBlogId:
|
|
5630
|
+
var outputSchema11 = z13.object({
|
|
5631
|
+
success: z13.boolean().describe("Whether the deletion was successful"),
|
|
5632
|
+
deletedBlogId: z13.string().describe("The ID of the deleted blog")
|
|
5145
5633
|
});
|
|
5146
5634
|
var handleDeleteBlog = async (context, params) => {
|
|
5147
5635
|
log.debug(`Deleting blog on shop: ${context.shopDomain}`);
|
|
@@ -5175,12 +5663,20 @@ function registerDeleteBlogTool() {
|
|
|
5175
5663
|
name: "delete-blog",
|
|
5176
5664
|
title: "Delete Blog",
|
|
5177
5665
|
description: "Permanently delete a blog by ID. This will also delete ALL articles within the blog. This action cannot be undone. Use list-blogs to find the blog ID first. Consider archiving articles before deletion if content may be needed later. **Typical workflow:** list-blogs \u2192 list-articles \u2192 delete-blog. **Prerequisites:** Blog ID. **Follow-ups:** None. Returns success confirmation and deleted blog ID.",
|
|
5178
|
-
inputSchema:
|
|
5179
|
-
outputSchema:
|
|
5666
|
+
inputSchema: inputSchema11,
|
|
5667
|
+
outputSchema: outputSchema11,
|
|
5180
5668
|
category: "content",
|
|
5181
5669
|
relationships: {
|
|
5182
5670
|
relatedTools: ["list-blogs", "list-articles"],
|
|
5183
5671
|
prerequisites: ["list-blogs"]
|
|
5672
|
+
},
|
|
5673
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5674
|
+
annotations: {
|
|
5675
|
+
readOnlyHint: false,
|
|
5676
|
+
destructiveHint: true,
|
|
5677
|
+
idempotentHint: false,
|
|
5678
|
+
// delete-* tools are NOT idempotent
|
|
5679
|
+
openWorldHint: true
|
|
5184
5680
|
}
|
|
5185
5681
|
},
|
|
5186
5682
|
handleDeleteBlog
|
|
@@ -5188,14 +5684,14 @@ function registerDeleteBlogTool() {
|
|
|
5188
5684
|
}
|
|
5189
5685
|
|
|
5190
5686
|
// src/tools/delete-collection.ts
|
|
5191
|
-
import { z as
|
|
5192
|
-
var
|
|
5193
|
-
collectionId:
|
|
5687
|
+
import { z as z14 } from "zod";
|
|
5688
|
+
var inputSchema12 = z14.object({
|
|
5689
|
+
collectionId: collectionIdSchema.describe(
|
|
5194
5690
|
'Collection ID to delete (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
|
|
5195
5691
|
)
|
|
5196
5692
|
});
|
|
5197
|
-
var
|
|
5198
|
-
deletedCollectionId:
|
|
5693
|
+
var outputSchema12 = z14.object({
|
|
5694
|
+
deletedCollectionId: z14.string().describe("The GID of the deleted collection")
|
|
5199
5695
|
});
|
|
5200
5696
|
var handleDeleteCollection = async (context, params) => {
|
|
5201
5697
|
log.debug(`Deleting collection on shop: ${context.shopDomain}`);
|
|
@@ -5225,29 +5721,88 @@ function registerDeleteCollectionTool() {
|
|
|
5225
5721
|
name: "delete-collection",
|
|
5226
5722
|
title: "Delete Collection",
|
|
5227
5723
|
description: "Permanently delete a collection from the store. This removes the collection but does NOT delete the products within it - they remain in the catalog. **Warning:** This action cannot be undone. **Prerequisites:** get-collection to verify the correct collection before deleting.",
|
|
5228
|
-
inputSchema:
|
|
5229
|
-
outputSchema:
|
|
5724
|
+
inputSchema: inputSchema12,
|
|
5725
|
+
outputSchema: outputSchema12,
|
|
5230
5726
|
// AI Agent Optimization (Story 5.5)
|
|
5231
5727
|
category: "collection",
|
|
5232
5728
|
relationships: {
|
|
5233
5729
|
relatedTools: ["list-collections"],
|
|
5234
5730
|
prerequisites: ["get-collection"]
|
|
5731
|
+
},
|
|
5732
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5733
|
+
annotations: {
|
|
5734
|
+
readOnlyHint: false,
|
|
5735
|
+
destructiveHint: true,
|
|
5736
|
+
// Permanently deletes collection
|
|
5737
|
+
idempotentHint: false,
|
|
5738
|
+
// Second call fails (already deleted)
|
|
5739
|
+
openWorldHint: true
|
|
5235
5740
|
}
|
|
5236
5741
|
},
|
|
5237
5742
|
handleDeleteCollection
|
|
5238
5743
|
);
|
|
5239
5744
|
}
|
|
5240
5745
|
|
|
5746
|
+
// src/tools/delete-market.ts
|
|
5747
|
+
import { z as z15 } from "zod";
|
|
5748
|
+
var inputSchema13 = z15.object({
|
|
5749
|
+
id: marketIdSchema.describe(
|
|
5750
|
+
'Shopify Market GID to delete (e.g., "gid://shopify/Market/123"). Use list-markets or get-market to find market IDs. WARNING: This action cannot be undone.'
|
|
5751
|
+
)
|
|
5752
|
+
});
|
|
5753
|
+
var outputSchema13 = z15.object({
|
|
5754
|
+
deletedId: z15.string().describe("Deleted market GID"),
|
|
5755
|
+
success: z15.boolean().describe("Whether the deletion was successful"),
|
|
5756
|
+
message: z15.string().describe("Confirmation message")
|
|
5757
|
+
});
|
|
5758
|
+
var handleDeleteMarket = async (context, params) => {
|
|
5759
|
+
log.debug(`Deleting market ${params.id} on shop: ${context.shopDomain}`);
|
|
5760
|
+
const deletedId = await deleteMarket(params.id);
|
|
5761
|
+
log.debug(`Deleted market: ${deletedId}`);
|
|
5762
|
+
return {
|
|
5763
|
+
deletedId,
|
|
5764
|
+
success: true,
|
|
5765
|
+
message: `Market ${deletedId} has been permanently deleted. All associated web presences, currency settings, and region configurations have been removed.`
|
|
5766
|
+
};
|
|
5767
|
+
};
|
|
5768
|
+
function registerDeleteMarketTool() {
|
|
5769
|
+
registerContextAwareTool(
|
|
5770
|
+
{
|
|
5771
|
+
name: "delete-market",
|
|
5772
|
+
title: "Delete Market",
|
|
5773
|
+
description: "Permanently delete a market from the store. **WARNING:** This action cannot be undone. Deleting a market removes all its configurations including web presences, currency settings, and region assignments. Products assigned to this market will no longer be available in those regions. **Prerequisites:** get-market to verify the correct market before deleting. **Note:** The primary market cannot be deleted.",
|
|
5774
|
+
inputSchema: inputSchema13,
|
|
5775
|
+
outputSchema: outputSchema13,
|
|
5776
|
+
// AI Agent Optimization
|
|
5777
|
+
category: "market",
|
|
5778
|
+
relationships: {
|
|
5779
|
+
relatedTools: ["get-market", "list-markets"],
|
|
5780
|
+
followUps: ["list-markets"]
|
|
5781
|
+
},
|
|
5782
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5783
|
+
annotations: {
|
|
5784
|
+
readOnlyHint: false,
|
|
5785
|
+
destructiveHint: true,
|
|
5786
|
+
// This is a destructive operation
|
|
5787
|
+
idempotentHint: false,
|
|
5788
|
+
// Deleting twice will fail the second time
|
|
5789
|
+
openWorldHint: true
|
|
5790
|
+
}
|
|
5791
|
+
},
|
|
5792
|
+
handleDeleteMarket
|
|
5793
|
+
);
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5241
5796
|
// src/tools/delete-page.ts
|
|
5242
|
-
import { z as
|
|
5243
|
-
var
|
|
5244
|
-
id:
|
|
5797
|
+
import { z as z16 } from "zod";
|
|
5798
|
+
var inputSchema14 = z16.object({
|
|
5799
|
+
id: pageIdSchema.describe(
|
|
5245
5800
|
'The page ID to delete (required). Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs. WARNING: This action is permanent and cannot be undone.'
|
|
5246
5801
|
)
|
|
5247
5802
|
});
|
|
5248
|
-
var
|
|
5249
|
-
success:
|
|
5250
|
-
deletedPageId:
|
|
5803
|
+
var outputSchema14 = z16.object({
|
|
5804
|
+
success: z16.boolean().describe("Whether the deletion was successful"),
|
|
5805
|
+
deletedPageId: z16.string().describe("The ID of the deleted page")
|
|
5251
5806
|
});
|
|
5252
5807
|
var handleDeletePage = async (context, params) => {
|
|
5253
5808
|
log.debug(`Deleting page on shop: ${context.shopDomain}`);
|
|
@@ -5281,13 +5836,21 @@ function registerDeletePageTool() {
|
|
|
5281
5836
|
name: "delete-page",
|
|
5282
5837
|
title: "Delete Page",
|
|
5283
5838
|
description: "Permanently delete a page by ID. This action cannot be undone. The page will no longer be accessible on the storefront. Use list-pages to find the page ID first. Consider unpublishing instead of deleting if you may need the content later. **Typical workflow:** get-page \u2192 delete-page. **Prerequisites:** Page ID. **Follow-ups:** None. Returns success confirmation and deleted page ID.",
|
|
5284
|
-
inputSchema:
|
|
5285
|
-
outputSchema:
|
|
5839
|
+
inputSchema: inputSchema14,
|
|
5840
|
+
outputSchema: outputSchema14,
|
|
5286
5841
|
category: "content",
|
|
5287
5842
|
relationships: {
|
|
5288
5843
|
relatedTools: ["get-page", "list-pages"],
|
|
5289
5844
|
prerequisites: ["get-page"],
|
|
5290
5845
|
alternatives: ["update-page"]
|
|
5846
|
+
},
|
|
5847
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5848
|
+
annotations: {
|
|
5849
|
+
readOnlyHint: false,
|
|
5850
|
+
destructiveHint: true,
|
|
5851
|
+
idempotentHint: false,
|
|
5852
|
+
// delete-* tools are NOT idempotent
|
|
5853
|
+
openWorldHint: true
|
|
5291
5854
|
}
|
|
5292
5855
|
},
|
|
5293
5856
|
handleDeletePage
|
|
@@ -5295,7 +5858,7 @@ function registerDeletePageTool() {
|
|
|
5295
5858
|
}
|
|
5296
5859
|
|
|
5297
5860
|
// src/tools/delete-product-image.ts
|
|
5298
|
-
import { z as
|
|
5861
|
+
import { z as z17 } from "zod";
|
|
5299
5862
|
var FILE_DELETE_MUTATION = `
|
|
5300
5863
|
mutation FileDelete($fileIds: [ID!]!) {
|
|
5301
5864
|
fileDelete(fileIds: $fileIds) {
|
|
@@ -5307,14 +5870,14 @@ var FILE_DELETE_MUTATION = `
|
|
|
5307
5870
|
}
|
|
5308
5871
|
}
|
|
5309
5872
|
`;
|
|
5310
|
-
var
|
|
5311
|
-
imageId:
|
|
5873
|
+
var inputSchema15 = z17.object({
|
|
5874
|
+
imageId: imageIdSchema.describe(
|
|
5312
5875
|
'Shopify image GID to delete (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the images array.'
|
|
5313
5876
|
)
|
|
5314
5877
|
});
|
|
5315
|
-
var
|
|
5316
|
-
success:
|
|
5317
|
-
deletedImageId:
|
|
5878
|
+
var outputSchema15 = z17.object({
|
|
5879
|
+
success: z17.boolean().describe("Whether the deletion was successful"),
|
|
5880
|
+
deletedImageId: z17.string().describe("ID of the deleted image")
|
|
5318
5881
|
});
|
|
5319
5882
|
var handleDeleteProductImage = async (context, params) => {
|
|
5320
5883
|
log.debug(`Deleting image on shop: ${context.shopDomain}`);
|
|
@@ -5365,13 +5928,22 @@ function registerDeleteProductImageTool() {
|
|
|
5365
5928
|
name: "delete-product-image",
|
|
5366
5929
|
title: "Delete Product Image",
|
|
5367
5930
|
description: "Remove an image from a product. This permanently deletes the image from Shopify's CDN. **Warning:** Cannot be undone - the image will need to be re-uploaded using add-product-image if needed again. **Prerequisites:** get-product to find image IDs in the images array.",
|
|
5368
|
-
inputSchema:
|
|
5369
|
-
outputSchema:
|
|
5931
|
+
inputSchema: inputSchema15,
|
|
5932
|
+
outputSchema: outputSchema15,
|
|
5370
5933
|
// AI Agent Optimization (Story 5.5)
|
|
5371
5934
|
category: "media",
|
|
5372
5935
|
relationships: {
|
|
5373
5936
|
relatedTools: ["get-product", "add-product-image"],
|
|
5374
5937
|
prerequisites: ["get-product"]
|
|
5938
|
+
},
|
|
5939
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5940
|
+
annotations: {
|
|
5941
|
+
readOnlyHint: false,
|
|
5942
|
+
destructiveHint: true,
|
|
5943
|
+
// Permanently deletes image from Shopify CDN
|
|
5944
|
+
idempotentHint: false,
|
|
5945
|
+
// Second call fails (already deleted)
|
|
5946
|
+
openWorldHint: true
|
|
5375
5947
|
}
|
|
5376
5948
|
},
|
|
5377
5949
|
handleDeleteProductImage
|
|
@@ -5379,7 +5951,7 @@ function registerDeleteProductImageTool() {
|
|
|
5379
5951
|
}
|
|
5380
5952
|
|
|
5381
5953
|
// src/tools/delete-product-metafields.ts
|
|
5382
|
-
import { z as
|
|
5954
|
+
import { z as z18 } from "zod";
|
|
5383
5955
|
|
|
5384
5956
|
// src/shopify/metafields.ts
|
|
5385
5957
|
var GET_PRODUCT_METAFIELDS_QUERY = `
|
|
@@ -5558,28 +6130,28 @@ async function deleteProductMetafields(productId, identifiers) {
|
|
|
5558
6130
|
}
|
|
5559
6131
|
|
|
5560
6132
|
// src/tools/delete-product-metafields.ts
|
|
5561
|
-
var metafieldIdentifierSchema =
|
|
5562
|
-
namespace:
|
|
5563
|
-
key:
|
|
6133
|
+
var metafieldIdentifierSchema = z18.object({
|
|
6134
|
+
namespace: z18.string().min(1).describe('Metafield namespace (e.g., "custom", "seo"). Must match existing metafield.'),
|
|
6135
|
+
key: z18.string().min(1).describe('Metafield key (e.g., "color_hex"). Must match existing metafield.')
|
|
5564
6136
|
});
|
|
5565
|
-
var
|
|
5566
|
-
productId:
|
|
6137
|
+
var inputSchema16 = z18.object({
|
|
6138
|
+
productId: productIdSchema.describe(
|
|
5567
6139
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
5568
6140
|
),
|
|
5569
|
-
identifiers:
|
|
6141
|
+
identifiers: z18.array(metafieldIdentifierSchema).min(1, "At least one metafield identifier is required").describe(
|
|
5570
6142
|
"Array of metafield identifiers to delete. Each identifier must have namespace and key. Use get-product-metafields first to verify which metafields exist."
|
|
5571
6143
|
)
|
|
5572
6144
|
});
|
|
5573
|
-
var
|
|
5574
|
-
success:
|
|
5575
|
-
deletedMetafields:
|
|
5576
|
-
|
|
5577
|
-
ownerId:
|
|
5578
|
-
namespace:
|
|
5579
|
-
key:
|
|
6145
|
+
var outputSchema16 = z18.object({
|
|
6146
|
+
success: z18.boolean().describe("Whether the operation succeeded"),
|
|
6147
|
+
deletedMetafields: z18.array(
|
|
6148
|
+
z18.object({
|
|
6149
|
+
ownerId: z18.string().describe("Product GID that owned the metafield"),
|
|
6150
|
+
namespace: z18.string().describe("Namespace of deleted metafield"),
|
|
6151
|
+
key: z18.string().describe("Key of deleted metafield")
|
|
5580
6152
|
})
|
|
5581
6153
|
).describe("Identifiers of successfully deleted metafields"),
|
|
5582
|
-
deletedCount:
|
|
6154
|
+
deletedCount: z18.number().describe("Number of metafields deleted")
|
|
5583
6155
|
});
|
|
5584
6156
|
var handleDeleteProductMetafields = async (context, params) => {
|
|
5585
6157
|
log.debug(
|
|
@@ -5609,13 +6181,22 @@ function registerDeleteProductMetafieldsTool() {
|
|
|
5609
6181
|
name: "delete-product-metafields",
|
|
5610
6182
|
title: "Delete Product Metafields",
|
|
5611
6183
|
description: "Remove metafields from a product. Identify metafields to delete by their namespace and key combination. Non-existent metafields are silently ignored (no error). **Warning:** This operation is irreversible - deleted metafield data cannot be recovered. **Prerequisites:** get-product-metafields to verify which metafields exist before deletion.",
|
|
5612
|
-
inputSchema:
|
|
5613
|
-
outputSchema:
|
|
6184
|
+
inputSchema: inputSchema16,
|
|
6185
|
+
outputSchema: outputSchema16,
|
|
5614
6186
|
// AI Agent Optimization (Story 5.5)
|
|
5615
6187
|
category: "seo",
|
|
5616
6188
|
relationships: {
|
|
5617
6189
|
relatedTools: ["get-product-metafields"],
|
|
5618
6190
|
prerequisites: ["get-product-metafields"]
|
|
6191
|
+
},
|
|
6192
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6193
|
+
annotations: {
|
|
6194
|
+
readOnlyHint: false,
|
|
6195
|
+
destructiveHint: true,
|
|
6196
|
+
// Partial data deletion - cannot be recovered
|
|
6197
|
+
idempotentHint: false,
|
|
6198
|
+
// Second call fails (already deleted)
|
|
6199
|
+
openWorldHint: true
|
|
5619
6200
|
}
|
|
5620
6201
|
},
|
|
5621
6202
|
handleDeleteProductMetafields
|
|
@@ -5623,13 +6204,15 @@ function registerDeleteProductMetafieldsTool() {
|
|
|
5623
6204
|
}
|
|
5624
6205
|
|
|
5625
6206
|
// src/tools/delete-product.ts
|
|
5626
|
-
import { z as
|
|
5627
|
-
var
|
|
5628
|
-
id:
|
|
6207
|
+
import { z as z19 } from "zod";
|
|
6208
|
+
var inputSchema17 = z19.object({
|
|
6209
|
+
id: productIdSchema.describe(
|
|
6210
|
+
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
|
|
6211
|
+
)
|
|
5629
6212
|
});
|
|
5630
|
-
var
|
|
5631
|
-
deletedProductId:
|
|
5632
|
-
title:
|
|
6213
|
+
var outputSchema17 = z19.object({
|
|
6214
|
+
deletedProductId: z19.string().describe("The ID of the deleted product"),
|
|
6215
|
+
title: z19.string().describe("The title of the deleted product (for confirmation)")
|
|
5633
6216
|
});
|
|
5634
6217
|
var handleDeleteProduct = async (context, params) => {
|
|
5635
6218
|
log.debug(`Deleting product on shop: ${context.shopDomain}`);
|
|
@@ -5671,13 +6254,22 @@ function registerDeleteProductTool() {
|
|
|
5671
6254
|
name: "delete-product",
|
|
5672
6255
|
title: "Delete Product",
|
|
5673
6256
|
description: "Delete a product from the Shopify store permanently. Requires the product ID (GID format). Returns confirmation with the deleted product ID and title. **Prerequisites:** get-product or list-products to find the correct product ID. **Warning:** This action cannot be undone.",
|
|
5674
|
-
inputSchema:
|
|
5675
|
-
outputSchema:
|
|
6257
|
+
inputSchema: inputSchema17,
|
|
6258
|
+
outputSchema: outputSchema17,
|
|
5676
6259
|
// AI Agent Optimization (Story 5.5)
|
|
5677
6260
|
category: "product",
|
|
5678
6261
|
relationships: {
|
|
5679
6262
|
relatedTools: ["list-products"],
|
|
5680
6263
|
prerequisites: ["get-product"]
|
|
6264
|
+
},
|
|
6265
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6266
|
+
annotations: {
|
|
6267
|
+
readOnlyHint: false,
|
|
6268
|
+
destructiveHint: true,
|
|
6269
|
+
// Permanently deletes product
|
|
6270
|
+
idempotentHint: false,
|
|
6271
|
+
// Second call fails (already deleted)
|
|
6272
|
+
openWorldHint: true
|
|
5681
6273
|
}
|
|
5682
6274
|
},
|
|
5683
6275
|
handleDeleteProduct
|
|
@@ -5685,15 +6277,15 @@ function registerDeleteProductTool() {
|
|
|
5685
6277
|
}
|
|
5686
6278
|
|
|
5687
6279
|
// src/tools/delete-redirect.ts
|
|
5688
|
-
import { z as
|
|
5689
|
-
var
|
|
5690
|
-
id:
|
|
6280
|
+
import { z as z20 } from "zod";
|
|
6281
|
+
var inputSchema18 = z20.object({
|
|
6282
|
+
id: redirectIdSchema.describe(
|
|
5691
6283
|
'Shopify redirect GID (e.g., "gid://shopify/UrlRedirect/123"). Use list-redirects to find redirect IDs.'
|
|
5692
6284
|
)
|
|
5693
6285
|
});
|
|
5694
|
-
var
|
|
5695
|
-
success:
|
|
5696
|
-
deletedRedirectId:
|
|
6286
|
+
var outputSchema18 = z20.object({
|
|
6287
|
+
success: z20.boolean().describe("Whether the deletion succeeded"),
|
|
6288
|
+
deletedRedirectId: z20.string().describe("ID of the deleted redirect")
|
|
5697
6289
|
});
|
|
5698
6290
|
var handleDeleteRedirect = async (context, params) => {
|
|
5699
6291
|
log.debug(`Deleting redirect on shop: ${context.shopDomain}`);
|
|
@@ -5735,13 +6327,22 @@ function registerDeleteRedirectTool() {
|
|
|
5735
6327
|
name: "delete-redirect",
|
|
5736
6328
|
title: "Delete URL Redirect",
|
|
5737
6329
|
description: "Remove a URL redirect by ID. **Warning:** Deleting means visitors to that path will see 404 error - can hurt SEO if old URL still receives traffic. **Prerequisites:** list-redirects to find redirect IDs.",
|
|
5738
|
-
inputSchema:
|
|
5739
|
-
outputSchema:
|
|
6330
|
+
inputSchema: inputSchema18,
|
|
6331
|
+
outputSchema: outputSchema18,
|
|
5740
6332
|
// AI Agent Optimization (Story 5.5)
|
|
5741
6333
|
category: "seo",
|
|
5742
6334
|
relationships: {
|
|
5743
6335
|
relatedTools: ["create-redirect"],
|
|
5744
6336
|
prerequisites: ["list-redirects"]
|
|
6337
|
+
},
|
|
6338
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6339
|
+
annotations: {
|
|
6340
|
+
readOnlyHint: false,
|
|
6341
|
+
destructiveHint: true,
|
|
6342
|
+
// Permanently removes redirect - can hurt SEO
|
|
6343
|
+
idempotentHint: false,
|
|
6344
|
+
// Second call fails (already deleted)
|
|
6345
|
+
openWorldHint: true
|
|
5745
6346
|
}
|
|
5746
6347
|
},
|
|
5747
6348
|
handleDeleteRedirect
|
|
@@ -5749,7 +6350,7 @@ function registerDeleteRedirectTool() {
|
|
|
5749
6350
|
}
|
|
5750
6351
|
|
|
5751
6352
|
// src/tools/get-bulk-inventory.ts
|
|
5752
|
-
import { z as
|
|
6353
|
+
import { z as z21 } from "zod";
|
|
5753
6354
|
|
|
5754
6355
|
// src/shopify/inventory.ts
|
|
5755
6356
|
var GET_INVENTORY_ITEM_QUERY = `
|
|
@@ -6320,46 +6921,46 @@ async function getBulkInventory(options) {
|
|
|
6320
6921
|
}
|
|
6321
6922
|
|
|
6322
6923
|
// src/tools/get-bulk-inventory.ts
|
|
6323
|
-
var
|
|
6324
|
-
productIds:
|
|
6924
|
+
var inputSchema19 = z21.object({
|
|
6925
|
+
productIds: z21.array(productIdSchema).min(1, { message: "productIds array cannot be empty" }).max(50, { message: "Maximum 50 product IDs allowed per request" }).describe(
|
|
6325
6926
|
'Array of Shopify product IDs (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 50 products per request. Get product IDs from list-products or search.'
|
|
6326
6927
|
),
|
|
6327
|
-
includeVariants:
|
|
6928
|
+
includeVariants: z21.boolean().default(true).describe(
|
|
6328
6929
|
"Include per-variant inventory breakdown. Default: true. Set to false for faster response with only product totals."
|
|
6329
6930
|
),
|
|
6330
|
-
locationId:
|
|
6931
|
+
locationId: locationIdSchema.optional().describe(
|
|
6331
6932
|
'Optional location ID to filter inventory (e.g., "gid://shopify/Location/789"). If not provided, returns total inventory across all locations.'
|
|
6332
6933
|
)
|
|
6333
6934
|
});
|
|
6334
|
-
var
|
|
6335
|
-
products:
|
|
6336
|
-
|
|
6337
|
-
productId:
|
|
6338
|
-
productTitle:
|
|
6339
|
-
totalInventory:
|
|
6340
|
-
variants:
|
|
6341
|
-
|
|
6342
|
-
variantId:
|
|
6343
|
-
variantTitle:
|
|
6344
|
-
sku:
|
|
6345
|
-
inventoryItemId:
|
|
6346
|
-
available:
|
|
6347
|
-
locations:
|
|
6348
|
-
|
|
6349
|
-
locationId:
|
|
6350
|
-
locationName:
|
|
6351
|
-
available:
|
|
6935
|
+
var outputSchema19 = z21.object({
|
|
6936
|
+
products: z21.array(
|
|
6937
|
+
z21.object({
|
|
6938
|
+
productId: z21.string().describe("Product GID"),
|
|
6939
|
+
productTitle: z21.string().describe("Product title"),
|
|
6940
|
+
totalInventory: z21.number().describe("Total inventory across all variants"),
|
|
6941
|
+
variants: z21.array(
|
|
6942
|
+
z21.object({
|
|
6943
|
+
variantId: z21.string().describe("Variant GID"),
|
|
6944
|
+
variantTitle: z21.string().describe('Variant title (e.g., "Red / Large")'),
|
|
6945
|
+
sku: z21.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
6946
|
+
inventoryItemId: z21.string().describe("Inventory item GID"),
|
|
6947
|
+
available: z21.number().describe("Available quantity"),
|
|
6948
|
+
locations: z21.array(
|
|
6949
|
+
z21.object({
|
|
6950
|
+
locationId: z21.string().describe("Location GID"),
|
|
6951
|
+
locationName: z21.string().describe("Human-readable location name"),
|
|
6952
|
+
available: z21.number().describe("Available quantity at this location")
|
|
6352
6953
|
})
|
|
6353
6954
|
).optional().describe("Per-location inventory breakdown")
|
|
6354
6955
|
})
|
|
6355
6956
|
).optional().describe("Variant details (if includeVariants=true)")
|
|
6356
6957
|
})
|
|
6357
6958
|
).describe("Products with inventory data"),
|
|
6358
|
-
notFound:
|
|
6359
|
-
summary:
|
|
6360
|
-
productsQueried:
|
|
6361
|
-
productsFound:
|
|
6362
|
-
totalInventory:
|
|
6959
|
+
notFound: z21.array(z21.string()).describe("Product IDs that were not found in the store"),
|
|
6960
|
+
summary: z21.object({
|
|
6961
|
+
productsQueried: z21.number().describe("Total number of product IDs provided"),
|
|
6962
|
+
productsFound: z21.number().describe("Number of products found in the store"),
|
|
6963
|
+
totalInventory: z21.number().describe("Sum of inventory across all found products")
|
|
6363
6964
|
}).describe("Summary statistics")
|
|
6364
6965
|
});
|
|
6365
6966
|
var handleGetBulkInventory = async (context, params) => {
|
|
@@ -6391,14 +6992,21 @@ function registerGetBulkInventoryTool() {
|
|
|
6391
6992
|
name: "get-bulk-inventory",
|
|
6392
6993
|
title: "Get Bulk Inventory",
|
|
6393
6994
|
description: "Efficiently query inventory levels for multiple products in a single request. Accepts up to 50 product IDs and returns total inventory plus optional per-variant breakdown. Use this instead of multiple get-inventory calls when checking stock across several products. Returns a summary with counts of products queried vs found, plus total inventory across all products. Products not found in the store are listed in the notFound array rather than causing an error. Perfect for cart verification, collection audits, and batch inventory checks. **Typical workflow:** list-products \u2192 get-bulk-inventory. **Prerequisites:** Product IDs. **Follow-ups:** update-inventory, get-inventory. Returns products with inventory data and summary statistics.",
|
|
6394
|
-
inputSchema:
|
|
6395
|
-
outputSchema:
|
|
6995
|
+
inputSchema: inputSchema19,
|
|
6996
|
+
outputSchema: outputSchema19,
|
|
6396
6997
|
category: "inventory",
|
|
6397
6998
|
relationships: {
|
|
6398
6999
|
relatedTools: ["list-products", "get-inventory", "update-inventory"],
|
|
6399
7000
|
prerequisites: ["list-products"],
|
|
6400
7001
|
followUps: ["update-inventory", "get-inventory"],
|
|
6401
7002
|
alternatives: ["get-inventory"]
|
|
7003
|
+
},
|
|
7004
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7005
|
+
annotations: {
|
|
7006
|
+
readOnlyHint: true,
|
|
7007
|
+
destructiveHint: false,
|
|
7008
|
+
idempotentHint: true,
|
|
7009
|
+
openWorldHint: true
|
|
6402
7010
|
}
|
|
6403
7011
|
},
|
|
6404
7012
|
handleGetBulkInventory
|
|
@@ -6406,30 +7014,30 @@ function registerGetBulkInventoryTool() {
|
|
|
6406
7014
|
}
|
|
6407
7015
|
|
|
6408
7016
|
// src/tools/get-collection.ts
|
|
6409
|
-
import { z as
|
|
6410
|
-
var
|
|
6411
|
-
collectionId:
|
|
7017
|
+
import { z as z22 } from "zod";
|
|
7018
|
+
var inputSchema20 = z22.object({
|
|
7019
|
+
collectionId: collectionIdSchema.describe(
|
|
6412
7020
|
'Collection ID (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections to find collection IDs.'
|
|
6413
7021
|
)
|
|
6414
7022
|
});
|
|
6415
|
-
var
|
|
6416
|
-
id:
|
|
6417
|
-
title:
|
|
6418
|
-
handle:
|
|
6419
|
-
description:
|
|
6420
|
-
descriptionHtml:
|
|
6421
|
-
seo:
|
|
6422
|
-
title:
|
|
6423
|
-
description:
|
|
7023
|
+
var outputSchema20 = z22.object({
|
|
7024
|
+
id: z22.string().describe("Collection GID"),
|
|
7025
|
+
title: z22.string().describe("Collection title"),
|
|
7026
|
+
handle: z22.string().describe("URL handle/slug"),
|
|
7027
|
+
description: z22.string().describe("Plain text description"),
|
|
7028
|
+
descriptionHtml: z22.string().describe("HTML description"),
|
|
7029
|
+
seo: z22.object({
|
|
7030
|
+
title: z22.string().nullable().describe("SEO title"),
|
|
7031
|
+
description: z22.string().nullable().describe("SEO description")
|
|
6424
7032
|
}),
|
|
6425
|
-
image:
|
|
6426
|
-
id:
|
|
6427
|
-
url:
|
|
6428
|
-
altText:
|
|
7033
|
+
image: z22.object({
|
|
7034
|
+
id: z22.string().describe("Image GID"),
|
|
7035
|
+
url: z22.string().describe("Image URL"),
|
|
7036
|
+
altText: z22.string().nullable().describe("Alt text")
|
|
6429
7037
|
}).nullable(),
|
|
6430
|
-
sortOrder:
|
|
6431
|
-
productsCount:
|
|
6432
|
-
updatedAt:
|
|
7038
|
+
sortOrder: z22.string().describe("Product sort order within collection"),
|
|
7039
|
+
productsCount: z22.number().describe("Number of products in collection"),
|
|
7040
|
+
updatedAt: z22.string().describe("Last update timestamp (ISO 8601)")
|
|
6433
7041
|
});
|
|
6434
7042
|
var handleGetCollection = async (context, params) => {
|
|
6435
7043
|
log.debug(`Getting collection on shop: ${context.shopDomain}`);
|
|
@@ -6449,13 +7057,20 @@ function registerGetCollectionTool() {
|
|
|
6449
7057
|
name: "get-collection",
|
|
6450
7058
|
title: "Get Collection",
|
|
6451
7059
|
description: "Retrieve complete details of a collection by ID. Returns all collection attributes including SEO metadata, product count, sort order, and timestamps. **Prerequisites:** list-collections to find collection IDs. **Follow-ups:** update-collection, delete-collection, add-products-to-collection.",
|
|
6452
|
-
inputSchema:
|
|
6453
|
-
outputSchema:
|
|
7060
|
+
inputSchema: inputSchema20,
|
|
7061
|
+
outputSchema: outputSchema20,
|
|
6454
7062
|
// AI Agent Optimization (Story 5.5)
|
|
6455
7063
|
category: "collection",
|
|
6456
7064
|
relationships: {
|
|
6457
7065
|
relatedTools: ["list-collections"],
|
|
6458
7066
|
followUps: ["update-collection", "delete-collection", "add-products-to-collection"]
|
|
7067
|
+
},
|
|
7068
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7069
|
+
annotations: {
|
|
7070
|
+
readOnlyHint: true,
|
|
7071
|
+
destructiveHint: false,
|
|
7072
|
+
idempotentHint: true,
|
|
7073
|
+
openWorldHint: true
|
|
6459
7074
|
}
|
|
6460
7075
|
},
|
|
6461
7076
|
handleGetCollection
|
|
@@ -6463,37 +7078,37 @@ function registerGetCollectionTool() {
|
|
|
6463
7078
|
}
|
|
6464
7079
|
|
|
6465
7080
|
// src/tools/get-inventory.ts
|
|
6466
|
-
import { z as
|
|
6467
|
-
var
|
|
6468
|
-
variantId:
|
|
7081
|
+
import { z as z23 } from "zod";
|
|
7082
|
+
var inputSchema21 = z23.object({
|
|
7083
|
+
variantId: variantIdSchema.optional().describe(
|
|
6469
7084
|
'The Shopify product variant ID (e.g., "gid://shopify/ProductVariant/123"). Use this when you have a variant ID from a product query (get-product or list-products).'
|
|
6470
7085
|
),
|
|
6471
|
-
inventoryItemId:
|
|
7086
|
+
inventoryItemId: inventoryItemIdSchema.optional().describe(
|
|
6472
7087
|
'The Shopify inventory item ID (e.g., "gid://shopify/InventoryItem/456"). Use this if you already have the inventory item ID from a previous query.'
|
|
6473
7088
|
),
|
|
6474
|
-
locationId:
|
|
7089
|
+
locationId: locationIdSchema.optional().describe(
|
|
6475
7090
|
'Optional location ID to filter results (e.g., "gid://shopify/Location/789"). If not provided, returns inventory at all locations.'
|
|
6476
7091
|
)
|
|
6477
7092
|
}).refine((data) => data.variantId || data.inventoryItemId, {
|
|
6478
7093
|
message: "Either variantId or inventoryItemId must be provided"
|
|
6479
7094
|
});
|
|
6480
|
-
var
|
|
6481
|
-
inventoryItemId:
|
|
6482
|
-
variantId:
|
|
6483
|
-
variantTitle:
|
|
6484
|
-
productId:
|
|
6485
|
-
productTitle:
|
|
6486
|
-
sku:
|
|
6487
|
-
tracked:
|
|
6488
|
-
totalAvailable:
|
|
6489
|
-
locations:
|
|
6490
|
-
|
|
6491
|
-
locationId:
|
|
6492
|
-
locationName:
|
|
6493
|
-
isActive:
|
|
6494
|
-
available:
|
|
6495
|
-
onHand:
|
|
6496
|
-
committed:
|
|
7095
|
+
var outputSchema21 = z23.object({
|
|
7096
|
+
inventoryItemId: z23.string().describe("Inventory item GID"),
|
|
7097
|
+
variantId: z23.string().describe("Product variant GID"),
|
|
7098
|
+
variantTitle: z23.string().describe('Variant title (e.g., "Red / Large")'),
|
|
7099
|
+
productId: z23.string().describe("Product GID"),
|
|
7100
|
+
productTitle: z23.string().describe("Product title"),
|
|
7101
|
+
sku: z23.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
7102
|
+
tracked: z23.boolean().describe("Whether inventory tracking is enabled for this item"),
|
|
7103
|
+
totalAvailable: z23.number().describe("Total available quantity across all locations"),
|
|
7104
|
+
locations: z23.array(
|
|
7105
|
+
z23.object({
|
|
7106
|
+
locationId: z23.string().describe("Location GID"),
|
|
7107
|
+
locationName: z23.string().describe("Human-readable location name"),
|
|
7108
|
+
isActive: z23.boolean().describe("Whether the location is active"),
|
|
7109
|
+
available: z23.number().describe("Quantity available for sale"),
|
|
7110
|
+
onHand: z23.number().describe("Physical quantity on hand"),
|
|
7111
|
+
committed: z23.number().describe("Quantity committed to orders")
|
|
6497
7112
|
})
|
|
6498
7113
|
).describe("Inventory levels by location")
|
|
6499
7114
|
});
|
|
@@ -6555,8 +7170,8 @@ function registerGetInventoryTool() {
|
|
|
6555
7170
|
name: "get-inventory",
|
|
6556
7171
|
title: "Get Inventory",
|
|
6557
7172
|
description: "Retrieve inventory levels for a product variant across all store locations. Provide either a variantId (from get-product or list-products) or inventoryItemId (if you have it directly). Returns available, on-hand, and committed quantities per location, plus totals. Useful for: checking stock before making availability promises, identifying which locations have stock, understanding inventory distribution across warehouse locations. Optionally filter by locationId to get inventory at a specific location only. **Typical workflow:** get-product \u2192 get-inventory \u2192 update-inventory. **Prerequisites:** Variant ID or Inventory Item ID. **Follow-ups:** update-inventory. Returns inventory levels per location with totals.",
|
|
6558
|
-
inputSchema:
|
|
6559
|
-
outputSchema:
|
|
7173
|
+
inputSchema: inputSchema21,
|
|
7174
|
+
outputSchema: outputSchema21,
|
|
6560
7175
|
category: "inventory",
|
|
6561
7176
|
relationships: {
|
|
6562
7177
|
relatedTools: [
|
|
@@ -6567,30 +7182,119 @@ function registerGetInventoryTool() {
|
|
|
6567
7182
|
],
|
|
6568
7183
|
prerequisites: ["get-product", "list-products"],
|
|
6569
7184
|
followUps: ["update-inventory"]
|
|
7185
|
+
},
|
|
7186
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7187
|
+
annotations: {
|
|
7188
|
+
readOnlyHint: true,
|
|
7189
|
+
destructiveHint: false,
|
|
7190
|
+
idempotentHint: true,
|
|
7191
|
+
openWorldHint: true
|
|
6570
7192
|
}
|
|
6571
7193
|
},
|
|
6572
7194
|
handleGetInventory
|
|
6573
7195
|
);
|
|
6574
7196
|
}
|
|
6575
7197
|
|
|
7198
|
+
// src/tools/get-market.ts
|
|
7199
|
+
import { z as z24 } from "zod";
|
|
7200
|
+
var inputSchema22 = z24.object({
|
|
7201
|
+
id: marketIdSchema.describe(
|
|
7202
|
+
'Shopify Market GID (e.g., "gid://shopify/Market/123"). Use list-markets to find market IDs.'
|
|
7203
|
+
)
|
|
7204
|
+
});
|
|
7205
|
+
var outputSchema22 = z24.object({
|
|
7206
|
+
id: z24.string().describe("Market GID"),
|
|
7207
|
+
name: z24.string().describe("Market name (not shown to customers)"),
|
|
7208
|
+
handle: z24.string().describe("URL handle"),
|
|
7209
|
+
enabled: z24.boolean().describe("Whether the market is enabled"),
|
|
7210
|
+
status: z24.enum(["ACTIVE", "INACTIVE"]).describe("Market status"),
|
|
7211
|
+
type: z24.string().describe("Market type (PRIMARY, SINGLE_COUNTRY, MULTI_COUNTRY, CONTINENT)"),
|
|
7212
|
+
currencySettings: z24.object({
|
|
7213
|
+
baseCurrency: z24.object({
|
|
7214
|
+
currencyCode: z24.string().describe("Base currency code (e.g., USD, EUR)"),
|
|
7215
|
+
currencyName: z24.string().optional().describe("Base currency name")
|
|
7216
|
+
}),
|
|
7217
|
+
localCurrencies: z24.boolean().describe("Whether local currencies are enabled")
|
|
7218
|
+
}).optional(),
|
|
7219
|
+
webPresences: z24.array(
|
|
7220
|
+
z24.object({
|
|
7221
|
+
id: z24.string().describe("Web presence GID"),
|
|
7222
|
+
defaultLocale: z24.object({
|
|
7223
|
+
locale: z24.string().describe("Locale code (e.g., en-CA)"),
|
|
7224
|
+
name: z24.string().describe("Locale display name")
|
|
7225
|
+
}),
|
|
7226
|
+
alternateLocales: z24.array(
|
|
7227
|
+
z24.object({
|
|
7228
|
+
locale: z24.string(),
|
|
7229
|
+
name: z24.string()
|
|
7230
|
+
})
|
|
7231
|
+
).optional(),
|
|
7232
|
+
subfolderSuffix: z24.string().optional().describe("Subfolder suffix (e.g., /en-ca)"),
|
|
7233
|
+
domain: z24.object({
|
|
7234
|
+
host: z24.string().describe("Domain host")
|
|
7235
|
+
}).optional(),
|
|
7236
|
+
rootUrls: z24.array(
|
|
7237
|
+
z24.object({
|
|
7238
|
+
locale: z24.string(),
|
|
7239
|
+
url: z24.string()
|
|
7240
|
+
})
|
|
7241
|
+
).optional()
|
|
7242
|
+
})
|
|
7243
|
+
).optional().describe("Web presences (SEO URL configurations)")
|
|
7244
|
+
});
|
|
7245
|
+
var handleGetMarket = async (context, params) => {
|
|
7246
|
+
log.debug(`Getting market ${params.id} on shop: ${context.shopDomain}`);
|
|
7247
|
+
const market = await getMarket(params.id);
|
|
7248
|
+
if (!market) {
|
|
7249
|
+
throw new Error(`Market not found: ${params.id}. Use list-markets to see available markets.`);
|
|
7250
|
+
}
|
|
7251
|
+
log.debug(`Retrieved market: ${market.name}`);
|
|
7252
|
+
return market;
|
|
7253
|
+
};
|
|
7254
|
+
function registerGetMarketTool() {
|
|
7255
|
+
registerContextAwareTool(
|
|
7256
|
+
{
|
|
7257
|
+
name: "get-market",
|
|
7258
|
+
title: "Get Market",
|
|
7259
|
+
description: "Retrieve full details of a specific market by ID. Returns market name, handle, status, type, currency settings, and web presences. Web presences define SEO URL structures (domains, subfolders, locales). **Prerequisites:** Use list-markets to find market IDs. **Follow-ups:** update-market, delete-market, create-web-presence.",
|
|
7260
|
+
inputSchema: inputSchema22,
|
|
7261
|
+
outputSchema: outputSchema22,
|
|
7262
|
+
// AI Agent Optimization
|
|
7263
|
+
category: "market",
|
|
7264
|
+
relationships: {
|
|
7265
|
+
relatedTools: ["list-markets"],
|
|
7266
|
+
followUps: ["update-market", "delete-market"]
|
|
7267
|
+
},
|
|
7268
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7269
|
+
annotations: {
|
|
7270
|
+
readOnlyHint: true,
|
|
7271
|
+
destructiveHint: false,
|
|
7272
|
+
idempotentHint: true,
|
|
7273
|
+
openWorldHint: true
|
|
7274
|
+
}
|
|
7275
|
+
},
|
|
7276
|
+
handleGetMarket
|
|
7277
|
+
);
|
|
7278
|
+
}
|
|
7279
|
+
|
|
6576
7280
|
// src/tools/get-page.ts
|
|
6577
|
-
import { z as
|
|
6578
|
-
var
|
|
6579
|
-
id:
|
|
7281
|
+
import { z as z25 } from "zod";
|
|
7282
|
+
var inputSchema23 = z25.object({
|
|
7283
|
+
id: pageIdSchema.describe(
|
|
6580
7284
|
'The page ID to retrieve. Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs.'
|
|
6581
7285
|
)
|
|
6582
7286
|
});
|
|
6583
|
-
var
|
|
6584
|
-
id:
|
|
6585
|
-
title:
|
|
6586
|
-
handle:
|
|
6587
|
-
body:
|
|
6588
|
-
bodySummary:
|
|
6589
|
-
isPublished:
|
|
6590
|
-
publishedAt:
|
|
6591
|
-
templateSuffix:
|
|
6592
|
-
createdAt:
|
|
6593
|
-
updatedAt:
|
|
7287
|
+
var outputSchema23 = z25.object({
|
|
7288
|
+
id: z25.string().describe("Page GID"),
|
|
7289
|
+
title: z25.string().describe("Page title"),
|
|
7290
|
+
handle: z25.string().describe("URL handle/slug"),
|
|
7291
|
+
body: z25.string().describe("HTML body content"),
|
|
7292
|
+
bodySummary: z25.string().describe("Plain text summary (first 150 chars)"),
|
|
7293
|
+
isPublished: z25.boolean().describe("Whether page is visible on storefront"),
|
|
7294
|
+
publishedAt: z25.string().nullable().describe("ISO timestamp when published"),
|
|
7295
|
+
templateSuffix: z25.string().nullable().describe("Custom template suffix"),
|
|
7296
|
+
createdAt: z25.string().describe("ISO timestamp of creation"),
|
|
7297
|
+
updatedAt: z25.string().describe("ISO timestamp of last update")
|
|
6594
7298
|
});
|
|
6595
7299
|
var handleGetPage = async (context, params) => {
|
|
6596
7300
|
log.debug(`Getting page on shop: ${context.shopDomain}`);
|
|
@@ -6610,13 +7314,20 @@ function registerGetPageTool() {
|
|
|
6610
7314
|
name: "get-page",
|
|
6611
7315
|
title: "Get Page",
|
|
6612
7316
|
description: "Retrieve details of a specific page by ID. Returns the full page including title, body content, handle, publish status, and timestamps. Use list-pages first to find page IDs. Useful for: inspecting page content before editing, verifying page details. **Typical workflow:** list-pages \u2192 get-page \u2192 update-page. **Prerequisites:** Page ID. **Follow-ups:** update-page, delete-page. Returns a single page object with full content.",
|
|
6613
|
-
inputSchema:
|
|
6614
|
-
outputSchema:
|
|
7317
|
+
inputSchema: inputSchema23,
|
|
7318
|
+
outputSchema: outputSchema23,
|
|
6615
7319
|
category: "content",
|
|
6616
7320
|
relationships: {
|
|
6617
7321
|
relatedTools: ["list-pages", "update-page", "delete-page"],
|
|
6618
7322
|
prerequisites: ["list-pages"],
|
|
6619
7323
|
followUps: ["update-page", "delete-page"]
|
|
7324
|
+
},
|
|
7325
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7326
|
+
annotations: {
|
|
7327
|
+
readOnlyHint: true,
|
|
7328
|
+
destructiveHint: false,
|
|
7329
|
+
idempotentHint: true,
|
|
7330
|
+
openWorldHint: true
|
|
6620
7331
|
}
|
|
6621
7332
|
},
|
|
6622
7333
|
handleGetPage
|
|
@@ -6624,27 +7335,27 @@ function registerGetPageTool() {
|
|
|
6624
7335
|
}
|
|
6625
7336
|
|
|
6626
7337
|
// src/tools/get-product-metafields.ts
|
|
6627
|
-
import { z as
|
|
6628
|
-
var
|
|
6629
|
-
productId:
|
|
7338
|
+
import { z as z26 } from "zod";
|
|
7339
|
+
var inputSchema24 = z26.object({
|
|
7340
|
+
productId: productIdSchema.describe(
|
|
6630
7341
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
6631
7342
|
),
|
|
6632
|
-
namespace:
|
|
7343
|
+
namespace: z26.string().optional().describe(
|
|
6633
7344
|
'Filter metafields by namespace (e.g., "custom", "seo", "my_app"). If not provided, returns metafields from all namespaces.'
|
|
6634
7345
|
),
|
|
6635
|
-
keys:
|
|
7346
|
+
keys: z26.array(z26.string()).optional().describe(
|
|
6636
7347
|
'Filter by specific metafield keys within the namespace (e.g., ["color", "size"]). Requires namespace to be specified for meaningful filtering.'
|
|
6637
7348
|
)
|
|
6638
7349
|
});
|
|
6639
|
-
var
|
|
6640
|
-
|
|
6641
|
-
id:
|
|
6642
|
-
namespace:
|
|
6643
|
-
key:
|
|
6644
|
-
value:
|
|
6645
|
-
type:
|
|
6646
|
-
createdAt:
|
|
6647
|
-
updatedAt:
|
|
7350
|
+
var outputSchema24 = z26.array(
|
|
7351
|
+
z26.object({
|
|
7352
|
+
id: z26.string().describe('Metafield GID (e.g., "gid://shopify/Metafield/123")'),
|
|
7353
|
+
namespace: z26.string().describe("Metafield namespace (grouping identifier)"),
|
|
7354
|
+
key: z26.string().describe("Metafield key (field name)"),
|
|
7355
|
+
value: z26.string().describe("Metafield value (the stored data)"),
|
|
7356
|
+
type: z26.string().describe('Metafield type (e.g., "single_line_text_field", "json", "number_integer")'),
|
|
7357
|
+
createdAt: z26.string().describe("Creation timestamp (ISO 8601)"),
|
|
7358
|
+
updatedAt: z26.string().describe("Last update timestamp (ISO 8601)")
|
|
6648
7359
|
})
|
|
6649
7360
|
);
|
|
6650
7361
|
var handleGetProductMetafields = async (context, params) => {
|
|
@@ -6669,14 +7380,21 @@ function registerGetProductMetafieldsTool() {
|
|
|
6669
7380
|
name: "get-product-metafields",
|
|
6670
7381
|
title: "Get Product Metafields",
|
|
6671
7382
|
description: 'Retrieve metafields attached to a product. Metafields store custom data like SEO schema markup, product specifications, or custom attributes. Filter by namespace to get specific app/integration data, or by keys to retrieve specific fields. Common namespaces include "custom" (user-defined), "seo" (search optimization), and app-specific namespaces. Returns all matching metafields with their id, namespace, key, value, type, and timestamps. **Prerequisites:** get-product to obtain the product ID. **Follow-ups:** set-product-metafields to update values, delete-product-metafields to remove.',
|
|
6672
|
-
inputSchema:
|
|
6673
|
-
outputSchema:
|
|
7383
|
+
inputSchema: inputSchema24,
|
|
7384
|
+
outputSchema: outputSchema24,
|
|
6674
7385
|
// AI Agent Optimization (Story 5.5)
|
|
6675
7386
|
category: "seo",
|
|
6676
7387
|
relationships: {
|
|
6677
7388
|
relatedTools: ["get-product", "set-product-metafields"],
|
|
6678
7389
|
prerequisites: ["get-product"],
|
|
6679
7390
|
followUps: ["set-product-metafields", "delete-product-metafields"]
|
|
7391
|
+
},
|
|
7392
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7393
|
+
annotations: {
|
|
7394
|
+
readOnlyHint: true,
|
|
7395
|
+
destructiveHint: false,
|
|
7396
|
+
idempotentHint: true,
|
|
7397
|
+
openWorldHint: true
|
|
6680
7398
|
}
|
|
6681
7399
|
},
|
|
6682
7400
|
handleGetProductMetafields
|
|
@@ -6684,51 +7402,51 @@ function registerGetProductMetafieldsTool() {
|
|
|
6684
7402
|
}
|
|
6685
7403
|
|
|
6686
7404
|
// src/tools/get-product.ts
|
|
6687
|
-
import { z as
|
|
6688
|
-
var
|
|
6689
|
-
id:
|
|
7405
|
+
import { z as z27 } from "zod";
|
|
7406
|
+
var inputSchema25 = z27.object({
|
|
7407
|
+
id: productIdSchema.optional().describe(
|
|
6690
7408
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use either id or handle.'
|
|
6691
7409
|
),
|
|
6692
|
-
handle:
|
|
7410
|
+
handle: z27.string().optional().describe(
|
|
6693
7411
|
'Product URL handle/slug (e.g., "premium-cotton-t-shirt"). Use either id or handle.'
|
|
6694
7412
|
)
|
|
6695
7413
|
}).refine((data) => data.id || data.handle, {
|
|
6696
7414
|
message: "Either id or handle must be provided"
|
|
6697
7415
|
});
|
|
6698
|
-
var
|
|
6699
|
-
id:
|
|
6700
|
-
title:
|
|
6701
|
-
handle:
|
|
6702
|
-
description:
|
|
6703
|
-
vendor:
|
|
6704
|
-
productType:
|
|
6705
|
-
status:
|
|
6706
|
-
tags:
|
|
6707
|
-
seo:
|
|
6708
|
-
title:
|
|
6709
|
-
description:
|
|
7416
|
+
var outputSchema25 = z27.object({
|
|
7417
|
+
id: z27.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
7418
|
+
title: z27.string().describe("Product title"),
|
|
7419
|
+
handle: z27.string().describe("URL handle/slug"),
|
|
7420
|
+
description: z27.string().nullable().describe("Product description (HTML)"),
|
|
7421
|
+
vendor: z27.string().describe("Vendor/brand name"),
|
|
7422
|
+
productType: z27.string().describe("Product type"),
|
|
7423
|
+
status: z27.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
7424
|
+
tags: z27.array(z27.string()).describe("Product tags"),
|
|
7425
|
+
seo: z27.object({
|
|
7426
|
+
title: z27.string().nullable().describe("SEO title for search engines"),
|
|
7427
|
+
description: z27.string().nullable().describe("SEO description/meta description")
|
|
6710
7428
|
}).describe("SEO metadata for search engine optimization"),
|
|
6711
|
-
createdAt:
|
|
6712
|
-
updatedAt:
|
|
6713
|
-
variants:
|
|
6714
|
-
|
|
6715
|
-
id:
|
|
6716
|
-
title:
|
|
6717
|
-
price:
|
|
6718
|
-
compareAtPrice:
|
|
6719
|
-
sku:
|
|
6720
|
-
barcode:
|
|
6721
|
-
inventoryQuantity:
|
|
7429
|
+
createdAt: z27.string().describe("Creation timestamp (ISO 8601)"),
|
|
7430
|
+
updatedAt: z27.string().describe("Last update timestamp (ISO 8601)"),
|
|
7431
|
+
variants: z27.array(
|
|
7432
|
+
z27.object({
|
|
7433
|
+
id: z27.string().describe("Variant GID"),
|
|
7434
|
+
title: z27.string().describe("Variant title"),
|
|
7435
|
+
price: z27.string().describe("Price"),
|
|
7436
|
+
compareAtPrice: z27.string().nullable().describe("Compare at price"),
|
|
7437
|
+
sku: z27.string().nullable().describe("SKU"),
|
|
7438
|
+
barcode: z27.string().nullable().describe("Barcode"),
|
|
7439
|
+
inventoryQuantity: z27.number().nullable().describe("Inventory quantity")
|
|
6722
7440
|
})
|
|
6723
7441
|
).describe("Product variants"),
|
|
6724
|
-
images:
|
|
6725
|
-
|
|
6726
|
-
id:
|
|
6727
|
-
url:
|
|
6728
|
-
altText:
|
|
7442
|
+
images: z27.array(
|
|
7443
|
+
z27.object({
|
|
7444
|
+
id: z27.string().describe("Image GID"),
|
|
7445
|
+
url: z27.string().describe("Image URL"),
|
|
7446
|
+
altText: z27.string().nullable().describe("Alt text")
|
|
6729
7447
|
})
|
|
6730
7448
|
).describe("Product images"),
|
|
6731
|
-
totalInventory:
|
|
7449
|
+
totalInventory: z27.number().describe("Total inventory across variants")
|
|
6732
7450
|
});
|
|
6733
7451
|
var handleGetProduct = async (context, params) => {
|
|
6734
7452
|
log.debug(`Getting product on shop: ${context.shopDomain}`);
|
|
@@ -6767,8 +7485,8 @@ function registerGetProductTool() {
|
|
|
6767
7485
|
name: "get-product",
|
|
6768
7486
|
title: "Get Product",
|
|
6769
7487
|
description: "Retrieve details of a specific product from the Shopify store. Provide either the product ID (GID format) or handle (URL slug). Returns full product details including title, description, vendor, type, status, tags, SEO metadata (title/description for search engines), variants (with pricing/SKU/inventory), and images. **Prerequisites:** Use list-products to find product IDs or handles. **Follow-ups:** update-product, delete-product, add-product-image, get-product-metafields.",
|
|
6770
|
-
inputSchema:
|
|
6771
|
-
outputSchema:
|
|
7488
|
+
inputSchema: inputSchema25,
|
|
7489
|
+
outputSchema: outputSchema25,
|
|
6772
7490
|
// AI Agent Optimization (Story 5.5)
|
|
6773
7491
|
category: "product",
|
|
6774
7492
|
relationships: {
|
|
@@ -6779,6 +7497,13 @@ function registerGetProductTool() {
|
|
|
6779
7497
|
"add-product-image",
|
|
6780
7498
|
"get-product-metafields"
|
|
6781
7499
|
]
|
|
7500
|
+
},
|
|
7501
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7502
|
+
annotations: {
|
|
7503
|
+
readOnlyHint: true,
|
|
7504
|
+
destructiveHint: false,
|
|
7505
|
+
idempotentHint: true,
|
|
7506
|
+
openWorldHint: true
|
|
6782
7507
|
}
|
|
6783
7508
|
},
|
|
6784
7509
|
handleGetProduct
|
|
@@ -6786,36 +7511,36 @@ function registerGetProductTool() {
|
|
|
6786
7511
|
}
|
|
6787
7512
|
|
|
6788
7513
|
// src/tools/list-articles.ts
|
|
6789
|
-
import { z as
|
|
6790
|
-
var
|
|
6791
|
-
first:
|
|
6792
|
-
cursor:
|
|
7514
|
+
import { z as z28 } from "zod";
|
|
7515
|
+
var inputSchema26 = z28.object({
|
|
7516
|
+
first: z28.number().int().min(1).max(50).optional().default(10).describe("Number of articles to return (1-50). Defaults to 10."),
|
|
7517
|
+
cursor: z28.string().optional().describe(
|
|
6793
7518
|
"Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
|
|
6794
7519
|
),
|
|
6795
|
-
query:
|
|
7520
|
+
query: z28.string().optional().describe(
|
|
6796
7521
|
"Search query to filter articles. Supports Shopify search syntax. Examples: 'title:Guide', 'tag:tutorial', 'blog_id:123456789'."
|
|
6797
7522
|
),
|
|
6798
|
-
blogId:
|
|
7523
|
+
blogId: z28.string().optional().describe(
|
|
6799
7524
|
'Filter articles by blog ID. Provide the numeric ID (not the full GID) to filter. Example: To filter blog "gid://shopify/Blog/123456", pass blogId as "123456".'
|
|
6800
7525
|
),
|
|
6801
|
-
sortKey:
|
|
7526
|
+
sortKey: z28.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT", "AUTHOR", "BLOG_TITLE"]).optional().describe("Sort order for results. Defaults to ID.")
|
|
6802
7527
|
});
|
|
6803
|
-
var
|
|
6804
|
-
articles:
|
|
6805
|
-
|
|
6806
|
-
id:
|
|
6807
|
-
title:
|
|
6808
|
-
handle:
|
|
6809
|
-
blog:
|
|
6810
|
-
id:
|
|
6811
|
-
title:
|
|
7528
|
+
var outputSchema26 = z28.object({
|
|
7529
|
+
articles: z28.array(
|
|
7530
|
+
z28.object({
|
|
7531
|
+
id: z28.string().describe("Article GID"),
|
|
7532
|
+
title: z28.string().describe("Article title"),
|
|
7533
|
+
handle: z28.string().describe("URL handle/slug"),
|
|
7534
|
+
blog: z28.object({
|
|
7535
|
+
id: z28.string().describe("Parent blog GID"),
|
|
7536
|
+
title: z28.string().describe("Parent blog title")
|
|
6812
7537
|
}),
|
|
6813
|
-
isPublished:
|
|
6814
|
-
publishedAt:
|
|
7538
|
+
isPublished: z28.boolean().describe("Whether article is visible on storefront"),
|
|
7539
|
+
publishedAt: z28.string().nullable().describe("ISO timestamp when published")
|
|
6815
7540
|
})
|
|
6816
7541
|
),
|
|
6817
|
-
hasNextPage:
|
|
6818
|
-
endCursor:
|
|
7542
|
+
hasNextPage: z28.boolean().describe("Whether more articles are available"),
|
|
7543
|
+
endCursor: z28.string().nullable().describe("Cursor for next page pagination")
|
|
6819
7544
|
});
|
|
6820
7545
|
var handleListArticles = async (context, params) => {
|
|
6821
7546
|
log.debug(`Listing articles on shop: ${context.shopDomain}`);
|
|
@@ -6859,12 +7584,19 @@ function registerListArticlesTool() {
|
|
|
6859
7584
|
name: "list-articles",
|
|
6860
7585
|
title: "List Articles",
|
|
6861
7586
|
description: "List articles with optional filtering and pagination. Use blogId to filter articles from a specific blog. Use query to search by title or tags. Returns article ID, title, handle, blog reference, and publish status. Useful for: finding articles to edit, auditing blog content, bulk operations. **Typical workflow:** list-blogs \u2192 list-articles \u2192 update-article. **Prerequisites:** None (optional Blog ID for filtering). **Follow-ups:** update-article, delete-article, create-article. Returns a paginated list of article summaries with a response summary.",
|
|
6862
|
-
inputSchema:
|
|
6863
|
-
outputSchema:
|
|
7587
|
+
inputSchema: inputSchema26,
|
|
7588
|
+
outputSchema: outputSchema26,
|
|
6864
7589
|
category: "content",
|
|
6865
7590
|
relationships: {
|
|
6866
7591
|
relatedTools: ["list-blogs", "create-article", "update-article"],
|
|
6867
7592
|
followUps: ["update-article", "delete-article", "create-article"]
|
|
7593
|
+
},
|
|
7594
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7595
|
+
annotations: {
|
|
7596
|
+
readOnlyHint: true,
|
|
7597
|
+
destructiveHint: false,
|
|
7598
|
+
idempotentHint: true,
|
|
7599
|
+
openWorldHint: true
|
|
6868
7600
|
}
|
|
6869
7601
|
},
|
|
6870
7602
|
handleListArticles
|
|
@@ -6872,29 +7604,29 @@ function registerListArticlesTool() {
|
|
|
6872
7604
|
}
|
|
6873
7605
|
|
|
6874
7606
|
// src/tools/list-blogs.ts
|
|
6875
|
-
import { z as
|
|
6876
|
-
var
|
|
6877
|
-
first:
|
|
6878
|
-
cursor:
|
|
7607
|
+
import { z as z29 } from "zod";
|
|
7608
|
+
var inputSchema27 = z29.object({
|
|
7609
|
+
first: z29.number().int().min(1).max(50).optional().default(10).describe("Number of blogs to return (1-50). Defaults to 10."),
|
|
7610
|
+
cursor: z29.string().optional().describe(
|
|
6879
7611
|
"Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
|
|
6880
7612
|
),
|
|
6881
|
-
query:
|
|
7613
|
+
query: z29.string().optional().describe(
|
|
6882
7614
|
"Search query to filter blogs. Supports Shopify search syntax. Examples: 'title:News', 'handle:company-blog'."
|
|
6883
7615
|
),
|
|
6884
|
-
sortKey:
|
|
7616
|
+
sortKey: z29.enum(["ID", "TITLE", "UPDATED_AT"]).optional().describe("Sort order for results. Defaults to ID.")
|
|
6885
7617
|
});
|
|
6886
|
-
var
|
|
6887
|
-
blogs:
|
|
6888
|
-
|
|
6889
|
-
id:
|
|
6890
|
-
title:
|
|
6891
|
-
handle:
|
|
6892
|
-
commentPolicy:
|
|
6893
|
-
articlesCount:
|
|
7618
|
+
var outputSchema27 = z29.object({
|
|
7619
|
+
blogs: z29.array(
|
|
7620
|
+
z29.object({
|
|
7621
|
+
id: z29.string().describe("Blog GID"),
|
|
7622
|
+
title: z29.string().describe("Blog title"),
|
|
7623
|
+
handle: z29.string().describe("URL handle/slug"),
|
|
7624
|
+
commentPolicy: z29.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy"),
|
|
7625
|
+
articlesCount: z29.number().describe("Number of articles in the blog")
|
|
6894
7626
|
})
|
|
6895
7627
|
),
|
|
6896
|
-
hasNextPage:
|
|
6897
|
-
endCursor:
|
|
7628
|
+
hasNextPage: z29.boolean().describe("Whether more blogs are available"),
|
|
7629
|
+
endCursor: z29.string().nullable().describe("Cursor for next page pagination")
|
|
6898
7630
|
});
|
|
6899
7631
|
var handleListBlogs = async (context, params) => {
|
|
6900
7632
|
log.debug(`Listing blogs on shop: ${context.shopDomain}`);
|
|
@@ -6932,12 +7664,19 @@ function registerListBlogsTool() {
|
|
|
6932
7664
|
name: "list-blogs",
|
|
6933
7665
|
title: "List Blogs",
|
|
6934
7666
|
description: "List all blogs with optional filtering and pagination. Use the query parameter to search blogs by title or handle. Returns blog ID, title, handle, and article count. Useful for: finding blogs to manage, auditing content, selecting a blog for new articles. **Typical workflow:** list-blogs \u2192 list-articles. **Prerequisites:** None. **Follow-ups:** create-article, update-blog, create-blog. Returns a paginated list of blog summaries with a response summary.",
|
|
6935
|
-
inputSchema:
|
|
6936
|
-
outputSchema:
|
|
7667
|
+
inputSchema: inputSchema27,
|
|
7668
|
+
outputSchema: outputSchema27,
|
|
6937
7669
|
category: "content",
|
|
6938
7670
|
relationships: {
|
|
6939
7671
|
relatedTools: ["create-blog", "update-blog", "list-articles"],
|
|
6940
7672
|
followUps: ["create-article", "update-blog", "create-blog"]
|
|
7673
|
+
},
|
|
7674
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7675
|
+
annotations: {
|
|
7676
|
+
readOnlyHint: true,
|
|
7677
|
+
destructiveHint: false,
|
|
7678
|
+
idempotentHint: true,
|
|
7679
|
+
openWorldHint: true
|
|
6941
7680
|
}
|
|
6942
7681
|
},
|
|
6943
7682
|
handleListBlogs
|
|
@@ -6945,40 +7684,40 @@ function registerListBlogsTool() {
|
|
|
6945
7684
|
}
|
|
6946
7685
|
|
|
6947
7686
|
// src/tools/list-collections.ts
|
|
6948
|
-
import { z as
|
|
6949
|
-
var
|
|
6950
|
-
first:
|
|
6951
|
-
after:
|
|
7687
|
+
import { z as z30 } from "zod";
|
|
7688
|
+
var inputSchema28 = z30.object({
|
|
7689
|
+
first: z30.number().int().min(1).max(50).optional().default(10).describe("Number of collections to return (1-50). Default: 10"),
|
|
7690
|
+
after: z30.string().optional().describe(
|
|
6952
7691
|
"Pagination cursor from previous response. Use the endCursor value from the previous page to get the next page of results."
|
|
6953
7692
|
),
|
|
6954
|
-
query:
|
|
7693
|
+
query: z30.string().optional().describe(
|
|
6955
7694
|
'Search query to filter collections. Searches across collection titles and handles. Example: "summer" finds collections with "summer" in the title or handle.'
|
|
6956
7695
|
),
|
|
6957
|
-
sortKey:
|
|
7696
|
+
sortKey: z30.enum(["TITLE", "UPDATED_AT", "ID", "RELEVANCE"]).optional().describe(
|
|
6958
7697
|
"Sort collections by: TITLE (alphabetical), UPDATED_AT (most recently updated first), ID (by collection ID), or RELEVANCE (best match when using query filter)"
|
|
6959
7698
|
)
|
|
6960
7699
|
});
|
|
6961
|
-
var
|
|
6962
|
-
collections:
|
|
6963
|
-
|
|
6964
|
-
id:
|
|
6965
|
-
title:
|
|
6966
|
-
handle:
|
|
6967
|
-
seo:
|
|
6968
|
-
title:
|
|
6969
|
-
description:
|
|
7700
|
+
var outputSchema28 = z30.object({
|
|
7701
|
+
collections: z30.array(
|
|
7702
|
+
z30.object({
|
|
7703
|
+
id: z30.string().describe("Collection GID"),
|
|
7704
|
+
title: z30.string().describe("Collection title"),
|
|
7705
|
+
handle: z30.string().describe("URL handle"),
|
|
7706
|
+
seo: z30.object({
|
|
7707
|
+
title: z30.string().nullable().describe("SEO title"),
|
|
7708
|
+
description: z30.string().nullable().describe("SEO description")
|
|
6970
7709
|
}),
|
|
6971
|
-
productsCount:
|
|
7710
|
+
productsCount: z30.number().describe("Number of products in collection")
|
|
6972
7711
|
})
|
|
6973
7712
|
),
|
|
6974
|
-
pageInfo:
|
|
6975
|
-
hasNextPage:
|
|
6976
|
-
endCursor:
|
|
7713
|
+
pageInfo: z30.object({
|
|
7714
|
+
hasNextPage: z30.boolean().describe("Whether more pages exist"),
|
|
7715
|
+
endCursor: z30.string().nullable().describe("Cursor for next page")
|
|
6977
7716
|
}),
|
|
6978
|
-
summary:
|
|
6979
|
-
totalReturned:
|
|
6980
|
-
hasMore:
|
|
6981
|
-
hint:
|
|
7717
|
+
summary: z30.object({
|
|
7718
|
+
totalReturned: z30.number().describe("Number of collections in this response"),
|
|
7719
|
+
hasMore: z30.boolean().describe("Whether more collections are available"),
|
|
7720
|
+
hint: z30.string().describe("Suggestion for next action")
|
|
6982
7721
|
}).describe("AI-friendly summary for context management")
|
|
6983
7722
|
});
|
|
6984
7723
|
var handleListCollections = async (context, params) => {
|
|
@@ -7005,13 +7744,20 @@ function registerListCollectionsTool() {
|
|
|
7005
7744
|
name: "list-collections",
|
|
7006
7745
|
title: "List Collections",
|
|
7007
7746
|
description: 'List and search collections with pagination and filtering. Use the query parameter for text search across collection titles and handles. Supports sorting by TITLE, UPDATED_AT, ID, or RELEVANCE. Returns up to 50 collections per page. Response includes summary with totalReturned, hasMore, and pagination hint. **Pagination:** Use "after" cursor from pageInfo.endCursor to get next page. **Follow-ups:** get-collection (for full details), update-collection, add-products-to-collection.',
|
|
7008
|
-
inputSchema:
|
|
7009
|
-
outputSchema:
|
|
7747
|
+
inputSchema: inputSchema28,
|
|
7748
|
+
outputSchema: outputSchema28,
|
|
7010
7749
|
// AI Agent Optimization (Story 5.5)
|
|
7011
7750
|
category: "collection",
|
|
7012
7751
|
relationships: {
|
|
7013
7752
|
relatedTools: ["get-collection"],
|
|
7014
7753
|
followUps: ["get-collection", "update-collection", "add-products-to-collection"]
|
|
7754
|
+
},
|
|
7755
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7756
|
+
annotations: {
|
|
7757
|
+
readOnlyHint: true,
|
|
7758
|
+
destructiveHint: false,
|
|
7759
|
+
idempotentHint: true,
|
|
7760
|
+
openWorldHint: true
|
|
7015
7761
|
}
|
|
7016
7762
|
},
|
|
7017
7763
|
handleListCollections
|
|
@@ -7019,47 +7765,47 @@ function registerListCollectionsTool() {
|
|
|
7019
7765
|
}
|
|
7020
7766
|
|
|
7021
7767
|
// src/tools/list-low-inventory.ts
|
|
7022
|
-
import { z as
|
|
7023
|
-
var
|
|
7024
|
-
threshold:
|
|
7768
|
+
import { z as z31 } from "zod";
|
|
7769
|
+
var inputSchema29 = z31.object({
|
|
7770
|
+
threshold: z31.number().int().min(0).default(10).describe(
|
|
7025
7771
|
"Products with total inventory at or below this number will be included. Default: 10. Use 0 to find only out-of-stock products."
|
|
7026
7772
|
),
|
|
7027
|
-
includeZero:
|
|
7773
|
+
includeZero: z31.boolean().default(true).describe(
|
|
7028
7774
|
"Include products with zero inventory. Default: true. Set to false to see only low-stock (not out-of-stock) products."
|
|
7029
7775
|
),
|
|
7030
|
-
status:
|
|
7776
|
+
status: z31.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
|
|
7031
7777
|
"Filter by product status. Default: ACTIVE. Use DRAFT to check pre-launch products, ARCHIVED for historical."
|
|
7032
7778
|
),
|
|
7033
|
-
first:
|
|
7034
|
-
after:
|
|
7779
|
+
first: z31.number().int().min(1).max(100).default(50).describe("Number of products to return per page. Default: 50, max: 100."),
|
|
7780
|
+
after: z31.string().optional().describe("Pagination cursor from previous response. Use pageInfo.endCursor to get next page.")
|
|
7035
7781
|
});
|
|
7036
|
-
var
|
|
7037
|
-
products:
|
|
7038
|
-
|
|
7039
|
-
productId:
|
|
7040
|
-
productTitle:
|
|
7041
|
-
status:
|
|
7042
|
-
totalInventory:
|
|
7043
|
-
isOutOfStock:
|
|
7044
|
-
variants:
|
|
7045
|
-
|
|
7046
|
-
variantId:
|
|
7047
|
-
variantTitle:
|
|
7048
|
-
sku:
|
|
7049
|
-
inventoryItemId:
|
|
7050
|
-
available:
|
|
7782
|
+
var outputSchema29 = z31.object({
|
|
7783
|
+
products: z31.array(
|
|
7784
|
+
z31.object({
|
|
7785
|
+
productId: z31.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
7786
|
+
productTitle: z31.string().describe("Product title"),
|
|
7787
|
+
status: z31.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
7788
|
+
totalInventory: z31.number().describe("Total inventory across all variants"),
|
|
7789
|
+
isOutOfStock: z31.boolean().describe("True if total inventory is zero"),
|
|
7790
|
+
variants: z31.array(
|
|
7791
|
+
z31.object({
|
|
7792
|
+
variantId: z31.string().describe("Variant GID"),
|
|
7793
|
+
variantTitle: z31.string().describe('Variant title (e.g., "Red / Large")'),
|
|
7794
|
+
sku: z31.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
7795
|
+
inventoryItemId: z31.string().describe("Inventory item GID"),
|
|
7796
|
+
available: z31.number().describe("Available quantity for sale")
|
|
7051
7797
|
})
|
|
7052
7798
|
).describe("Variant breakdown with inventory")
|
|
7053
7799
|
})
|
|
7054
7800
|
).describe("Products with low inventory, sorted by total inventory ascending"),
|
|
7055
|
-
pageInfo:
|
|
7056
|
-
hasNextPage:
|
|
7057
|
-
endCursor:
|
|
7801
|
+
pageInfo: z31.object({
|
|
7802
|
+
hasNextPage: z31.boolean().describe("Whether more products exist after this page"),
|
|
7803
|
+
endCursor: z31.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
|
|
7058
7804
|
}).describe("Pagination information"),
|
|
7059
|
-
summary:
|
|
7060
|
-
totalProducts:
|
|
7061
|
-
outOfStockCount:
|
|
7062
|
-
lowStockCount:
|
|
7805
|
+
summary: z31.object({
|
|
7806
|
+
totalProducts: z31.number().describe("Number of products in this response"),
|
|
7807
|
+
outOfStockCount: z31.number().describe("Number of products with zero inventory"),
|
|
7808
|
+
lowStockCount: z31.number().describe("Number of products with inventory > 0 but below threshold")
|
|
7063
7809
|
}).describe("Summary statistics")
|
|
7064
7810
|
});
|
|
7065
7811
|
var handleListLowInventory = async (context, params) => {
|
|
@@ -7083,44 +7829,125 @@ function registerListLowInventoryTool() {
|
|
|
7083
7829
|
name: "list-low-inventory",
|
|
7084
7830
|
title: "List Low Inventory",
|
|
7085
7831
|
description: 'Find products with low or zero inventory that may need restocking. Returns products sorted by inventory level (lowest first). Use threshold parameter to customize what counts as "low stock" (default: 10 units). Includes variant breakdown and summary statistics showing how many products are out-of-stock vs. low-stock. Perfect for daily inventory checks, restocking alerts, and identifying products at risk of stockout. Use includeZero=false to exclude completely sold-out items. Use status=DRAFT to check pre-launch products before publishing. **Typical workflow:** list-low-inventory \u2192 get-inventory \u2192 update-inventory. **Prerequisites:** None. **Follow-ups:** get-inventory, update-inventory. Returns paginated products with inventory levels and summary statistics.',
|
|
7086
|
-
inputSchema:
|
|
7087
|
-
outputSchema:
|
|
7832
|
+
inputSchema: inputSchema29,
|
|
7833
|
+
outputSchema: outputSchema29,
|
|
7088
7834
|
category: "inventory",
|
|
7089
7835
|
relationships: {
|
|
7090
7836
|
relatedTools: ["get-inventory", "update-inventory", "get-bulk-inventory"],
|
|
7091
7837
|
followUps: ["get-inventory", "update-inventory"]
|
|
7838
|
+
},
|
|
7839
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7840
|
+
annotations: {
|
|
7841
|
+
readOnlyHint: true,
|
|
7842
|
+
destructiveHint: false,
|
|
7843
|
+
idempotentHint: true,
|
|
7844
|
+
openWorldHint: true
|
|
7092
7845
|
}
|
|
7093
7846
|
},
|
|
7094
7847
|
handleListLowInventory
|
|
7095
7848
|
);
|
|
7096
7849
|
}
|
|
7097
7850
|
|
|
7851
|
+
// src/tools/list-markets.ts
|
|
7852
|
+
import { z as z32 } from "zod";
|
|
7853
|
+
var inputSchema30 = z32.object({
|
|
7854
|
+
first: z32.number().int().min(1).max(50).optional().default(10).describe("Number of markets to return (1-50). Default: 10"),
|
|
7855
|
+
after: z32.string().optional().describe(
|
|
7856
|
+
"Pagination cursor from previous response. Use the endCursor value from the previous page to get the next page of results."
|
|
7857
|
+
)
|
|
7858
|
+
});
|
|
7859
|
+
var outputSchema30 = z32.object({
|
|
7860
|
+
markets: z32.array(
|
|
7861
|
+
z32.object({
|
|
7862
|
+
id: z32.string().describe("Market GID"),
|
|
7863
|
+
name: z32.string().describe("Market name (not shown to customers)"),
|
|
7864
|
+
handle: z32.string().describe("URL handle"),
|
|
7865
|
+
enabled: z32.boolean().describe("Whether the market is enabled"),
|
|
7866
|
+
status: z32.enum(["ACTIVE", "INACTIVE"]).describe("Market status"),
|
|
7867
|
+
type: z32.string().describe("Market type (PRIMARY, SINGLE_COUNTRY, MULTI_COUNTRY, CONTINENT)"),
|
|
7868
|
+
currencySettings: z32.object({
|
|
7869
|
+
baseCurrency: z32.object({
|
|
7870
|
+
currencyCode: z32.string().describe("Base currency code (e.g., USD, EUR)")
|
|
7871
|
+
}),
|
|
7872
|
+
localCurrencies: z32.boolean().describe("Whether local currencies are enabled")
|
|
7873
|
+
}).optional()
|
|
7874
|
+
})
|
|
7875
|
+
),
|
|
7876
|
+
pageInfo: z32.object({
|
|
7877
|
+
hasNextPage: z32.boolean().describe("Whether more pages exist"),
|
|
7878
|
+
endCursor: z32.string().nullable().describe("Cursor for next page")
|
|
7879
|
+
}),
|
|
7880
|
+
summary: z32.object({
|
|
7881
|
+
totalReturned: z32.number().describe("Number of markets in this response"),
|
|
7882
|
+
hasMore: z32.boolean().describe("Whether more markets are available"),
|
|
7883
|
+
hint: z32.string().describe("Suggestion for next action")
|
|
7884
|
+
}).describe("AI-friendly summary for context management")
|
|
7885
|
+
});
|
|
7886
|
+
var handleListMarkets = async (context, params) => {
|
|
7887
|
+
log.debug(`Listing markets on shop: ${context.shopDomain}`);
|
|
7888
|
+
const result = await listMarkets(params.first, params.after);
|
|
7889
|
+
log.debug(`Listed ${result.markets.length} markets via tool`);
|
|
7890
|
+
return {
|
|
7891
|
+
...result,
|
|
7892
|
+
summary: {
|
|
7893
|
+
totalReturned: result.markets.length,
|
|
7894
|
+
hasMore: result.pageInfo.hasNextPage,
|
|
7895
|
+
hint: result.pageInfo.hasNextPage ? `Use "after" parameter with value "${result.pageInfo.endCursor}" to fetch next page` : "All markets returned"
|
|
7896
|
+
}
|
|
7897
|
+
};
|
|
7898
|
+
};
|
|
7899
|
+
function registerListMarketsTool() {
|
|
7900
|
+
registerContextAwareTool(
|
|
7901
|
+
{
|
|
7902
|
+
name: "list-markets",
|
|
7903
|
+
title: "List Markets",
|
|
7904
|
+
description: 'List all configured markets in the Shopify store with pagination. Markets enable regional targeting for international stores. Returns market id, name, handle, status, type, and currency settings. Response includes summary with totalReturned, hasMore, and pagination hint. **Pagination:** Use "after" cursor from pageInfo.endCursor to get next page. **Prerequisites:** Store must have Markets feature enabled. **Follow-ups:** get-market (for full details including web presences), create-market.',
|
|
7905
|
+
inputSchema: inputSchema30,
|
|
7906
|
+
outputSchema: outputSchema30,
|
|
7907
|
+
// AI Agent Optimization
|
|
7908
|
+
category: "market",
|
|
7909
|
+
relationships: {
|
|
7910
|
+
relatedTools: ["get-market"],
|
|
7911
|
+
followUps: ["get-market", "create-market", "update-market"]
|
|
7912
|
+
},
|
|
7913
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7914
|
+
annotations: {
|
|
7915
|
+
readOnlyHint: true,
|
|
7916
|
+
destructiveHint: false,
|
|
7917
|
+
idempotentHint: true,
|
|
7918
|
+
openWorldHint: true
|
|
7919
|
+
}
|
|
7920
|
+
},
|
|
7921
|
+
handleListMarkets
|
|
7922
|
+
);
|
|
7923
|
+
}
|
|
7924
|
+
|
|
7098
7925
|
// src/tools/list-pages.ts
|
|
7099
|
-
import { z as
|
|
7100
|
-
var
|
|
7101
|
-
first:
|
|
7926
|
+
import { z as z33 } from "zod";
|
|
7927
|
+
var inputSchema31 = z33.object({
|
|
7928
|
+
first: z33.number().int().min(1).max(50).optional().describe(
|
|
7102
7929
|
"Number of pages to return per request. Default: 10, Maximum: 50. Use pagination (cursor) to retrieve more results."
|
|
7103
7930
|
),
|
|
7104
|
-
cursor:
|
|
7931
|
+
cursor: z33.string().optional().describe(
|
|
7105
7932
|
"Pagination cursor from a previous list-pages response (endCursor). Use this to get the next page of results."
|
|
7106
7933
|
),
|
|
7107
|
-
query:
|
|
7934
|
+
query: z33.string().optional().describe(
|
|
7108
7935
|
"Search query to filter pages. Supports Shopify search syntax. Examples: 'title:About', 'handle:contact', 'created_at:>2024-01-01'."
|
|
7109
7936
|
),
|
|
7110
|
-
sortKey:
|
|
7937
|
+
sortKey: z33.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT"]).optional().describe("Field to sort pages by. Options: ID (default), TITLE, UPDATED_AT, PUBLISHED_AT.")
|
|
7111
7938
|
});
|
|
7112
|
-
var
|
|
7113
|
-
pages:
|
|
7114
|
-
|
|
7115
|
-
id:
|
|
7116
|
-
title:
|
|
7117
|
-
handle:
|
|
7118
|
-
isPublished:
|
|
7119
|
-
updatedAt:
|
|
7939
|
+
var outputSchema31 = z33.object({
|
|
7940
|
+
pages: z33.array(
|
|
7941
|
+
z33.object({
|
|
7942
|
+
id: z33.string().describe("Page GID"),
|
|
7943
|
+
title: z33.string().describe("Page title"),
|
|
7944
|
+
handle: z33.string().describe("URL handle/slug"),
|
|
7945
|
+
isPublished: z33.boolean().describe("Whether page is visible"),
|
|
7946
|
+
updatedAt: z33.string().describe("ISO timestamp of last update")
|
|
7120
7947
|
})
|
|
7121
7948
|
),
|
|
7122
|
-
hasNextPage:
|
|
7123
|
-
endCursor:
|
|
7949
|
+
hasNextPage: z33.boolean().describe("Whether more pages exist"),
|
|
7950
|
+
endCursor: z33.string().nullable().describe("Cursor for next page of results")
|
|
7124
7951
|
});
|
|
7125
7952
|
var handleListPages = async (context, params) => {
|
|
7126
7953
|
log.debug(`Listing pages on shop: ${context.shopDomain}`);
|
|
@@ -7146,12 +7973,19 @@ function registerListPagesTool() {
|
|
|
7146
7973
|
name: "list-pages",
|
|
7147
7974
|
title: "List Pages",
|
|
7148
7975
|
description: "List all pages with optional filtering and pagination. Use the query parameter to search pages by title or handle. Returns page ID, title, handle, and publish status. Use pagination (cursor) for stores with many pages. Useful for: finding pages to edit, auditing content, bulk operations. **Typical workflow:** list-pages \u2192 get-page. **Prerequisites:** None. **Follow-ups:** get-page, update-page, create-page. Returns a paginated list of page summaries with a response summary.",
|
|
7149
|
-
inputSchema:
|
|
7150
|
-
outputSchema:
|
|
7976
|
+
inputSchema: inputSchema31,
|
|
7977
|
+
outputSchema: outputSchema31,
|
|
7151
7978
|
category: "content",
|
|
7152
7979
|
relationships: {
|
|
7153
7980
|
relatedTools: ["get-page", "create-page"],
|
|
7154
7981
|
followUps: ["get-page", "update-page", "create-page"]
|
|
7982
|
+
},
|
|
7983
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7984
|
+
annotations: {
|
|
7985
|
+
readOnlyHint: true,
|
|
7986
|
+
destructiveHint: false,
|
|
7987
|
+
idempotentHint: true,
|
|
7988
|
+
openWorldHint: true
|
|
7155
7989
|
}
|
|
7156
7990
|
},
|
|
7157
7991
|
handleListPages
|
|
@@ -7159,46 +7993,46 @@ function registerListPagesTool() {
|
|
|
7159
7993
|
}
|
|
7160
7994
|
|
|
7161
7995
|
// src/tools/list-products.ts
|
|
7162
|
-
import { z as
|
|
7163
|
-
var
|
|
7164
|
-
first:
|
|
7165
|
-
after:
|
|
7166
|
-
query:
|
|
7996
|
+
import { z as z34 } from "zod";
|
|
7997
|
+
var inputSchema32 = z34.object({
|
|
7998
|
+
first: z34.number().int().min(1).max(50).optional().describe("Number of products to return (1-50). Default: 10"),
|
|
7999
|
+
after: z34.string().optional().describe("Pagination cursor from previous response's endCursor. Omit for first page."),
|
|
8000
|
+
query: z34.string().optional().describe(
|
|
7167
8001
|
'Search query for title, description, tags. Example: "summer dress", "organic cotton"'
|
|
7168
8002
|
),
|
|
7169
|
-
status:
|
|
8003
|
+
status: z34.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
|
|
7170
8004
|
"Filter by product status. ACTIVE = published, DRAFT = not published, ARCHIVED = hidden"
|
|
7171
8005
|
),
|
|
7172
|
-
vendor:
|
|
7173
|
-
productType:
|
|
7174
|
-
sortBy:
|
|
7175
|
-
sortOrder:
|
|
8006
|
+
vendor: z34.string().optional().describe('Filter by exact vendor name. Example: "Acme Corp", "Nike"'),
|
|
8007
|
+
productType: z34.string().optional().describe('Filter by exact product type. Example: "T-Shirts", "Shoes"'),
|
|
8008
|
+
sortBy: z34.enum(["TITLE", "CREATED_AT", "UPDATED_AT", "INVENTORY_TOTAL"]).optional().describe("Sort field. TITLE, CREATED_AT (default), UPDATED_AT, or INVENTORY_TOTAL"),
|
|
8009
|
+
sortOrder: z34.enum(["ASC", "DESC"]).optional().describe("Sort direction. ASC (default) for ascending, DESC for descending")
|
|
7176
8010
|
});
|
|
7177
|
-
var
|
|
7178
|
-
products:
|
|
7179
|
-
|
|
7180
|
-
id:
|
|
7181
|
-
title:
|
|
7182
|
-
handle:
|
|
7183
|
-
status:
|
|
7184
|
-
vendor:
|
|
7185
|
-
productType:
|
|
7186
|
-
totalInventory:
|
|
7187
|
-
primaryVariantPrice:
|
|
7188
|
-
imageUrl:
|
|
8011
|
+
var outputSchema32 = z34.object({
|
|
8012
|
+
products: z34.array(
|
|
8013
|
+
z34.object({
|
|
8014
|
+
id: z34.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
8015
|
+
title: z34.string().describe("Product title"),
|
|
8016
|
+
handle: z34.string().describe("URL handle/slug"),
|
|
8017
|
+
status: z34.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
8018
|
+
vendor: z34.string().describe("Vendor/brand name"),
|
|
8019
|
+
productType: z34.string().describe("Product type"),
|
|
8020
|
+
totalInventory: z34.number().describe("Total inventory across all variants"),
|
|
8021
|
+
primaryVariantPrice: z34.string().describe('Price of the first variant (e.g., "29.99")'),
|
|
8022
|
+
imageUrl: z34.string().nullable().describe("First image URL or null if no images")
|
|
7189
8023
|
})
|
|
7190
8024
|
).describe("Array of product summaries"),
|
|
7191
|
-
pageInfo:
|
|
7192
|
-
hasNextPage:
|
|
7193
|
-
hasPreviousPage:
|
|
7194
|
-
startCursor:
|
|
7195
|
-
endCursor:
|
|
8025
|
+
pageInfo: z34.object({
|
|
8026
|
+
hasNextPage: z34.boolean().describe("Whether more products exist after this page"),
|
|
8027
|
+
hasPreviousPage: z34.boolean().describe("Whether products exist before this page"),
|
|
8028
|
+
startCursor: z34.string().nullable().describe("Cursor for the first item in this page"),
|
|
8029
|
+
endCursor: z34.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
|
|
7196
8030
|
}).describe("Pagination information"),
|
|
7197
|
-
totalCount:
|
|
7198
|
-
summary:
|
|
7199
|
-
totalReturned:
|
|
7200
|
-
hasMore:
|
|
7201
|
-
hint:
|
|
8031
|
+
totalCount: z34.number().describe("Number of products returned in this page"),
|
|
8032
|
+
summary: z34.object({
|
|
8033
|
+
totalReturned: z34.number().describe("Number of products in this response"),
|
|
8034
|
+
hasMore: z34.boolean().describe("Whether more products are available"),
|
|
8035
|
+
hint: z34.string().describe("Suggestion for next action")
|
|
7202
8036
|
}).describe("AI-friendly summary for context management")
|
|
7203
8037
|
});
|
|
7204
8038
|
var handleListProducts = async (context, params) => {
|
|
@@ -7232,13 +8066,20 @@ function registerListProductsTool() {
|
|
|
7232
8066
|
name: "list-products",
|
|
7233
8067
|
title: "List Products",
|
|
7234
8068
|
description: 'List products from the Shopify store with pagination and filtering. Returns product summaries including title, status, vendor, type, price, and inventory. Supports filtering by status (ACTIVE/DRAFT/ARCHIVED), vendor, productType, and search query. Sort by TITLE, CREATED_AT, UPDATED_AT, or INVENTORY_TOTAL. Response includes summary with totalReturned, hasMore, and pagination hint. **Pagination:** Use "after" cursor from pageInfo.endCursor to get next page. **Follow-ups:** get-product (for full details), update-product, delete-product.',
|
|
7235
|
-
inputSchema:
|
|
7236
|
-
outputSchema:
|
|
8069
|
+
inputSchema: inputSchema32,
|
|
8070
|
+
outputSchema: outputSchema32,
|
|
7237
8071
|
// AI Agent Optimization (Story 5.5)
|
|
7238
8072
|
category: "product",
|
|
7239
8073
|
relationships: {
|
|
7240
8074
|
relatedTools: ["get-product"],
|
|
7241
8075
|
followUps: ["get-product", "update-product", "delete-product"]
|
|
8076
|
+
},
|
|
8077
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8078
|
+
annotations: {
|
|
8079
|
+
readOnlyHint: true,
|
|
8080
|
+
destructiveHint: false,
|
|
8081
|
+
idempotentHint: true,
|
|
8082
|
+
openWorldHint: true
|
|
7242
8083
|
}
|
|
7243
8084
|
},
|
|
7244
8085
|
handleListProducts
|
|
@@ -7246,30 +8087,30 @@ function registerListProductsTool() {
|
|
|
7246
8087
|
}
|
|
7247
8088
|
|
|
7248
8089
|
// src/tools/list-redirects.ts
|
|
7249
|
-
import { z as
|
|
7250
|
-
var
|
|
7251
|
-
first:
|
|
7252
|
-
cursor:
|
|
8090
|
+
import { z as z35 } from "zod";
|
|
8091
|
+
var inputSchema33 = z35.object({
|
|
8092
|
+
first: z35.number().int().min(1).max(50).optional().default(10).describe("Number of redirects to return (1-50, default: 10)."),
|
|
8093
|
+
cursor: z35.string().optional().describe(
|
|
7253
8094
|
"Pagination cursor from previous response (endCursor). Use this to fetch the next page of results."
|
|
7254
8095
|
),
|
|
7255
|
-
query:
|
|
8096
|
+
query: z35.string().optional().describe(
|
|
7256
8097
|
"Search filter. Supports 'path:' and 'target:' prefixes. Examples: 'path:/old-' to find redirects starting with /old-, 'target:/products/' to find redirects pointing to /products/."
|
|
7257
8098
|
)
|
|
7258
8099
|
});
|
|
7259
|
-
var
|
|
7260
|
-
redirects:
|
|
7261
|
-
|
|
7262
|
-
id:
|
|
7263
|
-
path:
|
|
7264
|
-
target:
|
|
8100
|
+
var outputSchema33 = z35.object({
|
|
8101
|
+
redirects: z35.array(
|
|
8102
|
+
z35.object({
|
|
8103
|
+
id: z35.string().describe("Redirect GID"),
|
|
8104
|
+
path: z35.string().describe("Source path"),
|
|
8105
|
+
target: z35.string().describe("Target URL")
|
|
7265
8106
|
})
|
|
7266
8107
|
),
|
|
7267
|
-
hasNextPage:
|
|
7268
|
-
endCursor:
|
|
7269
|
-
summary:
|
|
7270
|
-
totalReturned:
|
|
7271
|
-
hasMore:
|
|
7272
|
-
hint:
|
|
8108
|
+
hasNextPage: z35.boolean().describe("Whether more results exist"),
|
|
8109
|
+
endCursor: z35.string().nullable().describe("Cursor for fetching next page"),
|
|
8110
|
+
summary: z35.object({
|
|
8111
|
+
totalReturned: z35.number().describe("Number of redirects in this response"),
|
|
8112
|
+
hasMore: z35.boolean().describe("Whether more redirects are available"),
|
|
8113
|
+
hint: z35.string().describe("Suggestion for next action")
|
|
7273
8114
|
}).describe("AI-friendly summary for context management")
|
|
7274
8115
|
});
|
|
7275
8116
|
var handleListRedirects = async (context, params) => {
|
|
@@ -7307,13 +8148,20 @@ function registerListRedirectsTool() {
|
|
|
7307
8148
|
name: "list-redirects",
|
|
7308
8149
|
title: "List URL Redirects",
|
|
7309
8150
|
description: `List all URL redirects with optional filtering and pagination. Use query parameter to search by path or target (supports 'path:' and 'target:' prefixes). Response includes summary with totalReturned, hasMore, and pagination hint. **Pagination:** Use "cursor" with endCursor value to get next page. **Follow-ups:** delete-redirect to remove unwanted redirects.`,
|
|
7310
|
-
inputSchema:
|
|
7311
|
-
outputSchema:
|
|
8151
|
+
inputSchema: inputSchema33,
|
|
8152
|
+
outputSchema: outputSchema33,
|
|
7312
8153
|
// AI Agent Optimization (Story 5.5)
|
|
7313
8154
|
category: "seo",
|
|
7314
8155
|
relationships: {
|
|
7315
8156
|
relatedTools: ["create-redirect"],
|
|
7316
8157
|
followUps: ["delete-redirect"]
|
|
8158
|
+
},
|
|
8159
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8160
|
+
annotations: {
|
|
8161
|
+
readOnlyHint: true,
|
|
8162
|
+
destructiveHint: false,
|
|
8163
|
+
idempotentHint: true,
|
|
8164
|
+
openWorldHint: true
|
|
7317
8165
|
}
|
|
7318
8166
|
},
|
|
7319
8167
|
handleListRedirects
|
|
@@ -7321,18 +8169,18 @@ function registerListRedirectsTool() {
|
|
|
7321
8169
|
}
|
|
7322
8170
|
|
|
7323
8171
|
// src/tools/remove-products-from-collection.ts
|
|
7324
|
-
import { z as
|
|
7325
|
-
var
|
|
7326
|
-
collectionId:
|
|
8172
|
+
import { z as z36 } from "zod";
|
|
8173
|
+
var inputSchema34 = z36.object({
|
|
8174
|
+
collectionId: collectionIdSchema.describe(
|
|
7327
8175
|
'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
|
|
7328
8176
|
),
|
|
7329
|
-
productIds:
|
|
8177
|
+
productIds: z36.array(productIdSchema).min(1).max(250).describe(
|
|
7330
8178
|
'Array of product GIDs to remove (e.g., ["gid://shopify/Product/123", "gid://shopify/Product/456"]). Maximum 250 products per call. Use list-products to find valid product IDs.'
|
|
7331
8179
|
)
|
|
7332
8180
|
});
|
|
7333
|
-
var
|
|
7334
|
-
success:
|
|
7335
|
-
removedCount:
|
|
8181
|
+
var outputSchema34 = z36.object({
|
|
8182
|
+
success: z36.boolean().describe("Whether the operation succeeded"),
|
|
8183
|
+
removedCount: z36.number().describe("Number of products removed from the collection")
|
|
7336
8184
|
});
|
|
7337
8185
|
var handleRemoveProductsFromCollection = async (context, params) => {
|
|
7338
8186
|
log.debug(
|
|
@@ -7362,13 +8210,22 @@ function registerRemoveProductsFromCollectionTool() {
|
|
|
7362
8210
|
name: "remove-products-from-collection",
|
|
7363
8211
|
title: "Remove Products from Collection",
|
|
7364
8212
|
description: "Remove products from a collection by their product IDs. This does NOT delete the products - they remain in the catalog but are no longer part of this collection. Maximum 250 products per call. **Prerequisites:** get-collection to verify collection contents before removing.",
|
|
7365
|
-
inputSchema:
|
|
7366
|
-
outputSchema:
|
|
8213
|
+
inputSchema: inputSchema34,
|
|
8214
|
+
outputSchema: outputSchema34,
|
|
7367
8215
|
// AI Agent Optimization (Story 5.5)
|
|
7368
8216
|
category: "collection",
|
|
7369
8217
|
relationships: {
|
|
7370
8218
|
relatedTools: ["add-products-to-collection", "get-collection"],
|
|
7371
8219
|
prerequisites: ["get-collection"]
|
|
8220
|
+
},
|
|
8221
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8222
|
+
annotations: {
|
|
8223
|
+
readOnlyHint: false,
|
|
8224
|
+
destructiveHint: false,
|
|
8225
|
+
// Does not delete products, just removes from collection
|
|
8226
|
+
idempotentHint: true,
|
|
8227
|
+
// Removing already-removed products is idempotent
|
|
8228
|
+
openWorldHint: true
|
|
7372
8229
|
}
|
|
7373
8230
|
},
|
|
7374
8231
|
handleRemoveProductsFromCollection
|
|
@@ -7376,7 +8233,7 @@ function registerRemoveProductsFromCollectionTool() {
|
|
|
7376
8233
|
}
|
|
7377
8234
|
|
|
7378
8235
|
// src/tools/reorder-product-images.ts
|
|
7379
|
-
import { z as
|
|
8236
|
+
import { z as z37 } from "zod";
|
|
7380
8237
|
var PRODUCT_REORDER_MEDIA_MUTATION = `
|
|
7381
8238
|
mutation ProductReorderMedia($id: ID!, $moves: [MoveInput!]!) {
|
|
7382
8239
|
productReorderMedia(id: $id, moves: $moves) {
|
|
@@ -7391,23 +8248,23 @@ var PRODUCT_REORDER_MEDIA_MUTATION = `
|
|
|
7391
8248
|
}
|
|
7392
8249
|
}
|
|
7393
8250
|
`;
|
|
7394
|
-
var moveSchema =
|
|
7395
|
-
id:
|
|
7396
|
-
newPosition:
|
|
8251
|
+
var moveSchema = z37.object({
|
|
8252
|
+
id: imageIdSchema.describe('Image GID to move (e.g., "gid://shopify/MediaImage/123")'),
|
|
8253
|
+
newPosition: z37.number().int().min(0).describe("Zero-based target position. Position 0 is the featured image.")
|
|
7397
8254
|
});
|
|
7398
|
-
var
|
|
7399
|
-
productId:
|
|
8255
|
+
var inputSchema35 = z37.object({
|
|
8256
|
+
productId: productIdSchema.describe(
|
|
7400
8257
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
|
|
7401
8258
|
),
|
|
7402
|
-
moves:
|
|
8259
|
+
moves: z37.array(moveSchema).min(1).describe(
|
|
7403
8260
|
"Array of move operations. Each move specifies an image ID and its new position. Only include images whose position is actually changing. Positions are 0-indexed (position 0 is the featured/hero image)."
|
|
7404
8261
|
)
|
|
7405
8262
|
});
|
|
7406
|
-
var
|
|
7407
|
-
success:
|
|
7408
|
-
jobId:
|
|
7409
|
-
jobDone:
|
|
7410
|
-
message:
|
|
8263
|
+
var outputSchema35 = z37.object({
|
|
8264
|
+
success: z37.boolean().describe("Whether the reorder operation was initiated successfully"),
|
|
8265
|
+
jobId: z37.string().nullable().describe("Background job ID for tracking (may be null if processed immediately)"),
|
|
8266
|
+
jobDone: z37.boolean().describe("Whether the job completed immediately"),
|
|
8267
|
+
message: z37.string().describe("Human-readable message describing the result")
|
|
7411
8268
|
});
|
|
7412
8269
|
var handleReorderProductImages = async (context, params) => {
|
|
7413
8270
|
log.debug(`Reordering images on shop: ${context.shopDomain}`);
|
|
@@ -7475,13 +8332,21 @@ function registerReorderProductImagesTool() {
|
|
|
7475
8332
|
name: "reorder-product-images",
|
|
7476
8333
|
title: "Reorder Product Images",
|
|
7477
8334
|
description: "Change the display order of product images. The first image (position 0) is typically the featured/hero image shown in product listings. Provide an array of move operations specifying which image IDs should move to which positions. Positions are 0-indexed. This operation runs asynchronously in Shopify's background. **Prerequisites:** get-product to obtain current image IDs and their positions.",
|
|
7478
|
-
inputSchema:
|
|
7479
|
-
outputSchema:
|
|
8335
|
+
inputSchema: inputSchema35,
|
|
8336
|
+
outputSchema: outputSchema35,
|
|
7480
8337
|
// AI Agent Optimization (Story 5.5)
|
|
7481
8338
|
category: "media",
|
|
7482
8339
|
relationships: {
|
|
7483
8340
|
relatedTools: ["add-product-image", "update-product-image"],
|
|
7484
8341
|
prerequisites: ["get-product"]
|
|
8342
|
+
},
|
|
8343
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8344
|
+
annotations: {
|
|
8345
|
+
readOnlyHint: false,
|
|
8346
|
+
destructiveHint: false,
|
|
8347
|
+
idempotentHint: true,
|
|
8348
|
+
// Same order produces same result
|
|
8349
|
+
openWorldHint: true
|
|
7485
8350
|
}
|
|
7486
8351
|
},
|
|
7487
8352
|
handleReorderProductImages
|
|
@@ -7489,45 +8354,45 @@ function registerReorderProductImagesTool() {
|
|
|
7489
8354
|
}
|
|
7490
8355
|
|
|
7491
8356
|
// src/tools/set-product-metafields.ts
|
|
7492
|
-
import { z as
|
|
8357
|
+
import { z as z38 } from "zod";
|
|
7493
8358
|
var MAX_METAFIELDS_PER_CALL = 25;
|
|
7494
|
-
var metafieldInputSchema =
|
|
7495
|
-
namespace:
|
|
8359
|
+
var metafieldInputSchema = z38.object({
|
|
8360
|
+
namespace: z38.string().min(1).describe(
|
|
7496
8361
|
'Metafield namespace (grouping identifier, e.g., "custom", "seo", "my_app"). Use consistent namespaces to organize related metafields.'
|
|
7497
8362
|
),
|
|
7498
|
-
key:
|
|
8363
|
+
key: z38.string().min(1).describe(
|
|
7499
8364
|
'Metafield key (field name, e.g., "color_hex", "schema_markup"). Keys should be lowercase with underscores, unique within a namespace.'
|
|
7500
8365
|
),
|
|
7501
|
-
value:
|
|
7502
|
-
type:
|
|
8366
|
+
value: z38.string().describe("The value to store. All values are stored as strings; JSON should be stringified."),
|
|
8367
|
+
type: z38.string().min(1).describe(
|
|
7503
8368
|
'Metafield type. Required for creating new metafields. Common types: "single_line_text_field" (short text), "multi_line_text_field" (long text), "number_integer", "number_decimal", "boolean", "json", "url", "date", "date_time".'
|
|
7504
8369
|
)
|
|
7505
8370
|
});
|
|
7506
|
-
var
|
|
7507
|
-
productId:
|
|
8371
|
+
var inputSchema36 = z38.object({
|
|
8372
|
+
productId: productIdSchema.describe(
|
|
7508
8373
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
7509
8374
|
),
|
|
7510
|
-
metafields:
|
|
8375
|
+
metafields: z38.array(metafieldInputSchema).min(1, "At least one metafield is required").max(
|
|
7511
8376
|
MAX_METAFIELDS_PER_CALL,
|
|
7512
8377
|
`Maximum ${MAX_METAFIELDS_PER_CALL} metafields per call. Split into multiple calls for larger batches.`
|
|
7513
8378
|
).describe(
|
|
7514
8379
|
`Array of metafields to create or update (max ${MAX_METAFIELDS_PER_CALL} per call). Existing metafields with matching namespace+key are updated; new ones are created.`
|
|
7515
8380
|
)
|
|
7516
8381
|
});
|
|
7517
|
-
var
|
|
7518
|
-
success:
|
|
7519
|
-
metafields:
|
|
7520
|
-
|
|
7521
|
-
id:
|
|
7522
|
-
namespace:
|
|
7523
|
-
key:
|
|
7524
|
-
value:
|
|
7525
|
-
type:
|
|
7526
|
-
createdAt:
|
|
7527
|
-
updatedAt:
|
|
8382
|
+
var outputSchema36 = z38.object({
|
|
8383
|
+
success: z38.boolean().describe("Whether the operation succeeded"),
|
|
8384
|
+
metafields: z38.array(
|
|
8385
|
+
z38.object({
|
|
8386
|
+
id: z38.string().describe("Metafield GID"),
|
|
8387
|
+
namespace: z38.string().describe("Metafield namespace"),
|
|
8388
|
+
key: z38.string().describe("Metafield key"),
|
|
8389
|
+
value: z38.string().describe("Metafield value"),
|
|
8390
|
+
type: z38.string().describe("Metafield type"),
|
|
8391
|
+
createdAt: z38.string().describe("Creation timestamp (ISO 8601)"),
|
|
8392
|
+
updatedAt: z38.string().describe("Last update timestamp (ISO 8601)")
|
|
7528
8393
|
})
|
|
7529
8394
|
).describe("Created or updated metafields"),
|
|
7530
|
-
count:
|
|
8395
|
+
count: z38.number().describe("Number of metafields processed")
|
|
7531
8396
|
});
|
|
7532
8397
|
var handleSetProductMetafields = async (context, params) => {
|
|
7533
8398
|
log.debug(
|
|
@@ -7563,14 +8428,22 @@ function registerSetProductMetafieldsTool() {
|
|
|
7563
8428
|
name: "set-product-metafields",
|
|
7564
8429
|
title: "Set Product Metafields",
|
|
7565
8430
|
description: `Create or update up to ${MAX_METAFIELDS_PER_CALL} metafields on a product in a single operation. Each metafield requires: namespace (grouping identifier), key (field name), value (the data), and type. Existing metafields with matching namespace+key are updated; new ones are created. Common types: single_line_text_field, multi_line_text_field, number_integer, number_decimal, boolean, json. **Typical workflow:** create-product \u2192 set-product-metafields (add custom data). **Prerequisites:** get-product to obtain product ID, optionally get-product-metafields to check existing values. **Follow-ups:** get-product-metafields to verify changes.`,
|
|
7566
|
-
inputSchema:
|
|
7567
|
-
outputSchema:
|
|
8431
|
+
inputSchema: inputSchema36,
|
|
8432
|
+
outputSchema: outputSchema36,
|
|
7568
8433
|
// AI Agent Optimization (Story 5.5)
|
|
7569
8434
|
category: "seo",
|
|
7570
8435
|
relationships: {
|
|
7571
8436
|
relatedTools: ["get-product", "get-product-metafields"],
|
|
7572
8437
|
prerequisites: ["get-product"],
|
|
7573
8438
|
followUps: ["get-product-metafields"]
|
|
8439
|
+
},
|
|
8440
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8441
|
+
annotations: {
|
|
8442
|
+
readOnlyHint: false,
|
|
8443
|
+
destructiveHint: false,
|
|
8444
|
+
idempotentHint: true,
|
|
8445
|
+
// Same values produce same result
|
|
8446
|
+
openWorldHint: true
|
|
7574
8447
|
}
|
|
7575
8448
|
},
|
|
7576
8449
|
handleSetProductMetafields
|
|
@@ -7578,47 +8451,47 @@ function registerSetProductMetafieldsTool() {
|
|
|
7578
8451
|
}
|
|
7579
8452
|
|
|
7580
8453
|
// src/tools/update-article.ts
|
|
7581
|
-
import { z as
|
|
7582
|
-
var
|
|
7583
|
-
id:
|
|
8454
|
+
import { z as z39 } from "zod";
|
|
8455
|
+
var inputSchema37 = z39.object({
|
|
8456
|
+
id: articleIdSchema.describe(
|
|
7584
8457
|
'The article ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Article/12345". Use list-articles to find article IDs.'
|
|
7585
8458
|
),
|
|
7586
|
-
title:
|
|
7587
|
-
authorName:
|
|
7588
|
-
body:
|
|
7589
|
-
summary:
|
|
7590
|
-
tags:
|
|
8459
|
+
title: z39.string().optional().describe("The new title for the article. Only provided if changing the title."),
|
|
8460
|
+
authorName: z39.string().optional().describe("The new author name for the article."),
|
|
8461
|
+
body: z39.string().optional().describe("The new HTML body content of the article. Supports HTML markup for formatting."),
|
|
8462
|
+
summary: z39.string().optional().describe("A summary or excerpt of the article."),
|
|
8463
|
+
tags: z39.array(z39.string()).optional().describe(
|
|
7591
8464
|
"New tags for categorization. This replaces all existing tags. Example: ['guide', 'tutorial', 'beginner']."
|
|
7592
8465
|
),
|
|
7593
|
-
image:
|
|
7594
|
-
url:
|
|
7595
|
-
altText:
|
|
8466
|
+
image: z39.object({
|
|
8467
|
+
url: z39.string().url().describe("The URL of the featured image"),
|
|
8468
|
+
altText: z39.string().optional().describe("Alt text for accessibility")
|
|
7596
8469
|
}).optional().describe("Featured image for the article."),
|
|
7597
|
-
isPublished:
|
|
8470
|
+
isPublished: z39.boolean().optional().describe(
|
|
7598
8471
|
"Whether the article should be visible on the storefront. Set to true to publish, false to unpublish."
|
|
7599
8472
|
),
|
|
7600
|
-
publishDate:
|
|
8473
|
+
publishDate: z39.string().optional().describe(
|
|
7601
8474
|
"The date and time when the article should become visible (ISO 8601 format). Example: '2024-01-15T10:00:00Z'."
|
|
7602
8475
|
),
|
|
7603
|
-
handle:
|
|
8476
|
+
handle: z39.string().optional().describe(
|
|
7604
8477
|
"The new URL handle/slug for the article. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
|
|
7605
8478
|
),
|
|
7606
|
-
templateSuffix:
|
|
8479
|
+
templateSuffix: z39.string().optional().describe(
|
|
7607
8480
|
"The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
|
|
7608
8481
|
),
|
|
7609
|
-
redirectNewHandle:
|
|
8482
|
+
redirectNewHandle: z39.boolean().optional().describe(
|
|
7610
8483
|
"Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
|
|
7611
8484
|
)
|
|
7612
8485
|
});
|
|
7613
|
-
var
|
|
7614
|
-
id:
|
|
7615
|
-
title:
|
|
7616
|
-
handle:
|
|
7617
|
-
blog:
|
|
7618
|
-
id:
|
|
7619
|
-
title:
|
|
8486
|
+
var outputSchema37 = z39.object({
|
|
8487
|
+
id: z39.string().describe("Updated article GID"),
|
|
8488
|
+
title: z39.string().describe("Article title"),
|
|
8489
|
+
handle: z39.string().describe("URL handle/slug"),
|
|
8490
|
+
blog: z39.object({
|
|
8491
|
+
id: z39.string().describe("Parent blog GID"),
|
|
8492
|
+
title: z39.string().describe("Parent blog title")
|
|
7620
8493
|
}),
|
|
7621
|
-
isPublished:
|
|
8494
|
+
isPublished: z39.boolean().describe("Whether article is visible on storefront")
|
|
7622
8495
|
});
|
|
7623
8496
|
var handleUpdateArticle = async (context, params) => {
|
|
7624
8497
|
log.debug(`Updating article on shop: ${context.shopDomain}`);
|
|
@@ -7667,13 +8540,20 @@ function registerUpdateArticleTool() {
|
|
|
7667
8540
|
name: "update-article",
|
|
7668
8541
|
title: "Update Article",
|
|
7669
8542
|
description: "Update an existing article's attributes. You can update title, body content (HTML), summary, author, tags, featured image, publish status, and template suffix. Only provided fields are updated; others remain unchanged. Use redirectNewHandle: true when changing handles. Useful for: editing content, adding tags, publishing/unpublishing articles. **Typical workflow:** list-articles \u2192 update-article. **Prerequisites:** Article ID. **Follow-ups:** list-articles. Returns the updated article object.",
|
|
7670
|
-
inputSchema:
|
|
7671
|
-
outputSchema:
|
|
8543
|
+
inputSchema: inputSchema37,
|
|
8544
|
+
outputSchema: outputSchema37,
|
|
7672
8545
|
category: "content",
|
|
7673
8546
|
relationships: {
|
|
7674
8547
|
relatedTools: ["list-articles", "create-article", "delete-article"],
|
|
7675
8548
|
prerequisites: ["list-articles"],
|
|
7676
8549
|
followUps: ["list-articles"]
|
|
8550
|
+
},
|
|
8551
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8552
|
+
annotations: {
|
|
8553
|
+
readOnlyHint: false,
|
|
8554
|
+
destructiveHint: false,
|
|
8555
|
+
idempotentHint: true,
|
|
8556
|
+
openWorldHint: true
|
|
7677
8557
|
}
|
|
7678
8558
|
},
|
|
7679
8559
|
handleUpdateArticle
|
|
@@ -7681,30 +8561,30 @@ function registerUpdateArticleTool() {
|
|
|
7681
8561
|
}
|
|
7682
8562
|
|
|
7683
8563
|
// src/tools/update-blog.ts
|
|
7684
|
-
import { z as
|
|
7685
|
-
var
|
|
7686
|
-
id:
|
|
8564
|
+
import { z as z40 } from "zod";
|
|
8565
|
+
var inputSchema38 = z40.object({
|
|
8566
|
+
id: blogIdSchema.describe(
|
|
7687
8567
|
'The blog ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Blog/12345". Use list-blogs to find blog IDs.'
|
|
7688
8568
|
),
|
|
7689
|
-
title:
|
|
7690
|
-
handle:
|
|
8569
|
+
title: z40.string().optional().describe("The new title for the blog. Only provided if changing the title."),
|
|
8570
|
+
handle: z40.string().optional().describe(
|
|
7691
8571
|
"The new URL handle/slug for the blog. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
|
|
7692
8572
|
),
|
|
7693
|
-
commentPolicy:
|
|
8573
|
+
commentPolicy: z40.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
|
|
7694
8574
|
"Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately."
|
|
7695
8575
|
),
|
|
7696
|
-
templateSuffix:
|
|
8576
|
+
templateSuffix: z40.string().optional().describe(
|
|
7697
8577
|
"The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
|
|
7698
8578
|
),
|
|
7699
|
-
redirectNewHandle:
|
|
8579
|
+
redirectNewHandle: z40.boolean().optional().describe(
|
|
7700
8580
|
"Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
|
|
7701
8581
|
)
|
|
7702
8582
|
});
|
|
7703
|
-
var
|
|
7704
|
-
id:
|
|
7705
|
-
title:
|
|
7706
|
-
handle:
|
|
7707
|
-
commentPolicy:
|
|
8583
|
+
var outputSchema38 = z40.object({
|
|
8584
|
+
id: z40.string().describe("Updated blog GID"),
|
|
8585
|
+
title: z40.string().describe("Blog title"),
|
|
8586
|
+
handle: z40.string().describe("URL handle/slug"),
|
|
8587
|
+
commentPolicy: z40.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
|
|
7708
8588
|
});
|
|
7709
8589
|
var handleUpdateBlog = async (context, params) => {
|
|
7710
8590
|
log.debug(`Updating blog on shop: ${context.shopDomain}`);
|
|
@@ -7747,13 +8627,20 @@ function registerUpdateBlogTool() {
|
|
|
7747
8627
|
name: "update-blog",
|
|
7748
8628
|
title: "Update Blog",
|
|
7749
8629
|
description: "Update an existing blog's attributes. You can update title, handle (URL slug), comment policy (CLOSED, MODERATE, OPEN), and template suffix. Only provided fields are updated; others remain unchanged. Use redirectNewHandle: true when changing handles. Useful for: renaming blogs, changing comment settings, updating templates. **Typical workflow:** list-blogs \u2192 update-blog. **Prerequisites:** Blog ID. **Follow-ups:** list-blogs. Returns the updated blog object.",
|
|
7750
|
-
inputSchema:
|
|
7751
|
-
outputSchema:
|
|
8630
|
+
inputSchema: inputSchema38,
|
|
8631
|
+
outputSchema: outputSchema38,
|
|
7752
8632
|
category: "content",
|
|
7753
8633
|
relationships: {
|
|
7754
8634
|
relatedTools: ["list-blogs", "create-blog", "delete-blog"],
|
|
7755
8635
|
prerequisites: ["list-blogs"],
|
|
7756
8636
|
followUps: ["list-blogs"]
|
|
8637
|
+
},
|
|
8638
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8639
|
+
annotations: {
|
|
8640
|
+
readOnlyHint: false,
|
|
8641
|
+
destructiveHint: false,
|
|
8642
|
+
idempotentHint: true,
|
|
8643
|
+
openWorldHint: true
|
|
7757
8644
|
}
|
|
7758
8645
|
},
|
|
7759
8646
|
handleUpdateBlog
|
|
@@ -7761,21 +8648,21 @@ function registerUpdateBlogTool() {
|
|
|
7761
8648
|
}
|
|
7762
8649
|
|
|
7763
8650
|
// src/tools/update-collection.ts
|
|
7764
|
-
import { z as
|
|
7765
|
-
var
|
|
7766
|
-
collectionId:
|
|
8651
|
+
import { z as z41 } from "zod";
|
|
8652
|
+
var inputSchema39 = z41.object({
|
|
8653
|
+
collectionId: collectionIdSchema.describe(
|
|
7767
8654
|
'Collection ID to update (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
|
|
7768
8655
|
),
|
|
7769
|
-
title:
|
|
7770
|
-
handle:
|
|
8656
|
+
title: z41.string().min(1).optional().describe("New collection title"),
|
|
8657
|
+
handle: z41.string().optional().describe(
|
|
7771
8658
|
"New URL handle/slug. When changing, set redirectNewHandle: true to create automatic redirect from the old URL."
|
|
7772
8659
|
),
|
|
7773
|
-
descriptionHtml:
|
|
7774
|
-
seo:
|
|
7775
|
-
title:
|
|
7776
|
-
description:
|
|
8660
|
+
descriptionHtml: z41.string().optional().describe("New HTML description"),
|
|
8661
|
+
seo: z41.object({
|
|
8662
|
+
title: z41.string().optional().describe("New SEO title"),
|
|
8663
|
+
description: z41.string().optional().describe("New SEO meta description")
|
|
7777
8664
|
}).optional().describe("Updated SEO metadata"),
|
|
7778
|
-
sortOrder:
|
|
8665
|
+
sortOrder: z41.enum([
|
|
7779
8666
|
"ALPHA_ASC",
|
|
7780
8667
|
"ALPHA_DESC",
|
|
7781
8668
|
"BEST_SELLING",
|
|
@@ -7785,19 +8672,19 @@ var inputSchema35 = z36.object({
|
|
|
7785
8672
|
"PRICE_ASC",
|
|
7786
8673
|
"PRICE_DESC"
|
|
7787
8674
|
]).optional().describe("New product sort order within collection"),
|
|
7788
|
-
image:
|
|
7789
|
-
src:
|
|
7790
|
-
altText:
|
|
8675
|
+
image: z41.object({
|
|
8676
|
+
src: z41.string().url().describe("New image URL (publicly accessible)"),
|
|
8677
|
+
altText: z41.string().optional().describe("New alt text for accessibility")
|
|
7791
8678
|
}).optional().describe("New collection image"),
|
|
7792
|
-
templateSuffix:
|
|
7793
|
-
redirectNewHandle:
|
|
8679
|
+
templateSuffix: z41.string().optional().describe("New Liquid template suffix"),
|
|
8680
|
+
redirectNewHandle: z41.boolean().optional().describe(
|
|
7794
8681
|
"When changing the handle, set to true to create an automatic URL redirect from the old handle to the new one. Important for SEO to preserve link equity."
|
|
7795
8682
|
)
|
|
7796
8683
|
});
|
|
7797
|
-
var
|
|
7798
|
-
id:
|
|
7799
|
-
title:
|
|
7800
|
-
handle:
|
|
8684
|
+
var outputSchema39 = z41.object({
|
|
8685
|
+
id: z41.string().describe("Updated collection GID"),
|
|
8686
|
+
title: z41.string().describe("Collection title"),
|
|
8687
|
+
handle: z41.string().describe("Collection URL handle")
|
|
7801
8688
|
});
|
|
7802
8689
|
var handleUpdateCollection = async (context, params) => {
|
|
7803
8690
|
log.debug(`Updating collection on shop: ${context.shopDomain}`);
|
|
@@ -7836,14 +8723,22 @@ function registerUpdateCollectionTool() {
|
|
|
7836
8723
|
name: "update-collection",
|
|
7837
8724
|
title: "Update Collection",
|
|
7838
8725
|
description: "Update an existing collection's attributes. Any field can be updated including title, handle, description, SEO fields, sort order, and image. When changing the handle, set redirectNewHandle: true for automatic URL redirect (important for SEO). Only provided fields are modified; omitted fields remain unchanged. **Prerequisites:** get-collection to view current values before updating. **Follow-ups:** add-products-to-collection, remove-products-from-collection.",
|
|
7839
|
-
inputSchema:
|
|
7840
|
-
outputSchema:
|
|
8726
|
+
inputSchema: inputSchema39,
|
|
8727
|
+
outputSchema: outputSchema39,
|
|
7841
8728
|
// AI Agent Optimization (Story 5.5)
|
|
7842
8729
|
category: "collection",
|
|
7843
8730
|
relationships: {
|
|
7844
8731
|
relatedTools: ["get-collection", "list-collections"],
|
|
7845
8732
|
prerequisites: ["get-collection"],
|
|
7846
8733
|
followUps: ["add-products-to-collection", "remove-products-from-collection"]
|
|
8734
|
+
},
|
|
8735
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8736
|
+
annotations: {
|
|
8737
|
+
readOnlyHint: false,
|
|
8738
|
+
destructiveHint: false,
|
|
8739
|
+
idempotentHint: true,
|
|
8740
|
+
// Same update produces same result
|
|
8741
|
+
openWorldHint: true
|
|
7847
8742
|
}
|
|
7848
8743
|
},
|
|
7849
8744
|
handleUpdateCollection
|
|
@@ -7851,8 +8746,8 @@ function registerUpdateCollectionTool() {
|
|
|
7851
8746
|
}
|
|
7852
8747
|
|
|
7853
8748
|
// src/tools/update-inventory.ts
|
|
7854
|
-
import { z as
|
|
7855
|
-
var inventoryReasonEnum =
|
|
8749
|
+
import { z as z42 } from "zod";
|
|
8750
|
+
var inventoryReasonEnum = z42.enum([
|
|
7856
8751
|
"correction",
|
|
7857
8752
|
"cycle_count_available",
|
|
7858
8753
|
"damaged",
|
|
@@ -7871,20 +8766,20 @@ var inventoryReasonEnum = z37.enum([
|
|
|
7871
8766
|
"safety_stock",
|
|
7872
8767
|
"shrinkage"
|
|
7873
8768
|
]);
|
|
7874
|
-
var
|
|
7875
|
-
inventoryItemId:
|
|
8769
|
+
var inputSchema40 = z42.object({
|
|
8770
|
+
inventoryItemId: inventoryItemIdSchema.describe(
|
|
7876
8771
|
'The Shopify inventory item ID (e.g., "gid://shopify/InventoryItem/456"). Get this from get-inventory tool or from get-product response.'
|
|
7877
8772
|
),
|
|
7878
|
-
locationId:
|
|
8773
|
+
locationId: locationIdSchema.describe(
|
|
7879
8774
|
'The location ID where inventory should be updated (e.g., "gid://shopify/Location/789"). Get this from get-inventory tool.'
|
|
7880
8775
|
),
|
|
7881
|
-
setQuantity:
|
|
8776
|
+
setQuantity: z42.number().int().min(0).optional().describe(
|
|
7882
8777
|
"Set inventory to this exact quantity. Example: 100. Use for absolute stock counts after physical inventory. Cannot be used together with adjustQuantity."
|
|
7883
8778
|
),
|
|
7884
|
-
adjustQuantity:
|
|
8779
|
+
adjustQuantity: z42.number().int().optional().describe(
|
|
7885
8780
|
"Adjust inventory by this amount. Positive to add, negative to subtract. Example: -5 to reduce by 5, +10 to add 10. Use for incremental changes. Cannot be used together with setQuantity."
|
|
7886
8781
|
),
|
|
7887
|
-
name:
|
|
8782
|
+
name: z42.enum(["available", "on_hand"]).optional().default("available").describe(
|
|
7888
8783
|
'Which quantity to modify. "available" (default) for sellable stock, "on_hand" for physical count.'
|
|
7889
8784
|
),
|
|
7890
8785
|
reason: inventoryReasonEnum.optional().default("correction").describe(
|
|
@@ -7895,16 +8790,16 @@ var inputSchema36 = z37.object({
|
|
|
7895
8790
|
}).refine((data) => !(data.setQuantity !== void 0 && data.adjustQuantity !== void 0), {
|
|
7896
8791
|
message: "Cannot provide both setQuantity and adjustQuantity. Choose one."
|
|
7897
8792
|
});
|
|
7898
|
-
var
|
|
7899
|
-
success:
|
|
7900
|
-
inventoryItemId:
|
|
7901
|
-
locationId:
|
|
7902
|
-
locationName:
|
|
7903
|
-
previousQuantity:
|
|
7904
|
-
newQuantity:
|
|
7905
|
-
delta:
|
|
7906
|
-
name:
|
|
7907
|
-
reason:
|
|
8793
|
+
var outputSchema40 = z42.object({
|
|
8794
|
+
success: z42.boolean().describe("Whether the operation succeeded"),
|
|
8795
|
+
inventoryItemId: z42.string().describe("Inventory item GID"),
|
|
8796
|
+
locationId: z42.string().describe("Location GID where inventory was updated"),
|
|
8797
|
+
locationName: z42.string().describe("Human-readable location name"),
|
|
8798
|
+
previousQuantity: z42.number().describe("Quantity before the change"),
|
|
8799
|
+
newQuantity: z42.number().describe("Quantity after the change"),
|
|
8800
|
+
delta: z42.number().describe("The actual change applied (positive or negative)"),
|
|
8801
|
+
name: z42.string().describe("Which quantity was modified (available or on_hand)"),
|
|
8802
|
+
reason: z42.string().describe("Reason for the adjustment")
|
|
7908
8803
|
});
|
|
7909
8804
|
var handleUpdateInventory = async (context, params) => {
|
|
7910
8805
|
log.debug(`Updating inventory on shop: ${context.shopDomain}`);
|
|
@@ -7978,47 +8873,113 @@ function registerUpdateInventoryTool() {
|
|
|
7978
8873
|
name: "update-inventory",
|
|
7979
8874
|
title: "Update Inventory",
|
|
7980
8875
|
description: 'Update inventory quantity at a specific location. Use setQuantity for absolute values (e.g., setting stock to 50 after a count) or adjustQuantity for relative changes (e.g., +10 for received stock, -5 for shrinkage). Requires inventoryItemId (from get-inventory or get-product) and locationId (from get-inventory). Common reasons: "correction" (default), "received" (new stock), "shrinkage" (loss/theft), "damaged", "cycle_count_available" (stock take). Pre-validates that adjustments will not result in negative inventory. **Typical workflow:** get-inventory \u2192 update-inventory \u2192 get-inventory (to verify). **Prerequisites:** Inventory Item ID, Location ID. **Follow-ups:** get-inventory. Returns before/after quantities with delta.',
|
|
7981
|
-
inputSchema:
|
|
7982
|
-
outputSchema:
|
|
8876
|
+
inputSchema: inputSchema40,
|
|
8877
|
+
outputSchema: outputSchema40,
|
|
7983
8878
|
category: "inventory",
|
|
7984
8879
|
relationships: {
|
|
7985
8880
|
relatedTools: ["get-inventory", "get-bulk-inventory", "list-low-inventory"],
|
|
7986
8881
|
prerequisites: ["get-inventory"],
|
|
7987
8882
|
followUps: ["get-inventory"]
|
|
8883
|
+
},
|
|
8884
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8885
|
+
annotations: {
|
|
8886
|
+
readOnlyHint: false,
|
|
8887
|
+
destructiveHint: false,
|
|
8888
|
+
idempotentHint: true,
|
|
8889
|
+
openWorldHint: true
|
|
7988
8890
|
}
|
|
7989
8891
|
},
|
|
7990
8892
|
handleUpdateInventory
|
|
7991
8893
|
);
|
|
7992
8894
|
}
|
|
7993
8895
|
|
|
8896
|
+
// src/tools/update-market.ts
|
|
8897
|
+
import { z as z43 } from "zod";
|
|
8898
|
+
var inputSchema41 = z43.object({
|
|
8899
|
+
id: marketIdSchema.describe(
|
|
8900
|
+
'Shopify Market GID to update (e.g., "gid://shopify/Market/123"). Use list-markets to find market IDs.'
|
|
8901
|
+
),
|
|
8902
|
+
name: z43.string().min(1).optional().describe("New market name"),
|
|
8903
|
+
handle: z43.string().optional().describe("New URL handle/slug for the market"),
|
|
8904
|
+
enabled: z43.boolean().optional().describe("Whether the market should be enabled")
|
|
8905
|
+
});
|
|
8906
|
+
var outputSchema41 = z43.object({
|
|
8907
|
+
id: z43.string().describe("Updated market GID"),
|
|
8908
|
+
name: z43.string().describe("Market name"),
|
|
8909
|
+
handle: z43.string().describe("URL handle"),
|
|
8910
|
+
enabled: z43.boolean().describe("Whether the market is enabled")
|
|
8911
|
+
});
|
|
8912
|
+
var handleUpdateMarket = async (context, params) => {
|
|
8913
|
+
log.debug(`Updating market ${params.id} on shop: ${context.shopDomain}`);
|
|
8914
|
+
const input = {};
|
|
8915
|
+
if (params.name !== void 0) {
|
|
8916
|
+
input.name = params.name;
|
|
8917
|
+
}
|
|
8918
|
+
if (params.handle !== void 0) {
|
|
8919
|
+
input.handle = params.handle;
|
|
8920
|
+
}
|
|
8921
|
+
if (params.enabled !== void 0) {
|
|
8922
|
+
input.enabled = params.enabled;
|
|
8923
|
+
}
|
|
8924
|
+
const market = await updateMarket(params.id, input);
|
|
8925
|
+
log.debug(`Updated market: ${market.id}`);
|
|
8926
|
+
return market;
|
|
8927
|
+
};
|
|
8928
|
+
function registerUpdateMarketTool() {
|
|
8929
|
+
registerContextAwareTool(
|
|
8930
|
+
{
|
|
8931
|
+
name: "update-market",
|
|
8932
|
+
title: "Update Market",
|
|
8933
|
+
description: "Update an existing market's properties. Only provided fields are updated; omitted fields remain unchanged. Can update: name, handle, enabled status. **Prerequisites:** get-market to view current values before updating. **Follow-ups:** get-market (to verify), delete-market.",
|
|
8934
|
+
inputSchema: inputSchema41,
|
|
8935
|
+
outputSchema: outputSchema41,
|
|
8936
|
+
// AI Agent Optimization
|
|
8937
|
+
category: "market",
|
|
8938
|
+
relationships: {
|
|
8939
|
+
relatedTools: ["get-market", "list-markets"],
|
|
8940
|
+
followUps: ["get-market", "delete-market"]
|
|
8941
|
+
},
|
|
8942
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8943
|
+
annotations: {
|
|
8944
|
+
readOnlyHint: false,
|
|
8945
|
+
destructiveHint: false,
|
|
8946
|
+
idempotentHint: true,
|
|
8947
|
+
// Same update with same values produces same result
|
|
8948
|
+
openWorldHint: true
|
|
8949
|
+
}
|
|
8950
|
+
},
|
|
8951
|
+
handleUpdateMarket
|
|
8952
|
+
);
|
|
8953
|
+
}
|
|
8954
|
+
|
|
7994
8955
|
// src/tools/update-page.ts
|
|
7995
|
-
import { z as
|
|
7996
|
-
var
|
|
7997
|
-
id:
|
|
8956
|
+
import { z as z44 } from "zod";
|
|
8957
|
+
var inputSchema42 = z44.object({
|
|
8958
|
+
id: pageIdSchema.describe(
|
|
7998
8959
|
'The page ID to update (required). Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs.'
|
|
7999
8960
|
),
|
|
8000
|
-
title:
|
|
8001
|
-
body:
|
|
8961
|
+
title: z44.string().optional().describe("The new title for the page. Only provide if you want to change it."),
|
|
8962
|
+
body: z44.string().optional().describe(
|
|
8002
8963
|
"The new HTML body content for the page. Supports HTML markup. Only provide if you want to change it."
|
|
8003
8964
|
),
|
|
8004
|
-
handle:
|
|
8965
|
+
handle: z44.string().optional().describe(
|
|
8005
8966
|
"The new URL handle/slug for the page. Only provide if you want to change the URL. Consider setting redirectNewHandle to true when changing handles."
|
|
8006
8967
|
),
|
|
8007
|
-
isPublished:
|
|
8968
|
+
isPublished: z44.boolean().optional().describe(
|
|
8008
8969
|
"Whether the page should be visible on the storefront. Set to true to publish, false to unpublish."
|
|
8009
8970
|
),
|
|
8010
|
-
templateSuffix:
|
|
8971
|
+
templateSuffix: z44.string().optional().describe(
|
|
8011
8972
|
"The suffix of the Liquid template used to render the page. Set to empty string to use the default template."
|
|
8012
8973
|
),
|
|
8013
|
-
redirectNewHandle:
|
|
8974
|
+
redirectNewHandle: z44.boolean().optional().describe(
|
|
8014
8975
|
"Whether to create a redirect from the old handle to the new handle when changing handles. Recommended to preserve SEO value when changing page URLs."
|
|
8015
8976
|
)
|
|
8016
8977
|
});
|
|
8017
|
-
var
|
|
8018
|
-
id:
|
|
8019
|
-
title:
|
|
8020
|
-
handle:
|
|
8021
|
-
isPublished:
|
|
8978
|
+
var outputSchema42 = z44.object({
|
|
8979
|
+
id: z44.string().describe("Updated page GID"),
|
|
8980
|
+
title: z44.string().describe("Page title"),
|
|
8981
|
+
handle: z44.string().describe("URL handle/slug"),
|
|
8982
|
+
isPublished: z44.boolean().describe("Whether page is visible on storefront")
|
|
8022
8983
|
});
|
|
8023
8984
|
var handleUpdatePage = async (context, params) => {
|
|
8024
8985
|
log.debug(`Updating page on shop: ${context.shopDomain}`);
|
|
@@ -8062,13 +9023,20 @@ function registerUpdatePageTool() {
|
|
|
8062
9023
|
name: "update-page",
|
|
8063
9024
|
title: "Update Page",
|
|
8064
9025
|
description: "Update an existing page's attributes. You can update title, body content (HTML), handle (URL slug), publish status, and template suffix. Only provided fields are updated; others remain unchanged. Use redirectNewHandle: true when changing handles to preserve SEO. Useful for: editing content, publishing/unpublishing pages, changing URLs. **Typical workflow:** get-page \u2192 update-page. **Prerequisites:** Page ID. **Follow-ups:** get-page. Returns the updated page object.",
|
|
8065
|
-
inputSchema:
|
|
8066
|
-
outputSchema:
|
|
9026
|
+
inputSchema: inputSchema42,
|
|
9027
|
+
outputSchema: outputSchema42,
|
|
8067
9028
|
category: "content",
|
|
8068
9029
|
relationships: {
|
|
8069
9030
|
relatedTools: ["get-page", "list-pages"],
|
|
8070
9031
|
prerequisites: ["get-page"],
|
|
8071
9032
|
followUps: ["get-page"]
|
|
9033
|
+
},
|
|
9034
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
9035
|
+
annotations: {
|
|
9036
|
+
readOnlyHint: false,
|
|
9037
|
+
destructiveHint: false,
|
|
9038
|
+
idempotentHint: true,
|
|
9039
|
+
openWorldHint: true
|
|
8072
9040
|
}
|
|
8073
9041
|
},
|
|
8074
9042
|
handleUpdatePage
|
|
@@ -8076,7 +9044,7 @@ function registerUpdatePageTool() {
|
|
|
8076
9044
|
}
|
|
8077
9045
|
|
|
8078
9046
|
// src/tools/update-product-image.ts
|
|
8079
|
-
import { z as
|
|
9047
|
+
import { z as z45 } from "zod";
|
|
8080
9048
|
var FILE_UPDATE_MUTATION = `
|
|
8081
9049
|
mutation FileUpdate($files: [FileUpdateInput!]!) {
|
|
8082
9050
|
fileUpdate(files: $files) {
|
|
@@ -8096,18 +9064,18 @@ var FILE_UPDATE_MUTATION = `
|
|
|
8096
9064
|
}
|
|
8097
9065
|
}
|
|
8098
9066
|
`;
|
|
8099
|
-
var
|
|
8100
|
-
imageId:
|
|
9067
|
+
var inputSchema43 = z45.object({
|
|
9068
|
+
imageId: imageIdSchema.describe(
|
|
8101
9069
|
'Shopify image GID (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the media field.'
|
|
8102
9070
|
),
|
|
8103
|
-
altText:
|
|
9071
|
+
altText: z45.string().describe(
|
|
8104
9072
|
"New alt text for the image. Pass an empty string to clear the alt text. Alt text describes the image for screen readers and helps search engines understand image content."
|
|
8105
9073
|
)
|
|
8106
9074
|
});
|
|
8107
|
-
var
|
|
8108
|
-
id:
|
|
8109
|
-
url:
|
|
8110
|
-
altText:
|
|
9075
|
+
var outputSchema43 = z45.object({
|
|
9076
|
+
id: z45.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
|
|
9077
|
+
url: z45.string().nullable().describe("Shopify CDN URL for the image"),
|
|
9078
|
+
altText: z45.string().nullable().describe("Updated alt text for the image")
|
|
8111
9079
|
});
|
|
8112
9080
|
var handleUpdateProductImage = async (context, params) => {
|
|
8113
9081
|
log.debug(`Updating image alt text on shop: ${context.shopDomain}`);
|
|
@@ -8166,14 +9134,22 @@ function registerUpdateProductImageTool() {
|
|
|
8166
9134
|
name: "update-product-image",
|
|
8167
9135
|
title: "Update Product Image",
|
|
8168
9136
|
description: "Update the alt text of a product image for SEO and accessibility. Alt text describes the image for screen readers and helps search engines understand image content. Pass an empty string to clear alt text. **Typical workflow:** add-product-image \u2192 update-product-image (set alt text). **Prerequisites:** add-product-image or get-product to find image IDs. **Follow-ups:** reorder-product-images.",
|
|
8169
|
-
inputSchema:
|
|
8170
|
-
outputSchema:
|
|
9137
|
+
inputSchema: inputSchema43,
|
|
9138
|
+
outputSchema: outputSchema43,
|
|
8171
9139
|
// AI Agent Optimization (Story 5.5)
|
|
8172
9140
|
category: "media",
|
|
8173
9141
|
relationships: {
|
|
8174
9142
|
relatedTools: ["reorder-product-images", "add-product-image"],
|
|
8175
9143
|
prerequisites: ["add-product-image"],
|
|
8176
9144
|
followUps: ["reorder-product-images"]
|
|
9145
|
+
},
|
|
9146
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
9147
|
+
annotations: {
|
|
9148
|
+
readOnlyHint: false,
|
|
9149
|
+
destructiveHint: false,
|
|
9150
|
+
idempotentHint: true,
|
|
9151
|
+
// Same alt text update produces same result
|
|
9152
|
+
openWorldHint: true
|
|
8177
9153
|
}
|
|
8178
9154
|
},
|
|
8179
9155
|
handleUpdateProductImage
|
|
@@ -8181,19 +9157,19 @@ function registerUpdateProductImageTool() {
|
|
|
8181
9157
|
}
|
|
8182
9158
|
|
|
8183
9159
|
// src/tools/update-product-variant.ts
|
|
8184
|
-
import { z as
|
|
8185
|
-
var
|
|
8186
|
-
productId:
|
|
9160
|
+
import { z as z46 } from "zod";
|
|
9161
|
+
var inputSchema44 = z46.object({
|
|
9162
|
+
productId: productIdSchema.describe(
|
|
8187
9163
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use get-product to find the product ID containing the variant.'
|
|
8188
9164
|
),
|
|
8189
|
-
id:
|
|
9165
|
+
id: variantIdSchema.describe(
|
|
8190
9166
|
'Shopify variant GID (e.g., "gid://shopify/ProductVariant/456"). Required. Use get-product to find variant IDs for a product.'
|
|
8191
9167
|
),
|
|
8192
|
-
price:
|
|
8193
|
-
compareAtPrice:
|
|
9168
|
+
price: z46.string().optional().describe('New variant price as decimal string (e.g., "29.99")'),
|
|
9169
|
+
compareAtPrice: z46.string().nullable().optional().describe(
|
|
8194
9170
|
'Original price for sale display (e.g., "39.99"). When set higher than price, Shopify shows strikethrough pricing. Set to null to remove the compare at price.'
|
|
8195
9171
|
),
|
|
8196
|
-
barcode:
|
|
9172
|
+
barcode: z46.string().nullable().optional().describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
|
|
8197
9173
|
}).refine(
|
|
8198
9174
|
(data) => {
|
|
8199
9175
|
const { productId: _productId, id: _id, ...updateFields } = data;
|
|
@@ -8201,14 +9177,14 @@ var inputSchema39 = z40.object({
|
|
|
8201
9177
|
},
|
|
8202
9178
|
{ message: "At least one field to update must be provided (price, compareAtPrice, or barcode)" }
|
|
8203
9179
|
);
|
|
8204
|
-
var
|
|
8205
|
-
id:
|
|
8206
|
-
title:
|
|
8207
|
-
price:
|
|
8208
|
-
compareAtPrice:
|
|
8209
|
-
sku:
|
|
8210
|
-
barcode:
|
|
8211
|
-
inventoryQuantity:
|
|
9180
|
+
var outputSchema44 = z46.object({
|
|
9181
|
+
id: z46.string().describe('Variant GID (e.g., "gid://shopify/ProductVariant/123")'),
|
|
9182
|
+
title: z46.string().describe('Variant title (e.g., "Red / Medium")'),
|
|
9183
|
+
price: z46.string().describe("Current price as decimal string"),
|
|
9184
|
+
compareAtPrice: z46.string().nullable().describe("Compare at price for sale display"),
|
|
9185
|
+
sku: z46.string().nullable().describe("Stock keeping unit"),
|
|
9186
|
+
barcode: z46.string().nullable().describe("Barcode (UPC, EAN, etc.)"),
|
|
9187
|
+
inventoryQuantity: z46.number().nullable().describe("Available inventory quantity")
|
|
8212
9188
|
});
|
|
8213
9189
|
var handleUpdateProductVariant = async (context, params) => {
|
|
8214
9190
|
log.debug(`Updating product variant on shop: ${context.shopDomain}`);
|
|
@@ -8237,13 +9213,21 @@ function registerUpdateProductVariantTool() {
|
|
|
8237
9213
|
name: "update-product-variant",
|
|
8238
9214
|
title: "Update Product Variant",
|
|
8239
9215
|
description: 'Update an existing product variant in the Shopify store. Requires BOTH productId and variant id (GID format). Only provided fields are updated; others remain unchanged. Supports updating: price, compareAtPrice (for sale pricing), barcode. Returns the updated variant with all current details. To show sale pricing, set compareAtPrice higher than price (e.g., compareAtPrice: "39.99", price: "24.99"). **Prerequisites:** get-product to obtain productId and variant IDs. **Note:** SKU updates require separate inventory API calls.',
|
|
8240
|
-
inputSchema:
|
|
8241
|
-
outputSchema:
|
|
9216
|
+
inputSchema: inputSchema44,
|
|
9217
|
+
outputSchema: outputSchema44,
|
|
8242
9218
|
// AI Agent Optimization (Story 5.5)
|
|
8243
9219
|
category: "product",
|
|
8244
9220
|
relationships: {
|
|
8245
9221
|
relatedTools: ["get-product", "get-inventory"],
|
|
8246
9222
|
prerequisites: ["get-product"]
|
|
9223
|
+
},
|
|
9224
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
9225
|
+
annotations: {
|
|
9226
|
+
readOnlyHint: false,
|
|
9227
|
+
destructiveHint: false,
|
|
9228
|
+
idempotentHint: true,
|
|
9229
|
+
// Same update produces same result
|
|
9230
|
+
openWorldHint: true
|
|
8247
9231
|
}
|
|
8248
9232
|
},
|
|
8249
9233
|
handleUpdateProductVariant
|
|
@@ -8251,23 +9235,25 @@ function registerUpdateProductVariantTool() {
|
|
|
8251
9235
|
}
|
|
8252
9236
|
|
|
8253
9237
|
// src/tools/update-product.ts
|
|
8254
|
-
import { z as
|
|
8255
|
-
var
|
|
8256
|
-
id:
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
9238
|
+
import { z as z47 } from "zod";
|
|
9239
|
+
var inputSchema45 = z47.object({
|
|
9240
|
+
id: productIdSchema.describe(
|
|
9241
|
+
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
|
|
9242
|
+
),
|
|
9243
|
+
title: z47.string().min(1).optional().describe("New product title"),
|
|
9244
|
+
description: z47.string().optional().describe("New product description (HTML supported)"),
|
|
9245
|
+
vendor: z47.string().optional().describe("New product vendor/brand name"),
|
|
9246
|
+
productType: z47.string().optional().describe("New product type for categorization"),
|
|
9247
|
+
tags: z47.array(z47.string()).optional().describe("New tags (replaces existing tags)"),
|
|
9248
|
+
status: z47.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe("New product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden)"),
|
|
9249
|
+
seo: z47.object({
|
|
9250
|
+
title: z47.string().optional().describe("SEO title for search engine results"),
|
|
9251
|
+
description: z47.string().optional().describe("SEO meta description for search engines")
|
|
8266
9252
|
}).optional().describe("SEO metadata for search engine optimization"),
|
|
8267
|
-
handle:
|
|
9253
|
+
handle: z47.string().optional().describe(
|
|
8268
9254
|
'New URL handle/slug (e.g., "my-product"). Use with redirectNewHandle for URL changes.'
|
|
8269
9255
|
),
|
|
8270
|
-
redirectNewHandle:
|
|
9256
|
+
redirectNewHandle: z47.boolean().optional().describe(
|
|
8271
9257
|
"If true, creates automatic redirect from old handle to new handle. Use when changing handle."
|
|
8272
9258
|
)
|
|
8273
9259
|
}).refine(
|
|
@@ -8277,40 +9263,40 @@ var inputSchema40 = z41.object({
|
|
|
8277
9263
|
},
|
|
8278
9264
|
{ message: "At least one field to update must be provided" }
|
|
8279
9265
|
);
|
|
8280
|
-
var
|
|
8281
|
-
id:
|
|
8282
|
-
title:
|
|
8283
|
-
handle:
|
|
8284
|
-
description:
|
|
8285
|
-
vendor:
|
|
8286
|
-
productType:
|
|
8287
|
-
status:
|
|
8288
|
-
tags:
|
|
8289
|
-
seo:
|
|
8290
|
-
title:
|
|
8291
|
-
description:
|
|
9266
|
+
var outputSchema45 = z47.object({
|
|
9267
|
+
id: z47.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
9268
|
+
title: z47.string().describe("Product title"),
|
|
9269
|
+
handle: z47.string().describe("URL handle/slug"),
|
|
9270
|
+
description: z47.string().nullable().describe("Product description (HTML)"),
|
|
9271
|
+
vendor: z47.string().describe("Vendor/brand name"),
|
|
9272
|
+
productType: z47.string().describe("Product type"),
|
|
9273
|
+
status: z47.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
9274
|
+
tags: z47.array(z47.string()).describe("Product tags"),
|
|
9275
|
+
seo: z47.object({
|
|
9276
|
+
title: z47.string().nullable().describe("SEO title for search engines"),
|
|
9277
|
+
description: z47.string().nullable().describe("SEO description/meta description")
|
|
8292
9278
|
}).describe("SEO metadata for search engine optimization"),
|
|
8293
|
-
createdAt:
|
|
8294
|
-
updatedAt:
|
|
8295
|
-
variants:
|
|
8296
|
-
|
|
8297
|
-
id:
|
|
8298
|
-
title:
|
|
8299
|
-
price:
|
|
8300
|
-
compareAtPrice:
|
|
8301
|
-
sku:
|
|
8302
|
-
barcode:
|
|
8303
|
-
inventoryQuantity:
|
|
9279
|
+
createdAt: z47.string().describe("Creation timestamp (ISO 8601)"),
|
|
9280
|
+
updatedAt: z47.string().describe("Last update timestamp (ISO 8601)"),
|
|
9281
|
+
variants: z47.array(
|
|
9282
|
+
z47.object({
|
|
9283
|
+
id: z47.string().describe("Variant GID"),
|
|
9284
|
+
title: z47.string().describe("Variant title"),
|
|
9285
|
+
price: z47.string().describe("Price"),
|
|
9286
|
+
compareAtPrice: z47.string().nullable().describe("Compare at price"),
|
|
9287
|
+
sku: z47.string().nullable().describe("SKU"),
|
|
9288
|
+
barcode: z47.string().nullable().describe("Barcode"),
|
|
9289
|
+
inventoryQuantity: z47.number().nullable().describe("Inventory quantity")
|
|
8304
9290
|
})
|
|
8305
9291
|
).describe("Product variants"),
|
|
8306
|
-
images:
|
|
8307
|
-
|
|
8308
|
-
id:
|
|
8309
|
-
url:
|
|
8310
|
-
altText:
|
|
9292
|
+
images: z47.array(
|
|
9293
|
+
z47.object({
|
|
9294
|
+
id: z47.string().describe("Image GID"),
|
|
9295
|
+
url: z47.string().describe("Image URL"),
|
|
9296
|
+
altText: z47.string().nullable().describe("Alt text")
|
|
8311
9297
|
})
|
|
8312
9298
|
).describe("Product images"),
|
|
8313
|
-
totalInventory:
|
|
9299
|
+
totalInventory: z47.number().describe("Total inventory across variants")
|
|
8314
9300
|
});
|
|
8315
9301
|
var handleUpdateProduct = async (context, params) => {
|
|
8316
9302
|
log.debug(`Updating product on shop: ${context.shopDomain}`);
|
|
@@ -8339,14 +9325,22 @@ function registerUpdateProductTool() {
|
|
|
8339
9325
|
name: "update-product",
|
|
8340
9326
|
title: "Update Product",
|
|
8341
9327
|
description: "Update an existing product in the Shopify store. Requires the product ID (GID format). Only provided fields are updated; others remain unchanged. Supports updating: title, description, vendor, productType, tags, status, SEO metadata (seo.title, seo.description), and URL handle. Use handle + redirectNewHandle=true to change URLs with automatic redirect. Returns the updated product with all current details including SEO. **Prerequisites:** get-product to view current values before updating. **Follow-ups:** add-product-image, set-product-metafields.",
|
|
8342
|
-
inputSchema:
|
|
8343
|
-
outputSchema:
|
|
9328
|
+
inputSchema: inputSchema45,
|
|
9329
|
+
outputSchema: outputSchema45,
|
|
8344
9330
|
// AI Agent Optimization (Story 5.5)
|
|
8345
9331
|
category: "product",
|
|
8346
9332
|
relationships: {
|
|
8347
9333
|
relatedTools: ["list-products", "get-product"],
|
|
8348
9334
|
prerequisites: ["get-product"],
|
|
8349
9335
|
followUps: ["add-product-image", "set-product-metafields"]
|
|
9336
|
+
},
|
|
9337
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
9338
|
+
annotations: {
|
|
9339
|
+
readOnlyHint: false,
|
|
9340
|
+
destructiveHint: false,
|
|
9341
|
+
idempotentHint: true,
|
|
9342
|
+
// Same update produces same result
|
|
9343
|
+
openWorldHint: true
|
|
8350
9344
|
}
|
|
8351
9345
|
},
|
|
8352
9346
|
handleUpdateProduct
|
|
@@ -8362,7 +9356,9 @@ function setupToolHandlers(server) {
|
|
|
8362
9356
|
tools: tools.map((tool) => ({
|
|
8363
9357
|
name: tool.name,
|
|
8364
9358
|
description: tool.description,
|
|
8365
|
-
inputSchema: tool.inputSchema
|
|
9359
|
+
inputSchema: tool.inputSchema,
|
|
9360
|
+
// Include MCP tool annotations (Epic 9.5 - MCP Best Practices)
|
|
9361
|
+
annotations: tool.annotations
|
|
8366
9362
|
}))
|
|
8367
9363
|
};
|
|
8368
9364
|
});
|
|
@@ -8447,6 +9443,11 @@ function registerAllTools(server) {
|
|
|
8447
9443
|
registerGetInventoryTool();
|
|
8448
9444
|
registerListLowInventoryTool();
|
|
8449
9445
|
registerUpdateInventoryTool();
|
|
9446
|
+
registerCreateMarketTool();
|
|
9447
|
+
registerDeleteMarketTool();
|
|
9448
|
+
registerGetMarketTool();
|
|
9449
|
+
registerListMarketsTool();
|
|
9450
|
+
registerUpdateMarketTool();
|
|
8450
9451
|
const toolCount = getRegisteredTools().length;
|
|
8451
9452
|
log.debug(`Tool registration complete: ${toolCount} tools registered`);
|
|
8452
9453
|
}
|