@anton.andrusenko/shopify-mcp-admin 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +416 -6
- package/dist/index.js +1867 -183
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,7 +25,17 @@ var configSchema = z.object({
|
|
|
25
25
|
TRANSPORT: z.enum(["stdio", "http"]).default("stdio"),
|
|
26
26
|
// Store info cache TTL (milliseconds)
|
|
27
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)")
|
|
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
|
+
// Lazy loading configuration (Epic 12)
|
|
30
|
+
// Default: true - enables module-based lazy loading for optimal AI performance
|
|
31
|
+
// Set to 'false' or '0' to load all tools at startup (legacy mode)
|
|
32
|
+
SHOPIFY_MCP_LAZY_LOADING: z.string().optional().default("true").transform((val) => val === "true" || val === "1").describe("Enable lazy loading of tools via modules (default: true)"),
|
|
33
|
+
// Role preset configuration (Story 12.3: Progressive Loading)
|
|
34
|
+
// Automatically loads appropriate modules at startup based on role
|
|
35
|
+
// Valid roles: inventory-manager, product-manager, content-manager,
|
|
36
|
+
// seo-specialist, international-manager, full-access
|
|
37
|
+
// When not set, only core module is loaded (requires manual module loading)
|
|
38
|
+
SHOPIFY_MCP_ROLE: z.string().optional().describe("Role preset for automatic module loading at startup")
|
|
29
39
|
}).refine(
|
|
30
40
|
(data) => {
|
|
31
41
|
const hasLegacyAuth = !!data.SHOPIFY_ACCESS_TOKEN;
|
|
@@ -73,6 +83,12 @@ function isDebugEnabled(debugValue) {
|
|
|
73
83
|
const normalized = debugValue.toLowerCase().trim();
|
|
74
84
|
return normalized === "1" || normalized === "true";
|
|
75
85
|
}
|
|
86
|
+
function isLazyLoadingEnabled(config) {
|
|
87
|
+
return config.SHOPIFY_MCP_LAZY_LOADING;
|
|
88
|
+
}
|
|
89
|
+
function getConfiguredRole(config) {
|
|
90
|
+
return config.SHOPIFY_MCP_ROLE;
|
|
91
|
+
}
|
|
76
92
|
|
|
77
93
|
// src/config/index.ts
|
|
78
94
|
var _config = null;
|
|
@@ -917,9 +933,351 @@ var PRODUCTS_QUERY = `
|
|
|
917
933
|
}
|
|
918
934
|
}
|
|
919
935
|
`;
|
|
936
|
+
var STORE_LIMITS_QUERY = `
|
|
937
|
+
query ShopResourceLimits {
|
|
938
|
+
shop {
|
|
939
|
+
resourceLimits {
|
|
940
|
+
maxProductVariants
|
|
941
|
+
maxProductOptions
|
|
942
|
+
locationLimit
|
|
943
|
+
redirectLimitReached
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
`;
|
|
948
|
+
var STORE_FEATURES_QUERY = `
|
|
949
|
+
query ShopFeatures {
|
|
950
|
+
shop {
|
|
951
|
+
features {
|
|
952
|
+
giftCards
|
|
953
|
+
reports
|
|
954
|
+
storefront
|
|
955
|
+
bundles {
|
|
956
|
+
eligibleForBundles
|
|
957
|
+
}
|
|
958
|
+
sellsSubscriptions
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
`;
|
|
963
|
+
var STORE_CURRENCIES_QUERY = `
|
|
964
|
+
query ShopCurrencies {
|
|
965
|
+
shop {
|
|
966
|
+
currencyCode
|
|
967
|
+
enabledPresentmentCurrencies
|
|
968
|
+
currencyFormats {
|
|
969
|
+
moneyFormat
|
|
970
|
+
moneyWithCurrencyFormat
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
`;
|
|
975
|
+
var STORE_SHIPPING_QUERY = `
|
|
976
|
+
query ShopShipping {
|
|
977
|
+
shop {
|
|
978
|
+
shipsToCountries
|
|
979
|
+
countriesInShippingZones {
|
|
980
|
+
countryCodes
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
`;
|
|
985
|
+
var STORE_DOMAIN_QUERY = `
|
|
986
|
+
query ShopDomain {
|
|
987
|
+
shop {
|
|
988
|
+
primaryDomain {
|
|
989
|
+
host
|
|
990
|
+
url
|
|
991
|
+
sslEnabled
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
`;
|
|
996
|
+
var STORE_TAXES_QUERY = `
|
|
997
|
+
query ShopTaxes {
|
|
998
|
+
shop {
|
|
999
|
+
taxesIncluded
|
|
1000
|
+
taxShipping
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
`;
|
|
1004
|
+
var STORE_POLICIES_QUERY = `
|
|
1005
|
+
query ShopPolicies {
|
|
1006
|
+
shop {
|
|
1007
|
+
shopPolicies {
|
|
1008
|
+
title
|
|
1009
|
+
type
|
|
1010
|
+
url
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
`;
|
|
1015
|
+
var STORE_ALERTS_QUERY = `
|
|
1016
|
+
query ShopAlerts {
|
|
1017
|
+
shop {
|
|
1018
|
+
alerts {
|
|
1019
|
+
action {
|
|
1020
|
+
title
|
|
1021
|
+
url
|
|
1022
|
+
}
|
|
1023
|
+
description
|
|
1024
|
+
}
|
|
1025
|
+
setupRequired
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
`;
|
|
920
1029
|
|
|
921
|
-
// src/shopify/store-info.ts
|
|
1030
|
+
// src/shopify/store-info-extended.ts
|
|
922
1031
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1032
|
+
var ALERTS_CACHE_TTL_MS = 60 * 1e3;
|
|
1033
|
+
var _cachedStoreLimits = null;
|
|
1034
|
+
var _limitsTimestamp = 0;
|
|
1035
|
+
var _cachedStoreFeatures = null;
|
|
1036
|
+
var _featuresTimestamp = 0;
|
|
1037
|
+
var _cachedStoreCurrencies = null;
|
|
1038
|
+
var _currenciesTimestamp = 0;
|
|
1039
|
+
var _cachedStoreShipping = null;
|
|
1040
|
+
var _shippingTimestamp = 0;
|
|
1041
|
+
var _cachedStoreDomain = null;
|
|
1042
|
+
var _domainTimestamp = 0;
|
|
1043
|
+
var _cachedStoreTaxes = null;
|
|
1044
|
+
var _taxesTimestamp = 0;
|
|
1045
|
+
var _cachedStorePolicies = null;
|
|
1046
|
+
var _policiesTimestamp = 0;
|
|
1047
|
+
var _cachedStoreAlerts = null;
|
|
1048
|
+
var _alertsTimestamp = 0;
|
|
1049
|
+
async function getStoreLimits() {
|
|
1050
|
+
const now = Date.now();
|
|
1051
|
+
const config = getConfig();
|
|
1052
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1053
|
+
if (_cachedStoreLimits && now - _limitsTimestamp < ttl) {
|
|
1054
|
+
log.debug("Returning cached store limits");
|
|
1055
|
+
return _cachedStoreLimits;
|
|
1056
|
+
}
|
|
1057
|
+
log.debug("Fetching fresh store limits from Shopify");
|
|
1058
|
+
const client = await getShopifyClient();
|
|
1059
|
+
const response = await withRateLimit(
|
|
1060
|
+
() => client.graphql.request(STORE_LIMITS_QUERY),
|
|
1061
|
+
"getStoreLimits"
|
|
1062
|
+
);
|
|
1063
|
+
if (response.errors && response.errors.length > 0) {
|
|
1064
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1065
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1066
|
+
}
|
|
1067
|
+
if (!response.data?.shop?.resourceLimits) {
|
|
1068
|
+
throw new Error("Invalid response: missing shop.resourceLimits data");
|
|
1069
|
+
}
|
|
1070
|
+
_cachedStoreLimits = response.data.shop.resourceLimits;
|
|
1071
|
+
_limitsTimestamp = now;
|
|
1072
|
+
return _cachedStoreLimits;
|
|
1073
|
+
}
|
|
1074
|
+
async function getStoreFeatures() {
|
|
1075
|
+
const now = Date.now();
|
|
1076
|
+
const config = getConfig();
|
|
1077
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1078
|
+
if (_cachedStoreFeatures && now - _featuresTimestamp < ttl) {
|
|
1079
|
+
log.debug("Returning cached store features");
|
|
1080
|
+
return _cachedStoreFeatures;
|
|
1081
|
+
}
|
|
1082
|
+
log.debug("Fetching fresh store features from Shopify");
|
|
1083
|
+
const client = await getShopifyClient();
|
|
1084
|
+
const response = await withRateLimit(
|
|
1085
|
+
() => client.graphql.request(STORE_FEATURES_QUERY),
|
|
1086
|
+
"getStoreFeatures"
|
|
1087
|
+
);
|
|
1088
|
+
if (response.errors && response.errors.length > 0) {
|
|
1089
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1090
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1091
|
+
}
|
|
1092
|
+
if (!response.data?.shop?.features) {
|
|
1093
|
+
throw new Error("Invalid response: missing shop.features data");
|
|
1094
|
+
}
|
|
1095
|
+
_cachedStoreFeatures = response.data.shop.features;
|
|
1096
|
+
_featuresTimestamp = now;
|
|
1097
|
+
return _cachedStoreFeatures;
|
|
1098
|
+
}
|
|
1099
|
+
async function getStoreCurrencies() {
|
|
1100
|
+
const now = Date.now();
|
|
1101
|
+
const config = getConfig();
|
|
1102
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1103
|
+
if (_cachedStoreCurrencies && now - _currenciesTimestamp < ttl) {
|
|
1104
|
+
log.debug("Returning cached store currencies");
|
|
1105
|
+
return _cachedStoreCurrencies;
|
|
1106
|
+
}
|
|
1107
|
+
log.debug("Fetching fresh store currencies from Shopify");
|
|
1108
|
+
const client = await getShopifyClient();
|
|
1109
|
+
const response = await withRateLimit(
|
|
1110
|
+
() => client.graphql.request(STORE_CURRENCIES_QUERY),
|
|
1111
|
+
"getStoreCurrencies"
|
|
1112
|
+
);
|
|
1113
|
+
if (response.errors && response.errors.length > 0) {
|
|
1114
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1115
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1116
|
+
}
|
|
1117
|
+
if (!response.data?.shop) {
|
|
1118
|
+
throw new Error("Invalid response: missing shop data");
|
|
1119
|
+
}
|
|
1120
|
+
const { currencyCode, enabledPresentmentCurrencies, currencyFormats } = response.data.shop;
|
|
1121
|
+
_cachedStoreCurrencies = {
|
|
1122
|
+
currencyCode,
|
|
1123
|
+
enabledPresentmentCurrencies,
|
|
1124
|
+
currencyFormats
|
|
1125
|
+
};
|
|
1126
|
+
_currenciesTimestamp = now;
|
|
1127
|
+
return _cachedStoreCurrencies;
|
|
1128
|
+
}
|
|
1129
|
+
async function getStoreShipping() {
|
|
1130
|
+
const now = Date.now();
|
|
1131
|
+
const config = getConfig();
|
|
1132
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1133
|
+
if (_cachedStoreShipping && now - _shippingTimestamp < ttl) {
|
|
1134
|
+
log.debug("Returning cached store shipping");
|
|
1135
|
+
return _cachedStoreShipping;
|
|
1136
|
+
}
|
|
1137
|
+
log.debug("Fetching fresh store shipping from Shopify");
|
|
1138
|
+
const client = await getShopifyClient();
|
|
1139
|
+
const response = await withRateLimit(
|
|
1140
|
+
() => client.graphql.request(STORE_SHIPPING_QUERY),
|
|
1141
|
+
"getStoreShipping"
|
|
1142
|
+
);
|
|
1143
|
+
if (response.errors && response.errors.length > 0) {
|
|
1144
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1145
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1146
|
+
}
|
|
1147
|
+
if (!response.data?.shop) {
|
|
1148
|
+
throw new Error("Invalid response: missing shop data");
|
|
1149
|
+
}
|
|
1150
|
+
_cachedStoreShipping = {
|
|
1151
|
+
shipsToCountries: response.data.shop.shipsToCountries,
|
|
1152
|
+
countriesInShippingZones: response.data.shop.countriesInShippingZones
|
|
1153
|
+
};
|
|
1154
|
+
_shippingTimestamp = now;
|
|
1155
|
+
return _cachedStoreShipping;
|
|
1156
|
+
}
|
|
1157
|
+
async function getStoreDomain() {
|
|
1158
|
+
const now = Date.now();
|
|
1159
|
+
const config = getConfig();
|
|
1160
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1161
|
+
if (_cachedStoreDomain && now - _domainTimestamp < ttl) {
|
|
1162
|
+
log.debug("Returning cached store domain");
|
|
1163
|
+
return _cachedStoreDomain;
|
|
1164
|
+
}
|
|
1165
|
+
log.debug("Fetching fresh store domain from Shopify");
|
|
1166
|
+
const client = await getShopifyClient();
|
|
1167
|
+
const response = await withRateLimit(
|
|
1168
|
+
() => client.graphql.request(STORE_DOMAIN_QUERY),
|
|
1169
|
+
"getStoreDomain"
|
|
1170
|
+
);
|
|
1171
|
+
if (response.errors && response.errors.length > 0) {
|
|
1172
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1173
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1174
|
+
}
|
|
1175
|
+
if (!response.data?.shop?.primaryDomain) {
|
|
1176
|
+
throw new Error("Invalid response: missing shop.primaryDomain data");
|
|
1177
|
+
}
|
|
1178
|
+
_cachedStoreDomain = {
|
|
1179
|
+
primaryDomain: response.data.shop.primaryDomain
|
|
1180
|
+
};
|
|
1181
|
+
_domainTimestamp = now;
|
|
1182
|
+
return _cachedStoreDomain;
|
|
1183
|
+
}
|
|
1184
|
+
async function getStoreTaxes() {
|
|
1185
|
+
const now = Date.now();
|
|
1186
|
+
const config = getConfig();
|
|
1187
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1188
|
+
if (_cachedStoreTaxes && now - _taxesTimestamp < ttl) {
|
|
1189
|
+
log.debug("Returning cached store taxes");
|
|
1190
|
+
return _cachedStoreTaxes;
|
|
1191
|
+
}
|
|
1192
|
+
log.debug("Fetching fresh store taxes from Shopify");
|
|
1193
|
+
const client = await getShopifyClient();
|
|
1194
|
+
const response = await withRateLimit(
|
|
1195
|
+
() => client.graphql.request(STORE_TAXES_QUERY),
|
|
1196
|
+
"getStoreTaxes"
|
|
1197
|
+
);
|
|
1198
|
+
if (response.errors && response.errors.length > 0) {
|
|
1199
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1200
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1201
|
+
}
|
|
1202
|
+
if (response.data?.shop === void 0 || response.data?.shop === null) {
|
|
1203
|
+
throw new Error("Invalid response: missing shop data");
|
|
1204
|
+
}
|
|
1205
|
+
_cachedStoreTaxes = {
|
|
1206
|
+
taxesIncluded: response.data.shop.taxesIncluded,
|
|
1207
|
+
taxShipping: response.data.shop.taxShipping
|
|
1208
|
+
};
|
|
1209
|
+
_taxesTimestamp = now;
|
|
1210
|
+
return _cachedStoreTaxes;
|
|
1211
|
+
}
|
|
1212
|
+
async function getStorePolicies() {
|
|
1213
|
+
const now = Date.now();
|
|
1214
|
+
const config = getConfig();
|
|
1215
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS;
|
|
1216
|
+
if (_cachedStorePolicies && now - _policiesTimestamp < ttl) {
|
|
1217
|
+
log.debug("Returning cached store policies");
|
|
1218
|
+
return _cachedStorePolicies;
|
|
1219
|
+
}
|
|
1220
|
+
try {
|
|
1221
|
+
log.debug("Fetching fresh store policies from Shopify");
|
|
1222
|
+
const client = await getShopifyClient();
|
|
1223
|
+
const response = await withRateLimit(
|
|
1224
|
+
() => client.graphql.request(STORE_POLICIES_QUERY),
|
|
1225
|
+
"getStorePolicies"
|
|
1226
|
+
);
|
|
1227
|
+
if (response.errors && response.errors.length > 0) {
|
|
1228
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1229
|
+
if (errorMessages.toLowerCase().includes("access denied") || errorMessages.toLowerCase().includes("unauthorized")) {
|
|
1230
|
+
log.debug("read_legal_policies scope not available, returning empty policies");
|
|
1231
|
+
_cachedStorePolicies = { shopPolicies: [] };
|
|
1232
|
+
_policiesTimestamp = now;
|
|
1233
|
+
return _cachedStorePolicies;
|
|
1234
|
+
}
|
|
1235
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1236
|
+
}
|
|
1237
|
+
const policies = response.data?.shop?.shopPolicies ?? [];
|
|
1238
|
+
_cachedStorePolicies = { shopPolicies: policies };
|
|
1239
|
+
_policiesTimestamp = now;
|
|
1240
|
+
return _cachedStorePolicies;
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (error instanceof Error && (error.message.toLowerCase().includes("access denied") || error.message.toLowerCase().includes("unauthorized"))) {
|
|
1243
|
+
log.debug("read_legal_policies scope not available, returning empty policies");
|
|
1244
|
+
_cachedStorePolicies = { shopPolicies: [] };
|
|
1245
|
+
_policiesTimestamp = now;
|
|
1246
|
+
return _cachedStorePolicies;
|
|
1247
|
+
}
|
|
1248
|
+
throw error;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
async function getStoreAlerts() {
|
|
1252
|
+
const now = Date.now();
|
|
1253
|
+
const ttl = ALERTS_CACHE_TTL_MS;
|
|
1254
|
+
if (_cachedStoreAlerts && now - _alertsTimestamp < ttl) {
|
|
1255
|
+
log.debug("Returning cached store alerts");
|
|
1256
|
+
return _cachedStoreAlerts;
|
|
1257
|
+
}
|
|
1258
|
+
log.debug("Fetching fresh store alerts from Shopify");
|
|
1259
|
+
const client = await getShopifyClient();
|
|
1260
|
+
const response = await withRateLimit(
|
|
1261
|
+
() => client.graphql.request(STORE_ALERTS_QUERY),
|
|
1262
|
+
"getStoreAlerts"
|
|
1263
|
+
);
|
|
1264
|
+
if (response.errors && response.errors.length > 0) {
|
|
1265
|
+
const errorMessages = response.errors.map((e) => e.message).join(", ");
|
|
1266
|
+
throw new Error(`GraphQL errors: ${errorMessages}`);
|
|
1267
|
+
}
|
|
1268
|
+
if (response.data?.shop === void 0 || response.data?.shop === null) {
|
|
1269
|
+
throw new Error("Invalid response: missing shop data");
|
|
1270
|
+
}
|
|
1271
|
+
_cachedStoreAlerts = {
|
|
1272
|
+
alerts: response.data.shop.alerts ?? [],
|
|
1273
|
+
setupRequired: response.data.shop.setupRequired
|
|
1274
|
+
};
|
|
1275
|
+
_alertsTimestamp = now;
|
|
1276
|
+
return _cachedStoreAlerts;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// src/shopify/store-info.ts
|
|
1280
|
+
var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
923
1281
|
var _cachedStoreInfo = null;
|
|
924
1282
|
var _cacheTimestamp = 0;
|
|
925
1283
|
function transformShopResponse(shop) {
|
|
@@ -939,7 +1297,7 @@ function transformShopResponse(shop) {
|
|
|
939
1297
|
async function getStoreInfo() {
|
|
940
1298
|
const now = Date.now();
|
|
941
1299
|
const config = getConfig();
|
|
942
|
-
const ttl = config.STORE_INFO_CACHE_TTL_MS ??
|
|
1300
|
+
const ttl = config.STORE_INFO_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS2;
|
|
943
1301
|
if (_cachedStoreInfo && now - _cacheTimestamp < ttl) {
|
|
944
1302
|
log.debug("Returning cached store info");
|
|
945
1303
|
return _cachedStoreInfo;
|
|
@@ -968,14 +1326,45 @@ var packageJson = require2("../package.json");
|
|
|
968
1326
|
var SERVER_NAME = "shopify-mcp-admin";
|
|
969
1327
|
var SERVER_INSTRUCTIONS = `This MCP server provides access to a Shopify store's Admin API.
|
|
970
1328
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
-
|
|
1329
|
+
## TOOL MODULES (Modular Architecture)
|
|
1330
|
+
|
|
1331
|
+
Tools are organized into modules for optimal AI performance. Start with core tools and load additional modules as needed.
|
|
1332
|
+
|
|
1333
|
+
**Discovery & Loading:**
|
|
1334
|
+
- Use "list-modules" to see all available modules and their loading status
|
|
1335
|
+
- Use "load-module" with module name to enable additional tools
|
|
1336
|
+
- Tool responses may include suggestions for relevant modules to load
|
|
1337
|
+
|
|
1338
|
+
**Available Modules:**
|
|
1339
|
+
| Module | Description | Tools |
|
|
1340
|
+
|--------|-------------|-------|
|
|
1341
|
+
| core | Products, inventory, store info (always loaded) | 15 |
|
|
1342
|
+
| store-context | Extended store configuration (features, currencies, shipping) | 7 |
|
|
1343
|
+
| collections | Collection management and metafields | 10 |
|
|
1344
|
+
| product-extensions | Product metafields, images, and URL redirects | 9 |
|
|
1345
|
+
| content | Pages, blogs, and articles management | 22 |
|
|
1346
|
+
| international | Markets, web presence, and translations | 14 |
|
|
1347
|
+
| advanced-redirects | Bulk redirect operations | 4 |
|
|
1348
|
+
|
|
1349
|
+
**Role Presets (SHOPIFY_MCP_ROLE):**
|
|
1350
|
+
Pre-configured module sets for common workflows:
|
|
1351
|
+
- inventory-manager: Core only (15 tools) - stock management
|
|
1352
|
+
- product-manager: Core + store-context + collections + product-extensions (41 tools)
|
|
1353
|
+
- content-manager: Core + content (37 tools) - pages and blogs
|
|
1354
|
+
- seo-specialist: Core + collections + product-extensions + advanced-redirects (38 tools)
|
|
1355
|
+
- international-manager: Core + store-context + international + collections (46 tools)
|
|
1356
|
+
- full-access: All modules (81 tools) - full store administration
|
|
1357
|
+
|
|
1358
|
+
## KEY CONCEPTS
|
|
1359
|
+
|
|
1360
|
+
- **Products & Variants**: Products contain variants; each variant has its own price, SKU, and inventory
|
|
1361
|
+
- **Multi-location Inventory**: Inventory is tracked per variant AND per location
|
|
1362
|
+
- **Metafields**: Store custom data using namespace/key pairs on products, collections, pages, blogs, and articles
|
|
1363
|
+
- **Collections**: Organize products for navigation and SEO
|
|
1364
|
+
- **GID Format**: All IDs use Shopify GID format: "gid://shopify/Product/123"
|
|
1365
|
+
|
|
1366
|
+
## RESOURCE TYPES & GID FORMATS
|
|
977
1367
|
|
|
978
|
-
RESOURCE TYPES & GID FORMATS:
|
|
979
1368
|
- Products: gid://shopify/Product/{id}
|
|
980
1369
|
- Variants: gid://shopify/ProductVariant/{id}
|
|
981
1370
|
- Collections: gid://shopify/Collection/{id}
|
|
@@ -987,21 +1376,25 @@ RESOURCE TYPES & GID FORMATS:
|
|
|
987
1376
|
- InventoryItems: gid://shopify/InventoryItem/{id}
|
|
988
1377
|
- Locations: gid://shopify/Location/{id}
|
|
989
1378
|
|
|
990
|
-
BEST PRACTICES
|
|
991
|
-
|
|
992
|
-
- Use
|
|
993
|
-
-
|
|
994
|
-
-
|
|
995
|
-
-
|
|
1379
|
+
## BEST PRACTICES
|
|
1380
|
+
|
|
1381
|
+
- **Verify before modifying**: Use get-product to verify IDs before updates
|
|
1382
|
+
- **Proactive inventory**: Use list-low-inventory to identify stock issues
|
|
1383
|
+
- **SEO redirects**: When updating product handles, consider creating URL redirects
|
|
1384
|
+
- **Bulk operations**: Use bulk tools (get-bulk-inventory, bulk-delete-redirects) when available
|
|
1385
|
+
- **Metafields for SEO**: Store custom SEO data (JSON-LD, Open Graph) in metafields
|
|
1386
|
+
|
|
1387
|
+
## RATE LIMITS
|
|
996
1388
|
|
|
997
|
-
RATE LIMITS:
|
|
998
1389
|
- Shopify GraphQL has cost-based rate limiting (~50 points/sec)
|
|
999
1390
|
- Large queries may retry automatically with exponential backoff
|
|
1000
1391
|
- Rate limit errors include helpful retry suggestions
|
|
1001
1392
|
|
|
1002
|
-
WORKFLOW HINTS
|
|
1393
|
+
## WORKFLOW HINTS
|
|
1394
|
+
|
|
1003
1395
|
- Tool descriptions include **Prerequisites:** and **Follow-ups:** for multi-step operations
|
|
1004
|
-
- Check tool category and relationships for semantic grouping
|
|
1396
|
+
- Check tool category and relationships for semantic grouping
|
|
1397
|
+
- After successful operations, note any module suggestions in the response`;
|
|
1005
1398
|
function getServerVersion() {
|
|
1006
1399
|
return packageJson.version;
|
|
1007
1400
|
}
|
|
@@ -1022,8 +1415,12 @@ function createServer() {
|
|
|
1022
1415
|
logging: {}
|
|
1023
1416
|
// Enable logging capability (AC-9.5.1.2)
|
|
1024
1417
|
},
|
|
1025
|
-
instructions: SERVER_INSTRUCTIONS
|
|
1418
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
1026
1419
|
// Provide Shopify context to AI agents (AC-9.5.1.1)
|
|
1420
|
+
// Epic 12: Notification debouncing for module loading (AC-12.1.6)
|
|
1421
|
+
// When multiple tools are enabled in rapid succession (e.g., loading a module),
|
|
1422
|
+
// coalesce into a single notifications/tools/list_changed notification
|
|
1423
|
+
debouncedNotificationMethods: ["notifications/tools/list_changed"]
|
|
1027
1424
|
}
|
|
1028
1425
|
);
|
|
1029
1426
|
return server;
|
|
@@ -1067,71 +1464,375 @@ function registerResources(server) {
|
|
|
1067
1464
|
}
|
|
1068
1465
|
}
|
|
1069
1466
|
);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
...defaultOptions,
|
|
1106
|
-
name: options
|
|
1107
|
-
} : {
|
|
1108
|
-
...defaultOptions,
|
|
1109
|
-
...options
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
// node_modules/zod-to-json-schema/dist/esm/Refs.js
|
|
1113
|
-
var getRefs = (options) => {
|
|
1114
|
-
const _options = getDefaultOptions(options);
|
|
1115
|
-
const currentPath = _options.name !== void 0 ? [..._options.basePath, _options.definitionPath, _options.name] : _options.basePath;
|
|
1116
|
-
return {
|
|
1117
|
-
..._options,
|
|
1118
|
-
flags: { hasReferencedOpenAiAnyType: false },
|
|
1119
|
-
currentPath,
|
|
1120
|
-
propertyPath: void 0,
|
|
1121
|
-
seen: new Map(Object.entries(_options.definitions).map(([name, def]) => [
|
|
1122
|
-
def._def,
|
|
1123
|
-
{
|
|
1124
|
-
def: def._def,
|
|
1125
|
-
path: [..._options.basePath, _options.definitionPath, name],
|
|
1126
|
-
// Resolution of references will be forced even though seen, so it's ok that the schema is undefined here for now.
|
|
1127
|
-
jsonSchema: void 0
|
|
1467
|
+
server.resource(
|
|
1468
|
+
"store-limits",
|
|
1469
|
+
"shopify://store/limits",
|
|
1470
|
+
{
|
|
1471
|
+
description: "Resource limits for the Shopify store including max product variants, max product options, location limit, and redirect limit status. Use this to validate operations against store constraints.",
|
|
1472
|
+
mimeType: "application/json"
|
|
1473
|
+
},
|
|
1474
|
+
async () => {
|
|
1475
|
+
try {
|
|
1476
|
+
const limits = await getStoreLimits();
|
|
1477
|
+
return {
|
|
1478
|
+
contents: [
|
|
1479
|
+
{
|
|
1480
|
+
uri: "shopify://store/limits",
|
|
1481
|
+
mimeType: "application/json",
|
|
1482
|
+
text: JSON.stringify(limits, null, 2)
|
|
1483
|
+
}
|
|
1484
|
+
]
|
|
1485
|
+
};
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
log.error("Failed to fetch store limits", error instanceof Error ? error : void 0);
|
|
1488
|
+
return {
|
|
1489
|
+
contents: [
|
|
1490
|
+
{
|
|
1491
|
+
uri: "shopify://store/limits",
|
|
1492
|
+
mimeType: "application/json",
|
|
1493
|
+
text: JSON.stringify({
|
|
1494
|
+
error: "Failed to fetch store limits",
|
|
1495
|
+
message: sanitizeLogMessage(
|
|
1496
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1497
|
+
)
|
|
1498
|
+
})
|
|
1499
|
+
}
|
|
1500
|
+
]
|
|
1501
|
+
};
|
|
1128
1502
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1505
|
+
server.resource(
|
|
1506
|
+
"store-features",
|
|
1507
|
+
"shopify://store/features",
|
|
1508
|
+
{
|
|
1509
|
+
description: "Feature flags for the Shopify store including gift cards, reports, storefront, bundles eligibility, and subscriptions. Use this to determine available store capabilities.",
|
|
1510
|
+
mimeType: "application/json"
|
|
1511
|
+
},
|
|
1512
|
+
async () => {
|
|
1513
|
+
try {
|
|
1514
|
+
const features = await getStoreFeatures();
|
|
1515
|
+
return {
|
|
1516
|
+
contents: [
|
|
1517
|
+
{
|
|
1518
|
+
uri: "shopify://store/features",
|
|
1519
|
+
mimeType: "application/json",
|
|
1520
|
+
text: JSON.stringify(features, null, 2)
|
|
1521
|
+
}
|
|
1522
|
+
]
|
|
1523
|
+
};
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
log.error("Failed to fetch store features", error instanceof Error ? error : void 0);
|
|
1526
|
+
return {
|
|
1527
|
+
contents: [
|
|
1528
|
+
{
|
|
1529
|
+
uri: "shopify://store/features",
|
|
1530
|
+
mimeType: "application/json",
|
|
1531
|
+
text: JSON.stringify({
|
|
1532
|
+
error: "Failed to fetch store features",
|
|
1533
|
+
message: sanitizeLogMessage(
|
|
1534
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1535
|
+
)
|
|
1536
|
+
})
|
|
1537
|
+
}
|
|
1538
|
+
]
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
server.resource(
|
|
1544
|
+
"store-currencies",
|
|
1545
|
+
"shopify://store/currencies",
|
|
1546
|
+
{
|
|
1547
|
+
description: "Currency configuration for the Shopify store including base currency, enabled presentment currencies, and money format patterns. Use this to understand multi-currency setup.",
|
|
1548
|
+
mimeType: "application/json"
|
|
1549
|
+
},
|
|
1550
|
+
async () => {
|
|
1551
|
+
try {
|
|
1552
|
+
const currencies = await getStoreCurrencies();
|
|
1553
|
+
return {
|
|
1554
|
+
contents: [
|
|
1555
|
+
{
|
|
1556
|
+
uri: "shopify://store/currencies",
|
|
1557
|
+
mimeType: "application/json",
|
|
1558
|
+
text: JSON.stringify(currencies, null, 2)
|
|
1559
|
+
}
|
|
1560
|
+
]
|
|
1561
|
+
};
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
log.error("Failed to fetch store currencies", error instanceof Error ? error : void 0);
|
|
1564
|
+
return {
|
|
1565
|
+
contents: [
|
|
1566
|
+
{
|
|
1567
|
+
uri: "shopify://store/currencies",
|
|
1568
|
+
mimeType: "application/json",
|
|
1569
|
+
text: JSON.stringify({
|
|
1570
|
+
error: "Failed to fetch store currencies",
|
|
1571
|
+
message: sanitizeLogMessage(
|
|
1572
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1573
|
+
)
|
|
1574
|
+
})
|
|
1575
|
+
}
|
|
1576
|
+
]
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1581
|
+
server.resource(
|
|
1582
|
+
"store-shipping",
|
|
1583
|
+
"shopify://store/shipping",
|
|
1584
|
+
{
|
|
1585
|
+
description: "Shipping configuration for the Shopify store including ships-to countries and countries with configured shipping zones. Use this to understand international shipping capabilities.",
|
|
1586
|
+
mimeType: "application/json"
|
|
1587
|
+
},
|
|
1588
|
+
async () => {
|
|
1589
|
+
try {
|
|
1590
|
+
const shipping = await getStoreShipping();
|
|
1591
|
+
return {
|
|
1592
|
+
contents: [
|
|
1593
|
+
{
|
|
1594
|
+
uri: "shopify://store/shipping",
|
|
1595
|
+
mimeType: "application/json",
|
|
1596
|
+
text: JSON.stringify(shipping, null, 2)
|
|
1597
|
+
}
|
|
1598
|
+
]
|
|
1599
|
+
};
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
log.error("Failed to fetch store shipping", error instanceof Error ? error : void 0);
|
|
1602
|
+
return {
|
|
1603
|
+
contents: [
|
|
1604
|
+
{
|
|
1605
|
+
uri: "shopify://store/shipping",
|
|
1606
|
+
mimeType: "application/json",
|
|
1607
|
+
text: JSON.stringify({
|
|
1608
|
+
error: "Failed to fetch store shipping",
|
|
1609
|
+
message: sanitizeLogMessage(
|
|
1610
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1611
|
+
)
|
|
1612
|
+
})
|
|
1613
|
+
}
|
|
1614
|
+
]
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
server.resource(
|
|
1620
|
+
"store-domain",
|
|
1621
|
+
"shopify://store/domain",
|
|
1622
|
+
{
|
|
1623
|
+
description: "Primary domain configuration for the Shopify store including hostname, URL, and SSL status. Use this to construct proper URLs and understand store identity.",
|
|
1624
|
+
mimeType: "application/json"
|
|
1625
|
+
},
|
|
1626
|
+
async () => {
|
|
1627
|
+
try {
|
|
1628
|
+
const domain = await getStoreDomain();
|
|
1629
|
+
return {
|
|
1630
|
+
contents: [
|
|
1631
|
+
{
|
|
1632
|
+
uri: "shopify://store/domain",
|
|
1633
|
+
mimeType: "application/json",
|
|
1634
|
+
text: JSON.stringify(domain, null, 2)
|
|
1635
|
+
}
|
|
1636
|
+
]
|
|
1637
|
+
};
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
log.error("Failed to fetch store domain", error instanceof Error ? error : void 0);
|
|
1640
|
+
return {
|
|
1641
|
+
contents: [
|
|
1642
|
+
{
|
|
1643
|
+
uri: "shopify://store/domain",
|
|
1644
|
+
mimeType: "application/json",
|
|
1645
|
+
text: JSON.stringify({
|
|
1646
|
+
error: "Failed to fetch store domain",
|
|
1647
|
+
message: sanitizeLogMessage(
|
|
1648
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1649
|
+
)
|
|
1650
|
+
})
|
|
1651
|
+
}
|
|
1652
|
+
]
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
);
|
|
1657
|
+
server.resource(
|
|
1658
|
+
"store-taxes",
|
|
1659
|
+
"shopify://store/taxes",
|
|
1660
|
+
{
|
|
1661
|
+
description: "Tax configuration for the Shopify store including whether taxes are included in prices and tax shipping settings. Use this to understand pricing structure for tax calculations.",
|
|
1662
|
+
mimeType: "application/json"
|
|
1663
|
+
},
|
|
1664
|
+
async () => {
|
|
1665
|
+
try {
|
|
1666
|
+
const taxes = await getStoreTaxes();
|
|
1667
|
+
return {
|
|
1668
|
+
contents: [
|
|
1669
|
+
{
|
|
1670
|
+
uri: "shopify://store/taxes",
|
|
1671
|
+
mimeType: "application/json",
|
|
1672
|
+
text: JSON.stringify(taxes, null, 2)
|
|
1673
|
+
}
|
|
1674
|
+
]
|
|
1675
|
+
};
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
log.error("Failed to fetch store taxes", error instanceof Error ? error : void 0);
|
|
1678
|
+
return {
|
|
1679
|
+
contents: [
|
|
1680
|
+
{
|
|
1681
|
+
uri: "shopify://store/taxes",
|
|
1682
|
+
mimeType: "application/json",
|
|
1683
|
+
text: JSON.stringify({
|
|
1684
|
+
error: "Failed to fetch store taxes",
|
|
1685
|
+
message: sanitizeLogMessage(
|
|
1686
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1687
|
+
)
|
|
1688
|
+
})
|
|
1689
|
+
}
|
|
1690
|
+
]
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
);
|
|
1695
|
+
server.resource(
|
|
1696
|
+
"store-policies",
|
|
1697
|
+
"shopify://store/policies",
|
|
1698
|
+
{
|
|
1699
|
+
description: "Legal policies for the Shopify store (privacy policy, terms of service, refund policy). Returns empty array if read_legal_policies scope is not available. Use this to reference store policies in customer communications.",
|
|
1700
|
+
mimeType: "application/json"
|
|
1701
|
+
},
|
|
1702
|
+
async () => {
|
|
1703
|
+
try {
|
|
1704
|
+
const policies = await getStorePolicies();
|
|
1705
|
+
return {
|
|
1706
|
+
contents: [
|
|
1707
|
+
{
|
|
1708
|
+
uri: "shopify://store/policies",
|
|
1709
|
+
mimeType: "application/json",
|
|
1710
|
+
text: JSON.stringify(policies, null, 2)
|
|
1711
|
+
}
|
|
1712
|
+
]
|
|
1713
|
+
};
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
log.error("Failed to fetch store policies", error instanceof Error ? error : void 0);
|
|
1716
|
+
return {
|
|
1717
|
+
contents: [
|
|
1718
|
+
{
|
|
1719
|
+
uri: "shopify://store/policies",
|
|
1720
|
+
mimeType: "application/json",
|
|
1721
|
+
text: JSON.stringify({
|
|
1722
|
+
error: "Failed to fetch store policies",
|
|
1723
|
+
message: sanitizeLogMessage(
|
|
1724
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1725
|
+
)
|
|
1726
|
+
})
|
|
1727
|
+
}
|
|
1728
|
+
]
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
);
|
|
1733
|
+
server.resource(
|
|
1734
|
+
"store-alerts",
|
|
1735
|
+
"shopify://store/alerts",
|
|
1736
|
+
{
|
|
1737
|
+
description: "Active admin alerts and setup requirements for the Shopify store. Use this to proactively identify issues needing merchant attention.",
|
|
1738
|
+
mimeType: "application/json"
|
|
1739
|
+
},
|
|
1740
|
+
async () => {
|
|
1741
|
+
try {
|
|
1742
|
+
const alerts = await getStoreAlerts();
|
|
1743
|
+
return {
|
|
1744
|
+
contents: [
|
|
1745
|
+
{
|
|
1746
|
+
uri: "shopify://store/alerts",
|
|
1747
|
+
mimeType: "application/json",
|
|
1748
|
+
text: JSON.stringify(alerts, null, 2)
|
|
1749
|
+
}
|
|
1750
|
+
]
|
|
1751
|
+
};
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
log.error("Failed to fetch store alerts", error instanceof Error ? error : void 0);
|
|
1754
|
+
return {
|
|
1755
|
+
contents: [
|
|
1756
|
+
{
|
|
1757
|
+
uri: "shopify://store/alerts",
|
|
1758
|
+
mimeType: "application/json",
|
|
1759
|
+
text: JSON.stringify({
|
|
1760
|
+
error: "Failed to fetch store alerts",
|
|
1761
|
+
message: sanitizeLogMessage(
|
|
1762
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1763
|
+
)
|
|
1764
|
+
})
|
|
1765
|
+
}
|
|
1766
|
+
]
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/tools/index.ts
|
|
1774
|
+
import {
|
|
1775
|
+
CallToolRequestSchema,
|
|
1776
|
+
ListToolsRequestSchema
|
|
1777
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1778
|
+
|
|
1779
|
+
// node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
1780
|
+
var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
1781
|
+
var defaultOptions = {
|
|
1782
|
+
name: void 0,
|
|
1783
|
+
$refStrategy: "root",
|
|
1784
|
+
basePath: ["#"],
|
|
1785
|
+
effectStrategy: "input",
|
|
1786
|
+
pipeStrategy: "all",
|
|
1787
|
+
dateStrategy: "format:date-time",
|
|
1788
|
+
mapStrategy: "entries",
|
|
1789
|
+
removeAdditionalStrategy: "passthrough",
|
|
1790
|
+
allowedAdditionalProperties: true,
|
|
1791
|
+
rejectedAdditionalProperties: false,
|
|
1792
|
+
definitionPath: "definitions",
|
|
1793
|
+
target: "jsonSchema7",
|
|
1794
|
+
strictUnions: false,
|
|
1795
|
+
definitions: {},
|
|
1796
|
+
errorMessages: false,
|
|
1797
|
+
markdownDescription: false,
|
|
1798
|
+
patternStrategy: "escape",
|
|
1799
|
+
applyRegexFlags: false,
|
|
1800
|
+
emailStrategy: "format:email",
|
|
1801
|
+
base64Strategy: "contentEncoding:base64",
|
|
1802
|
+
nameStrategy: "ref",
|
|
1803
|
+
openAiAnyTypeName: "OpenAiAnyType"
|
|
1804
|
+
};
|
|
1805
|
+
var getDefaultOptions = (options) => typeof options === "string" ? {
|
|
1806
|
+
...defaultOptions,
|
|
1807
|
+
name: options
|
|
1808
|
+
} : {
|
|
1809
|
+
...defaultOptions,
|
|
1810
|
+
...options
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
// node_modules/zod-to-json-schema/dist/esm/Refs.js
|
|
1814
|
+
var getRefs = (options) => {
|
|
1815
|
+
const _options = getDefaultOptions(options);
|
|
1816
|
+
const currentPath = _options.name !== void 0 ? [..._options.basePath, _options.definitionPath, _options.name] : _options.basePath;
|
|
1817
|
+
return {
|
|
1818
|
+
..._options,
|
|
1819
|
+
flags: { hasReferencedOpenAiAnyType: false },
|
|
1820
|
+
currentPath,
|
|
1821
|
+
propertyPath: void 0,
|
|
1822
|
+
seen: new Map(Object.entries(_options.definitions).map(([name, def]) => [
|
|
1823
|
+
def._def,
|
|
1824
|
+
{
|
|
1825
|
+
def: def._def,
|
|
1826
|
+
path: [..._options.basePath, _options.definitionPath, name],
|
|
1827
|
+
// Resolution of references will be forced even though seen, so it's ok that the schema is undefined here for now.
|
|
1828
|
+
jsonSchema: void 0
|
|
1829
|
+
}
|
|
1830
|
+
]))
|
|
1831
|
+
};
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
// node_modules/zod-to-json-schema/dist/esm/errorMessages.js
|
|
1835
|
+
function addErrorMessage(res, key, errorMessage, refs) {
|
|
1135
1836
|
if (!refs?.errorMessages)
|
|
1136
1837
|
return;
|
|
1137
1838
|
if (errorMessage) {
|
|
@@ -2393,6 +3094,7 @@ function deriveDefaultAnnotations(name) {
|
|
|
2393
3094
|
};
|
|
2394
3095
|
}
|
|
2395
3096
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
3097
|
+
var toolEnabledState = /* @__PURE__ */ new Map();
|
|
2396
3098
|
function validateToolName(name) {
|
|
2397
3099
|
if (!name || name.trim() === "") {
|
|
2398
3100
|
throw new Error("Tool name cannot be empty");
|
|
@@ -2451,7 +3153,7 @@ function wrapToolHandler(toolName, schema, handler) {
|
|
|
2451
3153
|
};
|
|
2452
3154
|
}
|
|
2453
3155
|
function registerTool(definition, handler, options = {}) {
|
|
2454
|
-
const { name, title, description, inputSchema:
|
|
3156
|
+
const { name, title, description, inputSchema: inputSchema82, annotations } = definition;
|
|
2455
3157
|
try {
|
|
2456
3158
|
if (!options.skipNameValidation) {
|
|
2457
3159
|
validateToolName(name);
|
|
@@ -2459,8 +3161,8 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2459
3161
|
if (registeredTools.has(name)) {
|
|
2460
3162
|
throw new Error(`Tool "${name}" is already registered. Tool names must be unique.`);
|
|
2461
3163
|
}
|
|
2462
|
-
const jsonSchema = convertZodToJsonSchema(
|
|
2463
|
-
const wrappedHandler = wrapToolHandler(name,
|
|
3164
|
+
const jsonSchema = convertZodToJsonSchema(inputSchema82);
|
|
3165
|
+
const wrappedHandler = wrapToolHandler(name, inputSchema82, handler);
|
|
2464
3166
|
const finalAnnotations = {
|
|
2465
3167
|
...deriveDefaultAnnotations(name),
|
|
2466
3168
|
...annotations,
|
|
@@ -2472,7 +3174,7 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2472
3174
|
title,
|
|
2473
3175
|
description,
|
|
2474
3176
|
inputSchema: jsonSchema,
|
|
2475
|
-
zodSchema:
|
|
3177
|
+
zodSchema: inputSchema82,
|
|
2476
3178
|
handler: wrappedHandler,
|
|
2477
3179
|
annotations: finalAnnotations
|
|
2478
3180
|
};
|
|
@@ -2490,7 +3192,7 @@ function registerTool(definition, handler, options = {}) {
|
|
|
2490
3192
|
}
|
|
2491
3193
|
}
|
|
2492
3194
|
function getRegisteredTools() {
|
|
2493
|
-
return Array.from(registeredTools.values()).map((tool) => ({
|
|
3195
|
+
return Array.from(registeredTools.values()).filter((tool) => isToolEnabled(tool.name)).map((tool) => ({
|
|
2494
3196
|
name: tool.name,
|
|
2495
3197
|
description: `${tool.title}: ${tool.description}`,
|
|
2496
3198
|
inputSchema: tool.inputSchema,
|
|
@@ -2500,6 +3202,32 @@ function getRegisteredTools() {
|
|
|
2500
3202
|
function getToolByName(name) {
|
|
2501
3203
|
return registeredTools.get(name);
|
|
2502
3204
|
}
|
|
3205
|
+
function getToolNames() {
|
|
3206
|
+
return Array.from(registeredTools.keys());
|
|
3207
|
+
}
|
|
3208
|
+
function enableTool(name) {
|
|
3209
|
+
const tool = registeredTools.get(name);
|
|
3210
|
+
if (!tool) {
|
|
3211
|
+
log.debug(`Cannot enable tool: ${name} not found`);
|
|
3212
|
+
return false;
|
|
3213
|
+
}
|
|
3214
|
+
toolEnabledState.set(name, true);
|
|
3215
|
+
log.debug(`Tool enabled: ${name}`);
|
|
3216
|
+
return true;
|
|
3217
|
+
}
|
|
3218
|
+
function disableTool(name) {
|
|
3219
|
+
const tool = registeredTools.get(name);
|
|
3220
|
+
if (!tool) {
|
|
3221
|
+
log.debug(`Cannot disable tool: ${name} not found`);
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
3224
|
+
toolEnabledState.set(name, false);
|
|
3225
|
+
log.debug(`Tool disabled: ${name}`);
|
|
3226
|
+
return true;
|
|
3227
|
+
}
|
|
3228
|
+
function isToolEnabled(name) {
|
|
3229
|
+
return toolEnabledState.get(name) ?? true;
|
|
3230
|
+
}
|
|
2503
3231
|
function createHandlerWithContext(contextAwareHandler) {
|
|
2504
3232
|
return async (params) => {
|
|
2505
3233
|
const client = await getShopifyClient();
|
|
@@ -12466,143 +13194,995 @@ var handleSetCollectionMetafields = async (context, params) => {
|
|
|
12466
13194
|
function registerSetCollectionMetafieldsTool() {
|
|
12467
13195
|
registerContextAwareTool(
|
|
12468
13196
|
{
|
|
12469
|
-
name: "set-collection-metafields",
|
|
12470
|
-
title: "Set Collection Metafields",
|
|
12471
|
-
description: "Create or update up to 25 metafields on a collection in a single operation. Each metafield requires: namespace (grouping identifier), key (field name), value (the data), and type. Existing metafields with matching namespace+key are updated; new ones are created. Common types: single_line_text_field, multi_line_text_field, number_integer, number_decimal, boolean, json. **Typical workflow:** list-collections \u2192 set-collection-metafields (add custom data). **Prerequisites:** list-collections to obtain collection ID, optionally get-collection-metafields to check existing values. **Follow-ups:** get-collection-metafields to verify changes.",
|
|
12472
|
-
inputSchema: inputSchema69,
|
|
12473
|
-
outputSchema: outputSchema69,
|
|
12474
|
-
// AI Agent Optimization (Story 5.5)
|
|
12475
|
-
category: "collection",
|
|
13197
|
+
name: "set-collection-metafields",
|
|
13198
|
+
title: "Set Collection Metafields",
|
|
13199
|
+
description: "Create or update up to 25 metafields on a collection in a single operation. Each metafield requires: namespace (grouping identifier), key (field name), value (the data), and type. Existing metafields with matching namespace+key are updated; new ones are created. Common types: single_line_text_field, multi_line_text_field, number_integer, number_decimal, boolean, json. **Typical workflow:** list-collections \u2192 set-collection-metafields (add custom data). **Prerequisites:** list-collections to obtain collection ID, optionally get-collection-metafields to check existing values. **Follow-ups:** get-collection-metafields to verify changes.",
|
|
13200
|
+
inputSchema: inputSchema69,
|
|
13201
|
+
outputSchema: outputSchema69,
|
|
13202
|
+
// AI Agent Optimization (Story 5.5)
|
|
13203
|
+
category: "collection",
|
|
13204
|
+
relationships: {
|
|
13205
|
+
relatedTools: [
|
|
13206
|
+
"list-collections",
|
|
13207
|
+
"get-collection-metafields",
|
|
13208
|
+
"delete-collection-metafields"
|
|
13209
|
+
],
|
|
13210
|
+
prerequisites: ["list-collections"],
|
|
13211
|
+
followUps: ["get-collection-metafields"]
|
|
13212
|
+
},
|
|
13213
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices) - AC-10.6.6
|
|
13214
|
+
annotations: {
|
|
13215
|
+
readOnlyHint: false,
|
|
13216
|
+
destructiveHint: false,
|
|
13217
|
+
idempotentHint: true,
|
|
13218
|
+
openWorldHint: true
|
|
13219
|
+
}
|
|
13220
|
+
},
|
|
13221
|
+
handleSetCollectionMetafields
|
|
13222
|
+
);
|
|
13223
|
+
}
|
|
13224
|
+
|
|
13225
|
+
// src/tools/submit-redirect-import.ts
|
|
13226
|
+
import { z as z72 } from "zod";
|
|
13227
|
+
var inputSchema70 = z72.object({
|
|
13228
|
+
id: z72.string().min(1).describe(
|
|
13229
|
+
'Import ID from import-redirects tool (GID format, e.g., "gid://shopify/UrlRedirectImport/123"). This is required to submit the import job for processing.'
|
|
13230
|
+
)
|
|
13231
|
+
});
|
|
13232
|
+
var outputSchema70 = z72.object({
|
|
13233
|
+
id: z72.string().describe("Job GID for tracking the import operation"),
|
|
13234
|
+
done: z72.boolean().describe("Whether the job has completed. May be false if still processing.")
|
|
13235
|
+
});
|
|
13236
|
+
var handleSubmitRedirectImport = async (context, params) => {
|
|
13237
|
+
log.debug(`Submitting redirect import on shop: ${context.shopDomain}, id: ${params.id}`);
|
|
13238
|
+
if (!params.id || params.id.trim() === "") {
|
|
13239
|
+
throw new ToolError(
|
|
13240
|
+
"Import ID is required.",
|
|
13241
|
+
"Use import-redirects first to create an import job and get its ID."
|
|
13242
|
+
);
|
|
13243
|
+
}
|
|
13244
|
+
try {
|
|
13245
|
+
const job = await submitRedirectImport(params.id);
|
|
13246
|
+
log.debug(`Redirect import job submitted: ${job.id}, done: ${job.done}`);
|
|
13247
|
+
return job;
|
|
13248
|
+
} catch (error) {
|
|
13249
|
+
if (error instanceof Error && error.message.includes("not found")) {
|
|
13250
|
+
throw new ToolError(
|
|
13251
|
+
`Import not found (ID: ${params.id})`,
|
|
13252
|
+
"Verify the import ID is correct. Use import-redirects to create a new import if needed."
|
|
13253
|
+
);
|
|
13254
|
+
}
|
|
13255
|
+
if (error instanceof Error && error.message.includes("already submitted")) {
|
|
13256
|
+
throw new ToolError(
|
|
13257
|
+
"This import has already been submitted.",
|
|
13258
|
+
"Each import can only be submitted once. Create a new import with import-redirects if needed."
|
|
13259
|
+
);
|
|
13260
|
+
}
|
|
13261
|
+
throw error;
|
|
13262
|
+
}
|
|
13263
|
+
};
|
|
13264
|
+
function registerSubmitRedirectImportTool() {
|
|
13265
|
+
registerContextAwareTool(
|
|
13266
|
+
{
|
|
13267
|
+
name: "submit-redirect-import",
|
|
13268
|
+
title: "Submit Redirect Import",
|
|
13269
|
+
description: `Submit a redirect import job for processing. This is the second step in a two-step import workflow. Requires an import ID from the import-redirects tool. The import runs asynchronously in Shopify's background. The job may not be complete when the response returns - check the "done" field to see if processing has finished. **Typical workflow:** import-redirects (create & preview) \u2192 submit-redirect-import (execute). **Prerequisites:** import-redirects to create the import job and get its ID. **Note:** Each import can only be submitted once.`,
|
|
13270
|
+
inputSchema: inputSchema70,
|
|
13271
|
+
outputSchema: outputSchema70,
|
|
13272
|
+
// AI Agent Optimization (Story 5.5)
|
|
13273
|
+
category: "seo",
|
|
13274
|
+
relationships: {
|
|
13275
|
+
relatedTools: ["import-redirects", "list-redirects"],
|
|
13276
|
+
prerequisites: ["import-redirects"],
|
|
13277
|
+
followUps: ["list-redirects"]
|
|
13278
|
+
},
|
|
13279
|
+
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices) - AC-10.6.6
|
|
13280
|
+
annotations: {
|
|
13281
|
+
readOnlyHint: false,
|
|
13282
|
+
destructiveHint: false,
|
|
13283
|
+
idempotentHint: false,
|
|
13284
|
+
openWorldHint: true
|
|
13285
|
+
}
|
|
13286
|
+
},
|
|
13287
|
+
handleSubmitRedirectImport
|
|
13288
|
+
);
|
|
13289
|
+
}
|
|
13290
|
+
|
|
13291
|
+
// src/tools/get-store-alerts.ts
|
|
13292
|
+
import { z as z73 } from "zod";
|
|
13293
|
+
var inputSchema71 = z73.object({});
|
|
13294
|
+
var outputSchema71 = z73.object({
|
|
13295
|
+
alerts: z73.array(
|
|
13296
|
+
z73.object({
|
|
13297
|
+
action: z73.object({
|
|
13298
|
+
title: z73.string().describe('The text for the button in the alert (e.g., "Add credit card")'),
|
|
13299
|
+
url: z73.string().describe("The target URL that the button links to")
|
|
13300
|
+
}).describe("Action to resolve the alert"),
|
|
13301
|
+
description: z73.string().describe("Human-readable alert description")
|
|
13302
|
+
})
|
|
13303
|
+
).describe("Array of active admin alerts"),
|
|
13304
|
+
setupRequired: z73.boolean().describe("Whether store setup is incomplete")
|
|
13305
|
+
});
|
|
13306
|
+
async function handleGetStoreAlerts(context, _params) {
|
|
13307
|
+
log.debug(`Getting store alerts for shop: ${context.shopDomain}`);
|
|
13308
|
+
return await getStoreAlerts();
|
|
13309
|
+
}
|
|
13310
|
+
function registerGetStoreAlertsTool() {
|
|
13311
|
+
registerContextAwareTool(
|
|
13312
|
+
{
|
|
13313
|
+
name: "get-store-alerts",
|
|
13314
|
+
title: "Get Store Alerts",
|
|
13315
|
+
description: "Get active admin alerts and setup requirements for the connected Shopify store. **Prerequisites:** None. **Use this** to proactively identify issues needing merchant attention.",
|
|
13316
|
+
inputSchema: inputSchema71,
|
|
13317
|
+
outputSchema: outputSchema71,
|
|
13318
|
+
category: "store",
|
|
13319
|
+
relationships: {
|
|
13320
|
+
relatedTools: ["get-store-info", "list-low-inventory"],
|
|
13321
|
+
followUps: []
|
|
13322
|
+
},
|
|
13323
|
+
annotations: {
|
|
13324
|
+
readOnlyHint: true,
|
|
13325
|
+
destructiveHint: false,
|
|
13326
|
+
idempotentHint: true,
|
|
13327
|
+
openWorldHint: true
|
|
13328
|
+
}
|
|
13329
|
+
},
|
|
13330
|
+
handleGetStoreAlerts
|
|
13331
|
+
);
|
|
13332
|
+
}
|
|
13333
|
+
|
|
13334
|
+
// src/tools/get-store-currencies.ts
|
|
13335
|
+
import { z as z74 } from "zod";
|
|
13336
|
+
var inputSchema72 = z74.object({});
|
|
13337
|
+
var outputSchema72 = z74.object({
|
|
13338
|
+
currencyCode: z74.string().describe("Base currency code (e.g., USD)"),
|
|
13339
|
+
enabledPresentmentCurrencies: z74.array(z74.string()).describe("Enabled multi-currency codes"),
|
|
13340
|
+
currencyFormats: z74.object({
|
|
13341
|
+
moneyFormat: z74.string().describe("Money format pattern (e.g., ${{amount}})"),
|
|
13342
|
+
moneyWithCurrencyFormat: z74.string().describe("Money with currency format (e.g., ${{amount}} USD)")
|
|
13343
|
+
}).describe("Currency format patterns")
|
|
13344
|
+
});
|
|
13345
|
+
async function handleGetStoreCurrencies(context, _params) {
|
|
13346
|
+
log.debug(`Getting store currencies for shop: ${context.shopDomain}`);
|
|
13347
|
+
return await getStoreCurrencies();
|
|
13348
|
+
}
|
|
13349
|
+
function registerGetStoreCurrenciesTool() {
|
|
13350
|
+
registerContextAwareTool(
|
|
13351
|
+
{
|
|
13352
|
+
name: "get-store-currencies",
|
|
13353
|
+
title: "Get Store Currencies",
|
|
13354
|
+
description: "Get currency configuration for the connected Shopify store including base currency, enabled presentment currencies, and money format patterns. **Prerequisites:** None. **Use this** to understand multi-currency setup.",
|
|
13355
|
+
inputSchema: inputSchema72,
|
|
13356
|
+
outputSchema: outputSchema72,
|
|
13357
|
+
category: "store",
|
|
13358
|
+
relationships: {
|
|
13359
|
+
relatedTools: ["get-store-info", "get-store-limits"],
|
|
13360
|
+
followUps: ["list-markets", "list-products"]
|
|
13361
|
+
},
|
|
13362
|
+
annotations: {
|
|
13363
|
+
readOnlyHint: true,
|
|
13364
|
+
destructiveHint: false,
|
|
13365
|
+
idempotentHint: true,
|
|
13366
|
+
openWorldHint: true
|
|
13367
|
+
}
|
|
13368
|
+
},
|
|
13369
|
+
handleGetStoreCurrencies
|
|
13370
|
+
);
|
|
13371
|
+
}
|
|
13372
|
+
|
|
13373
|
+
// src/tools/get-store-domain.ts
|
|
13374
|
+
import { z as z75 } from "zod";
|
|
13375
|
+
var inputSchema73 = z75.object({});
|
|
13376
|
+
var outputSchema73 = z75.object({
|
|
13377
|
+
primaryDomain: z75.object({
|
|
13378
|
+
host: z75.string().describe('Domain hostname (e.g., "mystore.com")'),
|
|
13379
|
+
url: z75.string().describe('Full domain URL (e.g., "https://mystore.com")'),
|
|
13380
|
+
sslEnabled: z75.boolean().describe("Whether SSL is enabled")
|
|
13381
|
+
})
|
|
13382
|
+
});
|
|
13383
|
+
async function handleGetStoreDomain(context, _params) {
|
|
13384
|
+
log.debug(`Getting store domain for shop: ${context.shopDomain}`);
|
|
13385
|
+
return await getStoreDomain();
|
|
13386
|
+
}
|
|
13387
|
+
function registerGetStoreDomainTool() {
|
|
13388
|
+
registerContextAwareTool(
|
|
13389
|
+
{
|
|
13390
|
+
name: "get-store-domain",
|
|
13391
|
+
title: "Get Store Domain",
|
|
13392
|
+
description: "Get primary domain configuration for the connected Shopify store including hostname, URL, and SSL status. **Prerequisites:** None. **Use this** to construct proper URLs and understand store identity.",
|
|
13393
|
+
inputSchema: inputSchema73,
|
|
13394
|
+
outputSchema: outputSchema73,
|
|
13395
|
+
category: "store",
|
|
13396
|
+
relationships: {
|
|
13397
|
+
relatedTools: ["get-store-info", "get-store-shipping"],
|
|
13398
|
+
followUps: ["create-redirect"]
|
|
13399
|
+
},
|
|
13400
|
+
annotations: {
|
|
13401
|
+
readOnlyHint: true,
|
|
13402
|
+
destructiveHint: false,
|
|
13403
|
+
idempotentHint: true,
|
|
13404
|
+
openWorldHint: true
|
|
13405
|
+
}
|
|
13406
|
+
},
|
|
13407
|
+
handleGetStoreDomain
|
|
13408
|
+
);
|
|
13409
|
+
}
|
|
13410
|
+
|
|
13411
|
+
// src/tools/get-store-features.ts
|
|
13412
|
+
import { z as z76 } from "zod";
|
|
13413
|
+
var inputSchema74 = z76.object({});
|
|
13414
|
+
var outputSchema74 = z76.object({
|
|
13415
|
+
giftCards: z76.boolean().describe("Gift cards enabled"),
|
|
13416
|
+
reports: z76.boolean().describe("Reports enabled"),
|
|
13417
|
+
storefront: z76.boolean().describe("Storefront enabled"),
|
|
13418
|
+
bundles: z76.object({
|
|
13419
|
+
eligibleForBundles: z76.boolean().describe("Whether store is eligible for bundles")
|
|
13420
|
+
}).describe("Bundles feature configuration"),
|
|
13421
|
+
sellsSubscriptions: z76.boolean().describe("Subscriptions enabled")
|
|
13422
|
+
});
|
|
13423
|
+
async function handleGetStoreFeatures(context, _params) {
|
|
13424
|
+
log.debug(`Getting store features for shop: ${context.shopDomain}`);
|
|
13425
|
+
return await getStoreFeatures();
|
|
13426
|
+
}
|
|
13427
|
+
function registerGetStoreFeaturesTool() {
|
|
13428
|
+
registerContextAwareTool(
|
|
13429
|
+
{
|
|
13430
|
+
name: "get-store-features",
|
|
13431
|
+
title: "Get Store Features",
|
|
13432
|
+
description: "Get feature flags for the connected Shopify store including gift cards, reports, storefront, bundles eligibility, and subscriptions. **Prerequisites:** None. **Use this** to determine available store capabilities.",
|
|
13433
|
+
inputSchema: inputSchema74,
|
|
13434
|
+
outputSchema: outputSchema74,
|
|
13435
|
+
category: "store",
|
|
13436
|
+
relationships: {
|
|
13437
|
+
relatedTools: ["get-store-info", "get-store-limits"],
|
|
13438
|
+
followUps: ["list-products", "list-collections"]
|
|
13439
|
+
},
|
|
13440
|
+
annotations: {
|
|
13441
|
+
readOnlyHint: true,
|
|
13442
|
+
destructiveHint: false,
|
|
13443
|
+
idempotentHint: true,
|
|
13444
|
+
openWorldHint: true
|
|
13445
|
+
}
|
|
13446
|
+
},
|
|
13447
|
+
handleGetStoreFeatures
|
|
13448
|
+
);
|
|
13449
|
+
}
|
|
13450
|
+
|
|
13451
|
+
// src/tools/get-store-info.ts
|
|
13452
|
+
import { z as z77 } from "zod";
|
|
13453
|
+
var inputSchema75 = z77.object({});
|
|
13454
|
+
var outputSchema75 = z77.object({
|
|
13455
|
+
name: z77.string().describe("Store name"),
|
|
13456
|
+
domain: z77.string().describe("Store domain (without .myshopify.com)"),
|
|
13457
|
+
myshopifyDomain: z77.string().describe("Full myshopify.com domain"),
|
|
13458
|
+
plan: z77.object({
|
|
13459
|
+
displayName: z77.string().describe("Current plan name"),
|
|
13460
|
+
partnerDevelopment: z77.boolean().describe("Whether partner development store")
|
|
13461
|
+
}).describe("Store plan details"),
|
|
13462
|
+
currencyCode: z77.string().describe("Store currency code (e.g., USD, EUR)"),
|
|
13463
|
+
timezone: z77.string().describe("Store IANA timezone"),
|
|
13464
|
+
contactEmail: z77.string().describe("Store contact email")
|
|
13465
|
+
});
|
|
13466
|
+
async function handleGetStoreInfo(context, _params) {
|
|
13467
|
+
log.debug(`Getting store info for shop: ${context.shopDomain}`);
|
|
13468
|
+
return await getStoreInfo();
|
|
13469
|
+
}
|
|
13470
|
+
function registerGetStoreInfoTool() {
|
|
13471
|
+
registerContextAwareTool(
|
|
13472
|
+
{
|
|
13473
|
+
name: "get-store-info",
|
|
13474
|
+
title: "Get Store Info",
|
|
13475
|
+
description: "Get basic information about the connected Shopify store including name, domain, plan, currency, timezone, and contact email. **Prerequisites:** None. **Use this** to understand store context before performing operations.",
|
|
13476
|
+
inputSchema: inputSchema75,
|
|
13477
|
+
outputSchema: outputSchema75,
|
|
13478
|
+
category: "store",
|
|
13479
|
+
relationships: {
|
|
13480
|
+
relatedTools: [],
|
|
13481
|
+
followUps: ["list-products", "list-collections"]
|
|
13482
|
+
},
|
|
13483
|
+
annotations: {
|
|
13484
|
+
readOnlyHint: true,
|
|
13485
|
+
destructiveHint: false,
|
|
13486
|
+
idempotentHint: true,
|
|
13487
|
+
openWorldHint: true
|
|
13488
|
+
}
|
|
13489
|
+
},
|
|
13490
|
+
handleGetStoreInfo
|
|
13491
|
+
);
|
|
13492
|
+
}
|
|
13493
|
+
|
|
13494
|
+
// src/tools/get-store-limits.ts
|
|
13495
|
+
import { z as z78 } from "zod";
|
|
13496
|
+
var inputSchema76 = z78.object({});
|
|
13497
|
+
var outputSchema76 = z78.object({
|
|
13498
|
+
maxProductVariants: z78.number().describe("Maximum variants per product"),
|
|
13499
|
+
maxProductOptions: z78.number().describe("Maximum product options"),
|
|
13500
|
+
locationLimit: z78.number().describe("Maximum locations"),
|
|
13501
|
+
redirectLimitReached: z78.boolean().describe("Whether redirect limit is reached")
|
|
13502
|
+
});
|
|
13503
|
+
async function handleGetStoreLimits(context, _params) {
|
|
13504
|
+
log.debug(`Getting store limits for shop: ${context.shopDomain}`);
|
|
13505
|
+
return await getStoreLimits();
|
|
13506
|
+
}
|
|
13507
|
+
function registerGetStoreLimitsTool() {
|
|
13508
|
+
registerContextAwareTool(
|
|
13509
|
+
{
|
|
13510
|
+
name: "get-store-limits",
|
|
13511
|
+
title: "Get Store Limits",
|
|
13512
|
+
description: "Get resource limits for the connected Shopify store including max product variants, max product options, location limit, and redirect limit status. **Prerequisites:** None. **Use this** to validate operations against store constraints.",
|
|
13513
|
+
inputSchema: inputSchema76,
|
|
13514
|
+
outputSchema: outputSchema76,
|
|
13515
|
+
category: "store",
|
|
12476
13516
|
relationships: {
|
|
12477
|
-
relatedTools: [
|
|
12478
|
-
|
|
12479
|
-
"get-collection-metafields",
|
|
12480
|
-
"delete-collection-metafields"
|
|
12481
|
-
],
|
|
12482
|
-
prerequisites: ["list-collections"],
|
|
12483
|
-
followUps: ["get-collection-metafields"]
|
|
13517
|
+
relatedTools: ["get-store-info", "get-store-features"],
|
|
13518
|
+
followUps: ["list-products", "create-product"]
|
|
12484
13519
|
},
|
|
12485
|
-
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices) - AC-10.6.6
|
|
12486
13520
|
annotations: {
|
|
12487
|
-
readOnlyHint:
|
|
13521
|
+
readOnlyHint: true,
|
|
12488
13522
|
destructiveHint: false,
|
|
12489
13523
|
idempotentHint: true,
|
|
12490
13524
|
openWorldHint: true
|
|
12491
13525
|
}
|
|
12492
13526
|
},
|
|
12493
|
-
|
|
13527
|
+
handleGetStoreLimits
|
|
12494
13528
|
);
|
|
12495
13529
|
}
|
|
12496
13530
|
|
|
12497
|
-
// src/tools/
|
|
12498
|
-
import { z as
|
|
12499
|
-
var
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
13531
|
+
// src/tools/get-store-policies.ts
|
|
13532
|
+
import { z as z79 } from "zod";
|
|
13533
|
+
var inputSchema77 = z79.object({});
|
|
13534
|
+
var outputSchema77 = z79.object({
|
|
13535
|
+
shopPolicies: z79.array(
|
|
13536
|
+
z79.object({
|
|
13537
|
+
title: z79.string().describe('Policy title (e.g., "Privacy Policy")'),
|
|
13538
|
+
type: z79.string().describe('Policy type (e.g., "PRIVACY_POLICY")'),
|
|
13539
|
+
url: z79.string().describe("Full URL to the policy page")
|
|
13540
|
+
})
|
|
13541
|
+
).describe("Array of store policy items (empty if scope unavailable)")
|
|
12503
13542
|
});
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
13543
|
+
async function handleGetStorePolicies(context, _params) {
|
|
13544
|
+
log.debug(`Getting store policies for shop: ${context.shopDomain}`);
|
|
13545
|
+
return await getStorePolicies();
|
|
13546
|
+
}
|
|
13547
|
+
function registerGetStorePoliciesTool() {
|
|
13548
|
+
registerContextAwareTool(
|
|
13549
|
+
{
|
|
13550
|
+
name: "get-store-policies",
|
|
13551
|
+
title: "Get Store Policies",
|
|
13552
|
+
description: "Get legal policies for the connected Shopify store (privacy policy, terms of service, refund policy). Returns empty array if read_legal_policies scope is not available. **Prerequisites:** None. **Use this** to reference store policies in customer communications.",
|
|
13553
|
+
inputSchema: inputSchema77,
|
|
13554
|
+
outputSchema: outputSchema77,
|
|
13555
|
+
category: "store",
|
|
13556
|
+
relationships: {
|
|
13557
|
+
relatedTools: ["get-store-info", "get-store-domain"],
|
|
13558
|
+
followUps: []
|
|
13559
|
+
},
|
|
13560
|
+
annotations: {
|
|
13561
|
+
readOnlyHint: true,
|
|
13562
|
+
destructiveHint: false,
|
|
13563
|
+
idempotentHint: true,
|
|
13564
|
+
openWorldHint: true
|
|
13565
|
+
}
|
|
13566
|
+
},
|
|
13567
|
+
handleGetStorePolicies
|
|
13568
|
+
);
|
|
13569
|
+
}
|
|
13570
|
+
|
|
13571
|
+
// src/tools/get-store-shipping.ts
|
|
13572
|
+
import { z as z80 } from "zod";
|
|
13573
|
+
var inputSchema78 = z80.object({});
|
|
13574
|
+
var outputSchema78 = z80.object({
|
|
13575
|
+
shipsToCountries: z80.array(z80.string()).describe("Countries the store ships to"),
|
|
13576
|
+
countriesInShippingZones: z80.object({
|
|
13577
|
+
countryCodes: z80.array(z80.string()).describe("Countries with configured shipping zones")
|
|
13578
|
+
})
|
|
12507
13579
|
});
|
|
12508
|
-
|
|
12509
|
-
log.debug(`
|
|
12510
|
-
|
|
13580
|
+
async function handleGetStoreShipping(context, _params) {
|
|
13581
|
+
log.debug(`Getting store shipping for shop: ${context.shopDomain}`);
|
|
13582
|
+
return await getStoreShipping();
|
|
13583
|
+
}
|
|
13584
|
+
function registerGetStoreShippingTool() {
|
|
13585
|
+
registerContextAwareTool(
|
|
13586
|
+
{
|
|
13587
|
+
name: "get-store-shipping",
|
|
13588
|
+
title: "Get Store Shipping",
|
|
13589
|
+
description: "Get shipping configuration for the connected Shopify store including ships-to countries and countries with configured shipping zones. **Prerequisites:** None. **Use this** to understand international shipping capabilities.",
|
|
13590
|
+
inputSchema: inputSchema78,
|
|
13591
|
+
outputSchema: outputSchema78,
|
|
13592
|
+
category: "store",
|
|
13593
|
+
relationships: {
|
|
13594
|
+
relatedTools: ["get-store-info", "get-store-domain", "list-markets"],
|
|
13595
|
+
followUps: ["get-store-domain"]
|
|
13596
|
+
},
|
|
13597
|
+
annotations: {
|
|
13598
|
+
readOnlyHint: true,
|
|
13599
|
+
destructiveHint: false,
|
|
13600
|
+
idempotentHint: true,
|
|
13601
|
+
openWorldHint: true
|
|
13602
|
+
}
|
|
13603
|
+
},
|
|
13604
|
+
handleGetStoreShipping
|
|
13605
|
+
);
|
|
13606
|
+
}
|
|
13607
|
+
|
|
13608
|
+
// src/tools/get-store-taxes.ts
|
|
13609
|
+
import { z as z81 } from "zod";
|
|
13610
|
+
var inputSchema79 = z81.object({});
|
|
13611
|
+
var outputSchema79 = z81.object({
|
|
13612
|
+
taxesIncluded: z81.boolean().describe("Whether taxes are included in product prices"),
|
|
13613
|
+
taxShipping: z81.boolean().describe("Whether shipping is taxed")
|
|
13614
|
+
});
|
|
13615
|
+
async function handleGetStoreTaxes(context, _params) {
|
|
13616
|
+
log.debug(`Getting store taxes for shop: ${context.shopDomain}`);
|
|
13617
|
+
return await getStoreTaxes();
|
|
13618
|
+
}
|
|
13619
|
+
function registerGetStoreTaxesTool() {
|
|
13620
|
+
registerContextAwareTool(
|
|
13621
|
+
{
|
|
13622
|
+
name: "get-store-taxes",
|
|
13623
|
+
title: "Get Store Taxes",
|
|
13624
|
+
description: "Get tax configuration for the connected Shopify store including whether taxes are included in prices and tax shipping settings. **Prerequisites:** None. **Use this** to understand pricing structure for tax calculations.",
|
|
13625
|
+
inputSchema: inputSchema79,
|
|
13626
|
+
outputSchema: outputSchema79,
|
|
13627
|
+
category: "store",
|
|
13628
|
+
relationships: {
|
|
13629
|
+
relatedTools: ["get-store-info", "get-store-currencies"],
|
|
13630
|
+
followUps: []
|
|
13631
|
+
},
|
|
13632
|
+
annotations: {
|
|
13633
|
+
readOnlyHint: true,
|
|
13634
|
+
destructiveHint: false,
|
|
13635
|
+
idempotentHint: true,
|
|
13636
|
+
openWorldHint: true
|
|
13637
|
+
}
|
|
13638
|
+
},
|
|
13639
|
+
handleGetStoreTaxes
|
|
13640
|
+
);
|
|
13641
|
+
}
|
|
13642
|
+
|
|
13643
|
+
// src/tools/list-modules.ts
|
|
13644
|
+
import { z as z83 } from "zod";
|
|
13645
|
+
|
|
13646
|
+
// src/tools/modules/registry.ts
|
|
13647
|
+
var CORE_MODULE = {
|
|
13648
|
+
name: "core",
|
|
13649
|
+
description: "Essential product and inventory management tools. Includes product CRUD, variants, basic images, inventory operations, and store info. Always loaded at startup.",
|
|
13650
|
+
alwaysLoaded: true,
|
|
13651
|
+
dependencies: [],
|
|
13652
|
+
tools: [
|
|
13653
|
+
// Products (7)
|
|
13654
|
+
"create-product",
|
|
13655
|
+
"get-product",
|
|
13656
|
+
"update-product",
|
|
13657
|
+
"delete-product",
|
|
13658
|
+
"list-products",
|
|
13659
|
+
"update-product-variant",
|
|
13660
|
+
"add-product-image",
|
|
13661
|
+
// Inventory (4)
|
|
13662
|
+
"get-inventory",
|
|
13663
|
+
"update-inventory",
|
|
13664
|
+
"list-low-inventory",
|
|
13665
|
+
"get-bulk-inventory",
|
|
13666
|
+
// Store Info (2)
|
|
13667
|
+
"get-store-info",
|
|
13668
|
+
"get-store-limits",
|
|
13669
|
+
// Meta Tools (2) - Added by this story
|
|
13670
|
+
"list-modules",
|
|
13671
|
+
"load-module"
|
|
13672
|
+
]
|
|
13673
|
+
};
|
|
13674
|
+
var STORE_CONTEXT_MODULE = {
|
|
13675
|
+
name: "store-context",
|
|
13676
|
+
description: "Extended store configuration and context tools. Includes features, currencies, shipping countries, domain, taxes, policies, and alerts. Use for comprehensive store insights.",
|
|
13677
|
+
alwaysLoaded: false,
|
|
13678
|
+
dependencies: [],
|
|
13679
|
+
tools: [
|
|
13680
|
+
"get-store-features",
|
|
13681
|
+
"get-store-currencies",
|
|
13682
|
+
"get-store-shipping",
|
|
13683
|
+
"get-store-domain",
|
|
13684
|
+
"get-store-taxes",
|
|
13685
|
+
"get-store-policies",
|
|
13686
|
+
"get-store-alerts"
|
|
13687
|
+
]
|
|
13688
|
+
};
|
|
13689
|
+
var COLLECTIONS_MODULE = {
|
|
13690
|
+
name: "collections",
|
|
13691
|
+
description: "Product collection management tools. Includes collection CRUD, adding/removing products, and collection metafields. Use for organizing products and SEO.",
|
|
13692
|
+
alwaysLoaded: false,
|
|
13693
|
+
dependencies: [],
|
|
13694
|
+
tools: [
|
|
13695
|
+
// CRUD (7)
|
|
13696
|
+
"list-collections",
|
|
13697
|
+
"get-collection",
|
|
13698
|
+
"create-collection",
|
|
13699
|
+
"update-collection",
|
|
13700
|
+
"delete-collection",
|
|
13701
|
+
"add-products-to-collection",
|
|
13702
|
+
"remove-products-from-collection",
|
|
13703
|
+
// Metafields (3)
|
|
13704
|
+
"get-collection-metafields",
|
|
13705
|
+
"set-collection-metafields",
|
|
13706
|
+
"delete-collection-metafields"
|
|
13707
|
+
]
|
|
13708
|
+
};
|
|
13709
|
+
var PRODUCT_EXTENSIONS_MODULE = {
|
|
13710
|
+
name: "product-extensions",
|
|
13711
|
+
description: "Extended product management tools. Includes product metafields, advanced image operations (update, delete, reorder), and URL redirects for SEO.",
|
|
13712
|
+
alwaysLoaded: false,
|
|
13713
|
+
dependencies: [],
|
|
13714
|
+
tools: [
|
|
13715
|
+
// Metafields (3)
|
|
13716
|
+
"get-product-metafields",
|
|
13717
|
+
"set-product-metafields",
|
|
13718
|
+
"delete-product-metafields",
|
|
13719
|
+
// Images (3)
|
|
13720
|
+
"update-product-image",
|
|
13721
|
+
"delete-product-image",
|
|
13722
|
+
"reorder-product-images",
|
|
13723
|
+
// Redirects (3)
|
|
13724
|
+
"create-redirect",
|
|
13725
|
+
"list-redirects",
|
|
13726
|
+
"delete-redirect"
|
|
13727
|
+
]
|
|
13728
|
+
};
|
|
13729
|
+
var CONTENT_MODULE = {
|
|
13730
|
+
name: "content",
|
|
13731
|
+
description: "Content management tools for pages, blogs, and articles. Includes CRUD operations and metafields for all content types. Use for content marketing and SEO.",
|
|
13732
|
+
alwaysLoaded: false,
|
|
13733
|
+
dependencies: [],
|
|
13734
|
+
tools: [
|
|
13735
|
+
// Pages (8)
|
|
13736
|
+
"list-pages",
|
|
13737
|
+
"get-page",
|
|
13738
|
+
"create-page",
|
|
13739
|
+
"update-page",
|
|
13740
|
+
"delete-page",
|
|
13741
|
+
"get-page-metafields",
|
|
13742
|
+
"set-page-metafields",
|
|
13743
|
+
"delete-page-metafields",
|
|
13744
|
+
// Blogs (7)
|
|
13745
|
+
"list-blogs",
|
|
13746
|
+
"create-blog",
|
|
13747
|
+
"update-blog",
|
|
13748
|
+
"delete-blog",
|
|
13749
|
+
"get-blog-metafields",
|
|
13750
|
+
"set-blog-metafields",
|
|
13751
|
+
"delete-blog-metafields",
|
|
13752
|
+
// Articles (7)
|
|
13753
|
+
"list-articles",
|
|
13754
|
+
"create-article",
|
|
13755
|
+
"update-article",
|
|
13756
|
+
"delete-article",
|
|
13757
|
+
"get-article-metafields",
|
|
13758
|
+
"set-article-metafields",
|
|
13759
|
+
"delete-article-metafields"
|
|
13760
|
+
]
|
|
13761
|
+
};
|
|
13762
|
+
var INTERNATIONAL_MODULE = {
|
|
13763
|
+
name: "international",
|
|
13764
|
+
description: "International commerce and localization tools. Includes markets management, web presence configuration, and shop locale translations. Use for multi-region and multi-language stores.",
|
|
13765
|
+
alwaysLoaded: false,
|
|
13766
|
+
dependencies: [],
|
|
13767
|
+
tools: [
|
|
13768
|
+
// Markets (5)
|
|
13769
|
+
"list-markets",
|
|
13770
|
+
"get-market",
|
|
13771
|
+
"create-market",
|
|
13772
|
+
"update-market",
|
|
13773
|
+
"delete-market",
|
|
13774
|
+
// Web Presence (4)
|
|
13775
|
+
"list-web-presences",
|
|
13776
|
+
"create-web-presence",
|
|
13777
|
+
"update-web-presence",
|
|
13778
|
+
"delete-web-presence",
|
|
13779
|
+
// Translations (5)
|
|
13780
|
+
"list-shop-locales",
|
|
13781
|
+
"enable-shop-locale",
|
|
13782
|
+
"disable-shop-locale",
|
|
13783
|
+
"register-translations",
|
|
13784
|
+
"remove-translations"
|
|
13785
|
+
]
|
|
13786
|
+
};
|
|
13787
|
+
var ADVANCED_REDIRECTS_MODULE = {
|
|
13788
|
+
name: "advanced-redirects",
|
|
13789
|
+
description: "Bulk URL redirect operations for large-scale SEO management. Includes bulk delete, delete by search, and import capabilities. Requires product-extensions module.",
|
|
13790
|
+
alwaysLoaded: false,
|
|
13791
|
+
dependencies: ["product-extensions"],
|
|
13792
|
+
tools: [
|
|
13793
|
+
"bulk-delete-redirects",
|
|
13794
|
+
"bulk-delete-redirects-by-search",
|
|
13795
|
+
"import-redirects",
|
|
13796
|
+
"submit-redirect-import"
|
|
13797
|
+
]
|
|
13798
|
+
};
|
|
13799
|
+
var MODULE_DEFINITIONS = [
|
|
13800
|
+
CORE_MODULE,
|
|
13801
|
+
STORE_CONTEXT_MODULE,
|
|
13802
|
+
COLLECTIONS_MODULE,
|
|
13803
|
+
PRODUCT_EXTENSIONS_MODULE,
|
|
13804
|
+
CONTENT_MODULE,
|
|
13805
|
+
INTERNATIONAL_MODULE,
|
|
13806
|
+
ADVANCED_REDIRECTS_MODULE
|
|
13807
|
+
];
|
|
13808
|
+
var registry = null;
|
|
13809
|
+
function initializeRegistry() {
|
|
13810
|
+
const modules = /* @__PURE__ */ new Map();
|
|
13811
|
+
const loadedModules = /* @__PURE__ */ new Set();
|
|
13812
|
+
const toolModuleMap = /* @__PURE__ */ new Map();
|
|
13813
|
+
for (const module of MODULE_DEFINITIONS) {
|
|
13814
|
+
modules.set(module.name, module);
|
|
13815
|
+
if (module.alwaysLoaded) {
|
|
13816
|
+
loadedModules.add(module.name);
|
|
13817
|
+
}
|
|
13818
|
+
for (const toolName of module.tools) {
|
|
13819
|
+
toolModuleMap.set(toolName, module.name);
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
registry = {
|
|
13823
|
+
modules,
|
|
13824
|
+
loadedModules,
|
|
13825
|
+
toolModuleMap
|
|
13826
|
+
};
|
|
13827
|
+
log.debug(
|
|
13828
|
+
`Module registry initialized: ${modules.size} modules, ${toolModuleMap.size} tools mapped`
|
|
13829
|
+
);
|
|
13830
|
+
return registry;
|
|
13831
|
+
}
|
|
13832
|
+
function getRegistry() {
|
|
13833
|
+
if (!registry) {
|
|
13834
|
+
return initializeRegistry();
|
|
13835
|
+
}
|
|
13836
|
+
return registry;
|
|
13837
|
+
}
|
|
13838
|
+
function getModule(name) {
|
|
13839
|
+
return getRegistry().modules.get(name);
|
|
13840
|
+
}
|
|
13841
|
+
function isModuleLoaded(name) {
|
|
13842
|
+
return getRegistry().loadedModules.has(name);
|
|
13843
|
+
}
|
|
13844
|
+
function getModuleForTool(toolName) {
|
|
13845
|
+
return getRegistry().toolModuleMap.get(toolName);
|
|
13846
|
+
}
|
|
13847
|
+
function getLoadedModules() {
|
|
13848
|
+
return Array.from(getRegistry().loadedModules);
|
|
13849
|
+
}
|
|
13850
|
+
function markModuleLoaded(name) {
|
|
13851
|
+
getRegistry().loadedModules.add(name);
|
|
13852
|
+
log.debug(`Module marked as loaded: ${name}`);
|
|
13853
|
+
}
|
|
13854
|
+
function getToolsForModule(moduleName) {
|
|
13855
|
+
const module = getModule(moduleName);
|
|
13856
|
+
return module ? module.tools : [];
|
|
13857
|
+
}
|
|
13858
|
+
|
|
13859
|
+
// src/tools/modules/loading.ts
|
|
13860
|
+
async function loadModule(moduleName) {
|
|
13861
|
+
log.debug(`Loading module: ${moduleName}`);
|
|
13862
|
+
const module = getModule(moduleName);
|
|
13863
|
+
if (!module) {
|
|
13864
|
+
const availableModules = Array.from(getRegistry().modules.keys()).join(", ");
|
|
12511
13865
|
throw new ToolError(
|
|
12512
|
-
|
|
12513
|
-
|
|
13866
|
+
`Module '${moduleName}' not found. Available modules: ${availableModules}`,
|
|
13867
|
+
`Use list-modules to see available modules. Valid options: ${availableModules}`
|
|
12514
13868
|
);
|
|
12515
13869
|
}
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
13870
|
+
if (isModuleLoaded(moduleName)) {
|
|
13871
|
+
log.debug(`Module already loaded: ${moduleName}`);
|
|
13872
|
+
return {
|
|
13873
|
+
success: true,
|
|
13874
|
+
moduleName,
|
|
13875
|
+
toolsLoaded: 0,
|
|
13876
|
+
tools: [],
|
|
13877
|
+
dependenciesLoaded: []
|
|
13878
|
+
};
|
|
13879
|
+
}
|
|
13880
|
+
const dependenciesLoaded = [];
|
|
13881
|
+
const allLoadedTools = [];
|
|
13882
|
+
for (const depName of module.dependencies) {
|
|
13883
|
+
if (!isModuleLoaded(depName)) {
|
|
13884
|
+
log.debug(`Loading dependency: ${depName} for ${moduleName}`);
|
|
13885
|
+
const depResult = await loadModule(depName);
|
|
13886
|
+
if (depResult.success && depResult.toolsLoaded > 0) {
|
|
13887
|
+
dependenciesLoaded.push(depName);
|
|
13888
|
+
allLoadedTools.push(...depResult.tools);
|
|
13889
|
+
if (depResult.dependenciesLoaded.length > 0) {
|
|
13890
|
+
dependenciesLoaded.push(...depResult.dependenciesLoaded);
|
|
13891
|
+
}
|
|
13892
|
+
}
|
|
12526
13893
|
}
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
13894
|
+
}
|
|
13895
|
+
markModuleLoaded(moduleName);
|
|
13896
|
+
for (const toolName of module.tools) {
|
|
13897
|
+
enableTool(toolName);
|
|
13898
|
+
}
|
|
13899
|
+
const result = {
|
|
13900
|
+
success: true,
|
|
13901
|
+
moduleName,
|
|
13902
|
+
toolsLoaded: module.tools.length,
|
|
13903
|
+
tools: [...module.tools],
|
|
13904
|
+
dependenciesLoaded
|
|
13905
|
+
};
|
|
13906
|
+
log.debug(
|
|
13907
|
+
`Module loaded: ${moduleName} (${result.toolsLoaded} tools, ${dependenciesLoaded.length} dependencies)`
|
|
13908
|
+
);
|
|
13909
|
+
return result;
|
|
13910
|
+
}
|
|
13911
|
+
async function loadModules(moduleNames) {
|
|
13912
|
+
const results = [];
|
|
13913
|
+
for (const moduleName of moduleNames) {
|
|
13914
|
+
try {
|
|
13915
|
+
const result = await loadModule(moduleName);
|
|
13916
|
+
results.push(result);
|
|
13917
|
+
} catch (error) {
|
|
13918
|
+
results.push({
|
|
13919
|
+
success: false,
|
|
13920
|
+
moduleName,
|
|
13921
|
+
toolsLoaded: 0,
|
|
13922
|
+
tools: [],
|
|
13923
|
+
dependenciesLoaded: [],
|
|
13924
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
13925
|
+
});
|
|
12532
13926
|
}
|
|
12533
|
-
throw error;
|
|
12534
13927
|
}
|
|
12535
|
-
|
|
12536
|
-
|
|
13928
|
+
return results;
|
|
13929
|
+
}
|
|
13930
|
+
function getModuleStatus() {
|
|
13931
|
+
const registry2 = getRegistry();
|
|
13932
|
+
const modules = Array.from(registry2.modules.values()).map((module) => ({
|
|
13933
|
+
name: module.name,
|
|
13934
|
+
description: module.description,
|
|
13935
|
+
toolCount: module.tools.length,
|
|
13936
|
+
loaded: registry2.loadedModules.has(module.name),
|
|
13937
|
+
dependencies: module.dependencies,
|
|
13938
|
+
tools: module.tools
|
|
13939
|
+
}));
|
|
13940
|
+
let totalTools = 0;
|
|
13941
|
+
let loadedTools = 0;
|
|
13942
|
+
for (const module of modules) {
|
|
13943
|
+
totalTools += module.toolCount;
|
|
13944
|
+
if (module.loaded) {
|
|
13945
|
+
loadedTools += module.toolCount;
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13948
|
+
return {
|
|
13949
|
+
modules,
|
|
13950
|
+
summary: {
|
|
13951
|
+
totalModules: modules.length,
|
|
13952
|
+
loadedModules: getLoadedModules().length,
|
|
13953
|
+
totalTools,
|
|
13954
|
+
loadedTools
|
|
13955
|
+
}
|
|
13956
|
+
};
|
|
13957
|
+
}
|
|
13958
|
+
|
|
13959
|
+
// src/tools/modules/types.ts
|
|
13960
|
+
import { z as z82 } from "zod";
|
|
13961
|
+
var ToolModuleSchema = z82.object({
|
|
13962
|
+
/** Unique module identifier (kebab-case) */
|
|
13963
|
+
name: z82.string().regex(/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/, {
|
|
13964
|
+
message: "Module name must be kebab-case"
|
|
13965
|
+
}),
|
|
13966
|
+
/** Human-readable description for AI discovery */
|
|
13967
|
+
description: z82.string().min(10, {
|
|
13968
|
+
message: "Description must be at least 10 characters"
|
|
13969
|
+
}),
|
|
13970
|
+
/** Whether this module is loaded at server startup */
|
|
13971
|
+
alwaysLoaded: z82.boolean().default(false),
|
|
13972
|
+
/** Module names that must be loaded before this module */
|
|
13973
|
+
dependencies: z82.array(z82.string()).default([]),
|
|
13974
|
+
/** Tool names belonging to this module */
|
|
13975
|
+
tools: z82.array(z82.string())
|
|
13976
|
+
});
|
|
13977
|
+
var LOADABLE_MODULE_NAMES = [
|
|
13978
|
+
"store-context",
|
|
13979
|
+
"collections",
|
|
13980
|
+
"product-extensions",
|
|
13981
|
+
"content",
|
|
13982
|
+
"international",
|
|
13983
|
+
"advanced-redirects"
|
|
13984
|
+
];
|
|
13985
|
+
var ALL_MODULE_NAMES = ["core", ...LOADABLE_MODULE_NAMES];
|
|
13986
|
+
var ModuleInfoSchema = z82.object({
|
|
13987
|
+
name: z82.string(),
|
|
13988
|
+
description: z82.string(),
|
|
13989
|
+
toolCount: z82.number().int().nonnegative(),
|
|
13990
|
+
loaded: z82.boolean(),
|
|
13991
|
+
dependencies: z82.array(z82.string()),
|
|
13992
|
+
tools: z82.array(z82.string())
|
|
13993
|
+
});
|
|
13994
|
+
var ModuleSummarySchema = z82.object({
|
|
13995
|
+
totalModules: z82.number().int().nonnegative(),
|
|
13996
|
+
loadedModules: z82.number().int().nonnegative(),
|
|
13997
|
+
totalTools: z82.number().int().nonnegative(),
|
|
13998
|
+
loadedTools: z82.number().int().nonnegative()
|
|
13999
|
+
});
|
|
14000
|
+
var ListModulesResponseSchema = z82.object({
|
|
14001
|
+
modules: z82.array(ModuleInfoSchema),
|
|
14002
|
+
summary: ModuleSummarySchema
|
|
14003
|
+
});
|
|
14004
|
+
var ModuleLoadResultSchema = z82.object({
|
|
14005
|
+
success: z82.boolean(),
|
|
14006
|
+
moduleName: z82.string(),
|
|
14007
|
+
toolsLoaded: z82.number().int().nonnegative(),
|
|
14008
|
+
tools: z82.array(z82.string()),
|
|
14009
|
+
dependenciesLoaded: z82.array(z82.string()),
|
|
14010
|
+
error: z82.string().optional()
|
|
14011
|
+
});
|
|
14012
|
+
|
|
14013
|
+
// src/tools/list-modules.ts
|
|
14014
|
+
var inputSchema80 = z83.object({});
|
|
14015
|
+
async function handleListModules(_context, _params) {
|
|
14016
|
+
log.debug("Listing available modules");
|
|
14017
|
+
const status = getModuleStatus();
|
|
14018
|
+
log.debug(
|
|
14019
|
+
`Modules listed: ${status.summary.loadedModules}/${status.summary.totalModules} loaded, ${status.summary.loadedTools}/${status.summary.totalTools} tools available`
|
|
14020
|
+
);
|
|
14021
|
+
return status;
|
|
14022
|
+
}
|
|
14023
|
+
function registerListModulesTool() {
|
|
12537
14024
|
registerContextAwareTool(
|
|
12538
14025
|
{
|
|
12539
|
-
name: "
|
|
12540
|
-
title: "
|
|
12541
|
-
description:
|
|
12542
|
-
inputSchema:
|
|
12543
|
-
outputSchema:
|
|
12544
|
-
|
|
12545
|
-
category: "seo",
|
|
14026
|
+
name: "list-modules",
|
|
14027
|
+
title: "List Available Modules",
|
|
14028
|
+
description: "List all available tool modules and their loading status. Shows module names, descriptions, tool counts, dependencies, and whether each module is loaded. Use this to discover which modules can be loaded to extend functionality. **Note:** The core module is always loaded with essential product and inventory tools.",
|
|
14029
|
+
inputSchema: inputSchema80,
|
|
14030
|
+
outputSchema: ListModulesResponseSchema,
|
|
14031
|
+
category: "store",
|
|
12546
14032
|
relationships: {
|
|
12547
|
-
relatedTools: ["
|
|
12548
|
-
|
|
12549
|
-
followUps: ["list-redirects"]
|
|
14033
|
+
relatedTools: ["load-module"],
|
|
14034
|
+
followUps: ["load-module"]
|
|
12550
14035
|
},
|
|
12551
|
-
// MCP Tool Annotations (Epic 9.5 - MCP Best Practices) - AC-10.6.6
|
|
12552
14036
|
annotations: {
|
|
12553
|
-
readOnlyHint:
|
|
14037
|
+
readOnlyHint: true,
|
|
12554
14038
|
destructiveHint: false,
|
|
12555
|
-
idempotentHint:
|
|
12556
|
-
|
|
14039
|
+
idempotentHint: true,
|
|
14040
|
+
// Does NOT call Shopify API - internal state only
|
|
14041
|
+
openWorldHint: false
|
|
12557
14042
|
}
|
|
12558
14043
|
},
|
|
12559
|
-
|
|
14044
|
+
handleListModules
|
|
12560
14045
|
);
|
|
12561
14046
|
}
|
|
12562
14047
|
|
|
12563
|
-
// src/tools/
|
|
12564
|
-
import { z as
|
|
12565
|
-
var
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
myshopifyDomain: z73.string().describe("Full myshopify.com domain"),
|
|
12570
|
-
plan: z73.object({
|
|
12571
|
-
displayName: z73.string().describe("Current plan name"),
|
|
12572
|
-
partnerDevelopment: z73.boolean().describe("Whether partner development store")
|
|
12573
|
-
}).describe("Store plan details"),
|
|
12574
|
-
currencyCode: z73.string().describe("Store currency code (e.g., USD, EUR)"),
|
|
12575
|
-
timezone: z73.string().describe("Store IANA timezone"),
|
|
12576
|
-
contactEmail: z73.string().describe("Store contact email")
|
|
14048
|
+
// src/tools/load-module.ts
|
|
14049
|
+
import { z as z84 } from "zod";
|
|
14050
|
+
var inputSchema81 = z84.object({
|
|
14051
|
+
module: z84.enum(LOADABLE_MODULE_NAMES).describe(
|
|
14052
|
+
"Name of the module to load. Options: store-context, collections, product-extensions, content, international, advanced-redirects"
|
|
14053
|
+
)
|
|
12577
14054
|
});
|
|
12578
|
-
async function
|
|
12579
|
-
log.debug(`
|
|
12580
|
-
|
|
14055
|
+
async function handleLoadModule(_context, params) {
|
|
14056
|
+
log.debug(`Loading module: ${params.module}`);
|
|
14057
|
+
const result = await loadModule(params.module);
|
|
14058
|
+
if (result.success) {
|
|
14059
|
+
if (result.toolsLoaded > 0) {
|
|
14060
|
+
log.debug(
|
|
14061
|
+
`Module ${params.module} loaded: ${result.toolsLoaded} tools, ${result.dependenciesLoaded.length} dependencies`
|
|
14062
|
+
);
|
|
14063
|
+
} else {
|
|
14064
|
+
log.debug(`Module ${params.module} was already loaded`);
|
|
14065
|
+
}
|
|
14066
|
+
}
|
|
14067
|
+
return result;
|
|
12581
14068
|
}
|
|
12582
|
-
function
|
|
14069
|
+
function registerLoadModuleTool() {
|
|
12583
14070
|
registerContextAwareTool(
|
|
12584
14071
|
{
|
|
12585
|
-
name: "
|
|
12586
|
-
title: "
|
|
12587
|
-
description: "
|
|
12588
|
-
inputSchema:
|
|
12589
|
-
outputSchema:
|
|
14072
|
+
name: "load-module",
|
|
14073
|
+
title: "Load Tool Module",
|
|
14074
|
+
description: "Load a tool module to enable additional tools for a specific domain. Dependencies are automatically loaded. Use list-modules to see available modules. **Available modules:** store-context (store configuration), collections (product organization), product-extensions (metafields, images, redirects), content (pages, blogs, articles), international (markets, translations), advanced-redirects (bulk operations). **Note:** Once loaded, modules remain available for the session.",
|
|
14075
|
+
inputSchema: inputSchema81,
|
|
14076
|
+
outputSchema: ModuleLoadResultSchema,
|
|
12590
14077
|
category: "store",
|
|
12591
14078
|
relationships: {
|
|
12592
|
-
|
|
12593
|
-
|
|
14079
|
+
prerequisites: ["list-modules"],
|
|
14080
|
+
relatedTools: ["list-modules"]
|
|
12594
14081
|
},
|
|
12595
14082
|
annotations: {
|
|
12596
|
-
|
|
14083
|
+
// Not read-only: changes internal tool availability state
|
|
14084
|
+
readOnlyHint: false,
|
|
12597
14085
|
destructiveHint: false,
|
|
14086
|
+
// Idempotent: loading same module twice returns success
|
|
12598
14087
|
idempotentHint: true,
|
|
12599
|
-
|
|
14088
|
+
// Does NOT call Shopify API - internal state only
|
|
14089
|
+
openWorldHint: false
|
|
12600
14090
|
}
|
|
12601
14091
|
},
|
|
12602
|
-
|
|
14092
|
+
handleLoadModule
|
|
12603
14093
|
);
|
|
12604
14094
|
}
|
|
12605
14095
|
|
|
14096
|
+
// src/tools/modules/presets.ts
|
|
14097
|
+
import { z as z85 } from "zod";
|
|
14098
|
+
var RolePresetSchema = z85.object({
|
|
14099
|
+
description: z85.string(),
|
|
14100
|
+
modules: z85.array(z85.string())
|
|
14101
|
+
});
|
|
14102
|
+
var ROLE_PRESETS = {
|
|
14103
|
+
"inventory-manager": {
|
|
14104
|
+
description: "Stock management and inventory operations",
|
|
14105
|
+
modules: ["core"]
|
|
14106
|
+
},
|
|
14107
|
+
"product-manager": {
|
|
14108
|
+
description: "Full product catalog management",
|
|
14109
|
+
modules: ["core", "store-context", "collections", "product-extensions"]
|
|
14110
|
+
},
|
|
14111
|
+
"content-manager": {
|
|
14112
|
+
description: "Pages, blogs, and content marketing",
|
|
14113
|
+
modules: ["core", "content"]
|
|
14114
|
+
},
|
|
14115
|
+
"seo-specialist": {
|
|
14116
|
+
description: "SEO optimization and URL management",
|
|
14117
|
+
modules: ["core", "collections", "product-extensions", "advanced-redirects"]
|
|
14118
|
+
},
|
|
14119
|
+
"international-manager": {
|
|
14120
|
+
description: "Multi-market and translation management",
|
|
14121
|
+
modules: ["core", "store-context", "international", "collections"]
|
|
14122
|
+
},
|
|
14123
|
+
"full-access": {
|
|
14124
|
+
description: "All tools for store administrators",
|
|
14125
|
+
modules: [
|
|
14126
|
+
"core",
|
|
14127
|
+
"store-context",
|
|
14128
|
+
"collections",
|
|
14129
|
+
"product-extensions",
|
|
14130
|
+
"content",
|
|
14131
|
+
"international",
|
|
14132
|
+
"advanced-redirects"
|
|
14133
|
+
]
|
|
14134
|
+
}
|
|
14135
|
+
};
|
|
14136
|
+
var VALID_ROLE_NAMES = Object.keys(ROLE_PRESETS);
|
|
14137
|
+
function isValidRoleName(name) {
|
|
14138
|
+
return name in ROLE_PRESETS;
|
|
14139
|
+
}
|
|
14140
|
+
function getPresetModules(role) {
|
|
14141
|
+
return [...ROLE_PRESETS[role].modules];
|
|
14142
|
+
}
|
|
14143
|
+
|
|
14144
|
+
// src/tools/suggestions.ts
|
|
14145
|
+
var TOOL_SUGGESTIONS = {
|
|
14146
|
+
// Product tools → suggest product-extensions and collections
|
|
14147
|
+
"create-product": [
|
|
14148
|
+
{ name: "product-extensions", reason: "Add metafields, manage images" },
|
|
14149
|
+
{ name: "collections", reason: "Organize in collections" }
|
|
14150
|
+
],
|
|
14151
|
+
"get-product": [{ name: "product-extensions", reason: "View metafields, images" }],
|
|
14152
|
+
"update-product": [
|
|
14153
|
+
{ name: "product-extensions", reason: "Update metafields" },
|
|
14154
|
+
{ name: "collections", reason: "Manage collection membership" }
|
|
14155
|
+
],
|
|
14156
|
+
"list-products": [{ name: "collections", reason: "Categorize and filter by collection" }],
|
|
14157
|
+
"delete-product": [
|
|
14158
|
+
{ name: "product-extensions", reason: "Check redirects, clean up metafields" }
|
|
14159
|
+
],
|
|
14160
|
+
"update-product-variant": [{ name: "product-extensions", reason: "Manage variant metafields" }],
|
|
14161
|
+
"add-product-image": [{ name: "product-extensions", reason: "Reorder images, update alt text" }],
|
|
14162
|
+
// Inventory tools → suggest store-context for location info
|
|
14163
|
+
"get-inventory": [{ name: "store-context", reason: "Location information" }],
|
|
14164
|
+
"update-inventory": [{ name: "store-context", reason: "Location details" }],
|
|
14165
|
+
"list-low-inventory": [{ name: "store-context", reason: "Location context" }],
|
|
14166
|
+
"get-bulk-inventory": [{ name: "store-context", reason: "Location information" }],
|
|
14167
|
+
// Store info tools → suggest store-context for full configuration
|
|
14168
|
+
"get-store-info": [{ name: "store-context", reason: "Full store configuration" }],
|
|
14169
|
+
"get-store-limits": [{ name: "store-context", reason: "Additional store details" }]
|
|
14170
|
+
};
|
|
14171
|
+
function getSuggestionsForTool(toolName) {
|
|
14172
|
+
const suggestions = TOOL_SUGGESTIONS[toolName];
|
|
14173
|
+
if (!suggestions || suggestions.length === 0) {
|
|
14174
|
+
return null;
|
|
14175
|
+
}
|
|
14176
|
+
const unloadedSuggestions = suggestions.filter((s) => !isModuleLoaded(s.name));
|
|
14177
|
+
if (unloadedSuggestions.length === 0) {
|
|
14178
|
+
return null;
|
|
14179
|
+
}
|
|
14180
|
+
return {
|
|
14181
|
+
modules: unloadedSuggestions,
|
|
14182
|
+
hint: "Use load-module to enable these tools"
|
|
14183
|
+
};
|
|
14184
|
+
}
|
|
14185
|
+
|
|
12606
14186
|
// src/tools/index.ts
|
|
12607
14187
|
function setupToolHandlers(server) {
|
|
12608
14188
|
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -12638,8 +14218,42 @@ Suggestion: Use tools/list to see available tools. ${toolList}`
|
|
|
12638
14218
|
isError: true
|
|
12639
14219
|
};
|
|
12640
14220
|
}
|
|
14221
|
+
if (!isToolEnabled(name)) {
|
|
14222
|
+
const moduleName = getModuleForTool(name);
|
|
14223
|
+
const suggestion = moduleName ? `Load the '${moduleName}' module first: use load-module with module: "${moduleName}"` : "This tool is currently disabled. Use list-modules to see available modules.";
|
|
14224
|
+
log.debug(`Tool "${name}" is disabled, suggesting module: ${moduleName || "unknown"}`);
|
|
14225
|
+
return {
|
|
14226
|
+
content: [
|
|
14227
|
+
{
|
|
14228
|
+
type: "text",
|
|
14229
|
+
text: `Error: Tool "${name}" is not currently loaded.
|
|
14230
|
+
|
|
14231
|
+
Suggestion: ${suggestion}`
|
|
14232
|
+
}
|
|
14233
|
+
],
|
|
14234
|
+
isError: true
|
|
14235
|
+
};
|
|
14236
|
+
}
|
|
12641
14237
|
try {
|
|
12642
14238
|
const response = await tool.handler(params ?? {});
|
|
14239
|
+
const config = getConfig();
|
|
14240
|
+
if (isLazyLoadingEnabled(config) && !response.isError) {
|
|
14241
|
+
const suggestions = getSuggestionsForTool(name);
|
|
14242
|
+
if (suggestions) {
|
|
14243
|
+
const suggestionText = `
|
|
14244
|
+
---
|
|
14245
|
+
\u{1F4A1} **Related modules available:**
|
|
14246
|
+
${suggestions.modules.map((m) => `- **${m.name}**: ${m.reason}`).join("\n")}
|
|
14247
|
+
|
|
14248
|
+
_${suggestions.hint}_`;
|
|
14249
|
+
if (Array.isArray(response.content)) {
|
|
14250
|
+
response.content.push({
|
|
14251
|
+
type: "text",
|
|
14252
|
+
text: suggestionText
|
|
14253
|
+
});
|
|
14254
|
+
}
|
|
14255
|
+
}
|
|
14256
|
+
}
|
|
12643
14257
|
return response;
|
|
12644
14258
|
} catch (error) {
|
|
12645
14259
|
log.error(`Unexpected error in tool ${name}:`, error instanceof Error ? error : void 0);
|
|
@@ -12659,6 +14273,7 @@ Suggestion: Please try again or contact support if the issue persists.`
|
|
|
12659
14273
|
}
|
|
12660
14274
|
function registerAllTools(server) {
|
|
12661
14275
|
setupToolHandlers(server);
|
|
14276
|
+
initializeRegistry();
|
|
12662
14277
|
registerAddProductImageTool();
|
|
12663
14278
|
registerCreateProductTool();
|
|
12664
14279
|
registerDeleteProductTool();
|
|
@@ -12730,8 +14345,77 @@ function registerAllTools(server) {
|
|
|
12730
14345
|
registerImportRedirectsTool();
|
|
12731
14346
|
registerSubmitRedirectImportTool();
|
|
12732
14347
|
registerGetStoreInfoTool();
|
|
12733
|
-
|
|
12734
|
-
|
|
14348
|
+
registerGetStoreLimitsTool();
|
|
14349
|
+
registerGetStoreFeaturesTool();
|
|
14350
|
+
registerGetStoreCurrenciesTool();
|
|
14351
|
+
registerGetStoreShippingTool();
|
|
14352
|
+
registerGetStoreDomainTool();
|
|
14353
|
+
registerGetStoreTaxesTool();
|
|
14354
|
+
registerGetStorePoliciesTool();
|
|
14355
|
+
registerGetStoreAlertsTool();
|
|
14356
|
+
registerListModulesTool();
|
|
14357
|
+
registerLoadModuleTool();
|
|
14358
|
+
const totalToolCount = getToolNames().length;
|
|
14359
|
+
const config = getConfig();
|
|
14360
|
+
if (isLazyLoadingEnabled(config)) {
|
|
14361
|
+
applyLazyLoading();
|
|
14362
|
+
const roleResult = applyRolePreset(config);
|
|
14363
|
+
const enabledCount = getRegisteredTools().length;
|
|
14364
|
+
if (roleResult.applied) {
|
|
14365
|
+
log.info(
|
|
14366
|
+
`Role preset '${roleResult.role}' applied: ${enabledCount} tools active (${roleResult.modulesLoaded.length} modules loaded)`
|
|
14367
|
+
);
|
|
14368
|
+
} else {
|
|
14369
|
+
log.info(
|
|
14370
|
+
`Lazy loading enabled: ${enabledCount} core tools active, ${totalToolCount - enabledCount} tools available via modules`
|
|
14371
|
+
);
|
|
14372
|
+
}
|
|
14373
|
+
} else {
|
|
14374
|
+
log.info(`Lazy loading disabled (legacy mode): ${totalToolCount} tools registered`);
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14377
|
+
function applyLazyLoading() {
|
|
14378
|
+
const coreTools = new Set(getToolsForModule("core"));
|
|
14379
|
+
const allToolNames = getToolNames();
|
|
14380
|
+
let disabledCount = 0;
|
|
14381
|
+
for (const toolName of allToolNames) {
|
|
14382
|
+
if (!coreTools.has(toolName)) {
|
|
14383
|
+
disableTool(toolName);
|
|
14384
|
+
disabledCount++;
|
|
14385
|
+
}
|
|
14386
|
+
}
|
|
14387
|
+
log.debug(`Lazy loading: disabled ${disabledCount} non-core tools`);
|
|
14388
|
+
}
|
|
14389
|
+
function applyRolePreset(config) {
|
|
14390
|
+
const role = getConfiguredRole(config);
|
|
14391
|
+
if (!role) {
|
|
14392
|
+
return { applied: false, modulesLoaded: [] };
|
|
14393
|
+
}
|
|
14394
|
+
if (!isValidRoleName(role)) {
|
|
14395
|
+
log.warn(
|
|
14396
|
+
`Invalid role preset '${role}'. Valid roles: inventory-manager, product-manager, content-manager, seo-specialist, international-manager, full-access`
|
|
14397
|
+
);
|
|
14398
|
+
return {
|
|
14399
|
+
applied: false,
|
|
14400
|
+
modulesLoaded: [],
|
|
14401
|
+
warning: `Invalid role '${role}' - continuing with core tools only`
|
|
14402
|
+
};
|
|
14403
|
+
}
|
|
14404
|
+
const presetModules = getPresetModules(role);
|
|
14405
|
+
const modulesToLoad = presetModules.filter((m) => m !== "core");
|
|
14406
|
+
if (modulesToLoad.length === 0) {
|
|
14407
|
+
log.debug(`Role preset '${role}' requires only core module`);
|
|
14408
|
+
return { applied: true, role, modulesLoaded: [] };
|
|
14409
|
+
}
|
|
14410
|
+
loadModules(modulesToLoad).then((results) => {
|
|
14411
|
+
const successful = results.filter((r) => r.success).map((r) => r.moduleName);
|
|
14412
|
+
const failed = results.filter((r) => !r.success).map((r) => r.moduleName);
|
|
14413
|
+
if (failed.length > 0) {
|
|
14414
|
+
log.warn(`Role preset '${role}': failed to load modules: ${failed.join(", ")}`);
|
|
14415
|
+
}
|
|
14416
|
+
log.debug(`Role preset '${role}': loaded modules: ${successful.join(", ")}`);
|
|
14417
|
+
});
|
|
14418
|
+
return { applied: true, role, modulesLoaded: modulesToLoad };
|
|
12735
14419
|
}
|
|
12736
14420
|
|
|
12737
14421
|
// src/transports/stdio.ts
|