@anton.andrusenko/shopify-mcp-admin 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -11
- package/dist/index.js +1456 -769
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -10,8 +10,11 @@ import { z } from "zod";
|
|
|
10
10
|
var configSchema = z.object({
|
|
11
11
|
// Required - store identity
|
|
12
12
|
SHOPIFY_STORE_URL: z.string().min(1, "SHOPIFY_STORE_URL is required").regex(/\.myshopify\.com$/, "Must be a valid myshopify.com domain"),
|
|
13
|
-
//
|
|
14
|
-
SHOPIFY_ACCESS_TOKEN: z.string().
|
|
13
|
+
// Authentication Option 1: Legacy Custom App (static token)
|
|
14
|
+
SHOPIFY_ACCESS_TOKEN: z.string().optional(),
|
|
15
|
+
// Authentication Option 2: Dev Dashboard (OAuth 2.0 client credentials)
|
|
16
|
+
SHOPIFY_CLIENT_ID: z.string().optional(),
|
|
17
|
+
SHOPIFY_CLIENT_SECRET: z.string().optional(),
|
|
15
18
|
// Optional with defaults
|
|
16
19
|
SHOPIFY_API_VERSION: z.string().default("2025-10"),
|
|
17
20
|
DEBUG: z.string().optional(),
|
|
@@ -19,8 +22,52 @@ var configSchema = z.object({
|
|
|
19
22
|
PORT: z.string().default("3000").transform(Number),
|
|
20
23
|
// Transport selection (AC-2.2.6, AC-2.2.7)
|
|
21
24
|
// Default: stdio for Claude Desktop compatibility
|
|
22
|
-
TRANSPORT: z.enum(["stdio", "http"]).default("stdio")
|
|
23
|
-
|
|
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)")
|
|
29
|
+
}).refine(
|
|
30
|
+
(data) => {
|
|
31
|
+
const hasLegacyAuth = !!data.SHOPIFY_ACCESS_TOKEN;
|
|
32
|
+
const hasClientId = !!data.SHOPIFY_CLIENT_ID;
|
|
33
|
+
const hasClientSecret = !!data.SHOPIFY_CLIENT_SECRET;
|
|
34
|
+
const hasClientCredentials = hasClientId && hasClientSecret;
|
|
35
|
+
return hasLegacyAuth || hasClientCredentials;
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
message: "Authentication required: Provide either SHOPIFY_ACCESS_TOKEN (legacy) OR both SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET (Dev Dashboard)"
|
|
39
|
+
}
|
|
40
|
+
).refine(
|
|
41
|
+
(data) => {
|
|
42
|
+
const hasClientId = !!data.SHOPIFY_CLIENT_ID;
|
|
43
|
+
const hasClientSecret = !!data.SHOPIFY_CLIENT_SECRET;
|
|
44
|
+
if (hasClientId && !hasClientSecret) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
message: "Incomplete client credentials: SHOPIFY_CLIENT_SECRET is required when SHOPIFY_CLIENT_ID is provided"
|
|
51
|
+
}
|
|
52
|
+
).refine(
|
|
53
|
+
(data) => {
|
|
54
|
+
const hasClientId = !!data.SHOPIFY_CLIENT_ID;
|
|
55
|
+
const hasClientSecret = !!data.SHOPIFY_CLIENT_SECRET;
|
|
56
|
+
if (hasClientSecret && !hasClientId) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
message: "Incomplete client credentials: SHOPIFY_CLIENT_ID is required when SHOPIFY_CLIENT_SECRET is provided"
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
function getAuthMode(config) {
|
|
66
|
+
if (config.SHOPIFY_ACCESS_TOKEN) {
|
|
67
|
+
return "token";
|
|
68
|
+
}
|
|
69
|
+
return "client_credentials";
|
|
70
|
+
}
|
|
24
71
|
function isDebugEnabled(debugValue) {
|
|
25
72
|
if (!debugValue) return false;
|
|
26
73
|
const normalized = debugValue.toLowerCase().trim();
|
|
@@ -54,8 +101,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
54
101
|
// src/utils/logger.ts
|
|
55
102
|
var SANITIZATION_PATTERNS = [
|
|
56
103
|
{ pattern: /shpat_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
57
|
-
{ pattern: /
|
|
58
|
-
{ pattern: /
|
|
104
|
+
{ pattern: /shpua_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
105
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9_-]+/g, replacement: "Bearer [REDACTED]" },
|
|
106
|
+
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
107
|
+
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "client_secret=[REDACTED]" }
|
|
59
108
|
];
|
|
60
109
|
function sanitizeLogMessage(message) {
|
|
61
110
|
let result = message;
|
|
@@ -313,6 +362,283 @@ async function withRateLimit(operation, context, config = DEFAULT_RATE_LIMIT_CON
|
|
|
313
362
|
// src/shopify/client.ts
|
|
314
363
|
import "@shopify/shopify-api/adapters/node";
|
|
315
364
|
import { shopifyApi } from "@shopify/shopify-api";
|
|
365
|
+
|
|
366
|
+
// src/utils/errors.ts
|
|
367
|
+
var sanitizeErrorMessage = sanitizeLogMessage;
|
|
368
|
+
var ToolError = class _ToolError extends Error {
|
|
369
|
+
/** AI-friendly suggestion for error recovery */
|
|
370
|
+
suggestion;
|
|
371
|
+
/**
|
|
372
|
+
* Create a new ToolError
|
|
373
|
+
*
|
|
374
|
+
* @param message - The error message describing what went wrong
|
|
375
|
+
* @param suggestion - A helpful suggestion for how to resolve the error
|
|
376
|
+
*/
|
|
377
|
+
constructor(message, suggestion) {
|
|
378
|
+
super(message);
|
|
379
|
+
this.name = "ToolError";
|
|
380
|
+
this.suggestion = suggestion;
|
|
381
|
+
if (Error.captureStackTrace) {
|
|
382
|
+
Error.captureStackTrace(this, _ToolError);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
function extractErrorMessage(error) {
|
|
387
|
+
if (error instanceof Error) {
|
|
388
|
+
return error.message;
|
|
389
|
+
}
|
|
390
|
+
if (typeof error === "string") {
|
|
391
|
+
return error;
|
|
392
|
+
}
|
|
393
|
+
return "Unknown error";
|
|
394
|
+
}
|
|
395
|
+
function createToolError(error, suggestion) {
|
|
396
|
+
const message = extractErrorMessage(error);
|
|
397
|
+
const safeMessage = sanitizeErrorMessage(message);
|
|
398
|
+
const safeSuggestion = sanitizeErrorMessage(suggestion);
|
|
399
|
+
return {
|
|
400
|
+
isError: true,
|
|
401
|
+
content: [
|
|
402
|
+
{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: `Error: ${safeMessage}
|
|
405
|
+
|
|
406
|
+
Suggestion: ${safeSuggestion}`
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function safeStringify2(data) {
|
|
412
|
+
try {
|
|
413
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
414
|
+
return JSON.stringify(
|
|
415
|
+
data,
|
|
416
|
+
(_key, value) => {
|
|
417
|
+
if (typeof value === "object" && value !== null) {
|
|
418
|
+
if (seen.has(value)) {
|
|
419
|
+
return "[Circular]";
|
|
420
|
+
}
|
|
421
|
+
seen.add(value);
|
|
422
|
+
}
|
|
423
|
+
return value;
|
|
424
|
+
},
|
|
425
|
+
2
|
|
426
|
+
);
|
|
427
|
+
} catch {
|
|
428
|
+
return "[Unable to stringify]";
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function createToolSuccess(data) {
|
|
432
|
+
const text = safeStringify2(data);
|
|
433
|
+
return {
|
|
434
|
+
content: [
|
|
435
|
+
{
|
|
436
|
+
type: "text",
|
|
437
|
+
text
|
|
438
|
+
}
|
|
439
|
+
],
|
|
440
|
+
structuredContent: data
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/shopify/token-manager.ts
|
|
445
|
+
var TokenManager = class _TokenManager {
|
|
446
|
+
config;
|
|
447
|
+
cachedToken = null;
|
|
448
|
+
refreshPromise = null;
|
|
449
|
+
/**
|
|
450
|
+
* Refresh buffer: 5 minutes before expiry
|
|
451
|
+
*
|
|
452
|
+
* Tokens are refreshed when within this window of expiration
|
|
453
|
+
* to ensure requests don't fail due to token expiry mid-request.
|
|
454
|
+
*/
|
|
455
|
+
static REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
456
|
+
/**
|
|
457
|
+
* Create a new TokenManager instance
|
|
458
|
+
*
|
|
459
|
+
* @param config - Configuration containing store URL and OAuth credentials
|
|
460
|
+
*/
|
|
461
|
+
constructor(config) {
|
|
462
|
+
this.config = config;
|
|
463
|
+
log.debug("TokenManager initialized", { storeUrl: config.storeUrl });
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Get a valid access token
|
|
467
|
+
*
|
|
468
|
+
* Returns a cached token if valid and not expiring soon.
|
|
469
|
+
* Automatically fetches a new token if:
|
|
470
|
+
* - No token is cached
|
|
471
|
+
* - Cached token is within 5 minutes of expiration
|
|
472
|
+
*
|
|
473
|
+
* Concurrent calls are deduplicated - only one OAuth request
|
|
474
|
+
* is made even if multiple callers request a token simultaneously.
|
|
475
|
+
*
|
|
476
|
+
* @returns Promise resolving to a valid access token string
|
|
477
|
+
* @throws ToolError if token acquisition fails
|
|
478
|
+
*/
|
|
479
|
+
async getAccessToken() {
|
|
480
|
+
if (this.refreshPromise) {
|
|
481
|
+
log.debug("Token refresh in progress, waiting...");
|
|
482
|
+
return this.refreshPromise;
|
|
483
|
+
}
|
|
484
|
+
if (this.cachedToken && !this.needsRefresh()) {
|
|
485
|
+
log.debug("Using cached token");
|
|
486
|
+
return this.cachedToken.accessToken;
|
|
487
|
+
}
|
|
488
|
+
log.debug("Fetching new OAuth token");
|
|
489
|
+
this.refreshPromise = this.fetchNewToken().then((token) => {
|
|
490
|
+
this.cachedToken = token;
|
|
491
|
+
log.debug("Token cached successfully", {
|
|
492
|
+
expiresIn: Math.round((token.expiresAt - Date.now()) / 1e3)
|
|
493
|
+
});
|
|
494
|
+
return token.accessToken;
|
|
495
|
+
}).finally(() => {
|
|
496
|
+
this.refreshPromise = null;
|
|
497
|
+
});
|
|
498
|
+
return this.refreshPromise;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Fetch a new token from Shopify OAuth endpoint
|
|
502
|
+
*
|
|
503
|
+
* Implements OAuth 2.0 client credentials grant flow:
|
|
504
|
+
* - POST to /admin/oauth/access_token
|
|
505
|
+
* - Content-Type: application/x-www-form-urlencoded
|
|
506
|
+
* - Body: grant_type, client_id, client_secret
|
|
507
|
+
*
|
|
508
|
+
* @returns Promise resolving to cached token with expiry
|
|
509
|
+
* @throws ToolError on OAuth failure (401, 400, network error)
|
|
510
|
+
* @private
|
|
511
|
+
*/
|
|
512
|
+
async fetchNewToken() {
|
|
513
|
+
const tokenEndpoint = this.buildTokenEndpoint();
|
|
514
|
+
try {
|
|
515
|
+
const response = await fetch(tokenEndpoint, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: {
|
|
518
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
519
|
+
},
|
|
520
|
+
body: new URLSearchParams({
|
|
521
|
+
grant_type: "client_credentials",
|
|
522
|
+
client_id: this.config.clientId,
|
|
523
|
+
client_secret: this.config.clientSecret
|
|
524
|
+
})
|
|
525
|
+
});
|
|
526
|
+
if (!response.ok) {
|
|
527
|
+
const errorText = await response.text();
|
|
528
|
+
throw this.handleOAuthError(response.status, errorText);
|
|
529
|
+
}
|
|
530
|
+
const data = await response.json();
|
|
531
|
+
if (!data.access_token) {
|
|
532
|
+
throw new ToolError(
|
|
533
|
+
"Invalid OAuth response: missing access_token",
|
|
534
|
+
"This may be a temporary Shopify API issue. Try again in a few minutes."
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
const expiresAt = Date.now() + data.expires_in * 1e3;
|
|
538
|
+
return {
|
|
539
|
+
accessToken: data.access_token,
|
|
540
|
+
expiresAt
|
|
541
|
+
};
|
|
542
|
+
} catch (error) {
|
|
543
|
+
if (error instanceof ToolError) {
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
547
|
+
throw new ToolError(
|
|
548
|
+
`OAuth token request failed: ${message}`,
|
|
549
|
+
"Check your network connection and ensure SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET are correct."
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Build the OAuth token endpoint URL
|
|
555
|
+
*
|
|
556
|
+
* @returns Full URL to the Shopify OAuth token endpoint
|
|
557
|
+
* @private
|
|
558
|
+
*/
|
|
559
|
+
buildTokenEndpoint() {
|
|
560
|
+
const storeUrl = this.config.storeUrl.includes("://") ? this.config.storeUrl : `https://${this.config.storeUrl}`;
|
|
561
|
+
const baseUrl = storeUrl.replace(/\/$/, "");
|
|
562
|
+
return `${baseUrl}/admin/oauth/access_token`;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Check if the cached token needs to be refreshed
|
|
566
|
+
*
|
|
567
|
+
* Returns true if:
|
|
568
|
+
* - No token is cached
|
|
569
|
+
* - Token expires within REFRESH_BUFFER_MS (5 minutes)
|
|
570
|
+
*
|
|
571
|
+
* @returns true if token should be refreshed
|
|
572
|
+
* @private
|
|
573
|
+
*/
|
|
574
|
+
needsRefresh() {
|
|
575
|
+
if (!this.cachedToken) {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
const refreshThreshold = Date.now() + _TokenManager.REFRESH_BUFFER_MS;
|
|
579
|
+
return refreshThreshold >= this.cachedToken.expiresAt;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Handle OAuth error responses
|
|
583
|
+
*
|
|
584
|
+
* Creates appropriate ToolError with actionable suggestions
|
|
585
|
+
* based on HTTP status code.
|
|
586
|
+
*
|
|
587
|
+
* @param status - HTTP status code
|
|
588
|
+
* @param responseText - Error response body
|
|
589
|
+
* @returns ToolError with appropriate message and suggestion
|
|
590
|
+
* @private
|
|
591
|
+
*/
|
|
592
|
+
handleOAuthError(status, responseText) {
|
|
593
|
+
switch (status) {
|
|
594
|
+
case 401:
|
|
595
|
+
return new ToolError(
|
|
596
|
+
"OAuth authentication failed (HTTP 401)",
|
|
597
|
+
"Verify that SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET are correct. Ensure the app has the required scopes configured in the Dev Dashboard."
|
|
598
|
+
);
|
|
599
|
+
case 400:
|
|
600
|
+
return new ToolError(
|
|
601
|
+
"OAuth bad request (HTTP 400)",
|
|
602
|
+
"The OAuth request parameters are invalid. Ensure SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET are set correctly and the app is properly configured in the Dev Dashboard."
|
|
603
|
+
);
|
|
604
|
+
case 403:
|
|
605
|
+
return new ToolError(
|
|
606
|
+
"OAuth forbidden (HTTP 403)",
|
|
607
|
+
"The app may not have permission to access this store. Ensure the app is installed on the store and has the required access scopes."
|
|
608
|
+
);
|
|
609
|
+
case 404:
|
|
610
|
+
return new ToolError(
|
|
611
|
+
"OAuth endpoint not found (HTTP 404)",
|
|
612
|
+
`Verify SHOPIFY_STORE_URL is correct and includes the .myshopify.com domain. Current value appears incorrect. Received: ${responseText.slice(0, 100)}`
|
|
613
|
+
);
|
|
614
|
+
case 429:
|
|
615
|
+
return new ToolError(
|
|
616
|
+
"OAuth rate limited (HTTP 429)",
|
|
617
|
+
"Too many token requests. Wait a few minutes before trying again."
|
|
618
|
+
);
|
|
619
|
+
default:
|
|
620
|
+
return new ToolError(
|
|
621
|
+
`OAuth request failed (HTTP ${status})`,
|
|
622
|
+
`Unexpected error from Shopify OAuth endpoint. Response: ${responseText.slice(0, 200)}`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Clear the cached token
|
|
628
|
+
*
|
|
629
|
+
* Use this for:
|
|
630
|
+
* - Testing: Reset state between tests
|
|
631
|
+
* - Error recovery: Force new token after auth failures
|
|
632
|
+
*
|
|
633
|
+
* The next call to getAccessToken() will fetch a new token.
|
|
634
|
+
*/
|
|
635
|
+
clearCache() {
|
|
636
|
+
this.cachedToken = null;
|
|
637
|
+
log.debug("Token cache cleared");
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// src/shopify/client.ts
|
|
316
642
|
var DEFAULT_API_VERSION = "2025-10";
|
|
317
643
|
var SHOP_QUERY = `
|
|
318
644
|
query {
|
|
@@ -322,6 +648,7 @@ var SHOP_QUERY = `
|
|
|
322
648
|
}
|
|
323
649
|
`;
|
|
324
650
|
var _defaultClient = null;
|
|
651
|
+
var _tokenManager = null;
|
|
325
652
|
async function verifyConnectivity(client) {
|
|
326
653
|
const response = await client.graphql.request(SHOP_QUERY);
|
|
327
654
|
if (response.errors && response.errors.length > 0) {
|
|
@@ -377,18 +704,59 @@ async function createShopifyClient(credentials, options = {}) {
|
|
|
377
704
|
}
|
|
378
705
|
return client;
|
|
379
706
|
}
|
|
707
|
+
async function createClientWithTokenRefresh(credentials, tokenManager) {
|
|
708
|
+
const baseClient = buildClient(credentials);
|
|
709
|
+
let currentToken = credentials.accessToken;
|
|
710
|
+
return {
|
|
711
|
+
...baseClient,
|
|
712
|
+
graphql: {
|
|
713
|
+
request: async (query, options) => {
|
|
714
|
+
const token = await tokenManager.getAccessToken();
|
|
715
|
+
if (token !== currentToken) {
|
|
716
|
+
log.debug("Token refreshed, updating client credentials");
|
|
717
|
+
currentToken = token;
|
|
718
|
+
credentials.accessToken = token;
|
|
719
|
+
const refreshedClient = buildClient(credentials);
|
|
720
|
+
return refreshedClient.graphql.request(query, options);
|
|
721
|
+
}
|
|
722
|
+
return baseClient.graphql.request(query, options);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
380
727
|
async function getShopifyClient() {
|
|
381
728
|
if (_defaultClient !== null) {
|
|
382
729
|
return _defaultClient;
|
|
383
730
|
}
|
|
384
731
|
const config = getConfig();
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
accessToken: config.SHOPIFY_ACCESS_TOKEN,
|
|
388
|
-
apiVersion: config.SHOPIFY_API_VERSION
|
|
389
|
-
};
|
|
732
|
+
const authMode = getAuthMode(config);
|
|
733
|
+
log.info(`Initializing Shopify client with auth mode: ${authMode}`);
|
|
390
734
|
try {
|
|
391
|
-
|
|
735
|
+
if (authMode === "token") {
|
|
736
|
+
const credentials2 = {
|
|
737
|
+
storeUrl: config.SHOPIFY_STORE_URL,
|
|
738
|
+
accessToken: config.SHOPIFY_ACCESS_TOKEN,
|
|
739
|
+
apiVersion: config.SHOPIFY_API_VERSION
|
|
740
|
+
};
|
|
741
|
+
const client2 = await createShopifyClient(credentials2);
|
|
742
|
+
_defaultClient = client2;
|
|
743
|
+
return _defaultClient;
|
|
744
|
+
}
|
|
745
|
+
log.debug("Creating TokenManager for client_credentials auth");
|
|
746
|
+
_tokenManager = new TokenManager({
|
|
747
|
+
storeUrl: config.SHOPIFY_STORE_URL,
|
|
748
|
+
clientId: config.SHOPIFY_CLIENT_ID,
|
|
749
|
+
clientSecret: config.SHOPIFY_CLIENT_SECRET
|
|
750
|
+
});
|
|
751
|
+
const initialToken = await _tokenManager.getAccessToken();
|
|
752
|
+
log.debug("Initial OAuth token acquired successfully");
|
|
753
|
+
const credentials = {
|
|
754
|
+
storeUrl: config.SHOPIFY_STORE_URL,
|
|
755
|
+
accessToken: initialToken,
|
|
756
|
+
apiVersion: config.SHOPIFY_API_VERSION
|
|
757
|
+
};
|
|
758
|
+
const client = await createClientWithTokenRefresh(credentials, _tokenManager);
|
|
759
|
+
await verifyConnectivity(client);
|
|
392
760
|
_defaultClient = client;
|
|
393
761
|
return _defaultClient;
|
|
394
762
|
} catch (error) {
|
|
@@ -571,7 +939,7 @@ function transformShopResponse(shop) {
|
|
|
571
939
|
async function getStoreInfo() {
|
|
572
940
|
const now = Date.now();
|
|
573
941
|
const config = getConfig();
|
|
574
|
-
const ttl =
|
|
942
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
575
943
|
if (_cachedStoreInfo && now - _cacheTimestamp < ttl) {
|
|
576
944
|
log.debug("Returning cached store info");
|
|
577
945
|
return _cachedStoreInfo;
|
|
@@ -598,6 +966,42 @@ async function getStoreInfo() {
|
|
|
598
966
|
var require2 = createRequire(import.meta.url);
|
|
599
967
|
var packageJson = require2("../package.json");
|
|
600
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`;
|
|
601
1005
|
function getServerVersion() {
|
|
602
1006
|
return packageJson.version;
|
|
603
1007
|
}
|
|
@@ -613,9 +1017,13 @@ function createServer() {
|
|
|
613
1017
|
capabilities: {
|
|
614
1018
|
tools: {},
|
|
615
1019
|
// Enable tools capability (AC-2.1.4)
|
|
616
|
-
resources: {}
|
|
1020
|
+
resources: {},
|
|
617
1021
|
// Enable resources capability (AC-2.1.4)
|
|
618
|
-
|
|
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)
|
|
619
1027
|
}
|
|
620
1028
|
);
|
|
621
1029
|
return server;
|
|
@@ -1967,86 +2375,23 @@ function createSingleTenantContext(client, shopDomain) {
|
|
|
1967
2375
|
};
|
|
1968
2376
|
}
|
|
1969
2377
|
|
|
1970
|
-
// src/
|
|
1971
|
-
var
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
* Create a new ToolError
|
|
1977
|
-
*
|
|
1978
|
-
* @param message - The error message describing what went wrong
|
|
1979
|
-
* @param suggestion - A helpful suggestion for how to resolve the error
|
|
1980
|
-
*/
|
|
1981
|
-
constructor(message, suggestion) {
|
|
1982
|
-
super(message);
|
|
1983
|
-
this.name = "ToolError";
|
|
1984
|
-
this.suggestion = suggestion;
|
|
1985
|
-
if (Error.captureStackTrace) {
|
|
1986
|
-
Error.captureStackTrace(this, _ToolError);
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
};
|
|
1990
|
-
function extractErrorMessage(error) {
|
|
1991
|
-
if (error instanceof Error) {
|
|
1992
|
-
return error.message;
|
|
1993
|
-
}
|
|
1994
|
-
if (typeof error === "string") {
|
|
1995
|
-
return error;
|
|
1996
|
-
}
|
|
1997
|
-
return "Unknown error";
|
|
1998
|
-
}
|
|
1999
|
-
function createToolError(error, suggestion) {
|
|
2000
|
-
const message = extractErrorMessage(error);
|
|
2001
|
-
const safeMessage = sanitizeErrorMessage(message);
|
|
2002
|
-
const safeSuggestion = sanitizeErrorMessage(suggestion);
|
|
2003
|
-
return {
|
|
2004
|
-
isError: true,
|
|
2005
|
-
content: [
|
|
2006
|
-
{
|
|
2007
|
-
type: "text",
|
|
2008
|
-
text: `Error: ${safeMessage}
|
|
2009
|
-
|
|
2010
|
-
Suggestion: ${safeSuggestion}`
|
|
2011
|
-
}
|
|
2012
|
-
]
|
|
2013
|
-
};
|
|
2014
|
-
}
|
|
2015
|
-
function safeStringify2(data) {
|
|
2016
|
-
try {
|
|
2017
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
2018
|
-
return JSON.stringify(
|
|
2019
|
-
data,
|
|
2020
|
-
(_key, value) => {
|
|
2021
|
-
if (typeof value === "object" && value !== null) {
|
|
2022
|
-
if (seen.has(value)) {
|
|
2023
|
-
return "[Circular]";
|
|
2024
|
-
}
|
|
2025
|
-
seen.add(value);
|
|
2026
|
-
}
|
|
2027
|
-
return value;
|
|
2028
|
-
},
|
|
2029
|
-
2
|
|
2030
|
-
);
|
|
2031
|
-
} catch {
|
|
2032
|
-
return "[Unable to stringify]";
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
function createToolSuccess(data) {
|
|
2036
|
-
const text = safeStringify2(data);
|
|
2378
|
+
// src/tools/registration.ts
|
|
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-");
|
|
2037
2384
|
return {
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
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
|
|
2045
2393
|
};
|
|
2046
2394
|
}
|
|
2047
|
-
|
|
2048
|
-
// src/tools/registration.ts
|
|
2049
|
-
var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2050
2395
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
2051
2396
|
function validateToolName(name) {
|
|
2052
2397
|
if (!name || name.trim() === "") {
|
|
@@ -2106,7 +2451,7 @@ function wrapToolHandler(toolName, schema, handler) {
|
|
|
2106
2451
|
};
|
|
2107
2452
|
}
|
|
2108
2453
|
function registerTool(definition, handler, options = {}) {
|
|
2109
|
-
const { name, title, description, inputSchema: inputSchema41 } = definition;
|
|
2454
|
+
const { name, title, description, inputSchema: inputSchema41, annotations } = definition;
|
|
2110
2455
|
try {
|
|
2111
2456
|
if (!options.skipNameValidation) {
|
|
2112
2457
|
validateToolName(name);
|
|
@@ -2116,13 +2461,20 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2116
2461
|
}
|
|
2117
2462
|
const jsonSchema = convertZodToJsonSchema(inputSchema41);
|
|
2118
2463
|
const wrappedHandler = wrapToolHandler(name, inputSchema41, handler);
|
|
2464
|
+
const finalAnnotations = {
|
|
2465
|
+
...deriveDefaultAnnotations(name),
|
|
2466
|
+
...annotations,
|
|
2467
|
+
// Use definition title as fallback for annotation title
|
|
2468
|
+
title: annotations?.title ?? title
|
|
2469
|
+
};
|
|
2119
2470
|
const registeredTool = {
|
|
2120
2471
|
name,
|
|
2121
2472
|
title,
|
|
2122
2473
|
description,
|
|
2123
2474
|
inputSchema: jsonSchema,
|
|
2124
2475
|
zodSchema: inputSchema41,
|
|
2125
|
-
handler: wrappedHandler
|
|
2476
|
+
handler: wrappedHandler,
|
|
2477
|
+
annotations: finalAnnotations
|
|
2126
2478
|
};
|
|
2127
2479
|
registeredTools.set(name, registeredTool);
|
|
2128
2480
|
log.debug(`Registered tool: ${name}`);
|
|
@@ -2141,7 +2493,8 @@ function getRegisteredTools() {
|
|
|
2141
2493
|
return Array.from(registeredTools.values()).map((tool) => ({
|
|
2142
2494
|
name: tool.name,
|
|
2143
2495
|
description: `${tool.title}: ${tool.description}`,
|
|
2144
|
-
inputSchema: tool.inputSchema
|
|
2496
|
+
inputSchema: tool.inputSchema,
|
|
2497
|
+
annotations: tool.annotations
|
|
2145
2498
|
}));
|
|
2146
2499
|
}
|
|
2147
2500
|
function getToolByName(name) {
|
|
@@ -2161,7 +2514,7 @@ function registerContextAwareTool(definition, handler, options = {}) {
|
|
|
2161
2514
|
}
|
|
2162
2515
|
|
|
2163
2516
|
// src/tools/add-product-image.ts
|
|
2164
|
-
import { z as
|
|
2517
|
+
import { z as z3 } from "zod";
|
|
2165
2518
|
|
|
2166
2519
|
// src/shopify/mutations.ts
|
|
2167
2520
|
var PRODUCT_CREATE_MUTATION = `
|
|
@@ -2801,22 +3154,41 @@ async function addProductImage(productId, input) {
|
|
|
2801
3154
|
};
|
|
2802
3155
|
}
|
|
2803
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
|
+
|
|
2804
3176
|
// src/tools/add-product-image.ts
|
|
2805
|
-
var inputSchema =
|
|
2806
|
-
productId:
|
|
3177
|
+
var inputSchema = z3.object({
|
|
3178
|
+
productId: productIdSchema.describe(
|
|
2807
3179
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
|
|
2808
3180
|
),
|
|
2809
|
-
url:
|
|
3181
|
+
url: z3.string().url().describe(
|
|
2810
3182
|
"Publicly accessible image URL. Shopify will fetch and host the image. Required. Supported formats: JPEG, PNG, GIF, WEBP."
|
|
2811
3183
|
),
|
|
2812
|
-
altText:
|
|
3184
|
+
altText: z3.string().optional().describe(
|
|
2813
3185
|
"Alt text for accessibility and SEO. Describes the image for screen readers. Recommended for better accessibility and search rankings."
|
|
2814
3186
|
)
|
|
2815
3187
|
});
|
|
2816
|
-
var outputSchema =
|
|
2817
|
-
id:
|
|
2818
|
-
url:
|
|
2819
|
-
altText:
|
|
3188
|
+
var outputSchema = z3.object({
|
|
3189
|
+
id: z3.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
|
|
3190
|
+
url: z3.string().describe("Shopify CDN URL for the image"),
|
|
3191
|
+
altText: z3.string().nullable().describe("Alt text for the image")
|
|
2820
3192
|
});
|
|
2821
3193
|
var handleAddProductImage = async (context, params) => {
|
|
2822
3194
|
log.debug(`Adding image to product on shop: ${context.shopDomain}`);
|
|
@@ -2874,6 +3246,14 @@ function registerAddProductImageTool() {
|
|
|
2874
3246
|
relatedTools: ["get-product", "update-product-image"],
|
|
2875
3247
|
prerequisites: ["create-product"],
|
|
2876
3248
|
followUps: ["update-product-image", "reorder-product-images"]
|
|
3249
|
+
},
|
|
3250
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
3251
|
+
annotations: {
|
|
3252
|
+
readOnlyHint: false,
|
|
3253
|
+
destructiveHint: false,
|
|
3254
|
+
idempotentHint: false,
|
|
3255
|
+
// Adding same URL creates duplicate images!
|
|
3256
|
+
openWorldHint: true
|
|
2877
3257
|
}
|
|
2878
3258
|
},
|
|
2879
3259
|
handleAddProductImage
|
|
@@ -2881,7 +3261,7 @@ function registerAddProductImageTool() {
|
|
|
2881
3261
|
}
|
|
2882
3262
|
|
|
2883
3263
|
// src/tools/add-products-to-collection.ts
|
|
2884
|
-
import { z as
|
|
3264
|
+
import { z as z4 } from "zod";
|
|
2885
3265
|
|
|
2886
3266
|
// src/shopify/collections.ts
|
|
2887
3267
|
var GET_COLLECTION_QUERY = `
|
|
@@ -3282,18 +3662,18 @@ async function removeProductsFromCollection(collectionId, productIds) {
|
|
|
3282
3662
|
}
|
|
3283
3663
|
|
|
3284
3664
|
// src/tools/add-products-to-collection.ts
|
|
3285
|
-
var inputSchema2 =
|
|
3286
|
-
collectionId:
|
|
3665
|
+
var inputSchema2 = z4.object({
|
|
3666
|
+
collectionId: collectionIdSchema.describe(
|
|
3287
3667
|
'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
|
|
3288
3668
|
),
|
|
3289
|
-
productIds:
|
|
3669
|
+
productIds: z4.array(productIdSchema).min(1).max(250).describe(
|
|
3290
3670
|
'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.'
|
|
3291
3671
|
)
|
|
3292
3672
|
});
|
|
3293
|
-
var outputSchema2 =
|
|
3294
|
-
collectionId:
|
|
3295
|
-
productsCount:
|
|
3296
|
-
addedCount:
|
|
3673
|
+
var outputSchema2 = z4.object({
|
|
3674
|
+
collectionId: z4.string().describe("Collection GID"),
|
|
3675
|
+
productsCount: z4.number().describe("Total number of products now in the collection"),
|
|
3676
|
+
addedCount: z4.number().describe("Number of products added in this operation")
|
|
3297
3677
|
});
|
|
3298
3678
|
var handleAddProductsToCollection = async (context, params) => {
|
|
3299
3679
|
log.debug(
|
|
@@ -3337,6 +3717,14 @@ function registerAddProductsToCollectionTool() {
|
|
|
3337
3717
|
relatedTools: ["remove-products-from-collection", "list-products"],
|
|
3338
3718
|
prerequisites: ["create-collection", "list-products"],
|
|
3339
3719
|
followUps: ["get-collection"]
|
|
3720
|
+
},
|
|
3721
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
3722
|
+
annotations: {
|
|
3723
|
+
readOnlyHint: false,
|
|
3724
|
+
destructiveHint: false,
|
|
3725
|
+
idempotentHint: true,
|
|
3726
|
+
// Adding same products is idempotent (no-op)
|
|
3727
|
+
openWorldHint: true
|
|
3340
3728
|
}
|
|
3341
3729
|
},
|
|
3342
3730
|
handleAddProductsToCollection
|
|
@@ -3344,7 +3732,7 @@ function registerAddProductsToCollectionTool() {
|
|
|
3344
3732
|
}
|
|
3345
3733
|
|
|
3346
3734
|
// src/tools/create-article.ts
|
|
3347
|
-
import { z as
|
|
3735
|
+
import { z as z5 } from "zod";
|
|
3348
3736
|
|
|
3349
3737
|
// src/shopify/blogs.ts
|
|
3350
3738
|
var LIST_BLOGS_QUERY = `
|
|
@@ -3850,51 +4238,51 @@ async function deleteArticle(articleId) {
|
|
|
3850
4238
|
}
|
|
3851
4239
|
|
|
3852
4240
|
// src/tools/create-article.ts
|
|
3853
|
-
var inputSchema3 =
|
|
3854
|
-
blogId:
|
|
4241
|
+
var inputSchema3 = z5.object({
|
|
4242
|
+
blogId: blogIdSchema.describe(
|
|
3855
4243
|
'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.'
|
|
3856
4244
|
),
|
|
3857
|
-
title:
|
|
4245
|
+
title: z5.string().min(1).describe(
|
|
3858
4246
|
"The title of the article (required). This will be displayed as the article heading. Example: 'How to Use Our Products', '10 Tips for Beginners'."
|
|
3859
4247
|
),
|
|
3860
|
-
authorName:
|
|
4248
|
+
authorName: z5.string().min(1).describe(
|
|
3861
4249
|
"The name of the article author (required). This is displayed on the article. Example: 'John Doe', 'Marketing Team'."
|
|
3862
4250
|
),
|
|
3863
|
-
body:
|
|
4251
|
+
body: z5.string().optional().describe(
|
|
3864
4252
|
"The HTML body content of the article. Supports HTML markup for formatting. Example: '<h2>Introduction</h2><p>Welcome to our guide...</p>'"
|
|
3865
4253
|
),
|
|
3866
|
-
summary:
|
|
4254
|
+
summary: z5.string().optional().describe(
|
|
3867
4255
|
"A summary or excerpt of the article. Used for previews on blog listing pages. Can include HTML markup."
|
|
3868
4256
|
),
|
|
3869
|
-
tags:
|
|
4257
|
+
tags: z5.array(z5.string()).optional().describe(
|
|
3870
4258
|
"Tags for categorization. Helps organize articles and improve discoverability. Example: ['guide', 'tutorial', 'beginner']."
|
|
3871
4259
|
),
|
|
3872
|
-
image:
|
|
3873
|
-
url:
|
|
3874
|
-
altText:
|
|
4260
|
+
image: z5.object({
|
|
4261
|
+
url: z5.string().url().describe("The URL of the featured image"),
|
|
4262
|
+
altText: z5.string().optional().describe("Alt text for accessibility")
|
|
3875
4263
|
}).optional().describe("Featured image for the article."),
|
|
3876
|
-
isPublished:
|
|
4264
|
+
isPublished: z5.boolean().optional().describe(
|
|
3877
4265
|
"Whether the article should be visible on the storefront. Defaults to false (unpublished). Set to true to make the article immediately visible."
|
|
3878
4266
|
),
|
|
3879
|
-
publishDate:
|
|
4267
|
+
publishDate: z5.string().optional().describe(
|
|
3880
4268
|
"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."
|
|
3881
4269
|
),
|
|
3882
|
-
handle:
|
|
4270
|
+
handle: z5.string().optional().describe(
|
|
3883
4271
|
"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."
|
|
3884
4272
|
),
|
|
3885
|
-
templateSuffix:
|
|
4273
|
+
templateSuffix: z5.string().optional().describe(
|
|
3886
4274
|
"The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
|
|
3887
4275
|
)
|
|
3888
4276
|
});
|
|
3889
|
-
var outputSchema3 =
|
|
3890
|
-
id:
|
|
3891
|
-
title:
|
|
3892
|
-
handle:
|
|
3893
|
-
blog:
|
|
3894
|
-
id:
|
|
3895
|
-
title:
|
|
4277
|
+
var outputSchema3 = z5.object({
|
|
4278
|
+
id: z5.string().describe('Created article GID (e.g., "gid://shopify/Article/123")'),
|
|
4279
|
+
title: z5.string().describe("Article title"),
|
|
4280
|
+
handle: z5.string().describe("URL handle/slug"),
|
|
4281
|
+
blog: z5.object({
|
|
4282
|
+
id: z5.string().describe("Parent blog GID"),
|
|
4283
|
+
title: z5.string().describe("Parent blog title")
|
|
3896
4284
|
}),
|
|
3897
|
-
isPublished:
|
|
4285
|
+
isPublished: z5.boolean().describe("Whether article is visible on storefront")
|
|
3898
4286
|
});
|
|
3899
4287
|
var handleCreateArticle = async (context, params) => {
|
|
3900
4288
|
log.debug(`Creating article on shop: ${context.shopDomain}`);
|
|
@@ -3950,6 +4338,14 @@ function registerCreateArticleTool() {
|
|
|
3950
4338
|
relatedTools: ["list-blogs", "list-articles", "update-article"],
|
|
3951
4339
|
prerequisites: ["list-blogs"],
|
|
3952
4340
|
followUps: ["list-articles", "update-article"]
|
|
4341
|
+
},
|
|
4342
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4343
|
+
annotations: {
|
|
4344
|
+
readOnlyHint: false,
|
|
4345
|
+
destructiveHint: false,
|
|
4346
|
+
idempotentHint: false,
|
|
4347
|
+
// create-* tools are NOT idempotent
|
|
4348
|
+
openWorldHint: true
|
|
3953
4349
|
}
|
|
3954
4350
|
},
|
|
3955
4351
|
handleCreateArticle
|
|
@@ -3957,26 +4353,26 @@ function registerCreateArticleTool() {
|
|
|
3957
4353
|
}
|
|
3958
4354
|
|
|
3959
4355
|
// src/tools/create-blog.ts
|
|
3960
|
-
import { z as
|
|
3961
|
-
var inputSchema4 =
|
|
3962
|
-
title:
|
|
4356
|
+
import { z as z6 } from "zod";
|
|
4357
|
+
var inputSchema4 = z6.object({
|
|
4358
|
+
title: z6.string().min(1).describe(
|
|
3963
4359
|
"The title of the blog (required). This will be displayed as the blog heading. Example: 'Company News', 'Product Guides', 'Industry Insights'."
|
|
3964
4360
|
),
|
|
3965
|
-
handle:
|
|
4361
|
+
handle: z6.string().optional().describe(
|
|
3966
4362
|
"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."
|
|
3967
4363
|
),
|
|
3968
|
-
commentPolicy:
|
|
4364
|
+
commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
|
|
3969
4365
|
"Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately. Defaults to MODERATE if not specified."
|
|
3970
4366
|
),
|
|
3971
|
-
templateSuffix:
|
|
4367
|
+
templateSuffix: z6.string().optional().describe(
|
|
3972
4368
|
"The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
|
|
3973
4369
|
)
|
|
3974
4370
|
});
|
|
3975
|
-
var outputSchema4 =
|
|
3976
|
-
id:
|
|
3977
|
-
title:
|
|
3978
|
-
handle:
|
|
3979
|
-
commentPolicy:
|
|
4371
|
+
var outputSchema4 = z6.object({
|
|
4372
|
+
id: z6.string().describe('Created blog GID (e.g., "gid://shopify/Blog/123")'),
|
|
4373
|
+
title: z6.string().describe("Blog title"),
|
|
4374
|
+
handle: z6.string().describe("URL handle/slug"),
|
|
4375
|
+
commentPolicy: z6.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
|
|
3980
4376
|
});
|
|
3981
4377
|
var handleCreateBlog = async (context, params) => {
|
|
3982
4378
|
log.debug(`Creating blog on shop: ${context.shopDomain}`);
|
|
@@ -4018,6 +4414,14 @@ function registerCreateBlogTool() {
|
|
|
4018
4414
|
relationships: {
|
|
4019
4415
|
relatedTools: ["list-blogs", "update-blog", "create-article"],
|
|
4020
4416
|
followUps: ["create-article", "list-blogs"]
|
|
4417
|
+
},
|
|
4418
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4419
|
+
annotations: {
|
|
4420
|
+
readOnlyHint: false,
|
|
4421
|
+
destructiveHint: false,
|
|
4422
|
+
idempotentHint: false,
|
|
4423
|
+
// create-* tools are NOT idempotent
|
|
4424
|
+
openWorldHint: true
|
|
4021
4425
|
}
|
|
4022
4426
|
},
|
|
4023
4427
|
handleCreateBlog
|
|
@@ -4025,20 +4429,20 @@ function registerCreateBlogTool() {
|
|
|
4025
4429
|
}
|
|
4026
4430
|
|
|
4027
4431
|
// src/tools/create-collection.ts
|
|
4028
|
-
import { z as
|
|
4029
|
-
var inputSchema5 =
|
|
4030
|
-
title:
|
|
4031
|
-
handle:
|
|
4432
|
+
import { z as z7 } from "zod";
|
|
4433
|
+
var inputSchema5 = z7.object({
|
|
4434
|
+
title: z7.string().min(1).describe("Collection title (required). This will be displayed to customers."),
|
|
4435
|
+
handle: z7.string().optional().describe(
|
|
4032
4436
|
'URL handle/slug for the collection (e.g., "summer-sale"). Auto-generated from title if not provided.'
|
|
4033
4437
|
),
|
|
4034
|
-
descriptionHtml:
|
|
4438
|
+
descriptionHtml: z7.string().optional().describe(
|
|
4035
4439
|
"Collection description with HTML formatting. Displayed on collection pages in the store."
|
|
4036
4440
|
),
|
|
4037
|
-
seo:
|
|
4038
|
-
title:
|
|
4039
|
-
description:
|
|
4441
|
+
seo: z7.object({
|
|
4442
|
+
title: z7.string().optional().describe("SEO title for search engines"),
|
|
4443
|
+
description: z7.string().optional().describe("SEO meta description for search engines")
|
|
4040
4444
|
}).optional().describe("SEO metadata to optimize collection visibility in search results"),
|
|
4041
|
-
sortOrder:
|
|
4445
|
+
sortOrder: z7.enum([
|
|
4042
4446
|
"ALPHA_ASC",
|
|
4043
4447
|
"ALPHA_DESC",
|
|
4044
4448
|
"BEST_SELLING",
|
|
@@ -4050,18 +4454,18 @@ var inputSchema5 = z6.object({
|
|
|
4050
4454
|
]).optional().describe(
|
|
4051
4455
|
"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)"
|
|
4052
4456
|
),
|
|
4053
|
-
image:
|
|
4054
|
-
src:
|
|
4055
|
-
altText:
|
|
4457
|
+
image: z7.object({
|
|
4458
|
+
src: z7.string().url().describe("Publicly accessible image URL"),
|
|
4459
|
+
altText: z7.string().optional().describe("Alt text for accessibility and SEO")
|
|
4056
4460
|
}).optional().describe("Collection image displayed on collection pages"),
|
|
4057
|
-
templateSuffix:
|
|
4461
|
+
templateSuffix: z7.string().optional().describe(
|
|
4058
4462
|
'Liquid template suffix for custom collection templates. For example, "featured" uses collection.featured.liquid'
|
|
4059
4463
|
)
|
|
4060
4464
|
});
|
|
4061
|
-
var outputSchema5 =
|
|
4062
|
-
id:
|
|
4063
|
-
title:
|
|
4064
|
-
handle:
|
|
4465
|
+
var outputSchema5 = z7.object({
|
|
4466
|
+
id: z7.string().describe('Created collection GID (e.g., "gid://shopify/Collection/123")'),
|
|
4467
|
+
title: z7.string().describe("Collection title"),
|
|
4468
|
+
handle: z7.string().describe("Collection URL handle")
|
|
4065
4469
|
});
|
|
4066
4470
|
var handleCreateCollection = async (context, params) => {
|
|
4067
4471
|
log.debug(`Creating collection on shop: ${context.shopDomain}`);
|
|
@@ -4100,6 +4504,14 @@ function registerCreateCollectionTool() {
|
|
|
4100
4504
|
relationships: {
|
|
4101
4505
|
relatedTools: ["list-collections", "get-collection"],
|
|
4102
4506
|
followUps: ["add-products-to-collection", "update-collection"]
|
|
4507
|
+
},
|
|
4508
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4509
|
+
annotations: {
|
|
4510
|
+
readOnlyHint: false,
|
|
4511
|
+
destructiveHint: false,
|
|
4512
|
+
idempotentHint: false,
|
|
4513
|
+
// Each call creates a new collection
|
|
4514
|
+
openWorldHint: true
|
|
4103
4515
|
}
|
|
4104
4516
|
},
|
|
4105
4517
|
handleCreateCollection
|
|
@@ -4107,7 +4519,7 @@ function registerCreateCollectionTool() {
|
|
|
4107
4519
|
}
|
|
4108
4520
|
|
|
4109
4521
|
// src/tools/create-page.ts
|
|
4110
|
-
import { z as
|
|
4522
|
+
import { z as z8 } from "zod";
|
|
4111
4523
|
|
|
4112
4524
|
// src/shopify/pages.ts
|
|
4113
4525
|
var PAGE_QUERY = `
|
|
@@ -4389,28 +4801,28 @@ async function deletePage(pageId) {
|
|
|
4389
4801
|
}
|
|
4390
4802
|
|
|
4391
4803
|
// src/tools/create-page.ts
|
|
4392
|
-
var inputSchema6 =
|
|
4393
|
-
title:
|
|
4804
|
+
var inputSchema6 = z8.object({
|
|
4805
|
+
title: z8.string().min(1).describe(
|
|
4394
4806
|
"The title of the page (required). This will be displayed as the page heading. Example: 'About Us', 'Contact', 'Privacy Policy'."
|
|
4395
4807
|
),
|
|
4396
|
-
body:
|
|
4808
|
+
body: z8.string().optional().describe(
|
|
4397
4809
|
"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>'"
|
|
4398
4810
|
),
|
|
4399
|
-
handle:
|
|
4811
|
+
handle: z8.string().optional().describe(
|
|
4400
4812
|
"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."
|
|
4401
4813
|
),
|
|
4402
|
-
isPublished:
|
|
4814
|
+
isPublished: z8.boolean().optional().describe(
|
|
4403
4815
|
"Whether the page should be visible on the storefront. Defaults to false (unpublished). Set to true to make the page immediately visible."
|
|
4404
4816
|
),
|
|
4405
|
-
templateSuffix:
|
|
4817
|
+
templateSuffix: z8.string().optional().describe(
|
|
4406
4818
|
"The suffix of the Liquid template used to render the page. For example, 'contact' would use the template 'page.contact.liquid'."
|
|
4407
4819
|
)
|
|
4408
4820
|
});
|
|
4409
|
-
var outputSchema6 =
|
|
4410
|
-
id:
|
|
4411
|
-
title:
|
|
4412
|
-
handle:
|
|
4413
|
-
isPublished:
|
|
4821
|
+
var outputSchema6 = z8.object({
|
|
4822
|
+
id: z8.string().describe('Created page GID (e.g., "gid://shopify/Page/123")'),
|
|
4823
|
+
title: z8.string().describe("Page title"),
|
|
4824
|
+
handle: z8.string().describe("URL handle/slug"),
|
|
4825
|
+
isPublished: z8.boolean().describe("Whether page is visible on storefront")
|
|
4414
4826
|
});
|
|
4415
4827
|
var handleCreatePage = async (context, params) => {
|
|
4416
4828
|
log.debug(`Creating page on shop: ${context.shopDomain}`);
|
|
@@ -4453,6 +4865,14 @@ function registerCreatePageTool() {
|
|
|
4453
4865
|
relationships: {
|
|
4454
4866
|
relatedTools: ["list-pages", "get-page", "update-page"],
|
|
4455
4867
|
followUps: ["get-page", "update-page"]
|
|
4868
|
+
},
|
|
4869
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4870
|
+
annotations: {
|
|
4871
|
+
readOnlyHint: false,
|
|
4872
|
+
destructiveHint: false,
|
|
4873
|
+
idempotentHint: false,
|
|
4874
|
+
// create-* tools are NOT idempotent
|
|
4875
|
+
openWorldHint: true
|
|
4456
4876
|
}
|
|
4457
4877
|
},
|
|
4458
4878
|
handleCreatePage
|
|
@@ -4460,78 +4880,78 @@ function registerCreatePageTool() {
|
|
|
4460
4880
|
}
|
|
4461
4881
|
|
|
4462
4882
|
// src/tools/create-product.ts
|
|
4463
|
-
import { z as
|
|
4464
|
-
var inputSchema7 =
|
|
4883
|
+
import { z as z9 } from "zod";
|
|
4884
|
+
var inputSchema7 = z9.object({
|
|
4465
4885
|
// Required field
|
|
4466
|
-
title:
|
|
4886
|
+
title: z9.string().min(1, "title is required").describe('Product title (required). Example: "Premium Cotton T-Shirt"'),
|
|
4467
4887
|
// Optional core fields
|
|
4468
|
-
description:
|
|
4469
|
-
vendor:
|
|
4470
|
-
productType:
|
|
4471
|
-
tags:
|
|
4472
|
-
status:
|
|
4888
|
+
description: z9.string().optional().describe('Product description (HTML supported). Example: "<p>Soft cotton t-shirt.</p>"'),
|
|
4889
|
+
vendor: z9.string().optional().describe('Product vendor/brand name. Example: "Acme Corp"'),
|
|
4890
|
+
productType: z9.string().optional().describe('Product type for categorization. Example: "T-Shirts"'),
|
|
4891
|
+
tags: z9.array(z9.string()).optional().describe('Tags for search and filtering. Example: ["summer", "cotton", "casual"]'),
|
|
4892
|
+
status: z9.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().default("DRAFT").describe(
|
|
4473
4893
|
"Product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden). Default: DRAFT"
|
|
4474
4894
|
),
|
|
4475
4895
|
// SEO metadata
|
|
4476
|
-
seo:
|
|
4477
|
-
title:
|
|
4478
|
-
description:
|
|
4896
|
+
seo: z9.object({
|
|
4897
|
+
title: z9.string().optional().describe("SEO title for search engine results"),
|
|
4898
|
+
description: z9.string().optional().describe("SEO meta description for search engines")
|
|
4479
4899
|
}).optional().describe(
|
|
4480
4900
|
"SEO metadata for search engine optimization. If not provided, Shopify uses product title/description."
|
|
4481
4901
|
),
|
|
4482
4902
|
// Variants
|
|
4483
|
-
variants:
|
|
4484
|
-
|
|
4485
|
-
price:
|
|
4486
|
-
compareAtPrice:
|
|
4487
|
-
sku:
|
|
4488
|
-
barcode:
|
|
4489
|
-
inventoryQuantity:
|
|
4490
|
-
options:
|
|
4903
|
+
variants: z9.array(
|
|
4904
|
+
z9.object({
|
|
4905
|
+
price: z9.string().describe('Variant price as decimal string. Example: "29.99"'),
|
|
4906
|
+
compareAtPrice: z9.string().optional().describe('Original price for sale display. Example: "39.99"'),
|
|
4907
|
+
sku: z9.string().optional().describe('Stock keeping unit. Example: "SHIRT-001-RED-M"'),
|
|
4908
|
+
barcode: z9.string().optional().describe("Barcode (UPC, EAN, etc.)"),
|
|
4909
|
+
inventoryQuantity: z9.number().int().min(0).optional().describe("Initial inventory quantity (note: may require separate inventory API)"),
|
|
4910
|
+
options: z9.array(z9.string()).optional().describe('Variant options. Example: ["Red", "Medium"]')
|
|
4491
4911
|
})
|
|
4492
4912
|
).optional().describe("Product variants. If not provided, a default variant is created."),
|
|
4493
4913
|
// Images
|
|
4494
|
-
images:
|
|
4495
|
-
|
|
4496
|
-
url:
|
|
4497
|
-
altText:
|
|
4914
|
+
images: z9.array(
|
|
4915
|
+
z9.object({
|
|
4916
|
+
url: z9.string().url("Invalid image URL format").describe("Publicly accessible image URL"),
|
|
4917
|
+
altText: z9.string().optional().describe("Alt text for accessibility")
|
|
4498
4918
|
})
|
|
4499
4919
|
).optional().describe("Product images. Shopify fetches images from URLs.")
|
|
4500
4920
|
});
|
|
4501
|
-
var outputSchema7 =
|
|
4502
|
-
id:
|
|
4503
|
-
title:
|
|
4504
|
-
handle:
|
|
4505
|
-
description:
|
|
4506
|
-
vendor:
|
|
4507
|
-
productType:
|
|
4508
|
-
status:
|
|
4509
|
-
tags:
|
|
4510
|
-
seo:
|
|
4511
|
-
title:
|
|
4512
|
-
description:
|
|
4921
|
+
var outputSchema7 = z9.object({
|
|
4922
|
+
id: z9.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
4923
|
+
title: z9.string().describe("Product title"),
|
|
4924
|
+
handle: z9.string().describe("URL handle/slug"),
|
|
4925
|
+
description: z9.string().nullable().describe("Product description (HTML)"),
|
|
4926
|
+
vendor: z9.string().describe("Vendor/brand name"),
|
|
4927
|
+
productType: z9.string().describe("Product type"),
|
|
4928
|
+
status: z9.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
4929
|
+
tags: z9.array(z9.string()).describe("Product tags"),
|
|
4930
|
+
seo: z9.object({
|
|
4931
|
+
title: z9.string().nullable().describe("SEO title for search engines"),
|
|
4932
|
+
description: z9.string().nullable().describe("SEO description/meta description")
|
|
4513
4933
|
}).describe("SEO metadata for search engine optimization"),
|
|
4514
|
-
createdAt:
|
|
4515
|
-
updatedAt:
|
|
4516
|
-
variants:
|
|
4517
|
-
|
|
4518
|
-
id:
|
|
4519
|
-
title:
|
|
4520
|
-
price:
|
|
4521
|
-
compareAtPrice:
|
|
4522
|
-
sku:
|
|
4523
|
-
barcode:
|
|
4524
|
-
inventoryQuantity:
|
|
4934
|
+
createdAt: z9.string().describe("Creation timestamp (ISO 8601)"),
|
|
4935
|
+
updatedAt: z9.string().describe("Last update timestamp (ISO 8601)"),
|
|
4936
|
+
variants: z9.array(
|
|
4937
|
+
z9.object({
|
|
4938
|
+
id: z9.string().describe("Variant GID"),
|
|
4939
|
+
title: z9.string().describe("Variant title"),
|
|
4940
|
+
price: z9.string().describe("Price"),
|
|
4941
|
+
compareAtPrice: z9.string().nullable().describe("Compare at price"),
|
|
4942
|
+
sku: z9.string().nullable().describe("SKU"),
|
|
4943
|
+
barcode: z9.string().nullable().describe("Barcode"),
|
|
4944
|
+
inventoryQuantity: z9.number().nullable().describe("Inventory quantity")
|
|
4525
4945
|
})
|
|
4526
4946
|
).describe("Product variants"),
|
|
4527
|
-
images:
|
|
4528
|
-
|
|
4529
|
-
id:
|
|
4530
|
-
url:
|
|
4531
|
-
altText:
|
|
4947
|
+
images: z9.array(
|
|
4948
|
+
z9.object({
|
|
4949
|
+
id: z9.string().describe("Image GID"),
|
|
4950
|
+
url: z9.string().describe("Image URL"),
|
|
4951
|
+
altText: z9.string().nullable().describe("Alt text")
|
|
4532
4952
|
})
|
|
4533
4953
|
).describe("Product images"),
|
|
4534
|
-
totalInventory:
|
|
4954
|
+
totalInventory: z9.number().describe("Total inventory across variants")
|
|
4535
4955
|
});
|
|
4536
4956
|
var handleCreateProduct = async (context, params) => {
|
|
4537
4957
|
log.debug(`Creating product on shop: ${context.shopDomain}`);
|
|
@@ -4574,6 +4994,14 @@ function registerCreateProductTool() {
|
|
|
4574
4994
|
relatedTools: ["get-product", "update-product", "list-products"],
|
|
4575
4995
|
followUps: ["add-product-image", "set-product-metafields", "add-products-to-collection"],
|
|
4576
4996
|
alternatives: ["update-product"]
|
|
4997
|
+
},
|
|
4998
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
4999
|
+
annotations: {
|
|
5000
|
+
readOnlyHint: false,
|
|
5001
|
+
destructiveHint: false,
|
|
5002
|
+
idempotentHint: false,
|
|
5003
|
+
// Each call creates a new product
|
|
5004
|
+
openWorldHint: true
|
|
4577
5005
|
}
|
|
4578
5006
|
},
|
|
4579
5007
|
handleCreateProduct
|
|
@@ -4581,7 +5009,7 @@ function registerCreateProductTool() {
|
|
|
4581
5009
|
}
|
|
4582
5010
|
|
|
4583
5011
|
// src/tools/create-redirect.ts
|
|
4584
|
-
import { z as
|
|
5012
|
+
import { z as z10 } from "zod";
|
|
4585
5013
|
|
|
4586
5014
|
// src/shopify/redirects.ts
|
|
4587
5015
|
var URL_REDIRECT_CREATE_MUTATION = `
|
|
@@ -4729,20 +5157,20 @@ async function deleteRedirect(redirectId) {
|
|
|
4729
5157
|
}
|
|
4730
5158
|
|
|
4731
5159
|
// src/tools/create-redirect.ts
|
|
4732
|
-
var inputSchema8 =
|
|
4733
|
-
path:
|
|
5160
|
+
var inputSchema8 = z10.object({
|
|
5161
|
+
path: z10.string().min(1).refine((val) => val.startsWith("/"), {
|
|
4734
5162
|
message: "Path must start with '/' (e.g., '/old-product-url')"
|
|
4735
5163
|
}).describe(
|
|
4736
5164
|
"The source path to redirect FROM. Must start with '/'. Example: '/old-product-url' or '/collections/old-collection'."
|
|
4737
5165
|
),
|
|
4738
|
-
target:
|
|
5166
|
+
target: z10.string().min(1).describe(
|
|
4739
5167
|
"The destination URL to redirect TO. Can be relative ('/new-product-url') or absolute ('https://example.com/page')."
|
|
4740
5168
|
)
|
|
4741
5169
|
});
|
|
4742
|
-
var outputSchema8 =
|
|
4743
|
-
id:
|
|
4744
|
-
path:
|
|
4745
|
-
target:
|
|
5170
|
+
var outputSchema8 = z10.object({
|
|
5171
|
+
id: z10.string().describe('Created redirect GID (e.g., "gid://shopify/UrlRedirect/123")'),
|
|
5172
|
+
path: z10.string().describe("Source path that will redirect"),
|
|
5173
|
+
target: z10.string().describe("Target URL visitors will be redirected to")
|
|
4746
5174
|
});
|
|
4747
5175
|
var handleCreateRedirect = async (context, params) => {
|
|
4748
5176
|
log.debug(`Creating redirect on shop: ${context.shopDomain}`);
|
|
@@ -4783,6 +5211,14 @@ function registerCreateRedirectTool() {
|
|
|
4783
5211
|
relationships: {
|
|
4784
5212
|
relatedTools: ["list-redirects", "delete-redirect"],
|
|
4785
5213
|
followUps: ["list-redirects"]
|
|
5214
|
+
},
|
|
5215
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5216
|
+
annotations: {
|
|
5217
|
+
readOnlyHint: false,
|
|
5218
|
+
destructiveHint: false,
|
|
5219
|
+
idempotentHint: false,
|
|
5220
|
+
// Each call creates a new redirect (duplicate path fails)
|
|
5221
|
+
openWorldHint: true
|
|
4786
5222
|
}
|
|
4787
5223
|
},
|
|
4788
5224
|
handleCreateRedirect
|
|
@@ -4790,15 +5226,15 @@ function registerCreateRedirectTool() {
|
|
|
4790
5226
|
}
|
|
4791
5227
|
|
|
4792
5228
|
// src/tools/delete-article.ts
|
|
4793
|
-
import { z as
|
|
4794
|
-
var inputSchema9 =
|
|
4795
|
-
id:
|
|
5229
|
+
import { z as z11 } from "zod";
|
|
5230
|
+
var inputSchema9 = z11.object({
|
|
5231
|
+
id: articleIdSchema.describe(
|
|
4796
5232
|
'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.'
|
|
4797
5233
|
)
|
|
4798
5234
|
});
|
|
4799
|
-
var outputSchema9 =
|
|
4800
|
-
success:
|
|
4801
|
-
deletedArticleId:
|
|
5235
|
+
var outputSchema9 = z11.object({
|
|
5236
|
+
success: z11.boolean().describe("Whether the deletion was successful"),
|
|
5237
|
+
deletedArticleId: z11.string().describe("The ID of the deleted article")
|
|
4802
5238
|
});
|
|
4803
5239
|
var handleDeleteArticle = async (context, params) => {
|
|
4804
5240
|
log.debug(`Deleting article on shop: ${context.shopDomain}`);
|
|
@@ -4839,6 +5275,14 @@ function registerDeleteArticleTool() {
|
|
|
4839
5275
|
relatedTools: ["list-articles", "update-article"],
|
|
4840
5276
|
prerequisites: ["list-articles"],
|
|
4841
5277
|
alternatives: ["update-article"]
|
|
5278
|
+
},
|
|
5279
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5280
|
+
annotations: {
|
|
5281
|
+
readOnlyHint: false,
|
|
5282
|
+
destructiveHint: true,
|
|
5283
|
+
idempotentHint: false,
|
|
5284
|
+
// delete-* tools are NOT idempotent
|
|
5285
|
+
openWorldHint: true
|
|
4842
5286
|
}
|
|
4843
5287
|
},
|
|
4844
5288
|
handleDeleteArticle
|
|
@@ -4846,15 +5290,15 @@ function registerDeleteArticleTool() {
|
|
|
4846
5290
|
}
|
|
4847
5291
|
|
|
4848
5292
|
// src/tools/delete-blog.ts
|
|
4849
|
-
import { z as
|
|
4850
|
-
var inputSchema10 =
|
|
4851
|
-
id:
|
|
5293
|
+
import { z as z12 } from "zod";
|
|
5294
|
+
var inputSchema10 = z12.object({
|
|
5295
|
+
id: blogIdSchema.describe(
|
|
4852
5296
|
'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.'
|
|
4853
5297
|
)
|
|
4854
5298
|
});
|
|
4855
|
-
var outputSchema10 =
|
|
4856
|
-
success:
|
|
4857
|
-
deletedBlogId:
|
|
5299
|
+
var outputSchema10 = z12.object({
|
|
5300
|
+
success: z12.boolean().describe("Whether the deletion was successful"),
|
|
5301
|
+
deletedBlogId: z12.string().describe("The ID of the deleted blog")
|
|
4858
5302
|
});
|
|
4859
5303
|
var handleDeleteBlog = async (context, params) => {
|
|
4860
5304
|
log.debug(`Deleting blog on shop: ${context.shopDomain}`);
|
|
@@ -4894,6 +5338,14 @@ function registerDeleteBlogTool() {
|
|
|
4894
5338
|
relationships: {
|
|
4895
5339
|
relatedTools: ["list-blogs", "list-articles"],
|
|
4896
5340
|
prerequisites: ["list-blogs"]
|
|
5341
|
+
},
|
|
5342
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5343
|
+
annotations: {
|
|
5344
|
+
readOnlyHint: false,
|
|
5345
|
+
destructiveHint: true,
|
|
5346
|
+
idempotentHint: false,
|
|
5347
|
+
// delete-* tools are NOT idempotent
|
|
5348
|
+
openWorldHint: true
|
|
4897
5349
|
}
|
|
4898
5350
|
},
|
|
4899
5351
|
handleDeleteBlog
|
|
@@ -4901,14 +5353,14 @@ function registerDeleteBlogTool() {
|
|
|
4901
5353
|
}
|
|
4902
5354
|
|
|
4903
5355
|
// src/tools/delete-collection.ts
|
|
4904
|
-
import { z as
|
|
4905
|
-
var inputSchema11 =
|
|
4906
|
-
collectionId:
|
|
5356
|
+
import { z as z13 } from "zod";
|
|
5357
|
+
var inputSchema11 = z13.object({
|
|
5358
|
+
collectionId: collectionIdSchema.describe(
|
|
4907
5359
|
'Collection ID to delete (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
|
|
4908
5360
|
)
|
|
4909
5361
|
});
|
|
4910
|
-
var outputSchema11 =
|
|
4911
|
-
deletedCollectionId:
|
|
5362
|
+
var outputSchema11 = z13.object({
|
|
5363
|
+
deletedCollectionId: z13.string().describe("The GID of the deleted collection")
|
|
4912
5364
|
});
|
|
4913
5365
|
var handleDeleteCollection = async (context, params) => {
|
|
4914
5366
|
log.debug(`Deleting collection on shop: ${context.shopDomain}`);
|
|
@@ -4945,6 +5397,15 @@ function registerDeleteCollectionTool() {
|
|
|
4945
5397
|
relationships: {
|
|
4946
5398
|
relatedTools: ["list-collections"],
|
|
4947
5399
|
prerequisites: ["get-collection"]
|
|
5400
|
+
},
|
|
5401
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5402
|
+
annotations: {
|
|
5403
|
+
readOnlyHint: false,
|
|
5404
|
+
destructiveHint: true,
|
|
5405
|
+
// Permanently deletes collection
|
|
5406
|
+
idempotentHint: false,
|
|
5407
|
+
// Second call fails (already deleted)
|
|
5408
|
+
openWorldHint: true
|
|
4948
5409
|
}
|
|
4949
5410
|
},
|
|
4950
5411
|
handleDeleteCollection
|
|
@@ -4952,15 +5413,15 @@ function registerDeleteCollectionTool() {
|
|
|
4952
5413
|
}
|
|
4953
5414
|
|
|
4954
5415
|
// src/tools/delete-page.ts
|
|
4955
|
-
import { z as
|
|
4956
|
-
var inputSchema12 =
|
|
4957
|
-
id:
|
|
5416
|
+
import { z as z14 } from "zod";
|
|
5417
|
+
var inputSchema12 = z14.object({
|
|
5418
|
+
id: pageIdSchema.describe(
|
|
4958
5419
|
'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.'
|
|
4959
5420
|
)
|
|
4960
5421
|
});
|
|
4961
|
-
var outputSchema12 =
|
|
4962
|
-
success:
|
|
4963
|
-
deletedPageId:
|
|
5422
|
+
var outputSchema12 = z14.object({
|
|
5423
|
+
success: z14.boolean().describe("Whether the deletion was successful"),
|
|
5424
|
+
deletedPageId: z14.string().describe("The ID of the deleted page")
|
|
4964
5425
|
});
|
|
4965
5426
|
var handleDeletePage = async (context, params) => {
|
|
4966
5427
|
log.debug(`Deleting page on shop: ${context.shopDomain}`);
|
|
@@ -5001,6 +5462,14 @@ function registerDeletePageTool() {
|
|
|
5001
5462
|
relatedTools: ["get-page", "list-pages"],
|
|
5002
5463
|
prerequisites: ["get-page"],
|
|
5003
5464
|
alternatives: ["update-page"]
|
|
5465
|
+
},
|
|
5466
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5467
|
+
annotations: {
|
|
5468
|
+
readOnlyHint: false,
|
|
5469
|
+
destructiveHint: true,
|
|
5470
|
+
idempotentHint: false,
|
|
5471
|
+
// delete-* tools are NOT idempotent
|
|
5472
|
+
openWorldHint: true
|
|
5004
5473
|
}
|
|
5005
5474
|
},
|
|
5006
5475
|
handleDeletePage
|
|
@@ -5008,7 +5477,7 @@ function registerDeletePageTool() {
|
|
|
5008
5477
|
}
|
|
5009
5478
|
|
|
5010
5479
|
// src/tools/delete-product-image.ts
|
|
5011
|
-
import { z as
|
|
5480
|
+
import { z as z15 } from "zod";
|
|
5012
5481
|
var FILE_DELETE_MUTATION = `
|
|
5013
5482
|
mutation FileDelete($fileIds: [ID!]!) {
|
|
5014
5483
|
fileDelete(fileIds: $fileIds) {
|
|
@@ -5020,14 +5489,14 @@ var FILE_DELETE_MUTATION = `
|
|
|
5020
5489
|
}
|
|
5021
5490
|
}
|
|
5022
5491
|
`;
|
|
5023
|
-
var inputSchema13 =
|
|
5024
|
-
imageId:
|
|
5492
|
+
var inputSchema13 = z15.object({
|
|
5493
|
+
imageId: imageIdSchema.describe(
|
|
5025
5494
|
'Shopify image GID to delete (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the images array.'
|
|
5026
5495
|
)
|
|
5027
5496
|
});
|
|
5028
|
-
var outputSchema13 =
|
|
5029
|
-
success:
|
|
5030
|
-
deletedImageId:
|
|
5497
|
+
var outputSchema13 = z15.object({
|
|
5498
|
+
success: z15.boolean().describe("Whether the deletion was successful"),
|
|
5499
|
+
deletedImageId: z15.string().describe("ID of the deleted image")
|
|
5031
5500
|
});
|
|
5032
5501
|
var handleDeleteProductImage = async (context, params) => {
|
|
5033
5502
|
log.debug(`Deleting image on shop: ${context.shopDomain}`);
|
|
@@ -5085,6 +5554,15 @@ function registerDeleteProductImageTool() {
|
|
|
5085
5554
|
relationships: {
|
|
5086
5555
|
relatedTools: ["get-product", "add-product-image"],
|
|
5087
5556
|
prerequisites: ["get-product"]
|
|
5557
|
+
},
|
|
5558
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5559
|
+
annotations: {
|
|
5560
|
+
readOnlyHint: false,
|
|
5561
|
+
destructiveHint: true,
|
|
5562
|
+
// Permanently deletes image from Shopify CDN
|
|
5563
|
+
idempotentHint: false,
|
|
5564
|
+
// Second call fails (already deleted)
|
|
5565
|
+
openWorldHint: true
|
|
5088
5566
|
}
|
|
5089
5567
|
},
|
|
5090
5568
|
handleDeleteProductImage
|
|
@@ -5092,7 +5570,7 @@ function registerDeleteProductImageTool() {
|
|
|
5092
5570
|
}
|
|
5093
5571
|
|
|
5094
5572
|
// src/tools/delete-product-metafields.ts
|
|
5095
|
-
import { z as
|
|
5573
|
+
import { z as z16 } from "zod";
|
|
5096
5574
|
|
|
5097
5575
|
// src/shopify/metafields.ts
|
|
5098
5576
|
var GET_PRODUCT_METAFIELDS_QUERY = `
|
|
@@ -5271,28 +5749,28 @@ async function deleteProductMetafields(productId, identifiers) {
|
|
|
5271
5749
|
}
|
|
5272
5750
|
|
|
5273
5751
|
// src/tools/delete-product-metafields.ts
|
|
5274
|
-
var metafieldIdentifierSchema =
|
|
5275
|
-
namespace:
|
|
5276
|
-
key:
|
|
5752
|
+
var metafieldIdentifierSchema = z16.object({
|
|
5753
|
+
namespace: z16.string().min(1).describe('Metafield namespace (e.g., "custom", "seo"). Must match existing metafield.'),
|
|
5754
|
+
key: z16.string().min(1).describe('Metafield key (e.g., "color_hex"). Must match existing metafield.')
|
|
5277
5755
|
});
|
|
5278
|
-
var inputSchema14 =
|
|
5279
|
-
productId:
|
|
5756
|
+
var inputSchema14 = z16.object({
|
|
5757
|
+
productId: productIdSchema.describe(
|
|
5280
5758
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
5281
5759
|
),
|
|
5282
|
-
identifiers:
|
|
5760
|
+
identifiers: z16.array(metafieldIdentifierSchema).min(1, "At least one metafield identifier is required").describe(
|
|
5283
5761
|
"Array of metafield identifiers to delete. Each identifier must have namespace and key. Use get-product-metafields first to verify which metafields exist."
|
|
5284
5762
|
)
|
|
5285
5763
|
});
|
|
5286
|
-
var outputSchema14 =
|
|
5287
|
-
success:
|
|
5288
|
-
deletedMetafields:
|
|
5289
|
-
|
|
5290
|
-
ownerId:
|
|
5291
|
-
namespace:
|
|
5292
|
-
key:
|
|
5764
|
+
var outputSchema14 = z16.object({
|
|
5765
|
+
success: z16.boolean().describe("Whether the operation succeeded"),
|
|
5766
|
+
deletedMetafields: z16.array(
|
|
5767
|
+
z16.object({
|
|
5768
|
+
ownerId: z16.string().describe("Product GID that owned the metafield"),
|
|
5769
|
+
namespace: z16.string().describe("Namespace of deleted metafield"),
|
|
5770
|
+
key: z16.string().describe("Key of deleted metafield")
|
|
5293
5771
|
})
|
|
5294
5772
|
).describe("Identifiers of successfully deleted metafields"),
|
|
5295
|
-
deletedCount:
|
|
5773
|
+
deletedCount: z16.number().describe("Number of metafields deleted")
|
|
5296
5774
|
});
|
|
5297
5775
|
var handleDeleteProductMetafields = async (context, params) => {
|
|
5298
5776
|
log.debug(
|
|
@@ -5329,6 +5807,15 @@ function registerDeleteProductMetafieldsTool() {
|
|
|
5329
5807
|
relationships: {
|
|
5330
5808
|
relatedTools: ["get-product-metafields"],
|
|
5331
5809
|
prerequisites: ["get-product-metafields"]
|
|
5810
|
+
},
|
|
5811
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5812
|
+
annotations: {
|
|
5813
|
+
readOnlyHint: false,
|
|
5814
|
+
destructiveHint: true,
|
|
5815
|
+
// Partial data deletion - cannot be recovered
|
|
5816
|
+
idempotentHint: false,
|
|
5817
|
+
// Second call fails (already deleted)
|
|
5818
|
+
openWorldHint: true
|
|
5332
5819
|
}
|
|
5333
5820
|
},
|
|
5334
5821
|
handleDeleteProductMetafields
|
|
@@ -5336,13 +5823,15 @@ function registerDeleteProductMetafieldsTool() {
|
|
|
5336
5823
|
}
|
|
5337
5824
|
|
|
5338
5825
|
// src/tools/delete-product.ts
|
|
5339
|
-
import { z as
|
|
5340
|
-
var inputSchema15 =
|
|
5341
|
-
id:
|
|
5826
|
+
import { z as z17 } from "zod";
|
|
5827
|
+
var inputSchema15 = z17.object({
|
|
5828
|
+
id: productIdSchema.describe(
|
|
5829
|
+
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
|
|
5830
|
+
)
|
|
5342
5831
|
});
|
|
5343
|
-
var outputSchema15 =
|
|
5344
|
-
deletedProductId:
|
|
5345
|
-
title:
|
|
5832
|
+
var outputSchema15 = z17.object({
|
|
5833
|
+
deletedProductId: z17.string().describe("The ID of the deleted product"),
|
|
5834
|
+
title: z17.string().describe("The title of the deleted product (for confirmation)")
|
|
5346
5835
|
});
|
|
5347
5836
|
var handleDeleteProduct = async (context, params) => {
|
|
5348
5837
|
log.debug(`Deleting product on shop: ${context.shopDomain}`);
|
|
@@ -5391,6 +5880,15 @@ function registerDeleteProductTool() {
|
|
|
5391
5880
|
relationships: {
|
|
5392
5881
|
relatedTools: ["list-products"],
|
|
5393
5882
|
prerequisites: ["get-product"]
|
|
5883
|
+
},
|
|
5884
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5885
|
+
annotations: {
|
|
5886
|
+
readOnlyHint: false,
|
|
5887
|
+
destructiveHint: true,
|
|
5888
|
+
// Permanently deletes product
|
|
5889
|
+
idempotentHint: false,
|
|
5890
|
+
// Second call fails (already deleted)
|
|
5891
|
+
openWorldHint: true
|
|
5394
5892
|
}
|
|
5395
5893
|
},
|
|
5396
5894
|
handleDeleteProduct
|
|
@@ -5398,15 +5896,15 @@ function registerDeleteProductTool() {
|
|
|
5398
5896
|
}
|
|
5399
5897
|
|
|
5400
5898
|
// src/tools/delete-redirect.ts
|
|
5401
|
-
import { z as
|
|
5402
|
-
var inputSchema16 =
|
|
5403
|
-
id:
|
|
5899
|
+
import { z as z18 } from "zod";
|
|
5900
|
+
var inputSchema16 = z18.object({
|
|
5901
|
+
id: redirectIdSchema.describe(
|
|
5404
5902
|
'Shopify redirect GID (e.g., "gid://shopify/UrlRedirect/123"). Use list-redirects to find redirect IDs.'
|
|
5405
5903
|
)
|
|
5406
5904
|
});
|
|
5407
|
-
var outputSchema16 =
|
|
5408
|
-
success:
|
|
5409
|
-
deletedRedirectId:
|
|
5905
|
+
var outputSchema16 = z18.object({
|
|
5906
|
+
success: z18.boolean().describe("Whether the deletion succeeded"),
|
|
5907
|
+
deletedRedirectId: z18.string().describe("ID of the deleted redirect")
|
|
5410
5908
|
});
|
|
5411
5909
|
var handleDeleteRedirect = async (context, params) => {
|
|
5412
5910
|
log.debug(`Deleting redirect on shop: ${context.shopDomain}`);
|
|
@@ -5455,6 +5953,15 @@ function registerDeleteRedirectTool() {
|
|
|
5455
5953
|
relationships: {
|
|
5456
5954
|
relatedTools: ["create-redirect"],
|
|
5457
5955
|
prerequisites: ["list-redirects"]
|
|
5956
|
+
},
|
|
5957
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
5958
|
+
annotations: {
|
|
5959
|
+
readOnlyHint: false,
|
|
5960
|
+
destructiveHint: true,
|
|
5961
|
+
// Permanently removes redirect - can hurt SEO
|
|
5962
|
+
idempotentHint: false,
|
|
5963
|
+
// Second call fails (already deleted)
|
|
5964
|
+
openWorldHint: true
|
|
5458
5965
|
}
|
|
5459
5966
|
},
|
|
5460
5967
|
handleDeleteRedirect
|
|
@@ -5462,7 +5969,7 @@ function registerDeleteRedirectTool() {
|
|
|
5462
5969
|
}
|
|
5463
5970
|
|
|
5464
5971
|
// src/tools/get-bulk-inventory.ts
|
|
5465
|
-
import { z as
|
|
5972
|
+
import { z as z19 } from "zod";
|
|
5466
5973
|
|
|
5467
5974
|
// src/shopify/inventory.ts
|
|
5468
5975
|
var GET_INVENTORY_ITEM_QUERY = `
|
|
@@ -6033,46 +6540,46 @@ async function getBulkInventory(options) {
|
|
|
6033
6540
|
}
|
|
6034
6541
|
|
|
6035
6542
|
// src/tools/get-bulk-inventory.ts
|
|
6036
|
-
var inputSchema17 =
|
|
6037
|
-
productIds:
|
|
6543
|
+
var inputSchema17 = z19.object({
|
|
6544
|
+
productIds: z19.array(productIdSchema).min(1, { message: "productIds array cannot be empty" }).max(50, { message: "Maximum 50 product IDs allowed per request" }).describe(
|
|
6038
6545
|
'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.'
|
|
6039
6546
|
),
|
|
6040
|
-
includeVariants:
|
|
6547
|
+
includeVariants: z19.boolean().default(true).describe(
|
|
6041
6548
|
"Include per-variant inventory breakdown. Default: true. Set to false for faster response with only product totals."
|
|
6042
6549
|
),
|
|
6043
|
-
locationId:
|
|
6550
|
+
locationId: locationIdSchema.optional().describe(
|
|
6044
6551
|
'Optional location ID to filter inventory (e.g., "gid://shopify/Location/789"). If not provided, returns total inventory across all locations.'
|
|
6045
6552
|
)
|
|
6046
6553
|
});
|
|
6047
|
-
var outputSchema17 =
|
|
6048
|
-
products:
|
|
6049
|
-
|
|
6050
|
-
productId:
|
|
6051
|
-
productTitle:
|
|
6052
|
-
totalInventory:
|
|
6053
|
-
variants:
|
|
6054
|
-
|
|
6055
|
-
variantId:
|
|
6056
|
-
variantTitle:
|
|
6057
|
-
sku:
|
|
6058
|
-
inventoryItemId:
|
|
6059
|
-
available:
|
|
6060
|
-
locations:
|
|
6061
|
-
|
|
6062
|
-
locationId:
|
|
6063
|
-
locationName:
|
|
6064
|
-
available:
|
|
6554
|
+
var outputSchema17 = z19.object({
|
|
6555
|
+
products: z19.array(
|
|
6556
|
+
z19.object({
|
|
6557
|
+
productId: z19.string().describe("Product GID"),
|
|
6558
|
+
productTitle: z19.string().describe("Product title"),
|
|
6559
|
+
totalInventory: z19.number().describe("Total inventory across all variants"),
|
|
6560
|
+
variants: z19.array(
|
|
6561
|
+
z19.object({
|
|
6562
|
+
variantId: z19.string().describe("Variant GID"),
|
|
6563
|
+
variantTitle: z19.string().describe('Variant title (e.g., "Red / Large")'),
|
|
6564
|
+
sku: z19.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
6565
|
+
inventoryItemId: z19.string().describe("Inventory item GID"),
|
|
6566
|
+
available: z19.number().describe("Available quantity"),
|
|
6567
|
+
locations: z19.array(
|
|
6568
|
+
z19.object({
|
|
6569
|
+
locationId: z19.string().describe("Location GID"),
|
|
6570
|
+
locationName: z19.string().describe("Human-readable location name"),
|
|
6571
|
+
available: z19.number().describe("Available quantity at this location")
|
|
6065
6572
|
})
|
|
6066
6573
|
).optional().describe("Per-location inventory breakdown")
|
|
6067
6574
|
})
|
|
6068
6575
|
).optional().describe("Variant details (if includeVariants=true)")
|
|
6069
6576
|
})
|
|
6070
6577
|
).describe("Products with inventory data"),
|
|
6071
|
-
notFound:
|
|
6072
|
-
summary:
|
|
6073
|
-
productsQueried:
|
|
6074
|
-
productsFound:
|
|
6075
|
-
totalInventory:
|
|
6578
|
+
notFound: z19.array(z19.string()).describe("Product IDs that were not found in the store"),
|
|
6579
|
+
summary: z19.object({
|
|
6580
|
+
productsQueried: z19.number().describe("Total number of product IDs provided"),
|
|
6581
|
+
productsFound: z19.number().describe("Number of products found in the store"),
|
|
6582
|
+
totalInventory: z19.number().describe("Sum of inventory across all found products")
|
|
6076
6583
|
}).describe("Summary statistics")
|
|
6077
6584
|
});
|
|
6078
6585
|
var handleGetBulkInventory = async (context, params) => {
|
|
@@ -6112,6 +6619,13 @@ function registerGetBulkInventoryTool() {
|
|
|
6112
6619
|
prerequisites: ["list-products"],
|
|
6113
6620
|
followUps: ["update-inventory", "get-inventory"],
|
|
6114
6621
|
alternatives: ["get-inventory"]
|
|
6622
|
+
},
|
|
6623
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6624
|
+
annotations: {
|
|
6625
|
+
readOnlyHint: true,
|
|
6626
|
+
destructiveHint: false,
|
|
6627
|
+
idempotentHint: true,
|
|
6628
|
+
openWorldHint: true
|
|
6115
6629
|
}
|
|
6116
6630
|
},
|
|
6117
6631
|
handleGetBulkInventory
|
|
@@ -6119,30 +6633,30 @@ function registerGetBulkInventoryTool() {
|
|
|
6119
6633
|
}
|
|
6120
6634
|
|
|
6121
6635
|
// src/tools/get-collection.ts
|
|
6122
|
-
import { z as
|
|
6123
|
-
var inputSchema18 =
|
|
6124
|
-
collectionId:
|
|
6636
|
+
import { z as z20 } from "zod";
|
|
6637
|
+
var inputSchema18 = z20.object({
|
|
6638
|
+
collectionId: collectionIdSchema.describe(
|
|
6125
6639
|
'Collection ID (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections to find collection IDs.'
|
|
6126
6640
|
)
|
|
6127
6641
|
});
|
|
6128
|
-
var outputSchema18 =
|
|
6129
|
-
id:
|
|
6130
|
-
title:
|
|
6131
|
-
handle:
|
|
6132
|
-
description:
|
|
6133
|
-
descriptionHtml:
|
|
6134
|
-
seo:
|
|
6135
|
-
title:
|
|
6136
|
-
description:
|
|
6642
|
+
var outputSchema18 = z20.object({
|
|
6643
|
+
id: z20.string().describe("Collection GID"),
|
|
6644
|
+
title: z20.string().describe("Collection title"),
|
|
6645
|
+
handle: z20.string().describe("URL handle/slug"),
|
|
6646
|
+
description: z20.string().describe("Plain text description"),
|
|
6647
|
+
descriptionHtml: z20.string().describe("HTML description"),
|
|
6648
|
+
seo: z20.object({
|
|
6649
|
+
title: z20.string().nullable().describe("SEO title"),
|
|
6650
|
+
description: z20.string().nullable().describe("SEO description")
|
|
6137
6651
|
}),
|
|
6138
|
-
image:
|
|
6139
|
-
id:
|
|
6140
|
-
url:
|
|
6141
|
-
altText:
|
|
6652
|
+
image: z20.object({
|
|
6653
|
+
id: z20.string().describe("Image GID"),
|
|
6654
|
+
url: z20.string().describe("Image URL"),
|
|
6655
|
+
altText: z20.string().nullable().describe("Alt text")
|
|
6142
6656
|
}).nullable(),
|
|
6143
|
-
sortOrder:
|
|
6144
|
-
productsCount:
|
|
6145
|
-
updatedAt:
|
|
6657
|
+
sortOrder: z20.string().describe("Product sort order within collection"),
|
|
6658
|
+
productsCount: z20.number().describe("Number of products in collection"),
|
|
6659
|
+
updatedAt: z20.string().describe("Last update timestamp (ISO 8601)")
|
|
6146
6660
|
});
|
|
6147
6661
|
var handleGetCollection = async (context, params) => {
|
|
6148
6662
|
log.debug(`Getting collection on shop: ${context.shopDomain}`);
|
|
@@ -6169,6 +6683,13 @@ function registerGetCollectionTool() {
|
|
|
6169
6683
|
relationships: {
|
|
6170
6684
|
relatedTools: ["list-collections"],
|
|
6171
6685
|
followUps: ["update-collection", "delete-collection", "add-products-to-collection"]
|
|
6686
|
+
},
|
|
6687
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6688
|
+
annotations: {
|
|
6689
|
+
readOnlyHint: true,
|
|
6690
|
+
destructiveHint: false,
|
|
6691
|
+
idempotentHint: true,
|
|
6692
|
+
openWorldHint: true
|
|
6172
6693
|
}
|
|
6173
6694
|
},
|
|
6174
6695
|
handleGetCollection
|
|
@@ -6176,37 +6697,37 @@ function registerGetCollectionTool() {
|
|
|
6176
6697
|
}
|
|
6177
6698
|
|
|
6178
6699
|
// src/tools/get-inventory.ts
|
|
6179
|
-
import { z as
|
|
6180
|
-
var inputSchema19 =
|
|
6181
|
-
variantId:
|
|
6700
|
+
import { z as z21 } from "zod";
|
|
6701
|
+
var inputSchema19 = z21.object({
|
|
6702
|
+
variantId: variantIdSchema.optional().describe(
|
|
6182
6703
|
'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).'
|
|
6183
6704
|
),
|
|
6184
|
-
inventoryItemId:
|
|
6705
|
+
inventoryItemId: inventoryItemIdSchema.optional().describe(
|
|
6185
6706
|
'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.'
|
|
6186
6707
|
),
|
|
6187
|
-
locationId:
|
|
6708
|
+
locationId: locationIdSchema.optional().describe(
|
|
6188
6709
|
'Optional location ID to filter results (e.g., "gid://shopify/Location/789"). If not provided, returns inventory at all locations.'
|
|
6189
6710
|
)
|
|
6190
6711
|
}).refine((data) => data.variantId || data.inventoryItemId, {
|
|
6191
6712
|
message: "Either variantId or inventoryItemId must be provided"
|
|
6192
6713
|
});
|
|
6193
|
-
var outputSchema19 =
|
|
6194
|
-
inventoryItemId:
|
|
6195
|
-
variantId:
|
|
6196
|
-
variantTitle:
|
|
6197
|
-
productId:
|
|
6198
|
-
productTitle:
|
|
6199
|
-
sku:
|
|
6200
|
-
tracked:
|
|
6201
|
-
totalAvailable:
|
|
6202
|
-
locations:
|
|
6203
|
-
|
|
6204
|
-
locationId:
|
|
6205
|
-
locationName:
|
|
6206
|
-
isActive:
|
|
6207
|
-
available:
|
|
6208
|
-
onHand:
|
|
6209
|
-
committed:
|
|
6714
|
+
var outputSchema19 = z21.object({
|
|
6715
|
+
inventoryItemId: z21.string().describe("Inventory item GID"),
|
|
6716
|
+
variantId: z21.string().describe("Product variant GID"),
|
|
6717
|
+
variantTitle: z21.string().describe('Variant title (e.g., "Red / Large")'),
|
|
6718
|
+
productId: z21.string().describe("Product GID"),
|
|
6719
|
+
productTitle: z21.string().describe("Product title"),
|
|
6720
|
+
sku: z21.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
6721
|
+
tracked: z21.boolean().describe("Whether inventory tracking is enabled for this item"),
|
|
6722
|
+
totalAvailable: z21.number().describe("Total available quantity across all locations"),
|
|
6723
|
+
locations: z21.array(
|
|
6724
|
+
z21.object({
|
|
6725
|
+
locationId: z21.string().describe("Location GID"),
|
|
6726
|
+
locationName: z21.string().describe("Human-readable location name"),
|
|
6727
|
+
isActive: z21.boolean().describe("Whether the location is active"),
|
|
6728
|
+
available: z21.number().describe("Quantity available for sale"),
|
|
6729
|
+
onHand: z21.number().describe("Physical quantity on hand"),
|
|
6730
|
+
committed: z21.number().describe("Quantity committed to orders")
|
|
6210
6731
|
})
|
|
6211
6732
|
).describe("Inventory levels by location")
|
|
6212
6733
|
});
|
|
@@ -6280,6 +6801,13 @@ function registerGetInventoryTool() {
|
|
|
6280
6801
|
],
|
|
6281
6802
|
prerequisites: ["get-product", "list-products"],
|
|
6282
6803
|
followUps: ["update-inventory"]
|
|
6804
|
+
},
|
|
6805
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6806
|
+
annotations: {
|
|
6807
|
+
readOnlyHint: true,
|
|
6808
|
+
destructiveHint: false,
|
|
6809
|
+
idempotentHint: true,
|
|
6810
|
+
openWorldHint: true
|
|
6283
6811
|
}
|
|
6284
6812
|
},
|
|
6285
6813
|
handleGetInventory
|
|
@@ -6287,23 +6815,23 @@ function registerGetInventoryTool() {
|
|
|
6287
6815
|
}
|
|
6288
6816
|
|
|
6289
6817
|
// src/tools/get-page.ts
|
|
6290
|
-
import { z as
|
|
6291
|
-
var inputSchema20 =
|
|
6292
|
-
id:
|
|
6818
|
+
import { z as z22 } from "zod";
|
|
6819
|
+
var inputSchema20 = z22.object({
|
|
6820
|
+
id: pageIdSchema.describe(
|
|
6293
6821
|
'The page ID to retrieve. Must be a valid Shopify GID format. Example: "gid://shopify/Page/123456789". Use list-pages to find page IDs.'
|
|
6294
6822
|
)
|
|
6295
6823
|
});
|
|
6296
|
-
var outputSchema20 =
|
|
6297
|
-
id:
|
|
6298
|
-
title:
|
|
6299
|
-
handle:
|
|
6300
|
-
body:
|
|
6301
|
-
bodySummary:
|
|
6302
|
-
isPublished:
|
|
6303
|
-
publishedAt:
|
|
6304
|
-
templateSuffix:
|
|
6305
|
-
createdAt:
|
|
6306
|
-
updatedAt:
|
|
6824
|
+
var outputSchema20 = z22.object({
|
|
6825
|
+
id: z22.string().describe("Page GID"),
|
|
6826
|
+
title: z22.string().describe("Page title"),
|
|
6827
|
+
handle: z22.string().describe("URL handle/slug"),
|
|
6828
|
+
body: z22.string().describe("HTML body content"),
|
|
6829
|
+
bodySummary: z22.string().describe("Plain text summary (first 150 chars)"),
|
|
6830
|
+
isPublished: z22.boolean().describe("Whether page is visible on storefront"),
|
|
6831
|
+
publishedAt: z22.string().nullable().describe("ISO timestamp when published"),
|
|
6832
|
+
templateSuffix: z22.string().nullable().describe("Custom template suffix"),
|
|
6833
|
+
createdAt: z22.string().describe("ISO timestamp of creation"),
|
|
6834
|
+
updatedAt: z22.string().describe("ISO timestamp of last update")
|
|
6307
6835
|
});
|
|
6308
6836
|
var handleGetPage = async (context, params) => {
|
|
6309
6837
|
log.debug(`Getting page on shop: ${context.shopDomain}`);
|
|
@@ -6330,6 +6858,13 @@ function registerGetPageTool() {
|
|
|
6330
6858
|
relatedTools: ["list-pages", "update-page", "delete-page"],
|
|
6331
6859
|
prerequisites: ["list-pages"],
|
|
6332
6860
|
followUps: ["update-page", "delete-page"]
|
|
6861
|
+
},
|
|
6862
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6863
|
+
annotations: {
|
|
6864
|
+
readOnlyHint: true,
|
|
6865
|
+
destructiveHint: false,
|
|
6866
|
+
idempotentHint: true,
|
|
6867
|
+
openWorldHint: true
|
|
6333
6868
|
}
|
|
6334
6869
|
},
|
|
6335
6870
|
handleGetPage
|
|
@@ -6337,27 +6872,27 @@ function registerGetPageTool() {
|
|
|
6337
6872
|
}
|
|
6338
6873
|
|
|
6339
6874
|
// src/tools/get-product-metafields.ts
|
|
6340
|
-
import { z as
|
|
6341
|
-
var inputSchema21 =
|
|
6342
|
-
productId:
|
|
6875
|
+
import { z as z23 } from "zod";
|
|
6876
|
+
var inputSchema21 = z23.object({
|
|
6877
|
+
productId: productIdSchema.describe(
|
|
6343
6878
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
6344
6879
|
),
|
|
6345
|
-
namespace:
|
|
6880
|
+
namespace: z23.string().optional().describe(
|
|
6346
6881
|
'Filter metafields by namespace (e.g., "custom", "seo", "my_app"). If not provided, returns metafields from all namespaces.'
|
|
6347
6882
|
),
|
|
6348
|
-
keys:
|
|
6883
|
+
keys: z23.array(z23.string()).optional().describe(
|
|
6349
6884
|
'Filter by specific metafield keys within the namespace (e.g., ["color", "size"]). Requires namespace to be specified for meaningful filtering.'
|
|
6350
6885
|
)
|
|
6351
6886
|
});
|
|
6352
|
-
var outputSchema21 =
|
|
6353
|
-
|
|
6354
|
-
id:
|
|
6355
|
-
namespace:
|
|
6356
|
-
key:
|
|
6357
|
-
value:
|
|
6358
|
-
type:
|
|
6359
|
-
createdAt:
|
|
6360
|
-
updatedAt:
|
|
6887
|
+
var outputSchema21 = z23.array(
|
|
6888
|
+
z23.object({
|
|
6889
|
+
id: z23.string().describe('Metafield GID (e.g., "gid://shopify/Metafield/123")'),
|
|
6890
|
+
namespace: z23.string().describe("Metafield namespace (grouping identifier)"),
|
|
6891
|
+
key: z23.string().describe("Metafield key (field name)"),
|
|
6892
|
+
value: z23.string().describe("Metafield value (the stored data)"),
|
|
6893
|
+
type: z23.string().describe('Metafield type (e.g., "single_line_text_field", "json", "number_integer")'),
|
|
6894
|
+
createdAt: z23.string().describe("Creation timestamp (ISO 8601)"),
|
|
6895
|
+
updatedAt: z23.string().describe("Last update timestamp (ISO 8601)")
|
|
6361
6896
|
})
|
|
6362
6897
|
);
|
|
6363
6898
|
var handleGetProductMetafields = async (context, params) => {
|
|
@@ -6390,6 +6925,13 @@ function registerGetProductMetafieldsTool() {
|
|
|
6390
6925
|
relatedTools: ["get-product", "set-product-metafields"],
|
|
6391
6926
|
prerequisites: ["get-product"],
|
|
6392
6927
|
followUps: ["set-product-metafields", "delete-product-metafields"]
|
|
6928
|
+
},
|
|
6929
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
6930
|
+
annotations: {
|
|
6931
|
+
readOnlyHint: true,
|
|
6932
|
+
destructiveHint: false,
|
|
6933
|
+
idempotentHint: true,
|
|
6934
|
+
openWorldHint: true
|
|
6393
6935
|
}
|
|
6394
6936
|
},
|
|
6395
6937
|
handleGetProductMetafields
|
|
@@ -6397,51 +6939,51 @@ function registerGetProductMetafieldsTool() {
|
|
|
6397
6939
|
}
|
|
6398
6940
|
|
|
6399
6941
|
// src/tools/get-product.ts
|
|
6400
|
-
import { z as
|
|
6401
|
-
var inputSchema22 =
|
|
6402
|
-
id:
|
|
6942
|
+
import { z as z24 } from "zod";
|
|
6943
|
+
var inputSchema22 = z24.object({
|
|
6944
|
+
id: productIdSchema.optional().describe(
|
|
6403
6945
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use either id or handle.'
|
|
6404
6946
|
),
|
|
6405
|
-
handle:
|
|
6947
|
+
handle: z24.string().optional().describe(
|
|
6406
6948
|
'Product URL handle/slug (e.g., "premium-cotton-t-shirt"). Use either id or handle.'
|
|
6407
6949
|
)
|
|
6408
6950
|
}).refine((data) => data.id || data.handle, {
|
|
6409
6951
|
message: "Either id or handle must be provided"
|
|
6410
6952
|
});
|
|
6411
|
-
var outputSchema22 =
|
|
6412
|
-
id:
|
|
6413
|
-
title:
|
|
6414
|
-
handle:
|
|
6415
|
-
description:
|
|
6416
|
-
vendor:
|
|
6417
|
-
productType:
|
|
6418
|
-
status:
|
|
6419
|
-
tags:
|
|
6420
|
-
seo:
|
|
6421
|
-
title:
|
|
6422
|
-
description:
|
|
6953
|
+
var outputSchema22 = z24.object({
|
|
6954
|
+
id: z24.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
6955
|
+
title: z24.string().describe("Product title"),
|
|
6956
|
+
handle: z24.string().describe("URL handle/slug"),
|
|
6957
|
+
description: z24.string().nullable().describe("Product description (HTML)"),
|
|
6958
|
+
vendor: z24.string().describe("Vendor/brand name"),
|
|
6959
|
+
productType: z24.string().describe("Product type"),
|
|
6960
|
+
status: z24.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
6961
|
+
tags: z24.array(z24.string()).describe("Product tags"),
|
|
6962
|
+
seo: z24.object({
|
|
6963
|
+
title: z24.string().nullable().describe("SEO title for search engines"),
|
|
6964
|
+
description: z24.string().nullable().describe("SEO description/meta description")
|
|
6423
6965
|
}).describe("SEO metadata for search engine optimization"),
|
|
6424
|
-
createdAt:
|
|
6425
|
-
updatedAt:
|
|
6426
|
-
variants:
|
|
6427
|
-
|
|
6428
|
-
id:
|
|
6429
|
-
title:
|
|
6430
|
-
price:
|
|
6431
|
-
compareAtPrice:
|
|
6432
|
-
sku:
|
|
6433
|
-
barcode:
|
|
6434
|
-
inventoryQuantity:
|
|
6966
|
+
createdAt: z24.string().describe("Creation timestamp (ISO 8601)"),
|
|
6967
|
+
updatedAt: z24.string().describe("Last update timestamp (ISO 8601)"),
|
|
6968
|
+
variants: z24.array(
|
|
6969
|
+
z24.object({
|
|
6970
|
+
id: z24.string().describe("Variant GID"),
|
|
6971
|
+
title: z24.string().describe("Variant title"),
|
|
6972
|
+
price: z24.string().describe("Price"),
|
|
6973
|
+
compareAtPrice: z24.string().nullable().describe("Compare at price"),
|
|
6974
|
+
sku: z24.string().nullable().describe("SKU"),
|
|
6975
|
+
barcode: z24.string().nullable().describe("Barcode"),
|
|
6976
|
+
inventoryQuantity: z24.number().nullable().describe("Inventory quantity")
|
|
6435
6977
|
})
|
|
6436
6978
|
).describe("Product variants"),
|
|
6437
|
-
images:
|
|
6438
|
-
|
|
6439
|
-
id:
|
|
6440
|
-
url:
|
|
6441
|
-
altText:
|
|
6979
|
+
images: z24.array(
|
|
6980
|
+
z24.object({
|
|
6981
|
+
id: z24.string().describe("Image GID"),
|
|
6982
|
+
url: z24.string().describe("Image URL"),
|
|
6983
|
+
altText: z24.string().nullable().describe("Alt text")
|
|
6442
6984
|
})
|
|
6443
6985
|
).describe("Product images"),
|
|
6444
|
-
totalInventory:
|
|
6986
|
+
totalInventory: z24.number().describe("Total inventory across variants")
|
|
6445
6987
|
});
|
|
6446
6988
|
var handleGetProduct = async (context, params) => {
|
|
6447
6989
|
log.debug(`Getting product on shop: ${context.shopDomain}`);
|
|
@@ -6492,6 +7034,13 @@ function registerGetProductTool() {
|
|
|
6492
7034
|
"add-product-image",
|
|
6493
7035
|
"get-product-metafields"
|
|
6494
7036
|
]
|
|
7037
|
+
},
|
|
7038
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7039
|
+
annotations: {
|
|
7040
|
+
readOnlyHint: true,
|
|
7041
|
+
destructiveHint: false,
|
|
7042
|
+
idempotentHint: true,
|
|
7043
|
+
openWorldHint: true
|
|
6495
7044
|
}
|
|
6496
7045
|
},
|
|
6497
7046
|
handleGetProduct
|
|
@@ -6499,36 +7048,36 @@ function registerGetProductTool() {
|
|
|
6499
7048
|
}
|
|
6500
7049
|
|
|
6501
7050
|
// src/tools/list-articles.ts
|
|
6502
|
-
import { z as
|
|
6503
|
-
var inputSchema23 =
|
|
6504
|
-
first:
|
|
6505
|
-
cursor:
|
|
7051
|
+
import { z as z25 } from "zod";
|
|
7052
|
+
var inputSchema23 = z25.object({
|
|
7053
|
+
first: z25.number().int().min(1).max(50).optional().default(10).describe("Number of articles to return (1-50). Defaults to 10."),
|
|
7054
|
+
cursor: z25.string().optional().describe(
|
|
6506
7055
|
"Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
|
|
6507
7056
|
),
|
|
6508
|
-
query:
|
|
7057
|
+
query: z25.string().optional().describe(
|
|
6509
7058
|
"Search query to filter articles. Supports Shopify search syntax. Examples: 'title:Guide', 'tag:tutorial', 'blog_id:123456789'."
|
|
6510
7059
|
),
|
|
6511
|
-
blogId:
|
|
7060
|
+
blogId: z25.string().optional().describe(
|
|
6512
7061
|
'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".'
|
|
6513
7062
|
),
|
|
6514
|
-
sortKey:
|
|
7063
|
+
sortKey: z25.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT", "AUTHOR", "BLOG_TITLE"]).optional().describe("Sort order for results. Defaults to ID.")
|
|
6515
7064
|
});
|
|
6516
|
-
var outputSchema23 =
|
|
6517
|
-
articles:
|
|
6518
|
-
|
|
6519
|
-
id:
|
|
6520
|
-
title:
|
|
6521
|
-
handle:
|
|
6522
|
-
blog:
|
|
6523
|
-
id:
|
|
6524
|
-
title:
|
|
7065
|
+
var outputSchema23 = z25.object({
|
|
7066
|
+
articles: z25.array(
|
|
7067
|
+
z25.object({
|
|
7068
|
+
id: z25.string().describe("Article GID"),
|
|
7069
|
+
title: z25.string().describe("Article title"),
|
|
7070
|
+
handle: z25.string().describe("URL handle/slug"),
|
|
7071
|
+
blog: z25.object({
|
|
7072
|
+
id: z25.string().describe("Parent blog GID"),
|
|
7073
|
+
title: z25.string().describe("Parent blog title")
|
|
6525
7074
|
}),
|
|
6526
|
-
isPublished:
|
|
6527
|
-
publishedAt:
|
|
7075
|
+
isPublished: z25.boolean().describe("Whether article is visible on storefront"),
|
|
7076
|
+
publishedAt: z25.string().nullable().describe("ISO timestamp when published")
|
|
6528
7077
|
})
|
|
6529
7078
|
),
|
|
6530
|
-
hasNextPage:
|
|
6531
|
-
endCursor:
|
|
7079
|
+
hasNextPage: z25.boolean().describe("Whether more articles are available"),
|
|
7080
|
+
endCursor: z25.string().nullable().describe("Cursor for next page pagination")
|
|
6532
7081
|
});
|
|
6533
7082
|
var handleListArticles = async (context, params) => {
|
|
6534
7083
|
log.debug(`Listing articles on shop: ${context.shopDomain}`);
|
|
@@ -6578,6 +7127,13 @@ function registerListArticlesTool() {
|
|
|
6578
7127
|
relationships: {
|
|
6579
7128
|
relatedTools: ["list-blogs", "create-article", "update-article"],
|
|
6580
7129
|
followUps: ["update-article", "delete-article", "create-article"]
|
|
7130
|
+
},
|
|
7131
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7132
|
+
annotations: {
|
|
7133
|
+
readOnlyHint: true,
|
|
7134
|
+
destructiveHint: false,
|
|
7135
|
+
idempotentHint: true,
|
|
7136
|
+
openWorldHint: true
|
|
6581
7137
|
}
|
|
6582
7138
|
},
|
|
6583
7139
|
handleListArticles
|
|
@@ -6585,29 +7141,29 @@ function registerListArticlesTool() {
|
|
|
6585
7141
|
}
|
|
6586
7142
|
|
|
6587
7143
|
// src/tools/list-blogs.ts
|
|
6588
|
-
import { z as
|
|
6589
|
-
var inputSchema24 =
|
|
6590
|
-
first:
|
|
6591
|
-
cursor:
|
|
7144
|
+
import { z as z26 } from "zod";
|
|
7145
|
+
var inputSchema24 = z26.object({
|
|
7146
|
+
first: z26.number().int().min(1).max(50).optional().default(10).describe("Number of blogs to return (1-50). Defaults to 10."),
|
|
7147
|
+
cursor: z26.string().optional().describe(
|
|
6592
7148
|
"Pagination cursor from a previous response. Use the endCursor from the previous response to get the next page of results."
|
|
6593
7149
|
),
|
|
6594
|
-
query:
|
|
7150
|
+
query: z26.string().optional().describe(
|
|
6595
7151
|
"Search query to filter blogs. Supports Shopify search syntax. Examples: 'title:News', 'handle:company-blog'."
|
|
6596
7152
|
),
|
|
6597
|
-
sortKey:
|
|
7153
|
+
sortKey: z26.enum(["ID", "TITLE", "UPDATED_AT"]).optional().describe("Sort order for results. Defaults to ID.")
|
|
6598
7154
|
});
|
|
6599
|
-
var outputSchema24 =
|
|
6600
|
-
blogs:
|
|
6601
|
-
|
|
6602
|
-
id:
|
|
6603
|
-
title:
|
|
6604
|
-
handle:
|
|
6605
|
-
commentPolicy:
|
|
6606
|
-
articlesCount:
|
|
7155
|
+
var outputSchema24 = z26.object({
|
|
7156
|
+
blogs: z26.array(
|
|
7157
|
+
z26.object({
|
|
7158
|
+
id: z26.string().describe("Blog GID"),
|
|
7159
|
+
title: z26.string().describe("Blog title"),
|
|
7160
|
+
handle: z26.string().describe("URL handle/slug"),
|
|
7161
|
+
commentPolicy: z26.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy"),
|
|
7162
|
+
articlesCount: z26.number().describe("Number of articles in the blog")
|
|
6607
7163
|
})
|
|
6608
7164
|
),
|
|
6609
|
-
hasNextPage:
|
|
6610
|
-
endCursor:
|
|
7165
|
+
hasNextPage: z26.boolean().describe("Whether more blogs are available"),
|
|
7166
|
+
endCursor: z26.string().nullable().describe("Cursor for next page pagination")
|
|
6611
7167
|
});
|
|
6612
7168
|
var handleListBlogs = async (context, params) => {
|
|
6613
7169
|
log.debug(`Listing blogs on shop: ${context.shopDomain}`);
|
|
@@ -6651,6 +7207,13 @@ function registerListBlogsTool() {
|
|
|
6651
7207
|
relationships: {
|
|
6652
7208
|
relatedTools: ["create-blog", "update-blog", "list-articles"],
|
|
6653
7209
|
followUps: ["create-article", "update-blog", "create-blog"]
|
|
7210
|
+
},
|
|
7211
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7212
|
+
annotations: {
|
|
7213
|
+
readOnlyHint: true,
|
|
7214
|
+
destructiveHint: false,
|
|
7215
|
+
idempotentHint: true,
|
|
7216
|
+
openWorldHint: true
|
|
6654
7217
|
}
|
|
6655
7218
|
},
|
|
6656
7219
|
handleListBlogs
|
|
@@ -6658,40 +7221,40 @@ function registerListBlogsTool() {
|
|
|
6658
7221
|
}
|
|
6659
7222
|
|
|
6660
7223
|
// src/tools/list-collections.ts
|
|
6661
|
-
import { z as
|
|
6662
|
-
var inputSchema25 =
|
|
6663
|
-
first:
|
|
6664
|
-
after:
|
|
7224
|
+
import { z as z27 } from "zod";
|
|
7225
|
+
var inputSchema25 = z27.object({
|
|
7226
|
+
first: z27.number().int().min(1).max(50).optional().default(10).describe("Number of collections to return (1-50). Default: 10"),
|
|
7227
|
+
after: z27.string().optional().describe(
|
|
6665
7228
|
"Pagination cursor from previous response. Use the endCursor value from the previous page to get the next page of results."
|
|
6666
7229
|
),
|
|
6667
|
-
query:
|
|
7230
|
+
query: z27.string().optional().describe(
|
|
6668
7231
|
'Search query to filter collections. Searches across collection titles and handles. Example: "summer" finds collections with "summer" in the title or handle.'
|
|
6669
7232
|
),
|
|
6670
|
-
sortKey:
|
|
7233
|
+
sortKey: z27.enum(["TITLE", "UPDATED_AT", "ID", "RELEVANCE"]).optional().describe(
|
|
6671
7234
|
"Sort collections by: TITLE (alphabetical), UPDATED_AT (most recently updated first), ID (by collection ID), or RELEVANCE (best match when using query filter)"
|
|
6672
7235
|
)
|
|
6673
7236
|
});
|
|
6674
|
-
var outputSchema25 =
|
|
6675
|
-
collections:
|
|
6676
|
-
|
|
6677
|
-
id:
|
|
6678
|
-
title:
|
|
6679
|
-
handle:
|
|
6680
|
-
seo:
|
|
6681
|
-
title:
|
|
6682
|
-
description:
|
|
7237
|
+
var outputSchema25 = z27.object({
|
|
7238
|
+
collections: z27.array(
|
|
7239
|
+
z27.object({
|
|
7240
|
+
id: z27.string().describe("Collection GID"),
|
|
7241
|
+
title: z27.string().describe("Collection title"),
|
|
7242
|
+
handle: z27.string().describe("URL handle"),
|
|
7243
|
+
seo: z27.object({
|
|
7244
|
+
title: z27.string().nullable().describe("SEO title"),
|
|
7245
|
+
description: z27.string().nullable().describe("SEO description")
|
|
6683
7246
|
}),
|
|
6684
|
-
productsCount:
|
|
7247
|
+
productsCount: z27.number().describe("Number of products in collection")
|
|
6685
7248
|
})
|
|
6686
7249
|
),
|
|
6687
|
-
pageInfo:
|
|
6688
|
-
hasNextPage:
|
|
6689
|
-
endCursor:
|
|
7250
|
+
pageInfo: z27.object({
|
|
7251
|
+
hasNextPage: z27.boolean().describe("Whether more pages exist"),
|
|
7252
|
+
endCursor: z27.string().nullable().describe("Cursor for next page")
|
|
6690
7253
|
}),
|
|
6691
|
-
summary:
|
|
6692
|
-
totalReturned:
|
|
6693
|
-
hasMore:
|
|
6694
|
-
hint:
|
|
7254
|
+
summary: z27.object({
|
|
7255
|
+
totalReturned: z27.number().describe("Number of collections in this response"),
|
|
7256
|
+
hasMore: z27.boolean().describe("Whether more collections are available"),
|
|
7257
|
+
hint: z27.string().describe("Suggestion for next action")
|
|
6695
7258
|
}).describe("AI-friendly summary for context management")
|
|
6696
7259
|
});
|
|
6697
7260
|
var handleListCollections = async (context, params) => {
|
|
@@ -6725,6 +7288,13 @@ function registerListCollectionsTool() {
|
|
|
6725
7288
|
relationships: {
|
|
6726
7289
|
relatedTools: ["get-collection"],
|
|
6727
7290
|
followUps: ["get-collection", "update-collection", "add-products-to-collection"]
|
|
7291
|
+
},
|
|
7292
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7293
|
+
annotations: {
|
|
7294
|
+
readOnlyHint: true,
|
|
7295
|
+
destructiveHint: false,
|
|
7296
|
+
idempotentHint: true,
|
|
7297
|
+
openWorldHint: true
|
|
6728
7298
|
}
|
|
6729
7299
|
},
|
|
6730
7300
|
handleListCollections
|
|
@@ -6732,47 +7302,47 @@ function registerListCollectionsTool() {
|
|
|
6732
7302
|
}
|
|
6733
7303
|
|
|
6734
7304
|
// src/tools/list-low-inventory.ts
|
|
6735
|
-
import { z as
|
|
6736
|
-
var inputSchema26 =
|
|
6737
|
-
threshold:
|
|
7305
|
+
import { z as z28 } from "zod";
|
|
7306
|
+
var inputSchema26 = z28.object({
|
|
7307
|
+
threshold: z28.number().int().min(0).default(10).describe(
|
|
6738
7308
|
"Products with total inventory at or below this number will be included. Default: 10. Use 0 to find only out-of-stock products."
|
|
6739
7309
|
),
|
|
6740
|
-
includeZero:
|
|
7310
|
+
includeZero: z28.boolean().default(true).describe(
|
|
6741
7311
|
"Include products with zero inventory. Default: true. Set to false to see only low-stock (not out-of-stock) products."
|
|
6742
7312
|
),
|
|
6743
|
-
status:
|
|
7313
|
+
status: z28.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
|
|
6744
7314
|
"Filter by product status. Default: ACTIVE. Use DRAFT to check pre-launch products, ARCHIVED for historical."
|
|
6745
7315
|
),
|
|
6746
|
-
first:
|
|
6747
|
-
after:
|
|
7316
|
+
first: z28.number().int().min(1).max(100).default(50).describe("Number of products to return per page. Default: 50, max: 100."),
|
|
7317
|
+
after: z28.string().optional().describe("Pagination cursor from previous response. Use pageInfo.endCursor to get next page.")
|
|
6748
7318
|
});
|
|
6749
|
-
var outputSchema26 =
|
|
6750
|
-
products:
|
|
6751
|
-
|
|
6752
|
-
productId:
|
|
6753
|
-
productTitle:
|
|
6754
|
-
status:
|
|
6755
|
-
totalInventory:
|
|
6756
|
-
isOutOfStock:
|
|
6757
|
-
variants:
|
|
6758
|
-
|
|
6759
|
-
variantId:
|
|
6760
|
-
variantTitle:
|
|
6761
|
-
sku:
|
|
6762
|
-
inventoryItemId:
|
|
6763
|
-
available:
|
|
7319
|
+
var outputSchema26 = z28.object({
|
|
7320
|
+
products: z28.array(
|
|
7321
|
+
z28.object({
|
|
7322
|
+
productId: z28.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
7323
|
+
productTitle: z28.string().describe("Product title"),
|
|
7324
|
+
status: z28.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
7325
|
+
totalInventory: z28.number().describe("Total inventory across all variants"),
|
|
7326
|
+
isOutOfStock: z28.boolean().describe("True if total inventory is zero"),
|
|
7327
|
+
variants: z28.array(
|
|
7328
|
+
z28.object({
|
|
7329
|
+
variantId: z28.string().describe("Variant GID"),
|
|
7330
|
+
variantTitle: z28.string().describe('Variant title (e.g., "Red / Large")'),
|
|
7331
|
+
sku: z28.string().nullable().describe("SKU (Stock Keeping Unit)"),
|
|
7332
|
+
inventoryItemId: z28.string().describe("Inventory item GID"),
|
|
7333
|
+
available: z28.number().describe("Available quantity for sale")
|
|
6764
7334
|
})
|
|
6765
7335
|
).describe("Variant breakdown with inventory")
|
|
6766
7336
|
})
|
|
6767
7337
|
).describe("Products with low inventory, sorted by total inventory ascending"),
|
|
6768
|
-
pageInfo:
|
|
6769
|
-
hasNextPage:
|
|
6770
|
-
endCursor:
|
|
7338
|
+
pageInfo: z28.object({
|
|
7339
|
+
hasNextPage: z28.boolean().describe("Whether more products exist after this page"),
|
|
7340
|
+
endCursor: z28.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
|
|
6771
7341
|
}).describe("Pagination information"),
|
|
6772
|
-
summary:
|
|
6773
|
-
totalProducts:
|
|
6774
|
-
outOfStockCount:
|
|
6775
|
-
lowStockCount:
|
|
7342
|
+
summary: z28.object({
|
|
7343
|
+
totalProducts: z28.number().describe("Number of products in this response"),
|
|
7344
|
+
outOfStockCount: z28.number().describe("Number of products with zero inventory"),
|
|
7345
|
+
lowStockCount: z28.number().describe("Number of products with inventory > 0 but below threshold")
|
|
6776
7346
|
}).describe("Summary statistics")
|
|
6777
7347
|
});
|
|
6778
7348
|
var handleListLowInventory = async (context, params) => {
|
|
@@ -6802,6 +7372,13 @@ function registerListLowInventoryTool() {
|
|
|
6802
7372
|
relationships: {
|
|
6803
7373
|
relatedTools: ["get-inventory", "update-inventory", "get-bulk-inventory"],
|
|
6804
7374
|
followUps: ["get-inventory", "update-inventory"]
|
|
7375
|
+
},
|
|
7376
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7377
|
+
annotations: {
|
|
7378
|
+
readOnlyHint: true,
|
|
7379
|
+
destructiveHint: false,
|
|
7380
|
+
idempotentHint: true,
|
|
7381
|
+
openWorldHint: true
|
|
6805
7382
|
}
|
|
6806
7383
|
},
|
|
6807
7384
|
handleListLowInventory
|
|
@@ -6809,31 +7386,31 @@ function registerListLowInventoryTool() {
|
|
|
6809
7386
|
}
|
|
6810
7387
|
|
|
6811
7388
|
// src/tools/list-pages.ts
|
|
6812
|
-
import { z as
|
|
6813
|
-
var inputSchema27 =
|
|
6814
|
-
first:
|
|
7389
|
+
import { z as z29 } from "zod";
|
|
7390
|
+
var inputSchema27 = z29.object({
|
|
7391
|
+
first: z29.number().int().min(1).max(50).optional().describe(
|
|
6815
7392
|
"Number of pages to return per request. Default: 10, Maximum: 50. Use pagination (cursor) to retrieve more results."
|
|
6816
7393
|
),
|
|
6817
|
-
cursor:
|
|
7394
|
+
cursor: z29.string().optional().describe(
|
|
6818
7395
|
"Pagination cursor from a previous list-pages response (endCursor). Use this to get the next page of results."
|
|
6819
7396
|
),
|
|
6820
|
-
query:
|
|
7397
|
+
query: z29.string().optional().describe(
|
|
6821
7398
|
"Search query to filter pages. Supports Shopify search syntax. Examples: 'title:About', 'handle:contact', 'created_at:>2024-01-01'."
|
|
6822
7399
|
),
|
|
6823
|
-
sortKey:
|
|
7400
|
+
sortKey: z29.enum(["ID", "TITLE", "UPDATED_AT", "PUBLISHED_AT"]).optional().describe("Field to sort pages by. Options: ID (default), TITLE, UPDATED_AT, PUBLISHED_AT.")
|
|
6824
7401
|
});
|
|
6825
|
-
var outputSchema27 =
|
|
6826
|
-
pages:
|
|
6827
|
-
|
|
6828
|
-
id:
|
|
6829
|
-
title:
|
|
6830
|
-
handle:
|
|
6831
|
-
isPublished:
|
|
6832
|
-
updatedAt:
|
|
7402
|
+
var outputSchema27 = z29.object({
|
|
7403
|
+
pages: z29.array(
|
|
7404
|
+
z29.object({
|
|
7405
|
+
id: z29.string().describe("Page GID"),
|
|
7406
|
+
title: z29.string().describe("Page title"),
|
|
7407
|
+
handle: z29.string().describe("URL handle/slug"),
|
|
7408
|
+
isPublished: z29.boolean().describe("Whether page is visible"),
|
|
7409
|
+
updatedAt: z29.string().describe("ISO timestamp of last update")
|
|
6833
7410
|
})
|
|
6834
7411
|
),
|
|
6835
|
-
hasNextPage:
|
|
6836
|
-
endCursor:
|
|
7412
|
+
hasNextPage: z29.boolean().describe("Whether more pages exist"),
|
|
7413
|
+
endCursor: z29.string().nullable().describe("Cursor for next page of results")
|
|
6837
7414
|
});
|
|
6838
7415
|
var handleListPages = async (context, params) => {
|
|
6839
7416
|
log.debug(`Listing pages on shop: ${context.shopDomain}`);
|
|
@@ -6865,6 +7442,13 @@ function registerListPagesTool() {
|
|
|
6865
7442
|
relationships: {
|
|
6866
7443
|
relatedTools: ["get-page", "create-page"],
|
|
6867
7444
|
followUps: ["get-page", "update-page", "create-page"]
|
|
7445
|
+
},
|
|
7446
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7447
|
+
annotations: {
|
|
7448
|
+
readOnlyHint: true,
|
|
7449
|
+
destructiveHint: false,
|
|
7450
|
+
idempotentHint: true,
|
|
7451
|
+
openWorldHint: true
|
|
6868
7452
|
}
|
|
6869
7453
|
},
|
|
6870
7454
|
handleListPages
|
|
@@ -6872,46 +7456,46 @@ function registerListPagesTool() {
|
|
|
6872
7456
|
}
|
|
6873
7457
|
|
|
6874
7458
|
// src/tools/list-products.ts
|
|
6875
|
-
import { z as
|
|
6876
|
-
var inputSchema28 =
|
|
6877
|
-
first:
|
|
6878
|
-
after:
|
|
6879
|
-
query:
|
|
7459
|
+
import { z as z30 } from "zod";
|
|
7460
|
+
var inputSchema28 = z30.object({
|
|
7461
|
+
first: z30.number().int().min(1).max(50).optional().describe("Number of products to return (1-50). Default: 10"),
|
|
7462
|
+
after: z30.string().optional().describe("Pagination cursor from previous response's endCursor. Omit for first page."),
|
|
7463
|
+
query: z30.string().optional().describe(
|
|
6880
7464
|
'Search query for title, description, tags. Example: "summer dress", "organic cotton"'
|
|
6881
7465
|
),
|
|
6882
|
-
status:
|
|
7466
|
+
status: z30.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe(
|
|
6883
7467
|
"Filter by product status. ACTIVE = published, DRAFT = not published, ARCHIVED = hidden"
|
|
6884
7468
|
),
|
|
6885
|
-
vendor:
|
|
6886
|
-
productType:
|
|
6887
|
-
sortBy:
|
|
6888
|
-
sortOrder:
|
|
7469
|
+
vendor: z30.string().optional().describe('Filter by exact vendor name. Example: "Acme Corp", "Nike"'),
|
|
7470
|
+
productType: z30.string().optional().describe('Filter by exact product type. Example: "T-Shirts", "Shoes"'),
|
|
7471
|
+
sortBy: z30.enum(["TITLE", "CREATED_AT", "UPDATED_AT", "INVENTORY_TOTAL"]).optional().describe("Sort field. TITLE, CREATED_AT (default), UPDATED_AT, or INVENTORY_TOTAL"),
|
|
7472
|
+
sortOrder: z30.enum(["ASC", "DESC"]).optional().describe("Sort direction. ASC (default) for ascending, DESC for descending")
|
|
6889
7473
|
});
|
|
6890
|
-
var outputSchema28 =
|
|
6891
|
-
products:
|
|
6892
|
-
|
|
6893
|
-
id:
|
|
6894
|
-
title:
|
|
6895
|
-
handle:
|
|
6896
|
-
status:
|
|
6897
|
-
vendor:
|
|
6898
|
-
productType:
|
|
6899
|
-
totalInventory:
|
|
6900
|
-
primaryVariantPrice:
|
|
6901
|
-
imageUrl:
|
|
7474
|
+
var outputSchema28 = z30.object({
|
|
7475
|
+
products: z30.array(
|
|
7476
|
+
z30.object({
|
|
7477
|
+
id: z30.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
7478
|
+
title: z30.string().describe("Product title"),
|
|
7479
|
+
handle: z30.string().describe("URL handle/slug"),
|
|
7480
|
+
status: z30.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
7481
|
+
vendor: z30.string().describe("Vendor/brand name"),
|
|
7482
|
+
productType: z30.string().describe("Product type"),
|
|
7483
|
+
totalInventory: z30.number().describe("Total inventory across all variants"),
|
|
7484
|
+
primaryVariantPrice: z30.string().describe('Price of the first variant (e.g., "29.99")'),
|
|
7485
|
+
imageUrl: z30.string().nullable().describe("First image URL or null if no images")
|
|
6902
7486
|
})
|
|
6903
7487
|
).describe("Array of product summaries"),
|
|
6904
|
-
pageInfo:
|
|
6905
|
-
hasNextPage:
|
|
6906
|
-
hasPreviousPage:
|
|
6907
|
-
startCursor:
|
|
6908
|
-
endCursor:
|
|
7488
|
+
pageInfo: z30.object({
|
|
7489
|
+
hasNextPage: z30.boolean().describe("Whether more products exist after this page"),
|
|
7490
|
+
hasPreviousPage: z30.boolean().describe("Whether products exist before this page"),
|
|
7491
|
+
startCursor: z30.string().nullable().describe("Cursor for the first item in this page"),
|
|
7492
|
+
endCursor: z30.string().nullable().describe('Cursor for the last item - use as "after" to get next page')
|
|
6909
7493
|
}).describe("Pagination information"),
|
|
6910
|
-
totalCount:
|
|
6911
|
-
summary:
|
|
6912
|
-
totalReturned:
|
|
6913
|
-
hasMore:
|
|
6914
|
-
hint:
|
|
7494
|
+
totalCount: z30.number().describe("Number of products returned in this page"),
|
|
7495
|
+
summary: z30.object({
|
|
7496
|
+
totalReturned: z30.number().describe("Number of products in this response"),
|
|
7497
|
+
hasMore: z30.boolean().describe("Whether more products are available"),
|
|
7498
|
+
hint: z30.string().describe("Suggestion for next action")
|
|
6915
7499
|
}).describe("AI-friendly summary for context management")
|
|
6916
7500
|
});
|
|
6917
7501
|
var handleListProducts = async (context, params) => {
|
|
@@ -6952,6 +7536,13 @@ function registerListProductsTool() {
|
|
|
6952
7536
|
relationships: {
|
|
6953
7537
|
relatedTools: ["get-product"],
|
|
6954
7538
|
followUps: ["get-product", "update-product", "delete-product"]
|
|
7539
|
+
},
|
|
7540
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7541
|
+
annotations: {
|
|
7542
|
+
readOnlyHint: true,
|
|
7543
|
+
destructiveHint: false,
|
|
7544
|
+
idempotentHint: true,
|
|
7545
|
+
openWorldHint: true
|
|
6955
7546
|
}
|
|
6956
7547
|
},
|
|
6957
7548
|
handleListProducts
|
|
@@ -6959,30 +7550,30 @@ function registerListProductsTool() {
|
|
|
6959
7550
|
}
|
|
6960
7551
|
|
|
6961
7552
|
// src/tools/list-redirects.ts
|
|
6962
|
-
import { z as
|
|
6963
|
-
var inputSchema29 =
|
|
6964
|
-
first:
|
|
6965
|
-
cursor:
|
|
7553
|
+
import { z as z31 } from "zod";
|
|
7554
|
+
var inputSchema29 = z31.object({
|
|
7555
|
+
first: z31.number().int().min(1).max(50).optional().default(10).describe("Number of redirects to return (1-50, default: 10)."),
|
|
7556
|
+
cursor: z31.string().optional().describe(
|
|
6966
7557
|
"Pagination cursor from previous response (endCursor). Use this to fetch the next page of results."
|
|
6967
7558
|
),
|
|
6968
|
-
query:
|
|
7559
|
+
query: z31.string().optional().describe(
|
|
6969
7560
|
"Search filter. Supports 'path:' and 'target:' prefixes. Examples: 'path:/old-' to find redirects starting with /old-, 'target:/products/' to find redirects pointing to /products/."
|
|
6970
7561
|
)
|
|
6971
7562
|
});
|
|
6972
|
-
var outputSchema29 =
|
|
6973
|
-
redirects:
|
|
6974
|
-
|
|
6975
|
-
id:
|
|
6976
|
-
path:
|
|
6977
|
-
target:
|
|
7563
|
+
var outputSchema29 = z31.object({
|
|
7564
|
+
redirects: z31.array(
|
|
7565
|
+
z31.object({
|
|
7566
|
+
id: z31.string().describe("Redirect GID"),
|
|
7567
|
+
path: z31.string().describe("Source path"),
|
|
7568
|
+
target: z31.string().describe("Target URL")
|
|
6978
7569
|
})
|
|
6979
7570
|
),
|
|
6980
|
-
hasNextPage:
|
|
6981
|
-
endCursor:
|
|
6982
|
-
summary:
|
|
6983
|
-
totalReturned:
|
|
6984
|
-
hasMore:
|
|
6985
|
-
hint:
|
|
7571
|
+
hasNextPage: z31.boolean().describe("Whether more results exist"),
|
|
7572
|
+
endCursor: z31.string().nullable().describe("Cursor for fetching next page"),
|
|
7573
|
+
summary: z31.object({
|
|
7574
|
+
totalReturned: z31.number().describe("Number of redirects in this response"),
|
|
7575
|
+
hasMore: z31.boolean().describe("Whether more redirects are available"),
|
|
7576
|
+
hint: z31.string().describe("Suggestion for next action")
|
|
6986
7577
|
}).describe("AI-friendly summary for context management")
|
|
6987
7578
|
});
|
|
6988
7579
|
var handleListRedirects = async (context, params) => {
|
|
@@ -7027,6 +7618,13 @@ function registerListRedirectsTool() {
|
|
|
7027
7618
|
relationships: {
|
|
7028
7619
|
relatedTools: ["create-redirect"],
|
|
7029
7620
|
followUps: ["delete-redirect"]
|
|
7621
|
+
},
|
|
7622
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7623
|
+
annotations: {
|
|
7624
|
+
readOnlyHint: true,
|
|
7625
|
+
destructiveHint: false,
|
|
7626
|
+
idempotentHint: true,
|
|
7627
|
+
openWorldHint: true
|
|
7030
7628
|
}
|
|
7031
7629
|
},
|
|
7032
7630
|
handleListRedirects
|
|
@@ -7034,18 +7632,18 @@ function registerListRedirectsTool() {
|
|
|
7034
7632
|
}
|
|
7035
7633
|
|
|
7036
7634
|
// src/tools/remove-products-from-collection.ts
|
|
7037
|
-
import { z as
|
|
7038
|
-
var inputSchema30 =
|
|
7039
|
-
collectionId:
|
|
7635
|
+
import { z as z32 } from "zod";
|
|
7636
|
+
var inputSchema30 = z32.object({
|
|
7637
|
+
collectionId: collectionIdSchema.describe(
|
|
7040
7638
|
'Collection GID (e.g., "gid://shopify/Collection/123"). Use list-collections to find valid collection IDs.'
|
|
7041
7639
|
),
|
|
7042
|
-
productIds:
|
|
7640
|
+
productIds: z32.array(productIdSchema).min(1).max(250).describe(
|
|
7043
7641
|
'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.'
|
|
7044
7642
|
)
|
|
7045
7643
|
});
|
|
7046
|
-
var outputSchema30 =
|
|
7047
|
-
success:
|
|
7048
|
-
removedCount:
|
|
7644
|
+
var outputSchema30 = z32.object({
|
|
7645
|
+
success: z32.boolean().describe("Whether the operation succeeded"),
|
|
7646
|
+
removedCount: z32.number().describe("Number of products removed from the collection")
|
|
7049
7647
|
});
|
|
7050
7648
|
var handleRemoveProductsFromCollection = async (context, params) => {
|
|
7051
7649
|
log.debug(
|
|
@@ -7082,6 +7680,15 @@ function registerRemoveProductsFromCollectionTool() {
|
|
|
7082
7680
|
relationships: {
|
|
7083
7681
|
relatedTools: ["add-products-to-collection", "get-collection"],
|
|
7084
7682
|
prerequisites: ["get-collection"]
|
|
7683
|
+
},
|
|
7684
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7685
|
+
annotations: {
|
|
7686
|
+
readOnlyHint: false,
|
|
7687
|
+
destructiveHint: false,
|
|
7688
|
+
// Does not delete products, just removes from collection
|
|
7689
|
+
idempotentHint: true,
|
|
7690
|
+
// Removing already-removed products is idempotent
|
|
7691
|
+
openWorldHint: true
|
|
7085
7692
|
}
|
|
7086
7693
|
},
|
|
7087
7694
|
handleRemoveProductsFromCollection
|
|
@@ -7089,7 +7696,7 @@ function registerRemoveProductsFromCollectionTool() {
|
|
|
7089
7696
|
}
|
|
7090
7697
|
|
|
7091
7698
|
// src/tools/reorder-product-images.ts
|
|
7092
|
-
import { z as
|
|
7699
|
+
import { z as z33 } from "zod";
|
|
7093
7700
|
var PRODUCT_REORDER_MEDIA_MUTATION = `
|
|
7094
7701
|
mutation ProductReorderMedia($id: ID!, $moves: [MoveInput!]!) {
|
|
7095
7702
|
productReorderMedia(id: $id, moves: $moves) {
|
|
@@ -7104,23 +7711,23 @@ var PRODUCT_REORDER_MEDIA_MUTATION = `
|
|
|
7104
7711
|
}
|
|
7105
7712
|
}
|
|
7106
7713
|
`;
|
|
7107
|
-
var moveSchema =
|
|
7108
|
-
id:
|
|
7109
|
-
newPosition:
|
|
7714
|
+
var moveSchema = z33.object({
|
|
7715
|
+
id: imageIdSchema.describe('Image GID to move (e.g., "gid://shopify/MediaImage/123")'),
|
|
7716
|
+
newPosition: z33.number().int().min(0).describe("Zero-based target position. Position 0 is the featured image.")
|
|
7110
7717
|
});
|
|
7111
|
-
var inputSchema31 =
|
|
7112
|
-
productId:
|
|
7718
|
+
var inputSchema31 = z33.object({
|
|
7719
|
+
productId: productIdSchema.describe(
|
|
7113
7720
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use list-products to find product IDs.'
|
|
7114
7721
|
),
|
|
7115
|
-
moves:
|
|
7722
|
+
moves: z33.array(moveSchema).min(1).describe(
|
|
7116
7723
|
"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)."
|
|
7117
7724
|
)
|
|
7118
7725
|
});
|
|
7119
|
-
var outputSchema31 =
|
|
7120
|
-
success:
|
|
7121
|
-
jobId:
|
|
7122
|
-
jobDone:
|
|
7123
|
-
message:
|
|
7726
|
+
var outputSchema31 = z33.object({
|
|
7727
|
+
success: z33.boolean().describe("Whether the reorder operation was initiated successfully"),
|
|
7728
|
+
jobId: z33.string().nullable().describe("Background job ID for tracking (may be null if processed immediately)"),
|
|
7729
|
+
jobDone: z33.boolean().describe("Whether the job completed immediately"),
|
|
7730
|
+
message: z33.string().describe("Human-readable message describing the result")
|
|
7124
7731
|
});
|
|
7125
7732
|
var handleReorderProductImages = async (context, params) => {
|
|
7126
7733
|
log.debug(`Reordering images on shop: ${context.shopDomain}`);
|
|
@@ -7195,6 +7802,14 @@ function registerReorderProductImagesTool() {
|
|
|
7195
7802
|
relationships: {
|
|
7196
7803
|
relatedTools: ["add-product-image", "update-product-image"],
|
|
7197
7804
|
prerequisites: ["get-product"]
|
|
7805
|
+
},
|
|
7806
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7807
|
+
annotations: {
|
|
7808
|
+
readOnlyHint: false,
|
|
7809
|
+
destructiveHint: false,
|
|
7810
|
+
idempotentHint: true,
|
|
7811
|
+
// Same order produces same result
|
|
7812
|
+
openWorldHint: true
|
|
7198
7813
|
}
|
|
7199
7814
|
},
|
|
7200
7815
|
handleReorderProductImages
|
|
@@ -7202,45 +7817,45 @@ function registerReorderProductImagesTool() {
|
|
|
7202
7817
|
}
|
|
7203
7818
|
|
|
7204
7819
|
// src/tools/set-product-metafields.ts
|
|
7205
|
-
import { z as
|
|
7820
|
+
import { z as z34 } from "zod";
|
|
7206
7821
|
var MAX_METAFIELDS_PER_CALL = 25;
|
|
7207
|
-
var metafieldInputSchema =
|
|
7208
|
-
namespace:
|
|
7822
|
+
var metafieldInputSchema = z34.object({
|
|
7823
|
+
namespace: z34.string().min(1).describe(
|
|
7209
7824
|
'Metafield namespace (grouping identifier, e.g., "custom", "seo", "my_app"). Use consistent namespaces to organize related metafields.'
|
|
7210
7825
|
),
|
|
7211
|
-
key:
|
|
7826
|
+
key: z34.string().min(1).describe(
|
|
7212
7827
|
'Metafield key (field name, e.g., "color_hex", "schema_markup"). Keys should be lowercase with underscores, unique within a namespace.'
|
|
7213
7828
|
),
|
|
7214
|
-
value:
|
|
7215
|
-
type:
|
|
7829
|
+
value: z34.string().describe("The value to store. All values are stored as strings; JSON should be stringified."),
|
|
7830
|
+
type: z34.string().min(1).describe(
|
|
7216
7831
|
'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".'
|
|
7217
7832
|
)
|
|
7218
7833
|
});
|
|
7219
|
-
var inputSchema32 =
|
|
7220
|
-
productId:
|
|
7834
|
+
var inputSchema32 = z34.object({
|
|
7835
|
+
productId: productIdSchema.describe(
|
|
7221
7836
|
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Use get-product or list-products to find product IDs.'
|
|
7222
7837
|
),
|
|
7223
|
-
metafields:
|
|
7838
|
+
metafields: z34.array(metafieldInputSchema).min(1, "At least one metafield is required").max(
|
|
7224
7839
|
MAX_METAFIELDS_PER_CALL,
|
|
7225
7840
|
`Maximum ${MAX_METAFIELDS_PER_CALL} metafields per call. Split into multiple calls for larger batches.`
|
|
7226
7841
|
).describe(
|
|
7227
7842
|
`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.`
|
|
7228
7843
|
)
|
|
7229
7844
|
});
|
|
7230
|
-
var outputSchema32 =
|
|
7231
|
-
success:
|
|
7232
|
-
metafields:
|
|
7233
|
-
|
|
7234
|
-
id:
|
|
7235
|
-
namespace:
|
|
7236
|
-
key:
|
|
7237
|
-
value:
|
|
7238
|
-
type:
|
|
7239
|
-
createdAt:
|
|
7240
|
-
updatedAt:
|
|
7845
|
+
var outputSchema32 = z34.object({
|
|
7846
|
+
success: z34.boolean().describe("Whether the operation succeeded"),
|
|
7847
|
+
metafields: z34.array(
|
|
7848
|
+
z34.object({
|
|
7849
|
+
id: z34.string().describe("Metafield GID"),
|
|
7850
|
+
namespace: z34.string().describe("Metafield namespace"),
|
|
7851
|
+
key: z34.string().describe("Metafield key"),
|
|
7852
|
+
value: z34.string().describe("Metafield value"),
|
|
7853
|
+
type: z34.string().describe("Metafield type"),
|
|
7854
|
+
createdAt: z34.string().describe("Creation timestamp (ISO 8601)"),
|
|
7855
|
+
updatedAt: z34.string().describe("Last update timestamp (ISO 8601)")
|
|
7241
7856
|
})
|
|
7242
7857
|
).describe("Created or updated metafields"),
|
|
7243
|
-
count:
|
|
7858
|
+
count: z34.number().describe("Number of metafields processed")
|
|
7244
7859
|
});
|
|
7245
7860
|
var handleSetProductMetafields = async (context, params) => {
|
|
7246
7861
|
log.debug(
|
|
@@ -7284,6 +7899,14 @@ function registerSetProductMetafieldsTool() {
|
|
|
7284
7899
|
relatedTools: ["get-product", "get-product-metafields"],
|
|
7285
7900
|
prerequisites: ["get-product"],
|
|
7286
7901
|
followUps: ["get-product-metafields"]
|
|
7902
|
+
},
|
|
7903
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
7904
|
+
annotations: {
|
|
7905
|
+
readOnlyHint: false,
|
|
7906
|
+
destructiveHint: false,
|
|
7907
|
+
idempotentHint: true,
|
|
7908
|
+
// Same values produce same result
|
|
7909
|
+
openWorldHint: true
|
|
7287
7910
|
}
|
|
7288
7911
|
},
|
|
7289
7912
|
handleSetProductMetafields
|
|
@@ -7291,47 +7914,47 @@ function registerSetProductMetafieldsTool() {
|
|
|
7291
7914
|
}
|
|
7292
7915
|
|
|
7293
7916
|
// src/tools/update-article.ts
|
|
7294
|
-
import { z as
|
|
7295
|
-
var inputSchema33 =
|
|
7296
|
-
id:
|
|
7917
|
+
import { z as z35 } from "zod";
|
|
7918
|
+
var inputSchema33 = z35.object({
|
|
7919
|
+
id: articleIdSchema.describe(
|
|
7297
7920
|
'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.'
|
|
7298
7921
|
),
|
|
7299
|
-
title:
|
|
7300
|
-
authorName:
|
|
7301
|
-
body:
|
|
7302
|
-
summary:
|
|
7303
|
-
tags:
|
|
7922
|
+
title: z35.string().optional().describe("The new title for the article. Only provided if changing the title."),
|
|
7923
|
+
authorName: z35.string().optional().describe("The new author name for the article."),
|
|
7924
|
+
body: z35.string().optional().describe("The new HTML body content of the article. Supports HTML markup for formatting."),
|
|
7925
|
+
summary: z35.string().optional().describe("A summary or excerpt of the article."),
|
|
7926
|
+
tags: z35.array(z35.string()).optional().describe(
|
|
7304
7927
|
"New tags for categorization. This replaces all existing tags. Example: ['guide', 'tutorial', 'beginner']."
|
|
7305
7928
|
),
|
|
7306
|
-
image:
|
|
7307
|
-
url:
|
|
7308
|
-
altText:
|
|
7929
|
+
image: z35.object({
|
|
7930
|
+
url: z35.string().url().describe("The URL of the featured image"),
|
|
7931
|
+
altText: z35.string().optional().describe("Alt text for accessibility")
|
|
7309
7932
|
}).optional().describe("Featured image for the article."),
|
|
7310
|
-
isPublished:
|
|
7933
|
+
isPublished: z35.boolean().optional().describe(
|
|
7311
7934
|
"Whether the article should be visible on the storefront. Set to true to publish, false to unpublish."
|
|
7312
7935
|
),
|
|
7313
|
-
publishDate:
|
|
7936
|
+
publishDate: z35.string().optional().describe(
|
|
7314
7937
|
"The date and time when the article should become visible (ISO 8601 format). Example: '2024-01-15T10:00:00Z'."
|
|
7315
7938
|
),
|
|
7316
|
-
handle:
|
|
7939
|
+
handle: z35.string().optional().describe(
|
|
7317
7940
|
"The new URL handle/slug for the article. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
|
|
7318
7941
|
),
|
|
7319
|
-
templateSuffix:
|
|
7942
|
+
templateSuffix: z35.string().optional().describe(
|
|
7320
7943
|
"The suffix of the Liquid template used to render the article. For example, 'featured' would use the template 'article.featured.liquid'."
|
|
7321
7944
|
),
|
|
7322
|
-
redirectNewHandle:
|
|
7945
|
+
redirectNewHandle: z35.boolean().optional().describe(
|
|
7323
7946
|
"Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
|
|
7324
7947
|
)
|
|
7325
7948
|
});
|
|
7326
|
-
var outputSchema33 =
|
|
7327
|
-
id:
|
|
7328
|
-
title:
|
|
7329
|
-
handle:
|
|
7330
|
-
blog:
|
|
7331
|
-
id:
|
|
7332
|
-
title:
|
|
7949
|
+
var outputSchema33 = z35.object({
|
|
7950
|
+
id: z35.string().describe("Updated article GID"),
|
|
7951
|
+
title: z35.string().describe("Article title"),
|
|
7952
|
+
handle: z35.string().describe("URL handle/slug"),
|
|
7953
|
+
blog: z35.object({
|
|
7954
|
+
id: z35.string().describe("Parent blog GID"),
|
|
7955
|
+
title: z35.string().describe("Parent blog title")
|
|
7333
7956
|
}),
|
|
7334
|
-
isPublished:
|
|
7957
|
+
isPublished: z35.boolean().describe("Whether article is visible on storefront")
|
|
7335
7958
|
});
|
|
7336
7959
|
var handleUpdateArticle = async (context, params) => {
|
|
7337
7960
|
log.debug(`Updating article on shop: ${context.shopDomain}`);
|
|
@@ -7387,6 +8010,13 @@ function registerUpdateArticleTool() {
|
|
|
7387
8010
|
relatedTools: ["list-articles", "create-article", "delete-article"],
|
|
7388
8011
|
prerequisites: ["list-articles"],
|
|
7389
8012
|
followUps: ["list-articles"]
|
|
8013
|
+
},
|
|
8014
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8015
|
+
annotations: {
|
|
8016
|
+
readOnlyHint: false,
|
|
8017
|
+
destructiveHint: false,
|
|
8018
|
+
idempotentHint: true,
|
|
8019
|
+
openWorldHint: true
|
|
7390
8020
|
}
|
|
7391
8021
|
},
|
|
7392
8022
|
handleUpdateArticle
|
|
@@ -7394,30 +8024,30 @@ function registerUpdateArticleTool() {
|
|
|
7394
8024
|
}
|
|
7395
8025
|
|
|
7396
8026
|
// src/tools/update-blog.ts
|
|
7397
|
-
import { z as
|
|
7398
|
-
var inputSchema34 =
|
|
7399
|
-
id:
|
|
8027
|
+
import { z as z36 } from "zod";
|
|
8028
|
+
var inputSchema34 = z36.object({
|
|
8029
|
+
id: blogIdSchema.describe(
|
|
7400
8030
|
'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.'
|
|
7401
8031
|
),
|
|
7402
|
-
title:
|
|
7403
|
-
handle:
|
|
8032
|
+
title: z36.string().optional().describe("The new title for the blog. Only provided if changing the title."),
|
|
8033
|
+
handle: z36.string().optional().describe(
|
|
7404
8034
|
"The new URL handle/slug for the blog. Set redirectNewHandle to true to automatically redirect the old URL to the new one."
|
|
7405
8035
|
),
|
|
7406
|
-
commentPolicy:
|
|
8036
|
+
commentPolicy: z36.enum(["CLOSED", "MODERATE", "OPEN"]).optional().describe(
|
|
7407
8037
|
"Comment moderation policy for the blog. CLOSED: Comments disabled. MODERATE: Comments require approval. OPEN: Comments appear immediately."
|
|
7408
8038
|
),
|
|
7409
|
-
templateSuffix:
|
|
8039
|
+
templateSuffix: z36.string().optional().describe(
|
|
7410
8040
|
"The suffix of the Liquid template used to render the blog. For example, 'news' would use the template 'blog.news.liquid'."
|
|
7411
8041
|
),
|
|
7412
|
-
redirectNewHandle:
|
|
8042
|
+
redirectNewHandle: z36.boolean().optional().describe(
|
|
7413
8043
|
"Whether to automatically redirect the old URL to the new handle. Set to true when changing the handle to preserve SEO and existing links."
|
|
7414
8044
|
)
|
|
7415
8045
|
});
|
|
7416
|
-
var outputSchema34 =
|
|
7417
|
-
id:
|
|
7418
|
-
title:
|
|
7419
|
-
handle:
|
|
7420
|
-
commentPolicy:
|
|
8046
|
+
var outputSchema34 = z36.object({
|
|
8047
|
+
id: z36.string().describe("Updated blog GID"),
|
|
8048
|
+
title: z36.string().describe("Blog title"),
|
|
8049
|
+
handle: z36.string().describe("URL handle/slug"),
|
|
8050
|
+
commentPolicy: z36.enum(["CLOSED", "MODERATE", "OPEN"]).describe("Comment moderation policy")
|
|
7421
8051
|
});
|
|
7422
8052
|
var handleUpdateBlog = async (context, params) => {
|
|
7423
8053
|
log.debug(`Updating blog on shop: ${context.shopDomain}`);
|
|
@@ -7467,6 +8097,13 @@ function registerUpdateBlogTool() {
|
|
|
7467
8097
|
relatedTools: ["list-blogs", "create-blog", "delete-blog"],
|
|
7468
8098
|
prerequisites: ["list-blogs"],
|
|
7469
8099
|
followUps: ["list-blogs"]
|
|
8100
|
+
},
|
|
8101
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8102
|
+
annotations: {
|
|
8103
|
+
readOnlyHint: false,
|
|
8104
|
+
destructiveHint: false,
|
|
8105
|
+
idempotentHint: true,
|
|
8106
|
+
openWorldHint: true
|
|
7470
8107
|
}
|
|
7471
8108
|
},
|
|
7472
8109
|
handleUpdateBlog
|
|
@@ -7474,21 +8111,21 @@ function registerUpdateBlogTool() {
|
|
|
7474
8111
|
}
|
|
7475
8112
|
|
|
7476
8113
|
// src/tools/update-collection.ts
|
|
7477
|
-
import { z as
|
|
7478
|
-
var inputSchema35 =
|
|
7479
|
-
collectionId:
|
|
8114
|
+
import { z as z37 } from "zod";
|
|
8115
|
+
var inputSchema35 = z37.object({
|
|
8116
|
+
collectionId: collectionIdSchema.describe(
|
|
7480
8117
|
'Collection ID to update (GID format, e.g., "gid://shopify/Collection/123"). Use list-collections or get-collection to find collection IDs.'
|
|
7481
8118
|
),
|
|
7482
|
-
title:
|
|
7483
|
-
handle:
|
|
8119
|
+
title: z37.string().min(1).optional().describe("New collection title"),
|
|
8120
|
+
handle: z37.string().optional().describe(
|
|
7484
8121
|
"New URL handle/slug. When changing, set redirectNewHandle: true to create automatic redirect from the old URL."
|
|
7485
8122
|
),
|
|
7486
|
-
descriptionHtml:
|
|
7487
|
-
seo:
|
|
7488
|
-
title:
|
|
7489
|
-
description:
|
|
8123
|
+
descriptionHtml: z37.string().optional().describe("New HTML description"),
|
|
8124
|
+
seo: z37.object({
|
|
8125
|
+
title: z37.string().optional().describe("New SEO title"),
|
|
8126
|
+
description: z37.string().optional().describe("New SEO meta description")
|
|
7490
8127
|
}).optional().describe("Updated SEO metadata"),
|
|
7491
|
-
sortOrder:
|
|
8128
|
+
sortOrder: z37.enum([
|
|
7492
8129
|
"ALPHA_ASC",
|
|
7493
8130
|
"ALPHA_DESC",
|
|
7494
8131
|
"BEST_SELLING",
|
|
@@ -7498,19 +8135,19 @@ var inputSchema35 = z36.object({
|
|
|
7498
8135
|
"PRICE_ASC",
|
|
7499
8136
|
"PRICE_DESC"
|
|
7500
8137
|
]).optional().describe("New product sort order within collection"),
|
|
7501
|
-
image:
|
|
7502
|
-
src:
|
|
7503
|
-
altText:
|
|
8138
|
+
image: z37.object({
|
|
8139
|
+
src: z37.string().url().describe("New image URL (publicly accessible)"),
|
|
8140
|
+
altText: z37.string().optional().describe("New alt text for accessibility")
|
|
7504
8141
|
}).optional().describe("New collection image"),
|
|
7505
|
-
templateSuffix:
|
|
7506
|
-
redirectNewHandle:
|
|
8142
|
+
templateSuffix: z37.string().optional().describe("New Liquid template suffix"),
|
|
8143
|
+
redirectNewHandle: z37.boolean().optional().describe(
|
|
7507
8144
|
"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."
|
|
7508
8145
|
)
|
|
7509
8146
|
});
|
|
7510
|
-
var outputSchema35 =
|
|
7511
|
-
id:
|
|
7512
|
-
title:
|
|
7513
|
-
handle:
|
|
8147
|
+
var outputSchema35 = z37.object({
|
|
8148
|
+
id: z37.string().describe("Updated collection GID"),
|
|
8149
|
+
title: z37.string().describe("Collection title"),
|
|
8150
|
+
handle: z37.string().describe("Collection URL handle")
|
|
7514
8151
|
});
|
|
7515
8152
|
var handleUpdateCollection = async (context, params) => {
|
|
7516
8153
|
log.debug(`Updating collection on shop: ${context.shopDomain}`);
|
|
@@ -7557,6 +8194,14 @@ function registerUpdateCollectionTool() {
|
|
|
7557
8194
|
relatedTools: ["get-collection", "list-collections"],
|
|
7558
8195
|
prerequisites: ["get-collection"],
|
|
7559
8196
|
followUps: ["add-products-to-collection", "remove-products-from-collection"]
|
|
8197
|
+
},
|
|
8198
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8199
|
+
annotations: {
|
|
8200
|
+
readOnlyHint: false,
|
|
8201
|
+
destructiveHint: false,
|
|
8202
|
+
idempotentHint: true,
|
|
8203
|
+
// Same update produces same result
|
|
8204
|
+
openWorldHint: true
|
|
7560
8205
|
}
|
|
7561
8206
|
},
|
|
7562
8207
|
handleUpdateCollection
|
|
@@ -7564,8 +8209,8 @@ function registerUpdateCollectionTool() {
|
|
|
7564
8209
|
}
|
|
7565
8210
|
|
|
7566
8211
|
// src/tools/update-inventory.ts
|
|
7567
|
-
import { z as
|
|
7568
|
-
var inventoryReasonEnum =
|
|
8212
|
+
import { z as z38 } from "zod";
|
|
8213
|
+
var inventoryReasonEnum = z38.enum([
|
|
7569
8214
|
"correction",
|
|
7570
8215
|
"cycle_count_available",
|
|
7571
8216
|
"damaged",
|
|
@@ -7584,20 +8229,20 @@ var inventoryReasonEnum = z37.enum([
|
|
|
7584
8229
|
"safety_stock",
|
|
7585
8230
|
"shrinkage"
|
|
7586
8231
|
]);
|
|
7587
|
-
var inputSchema36 =
|
|
7588
|
-
inventoryItemId:
|
|
8232
|
+
var inputSchema36 = z38.object({
|
|
8233
|
+
inventoryItemId: inventoryItemIdSchema.describe(
|
|
7589
8234
|
'The Shopify inventory item ID (e.g., "gid://shopify/InventoryItem/456"). Get this from get-inventory tool or from get-product response.'
|
|
7590
8235
|
),
|
|
7591
|
-
locationId:
|
|
8236
|
+
locationId: locationIdSchema.describe(
|
|
7592
8237
|
'The location ID where inventory should be updated (e.g., "gid://shopify/Location/789"). Get this from get-inventory tool.'
|
|
7593
8238
|
),
|
|
7594
|
-
setQuantity:
|
|
8239
|
+
setQuantity: z38.number().int().min(0).optional().describe(
|
|
7595
8240
|
"Set inventory to this exact quantity. Example: 100. Use for absolute stock counts after physical inventory. Cannot be used together with adjustQuantity."
|
|
7596
8241
|
),
|
|
7597
|
-
adjustQuantity:
|
|
8242
|
+
adjustQuantity: z38.number().int().optional().describe(
|
|
7598
8243
|
"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."
|
|
7599
8244
|
),
|
|
7600
|
-
name:
|
|
8245
|
+
name: z38.enum(["available", "on_hand"]).optional().default("available").describe(
|
|
7601
8246
|
'Which quantity to modify. "available" (default) for sellable stock, "on_hand" for physical count.'
|
|
7602
8247
|
),
|
|
7603
8248
|
reason: inventoryReasonEnum.optional().default("correction").describe(
|
|
@@ -7608,16 +8253,16 @@ var inputSchema36 = z37.object({
|
|
|
7608
8253
|
}).refine((data) => !(data.setQuantity !== void 0 && data.adjustQuantity !== void 0), {
|
|
7609
8254
|
message: "Cannot provide both setQuantity and adjustQuantity. Choose one."
|
|
7610
8255
|
});
|
|
7611
|
-
var outputSchema36 =
|
|
7612
|
-
success:
|
|
7613
|
-
inventoryItemId:
|
|
7614
|
-
locationId:
|
|
7615
|
-
locationName:
|
|
7616
|
-
previousQuantity:
|
|
7617
|
-
newQuantity:
|
|
7618
|
-
delta:
|
|
7619
|
-
name:
|
|
7620
|
-
reason:
|
|
8256
|
+
var outputSchema36 = z38.object({
|
|
8257
|
+
success: z38.boolean().describe("Whether the operation succeeded"),
|
|
8258
|
+
inventoryItemId: z38.string().describe("Inventory item GID"),
|
|
8259
|
+
locationId: z38.string().describe("Location GID where inventory was updated"),
|
|
8260
|
+
locationName: z38.string().describe("Human-readable location name"),
|
|
8261
|
+
previousQuantity: z38.number().describe("Quantity before the change"),
|
|
8262
|
+
newQuantity: z38.number().describe("Quantity after the change"),
|
|
8263
|
+
delta: z38.number().describe("The actual change applied (positive or negative)"),
|
|
8264
|
+
name: z38.string().describe("Which quantity was modified (available or on_hand)"),
|
|
8265
|
+
reason: z38.string().describe("Reason for the adjustment")
|
|
7621
8266
|
});
|
|
7622
8267
|
var handleUpdateInventory = async (context, params) => {
|
|
7623
8268
|
log.debug(`Updating inventory on shop: ${context.shopDomain}`);
|
|
@@ -7698,6 +8343,13 @@ function registerUpdateInventoryTool() {
|
|
|
7698
8343
|
relatedTools: ["get-inventory", "get-bulk-inventory", "list-low-inventory"],
|
|
7699
8344
|
prerequisites: ["get-inventory"],
|
|
7700
8345
|
followUps: ["get-inventory"]
|
|
8346
|
+
},
|
|
8347
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8348
|
+
annotations: {
|
|
8349
|
+
readOnlyHint: false,
|
|
8350
|
+
destructiveHint: false,
|
|
8351
|
+
idempotentHint: true,
|
|
8352
|
+
openWorldHint: true
|
|
7701
8353
|
}
|
|
7702
8354
|
},
|
|
7703
8355
|
handleUpdateInventory
|
|
@@ -7705,33 +8357,33 @@ function registerUpdateInventoryTool() {
|
|
|
7705
8357
|
}
|
|
7706
8358
|
|
|
7707
8359
|
// src/tools/update-page.ts
|
|
7708
|
-
import { z as
|
|
7709
|
-
var inputSchema37 =
|
|
7710
|
-
id:
|
|
8360
|
+
import { z as z39 } from "zod";
|
|
8361
|
+
var inputSchema37 = z39.object({
|
|
8362
|
+
id: pageIdSchema.describe(
|
|
7711
8363
|
'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.'
|
|
7712
8364
|
),
|
|
7713
|
-
title:
|
|
7714
|
-
body:
|
|
8365
|
+
title: z39.string().optional().describe("The new title for the page. Only provide if you want to change it."),
|
|
8366
|
+
body: z39.string().optional().describe(
|
|
7715
8367
|
"The new HTML body content for the page. Supports HTML markup. Only provide if you want to change it."
|
|
7716
8368
|
),
|
|
7717
|
-
handle:
|
|
8369
|
+
handle: z39.string().optional().describe(
|
|
7718
8370
|
"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."
|
|
7719
8371
|
),
|
|
7720
|
-
isPublished:
|
|
8372
|
+
isPublished: z39.boolean().optional().describe(
|
|
7721
8373
|
"Whether the page should be visible on the storefront. Set to true to publish, false to unpublish."
|
|
7722
8374
|
),
|
|
7723
|
-
templateSuffix:
|
|
8375
|
+
templateSuffix: z39.string().optional().describe(
|
|
7724
8376
|
"The suffix of the Liquid template used to render the page. Set to empty string to use the default template."
|
|
7725
8377
|
),
|
|
7726
|
-
redirectNewHandle:
|
|
8378
|
+
redirectNewHandle: z39.boolean().optional().describe(
|
|
7727
8379
|
"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."
|
|
7728
8380
|
)
|
|
7729
8381
|
});
|
|
7730
|
-
var outputSchema37 =
|
|
7731
|
-
id:
|
|
7732
|
-
title:
|
|
7733
|
-
handle:
|
|
7734
|
-
isPublished:
|
|
8382
|
+
var outputSchema37 = z39.object({
|
|
8383
|
+
id: z39.string().describe("Updated page GID"),
|
|
8384
|
+
title: z39.string().describe("Page title"),
|
|
8385
|
+
handle: z39.string().describe("URL handle/slug"),
|
|
8386
|
+
isPublished: z39.boolean().describe("Whether page is visible on storefront")
|
|
7735
8387
|
});
|
|
7736
8388
|
var handleUpdatePage = async (context, params) => {
|
|
7737
8389
|
log.debug(`Updating page on shop: ${context.shopDomain}`);
|
|
@@ -7782,6 +8434,13 @@ function registerUpdatePageTool() {
|
|
|
7782
8434
|
relatedTools: ["get-page", "list-pages"],
|
|
7783
8435
|
prerequisites: ["get-page"],
|
|
7784
8436
|
followUps: ["get-page"]
|
|
8437
|
+
},
|
|
8438
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8439
|
+
annotations: {
|
|
8440
|
+
readOnlyHint: false,
|
|
8441
|
+
destructiveHint: false,
|
|
8442
|
+
idempotentHint: true,
|
|
8443
|
+
openWorldHint: true
|
|
7785
8444
|
}
|
|
7786
8445
|
},
|
|
7787
8446
|
handleUpdatePage
|
|
@@ -7789,7 +8448,7 @@ function registerUpdatePageTool() {
|
|
|
7789
8448
|
}
|
|
7790
8449
|
|
|
7791
8450
|
// src/tools/update-product-image.ts
|
|
7792
|
-
import { z as
|
|
8451
|
+
import { z as z40 } from "zod";
|
|
7793
8452
|
var FILE_UPDATE_MUTATION = `
|
|
7794
8453
|
mutation FileUpdate($files: [FileUpdateInput!]!) {
|
|
7795
8454
|
fileUpdate(files: $files) {
|
|
@@ -7809,18 +8468,18 @@ var FILE_UPDATE_MUTATION = `
|
|
|
7809
8468
|
}
|
|
7810
8469
|
}
|
|
7811
8470
|
`;
|
|
7812
|
-
var inputSchema38 =
|
|
7813
|
-
imageId:
|
|
8471
|
+
var inputSchema38 = z40.object({
|
|
8472
|
+
imageId: imageIdSchema.describe(
|
|
7814
8473
|
'Shopify image GID (e.g., "gid://shopify/MediaImage/123"). Required. Use get-product to find image IDs in the media field.'
|
|
7815
8474
|
),
|
|
7816
|
-
altText:
|
|
8475
|
+
altText: z40.string().describe(
|
|
7817
8476
|
"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."
|
|
7818
8477
|
)
|
|
7819
8478
|
});
|
|
7820
|
-
var outputSchema38 =
|
|
7821
|
-
id:
|
|
7822
|
-
url:
|
|
7823
|
-
altText:
|
|
8479
|
+
var outputSchema38 = z40.object({
|
|
8480
|
+
id: z40.string().describe('Image GID (e.g., "gid://shopify/MediaImage/123")'),
|
|
8481
|
+
url: z40.string().nullable().describe("Shopify CDN URL for the image"),
|
|
8482
|
+
altText: z40.string().nullable().describe("Updated alt text for the image")
|
|
7824
8483
|
});
|
|
7825
8484
|
var handleUpdateProductImage = async (context, params) => {
|
|
7826
8485
|
log.debug(`Updating image alt text on shop: ${context.shopDomain}`);
|
|
@@ -7887,6 +8546,14 @@ function registerUpdateProductImageTool() {
|
|
|
7887
8546
|
relatedTools: ["reorder-product-images", "add-product-image"],
|
|
7888
8547
|
prerequisites: ["add-product-image"],
|
|
7889
8548
|
followUps: ["reorder-product-images"]
|
|
8549
|
+
},
|
|
8550
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8551
|
+
annotations: {
|
|
8552
|
+
readOnlyHint: false,
|
|
8553
|
+
destructiveHint: false,
|
|
8554
|
+
idempotentHint: true,
|
|
8555
|
+
// Same alt text update produces same result
|
|
8556
|
+
openWorldHint: true
|
|
7890
8557
|
}
|
|
7891
8558
|
},
|
|
7892
8559
|
handleUpdateProductImage
|
|
@@ -7894,19 +8561,19 @@ function registerUpdateProductImageTool() {
|
|
|
7894
8561
|
}
|
|
7895
8562
|
|
|
7896
8563
|
// src/tools/update-product-variant.ts
|
|
7897
|
-
import { z as
|
|
7898
|
-
var inputSchema39 =
|
|
7899
|
-
productId:
|
|
8564
|
+
import { z as z41 } from "zod";
|
|
8565
|
+
var inputSchema39 = z41.object({
|
|
8566
|
+
productId: productIdSchema.describe(
|
|
7900
8567
|
'Shopify product GID (e.g., "gid://shopify/Product/123"). Required. Use get-product to find the product ID containing the variant.'
|
|
7901
8568
|
),
|
|
7902
|
-
id:
|
|
8569
|
+
id: variantIdSchema.describe(
|
|
7903
8570
|
'Shopify variant GID (e.g., "gid://shopify/ProductVariant/456"). Required. Use get-product to find variant IDs for a product.'
|
|
7904
8571
|
),
|
|
7905
|
-
price:
|
|
7906
|
-
compareAtPrice:
|
|
8572
|
+
price: z41.string().optional().describe('New variant price as decimal string (e.g., "29.99")'),
|
|
8573
|
+
compareAtPrice: z41.string().nullable().optional().describe(
|
|
7907
8574
|
'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.'
|
|
7908
8575
|
),
|
|
7909
|
-
barcode:
|
|
8576
|
+
barcode: z41.string().nullable().optional().describe("Barcode (UPC, EAN, ISBN, etc.). Set to null to remove.")
|
|
7910
8577
|
}).refine(
|
|
7911
8578
|
(data) => {
|
|
7912
8579
|
const { productId: _productId, id: _id, ...updateFields } = data;
|
|
@@ -7914,14 +8581,14 @@ var inputSchema39 = z40.object({
|
|
|
7914
8581
|
},
|
|
7915
8582
|
{ message: "At least one field to update must be provided (price, compareAtPrice, or barcode)" }
|
|
7916
8583
|
);
|
|
7917
|
-
var outputSchema39 =
|
|
7918
|
-
id:
|
|
7919
|
-
title:
|
|
7920
|
-
price:
|
|
7921
|
-
compareAtPrice:
|
|
7922
|
-
sku:
|
|
7923
|
-
barcode:
|
|
7924
|
-
inventoryQuantity:
|
|
8584
|
+
var outputSchema39 = z41.object({
|
|
8585
|
+
id: z41.string().describe('Variant GID (e.g., "gid://shopify/ProductVariant/123")'),
|
|
8586
|
+
title: z41.string().describe('Variant title (e.g., "Red / Medium")'),
|
|
8587
|
+
price: z41.string().describe("Current price as decimal string"),
|
|
8588
|
+
compareAtPrice: z41.string().nullable().describe("Compare at price for sale display"),
|
|
8589
|
+
sku: z41.string().nullable().describe("Stock keeping unit"),
|
|
8590
|
+
barcode: z41.string().nullable().describe("Barcode (UPC, EAN, etc.)"),
|
|
8591
|
+
inventoryQuantity: z41.number().nullable().describe("Available inventory quantity")
|
|
7925
8592
|
});
|
|
7926
8593
|
var handleUpdateProductVariant = async (context, params) => {
|
|
7927
8594
|
log.debug(`Updating product variant on shop: ${context.shopDomain}`);
|
|
@@ -7957,6 +8624,14 @@ function registerUpdateProductVariantTool() {
|
|
|
7957
8624
|
relationships: {
|
|
7958
8625
|
relatedTools: ["get-product", "get-inventory"],
|
|
7959
8626
|
prerequisites: ["get-product"]
|
|
8627
|
+
},
|
|
8628
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8629
|
+
annotations: {
|
|
8630
|
+
readOnlyHint: false,
|
|
8631
|
+
destructiveHint: false,
|
|
8632
|
+
idempotentHint: true,
|
|
8633
|
+
// Same update produces same result
|
|
8634
|
+
openWorldHint: true
|
|
7960
8635
|
}
|
|
7961
8636
|
},
|
|
7962
8637
|
handleUpdateProductVariant
|
|
@@ -7964,23 +8639,25 @@ function registerUpdateProductVariantTool() {
|
|
|
7964
8639
|
}
|
|
7965
8640
|
|
|
7966
8641
|
// src/tools/update-product.ts
|
|
7967
|
-
import { z as
|
|
7968
|
-
var inputSchema40 =
|
|
7969
|
-
id:
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
8642
|
+
import { z as z42 } from "zod";
|
|
8643
|
+
var inputSchema40 = z42.object({
|
|
8644
|
+
id: productIdSchema.describe(
|
|
8645
|
+
'Shopify product ID (GID format, e.g., "gid://shopify/Product/123"). Required.'
|
|
8646
|
+
),
|
|
8647
|
+
title: z42.string().min(1).optional().describe("New product title"),
|
|
8648
|
+
description: z42.string().optional().describe("New product description (HTML supported)"),
|
|
8649
|
+
vendor: z42.string().optional().describe("New product vendor/brand name"),
|
|
8650
|
+
productType: z42.string().optional().describe("New product type for categorization"),
|
|
8651
|
+
tags: z42.array(z42.string()).optional().describe("New tags (replaces existing tags)"),
|
|
8652
|
+
status: z42.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).optional().describe("New product status: ACTIVE (visible), DRAFT (hidden), ARCHIVED (hidden)"),
|
|
8653
|
+
seo: z42.object({
|
|
8654
|
+
title: z42.string().optional().describe("SEO title for search engine results"),
|
|
8655
|
+
description: z42.string().optional().describe("SEO meta description for search engines")
|
|
7979
8656
|
}).optional().describe("SEO metadata for search engine optimization"),
|
|
7980
|
-
handle:
|
|
8657
|
+
handle: z42.string().optional().describe(
|
|
7981
8658
|
'New URL handle/slug (e.g., "my-product"). Use with redirectNewHandle for URL changes.'
|
|
7982
8659
|
),
|
|
7983
|
-
redirectNewHandle:
|
|
8660
|
+
redirectNewHandle: z42.boolean().optional().describe(
|
|
7984
8661
|
"If true, creates automatic redirect from old handle to new handle. Use when changing handle."
|
|
7985
8662
|
)
|
|
7986
8663
|
}).refine(
|
|
@@ -7990,40 +8667,40 @@ var inputSchema40 = z41.object({
|
|
|
7990
8667
|
},
|
|
7991
8668
|
{ message: "At least one field to update must be provided" }
|
|
7992
8669
|
);
|
|
7993
|
-
var outputSchema40 =
|
|
7994
|
-
id:
|
|
7995
|
-
title:
|
|
7996
|
-
handle:
|
|
7997
|
-
description:
|
|
7998
|
-
vendor:
|
|
7999
|
-
productType:
|
|
8000
|
-
status:
|
|
8001
|
-
tags:
|
|
8002
|
-
seo:
|
|
8003
|
-
title:
|
|
8004
|
-
description:
|
|
8670
|
+
var outputSchema40 = z42.object({
|
|
8671
|
+
id: z42.string().describe('Product GID (e.g., "gid://shopify/Product/123")'),
|
|
8672
|
+
title: z42.string().describe("Product title"),
|
|
8673
|
+
handle: z42.string().describe("URL handle/slug"),
|
|
8674
|
+
description: z42.string().nullable().describe("Product description (HTML)"),
|
|
8675
|
+
vendor: z42.string().describe("Vendor/brand name"),
|
|
8676
|
+
productType: z42.string().describe("Product type"),
|
|
8677
|
+
status: z42.enum(["ACTIVE", "DRAFT", "ARCHIVED"]).describe("Product status"),
|
|
8678
|
+
tags: z42.array(z42.string()).describe("Product tags"),
|
|
8679
|
+
seo: z42.object({
|
|
8680
|
+
title: z42.string().nullable().describe("SEO title for search engines"),
|
|
8681
|
+
description: z42.string().nullable().describe("SEO description/meta description")
|
|
8005
8682
|
}).describe("SEO metadata for search engine optimization"),
|
|
8006
|
-
createdAt:
|
|
8007
|
-
updatedAt:
|
|
8008
|
-
variants:
|
|
8009
|
-
|
|
8010
|
-
id:
|
|
8011
|
-
title:
|
|
8012
|
-
price:
|
|
8013
|
-
compareAtPrice:
|
|
8014
|
-
sku:
|
|
8015
|
-
barcode:
|
|
8016
|
-
inventoryQuantity:
|
|
8683
|
+
createdAt: z42.string().describe("Creation timestamp (ISO 8601)"),
|
|
8684
|
+
updatedAt: z42.string().describe("Last update timestamp (ISO 8601)"),
|
|
8685
|
+
variants: z42.array(
|
|
8686
|
+
z42.object({
|
|
8687
|
+
id: z42.string().describe("Variant GID"),
|
|
8688
|
+
title: z42.string().describe("Variant title"),
|
|
8689
|
+
price: z42.string().describe("Price"),
|
|
8690
|
+
compareAtPrice: z42.string().nullable().describe("Compare at price"),
|
|
8691
|
+
sku: z42.string().nullable().describe("SKU"),
|
|
8692
|
+
barcode: z42.string().nullable().describe("Barcode"),
|
|
8693
|
+
inventoryQuantity: z42.number().nullable().describe("Inventory quantity")
|
|
8017
8694
|
})
|
|
8018
8695
|
).describe("Product variants"),
|
|
8019
|
-
images:
|
|
8020
|
-
|
|
8021
|
-
id:
|
|
8022
|
-
url:
|
|
8023
|
-
altText:
|
|
8696
|
+
images: z42.array(
|
|
8697
|
+
z42.object({
|
|
8698
|
+
id: z42.string().describe("Image GID"),
|
|
8699
|
+
url: z42.string().describe("Image URL"),
|
|
8700
|
+
altText: z42.string().nullable().describe("Alt text")
|
|
8024
8701
|
})
|
|
8025
8702
|
).describe("Product images"),
|
|
8026
|
-
totalInventory:
|
|
8703
|
+
totalInventory: z42.number().describe("Total inventory across variants")
|
|
8027
8704
|
});
|
|
8028
8705
|
var handleUpdateProduct = async (context, params) => {
|
|
8029
8706
|
log.debug(`Updating product on shop: ${context.shopDomain}`);
|
|
@@ -8060,6 +8737,14 @@ function registerUpdateProductTool() {
|
|
|
8060
8737
|
relatedTools: ["list-products", "get-product"],
|
|
8061
8738
|
prerequisites: ["get-product"],
|
|
8062
8739
|
followUps: ["add-product-image", "set-product-metafields"]
|
|
8740
|
+
},
|
|
8741
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices)
|
|
8742
|
+
annotations: {
|
|
8743
|
+
readOnlyHint: false,
|
|
8744
|
+
destructiveHint: false,
|
|
8745
|
+
idempotentHint: true,
|
|
8746
|
+
// Same update produces same result
|
|
8747
|
+
openWorldHint: true
|
|
8063
8748
|
}
|
|
8064
8749
|
},
|
|
8065
8750
|
handleUpdateProduct
|
|
@@ -8075,7 +8760,9 @@ function setupToolHandlers(server) {
|
|
|
8075
8760
|
tools: tools.map((tool) => ({
|
|
8076
8761
|
name: tool.name,
|
|
8077
8762
|
description: tool.description,
|
|
8078
|
-
inputSchema: tool.inputSchema
|
|
8763
|
+
inputSchema: tool.inputSchema,
|
|
8764
|
+
// Include MCP tool annotations (Epic 9.5 - MCP Best Practices)
|
|
8765
|
+
annotations: tool.annotations
|
|
8079
8766
|
}))
|
|
8080
8767
|
};
|
|
8081
8768
|
});
|