shopify-client.ts•3.11 kB
import { createStorefrontApiClient } from "@shopify/storefront-api-client";
import { getStoreConfig, getEnabledStoreConfigs, type StoreConfig } from "../config/stores.js";
// Client cache to avoid recreating clients
const clientCache = new Map<string, ReturnType<typeof createStorefrontApiClient>>();
function getStorefrontClient(storeId: string) {
if (clientCache.has(storeId)) {
return clientCache.get(storeId)!;
}
const config = getStoreConfig(storeId);
if (!config) {
throw new Error(`Store configuration not found for storeId: ${storeId}`);
}
if (!config.enabled) {
throw new Error(`Store is disabled: ${storeId}`);
}
if (!config.domain || !config.storefrontAccessToken) {
throw new Error(`Incomplete configuration for store: ${storeId} (${config.name})`);
}
const client = createStorefrontApiClient({
storeDomain: config.domain,
apiVersion: config.apiVersion,
publicAccessToken: config.storefrontAccessToken,
});
clientCache.set(storeId, client);
return client;
}
export async function requestStorefrontApi({
storeId,
query,
variables,
}: {
storeId: string;
query: string;
variables?: Record<string, unknown>;
}) {
const store = getStoreConfig(storeId);
if (!store) {
throw new Error(`Store configuration not found for storeId: ${storeId}`);
}
const client = getStorefrontClient(storeId);
console.info(
`[${new Date().toISOString()}] INFO: Running query on store ${storeId} (${store.name}): ${query} with variables: ${JSON.stringify(
variables,
null,
2,
)}`,
);
const { data, errors } = await client.request(query, {
variables,
});
return { data, errors };
}
export function listAvailableStores(): string[] {
return getEnabledStoreConfigs().map(store => {
return `${store.id}: ${store.name} (${store.domain})`;
});
}
// --- Define Content Block Types ---
// Only TextContentBlock needed
export type TextContentBlock = {
type: "text";
text: string;
};
// Define the overall result structure
export type McpToolResult = { content: TextContentBlock[] };
// --- Helper Functions ---
function textResult(text: string): McpToolResult {
return { content: [{ type: "text", text }] };
}
export function handleStorefrontApiResult(proxyResult: {
data: unknown;
errors?: unknown;
}) {
if (!proxyResult.errors) {
return jsonResult(proxyResult.data);
}
const errors = Array.isArray(proxyResult.errors)
? proxyResult.errors
: [proxyResult.errors];
// Log the full error details
console.error(
`[${new Date().toISOString()}] ERROR: GraphQL Errors:`,
JSON.stringify(errors, null, 2),
);
const message =
(errors[0] as { message?: string })?.message ||
"Tool execution failed via proxy.";
throw new Error(message);
}
function jsonResult(data: unknown): McpToolResult {
try {
const jsonString = JSON.stringify(data, null, 2);
return {
content: [{ type: "text", text: `\`\`\`json\n${jsonString}\n\`\`\`` }],
};
} catch (e) {
console.error(
`[${new Date().toISOString()}] ERROR: Failed to stringify result data:`,
e,
);
return textResult("Error: Could not serialize result data.");
}
}