@rafaelcg/adobe-commerce-dev-mcp
Version:
A command line tool for setting up Adobe Commerce MCP server
263 lines (262 loc) • 11.5 kB
JavaScript
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import zlib from "zlib";
import { existsSync } from "fs";
// Get the directory name for the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Path to the schema file in the data folder
export const SCHEMA_FILE_PATH = path.join(__dirname, "..", "..", "data", "schema_2.4.7.json");
// Function to load schema content, handling decompression if needed
export async function loadSchemaContent(schemaPath) {
const gzippedSchemaPath = `${schemaPath}.gz`;
// If uncompressed file doesn't exist but gzipped does, decompress it
if (!existsSync(schemaPath) && existsSync(gzippedSchemaPath)) {
console.error(`[adobe-commerce-schema-tool] Decompressing GraphQL schema from ${gzippedSchemaPath}`);
const compressedData = await fs.readFile(gzippedSchemaPath);
const schemaContent = zlib.gunzipSync(compressedData).toString("utf-8");
// Save the uncompressed content to disk
await fs.writeFile(schemaPath, schemaContent, "utf-8");
console.error(`[adobe-commerce-schema-tool] Saved uncompressed schema to ${schemaPath}`);
return schemaContent;
}
console.error(`[adobe-commerce-schema-tool] Reading GraphQL schema from ${schemaPath}`);
return fs.readFile(schemaPath, "utf8");
}
// Maximum number of fields to extract from an object
export const MAX_FIELDS_TO_SHOW = 50;
// Helper function to filter, sort, and truncate schema items
export const filterAndSortItems = (items, searchTerm, maxItems) => {
// Filter items based on search term
const filtered = items.filter((item) => item.name?.toLowerCase().includes(searchTerm));
// Sort filtered items by name length (shorter names first)
filtered.sort((a, b) => {
if (!a.name)
return 1;
if (!b.name)
return -1;
return a.name.length - b.name.length;
});
// Return truncation info and limited items
return {
wasTruncated: filtered.length > maxItems,
items: filtered.slice(0, maxItems),
};
};
// Helper functions to format GraphQL schema types as plain text
export const formatType = (type) => {
if (!type)
return "null";
if (type.kind === "NON_NULL") {
return `${formatType(type.ofType)}!`;
}
else if (type.kind === "LIST") {
return `[${formatType(type.ofType)}]`;
}
else {
return type.name;
}
};
export const formatArg = (arg) => {
return `${arg.name}: ${formatType(arg.type)}${arg.defaultValue !== null ? ` = ${arg.defaultValue}` : ""}`;
};
export const formatField = (field) => {
let result = ` ${field.name}`;
// Add arguments if present
if (field.args && field.args.length > 0) {
result += `(${field.args.map(formatArg).join(", ")})`;
}
result += `: ${formatType(field.type)}`;
// Add deprecation info if present
if (field.isDeprecated) {
result += ` @deprecated`;
if (field.deprecationReason) {
result += ` (${field.deprecationReason})`;
}
}
return result;
};
export const formatSchemaType = (item) => {
let result = `${item.kind} ${item.name}`;
if (item.description) {
// Truncate description if too long
const maxDescLength = 150;
const desc = item.description.replace(/\n/g, " ");
result += `\n Description: ${desc.length > maxDescLength
? desc.substring(0, maxDescLength) + "..."
: desc}`;
}
// Add interfaces if present
if (item.interfaces && item.interfaces.length > 0) {
result += `\n Implements: ${item.interfaces
.map((i) => i.name)
.join(", ")}`;
}
// For INPUT_OBJECT types, use inputFields instead of fields
if (item.kind === "INPUT_OBJECT" &&
item.inputFields &&
item.inputFields.length > 0) {
result += "\n Input Fields:";
// Extract at most MAX_FIELDS_TO_SHOW fields
const fieldsToShow = item.inputFields.slice(0, MAX_FIELDS_TO_SHOW);
for (const field of fieldsToShow) {
result += `\n${formatField(field)}`;
}
if (item.inputFields.length > MAX_FIELDS_TO_SHOW) {
result += `\n ... and ${item.inputFields.length - MAX_FIELDS_TO_SHOW} more input fields`;
}
}
// For regular object types, use fields
else if (item.fields && item.fields.length > 0) {
result += "\n Fields:";
// Extract at most MAX_FIELDS_TO_SHOW fields
const fieldsToShow = item.fields.slice(0, MAX_FIELDS_TO_SHOW);
for (const field of fieldsToShow) {
result += `\n${formatField(field)}`;
}
if (item.fields.length > MAX_FIELDS_TO_SHOW) {
result += `\n ... and ${item.fields.length - MAX_FIELDS_TO_SHOW} more fields`;
}
}
return result;
};
export const formatGraphqlOperation = (query) => {
let result = `${query.name}`;
if (query.description) {
// Truncate description if too long
const maxDescLength = 100;
const desc = query.description.replace(/\n/g, " ");
result += `\n Description: ${desc.length > maxDescLength
? desc.substring(0, maxDescLength) + "..."
: desc}`;
}
// Add arguments if present
if (query.args && query.args.length > 0) {
result += "\n Arguments:";
for (const arg of query.args) {
result += `\n ${formatArg(arg)}`;
}
}
// Add return type
result += `\n Returns: ${formatType(query.type)}`;
return result;
};
// Function to search and format schema data
export async function searchAdobeCommerceSchema(query, { filter = ["all"], } = {}) {
try {
const schemaContent = await loadSchemaContent(SCHEMA_FILE_PATH);
// Parse the schema content
const schemaJson = JSON.parse(schemaContent);
// If a query is provided, filter the schema
let resultSchema = schemaJson;
let wasTruncated = false;
let queriesWereTruncated = false;
let mutationsWereTruncated = false;
if (query && query.trim()) {
// Normalize search term: remove trailing 's' and remove all spaces
let normalizedQuery = query.trim();
if (normalizedQuery.endsWith("s")) {
normalizedQuery = normalizedQuery.slice(0, -1);
}
normalizedQuery = normalizedQuery.replace(/\s+/g, "");
console.error(`[adobe-commerce-schema-tool] Filtering schema with query: ${query} (normalized: ${normalizedQuery})`);
const searchTerm = normalizedQuery.toLowerCase();
// Example filtering logic (adjust based on actual schema structure)
if (schemaJson?.data?.__schema?.types) {
const MAX_RESULTS = 10;
// Process types
const processedTypes = filterAndSortItems(schemaJson.data.__schema.types, searchTerm, MAX_RESULTS);
wasTruncated = processedTypes.wasTruncated;
const limitedTypes = processedTypes.items;
// Find the Query and Mutation types
const queryType = schemaJson.data.__schema.types.find((type) => type.name === "QueryRoot");
const mutationType = schemaJson.data.__schema.types.find((type) => type.name === "Mutation");
// Process queries if available
let matchingQueries = [];
if (queryType &&
queryType.fields &&
(filter.includes("all") || filter.includes("queries"))) {
const processedQueries = filterAndSortItems(queryType.fields, searchTerm, MAX_RESULTS);
queriesWereTruncated = processedQueries.wasTruncated;
matchingQueries = processedQueries.items;
}
// Process mutations if available
let matchingMutations = [];
if (mutationType &&
mutationType.fields &&
(filter.includes("all") || filter.includes("mutations"))) {
const processedMutations = filterAndSortItems(mutationType.fields, searchTerm, MAX_RESULTS);
mutationsWereTruncated = processedMutations.wasTruncated;
matchingMutations = processedMutations.items;
}
// Create a modified schema that includes matching types
resultSchema = {
data: {
__schema: {
...schemaJson.data.__schema,
types: limitedTypes,
matchingQueries,
matchingMutations,
},
},
};
}
}
// Create the response text with truncation message if needed
let responseText = "";
if (filter.includes("all") || filter.includes("types")) {
responseText += "## Matching GraphQL Types:\n";
if (wasTruncated) {
responseText += `(Results limited to 10 items. Refine your search for more specific results.)\n\n`;
}
if (resultSchema.data.__schema.types.length > 0) {
responseText +=
resultSchema.data.__schema.types.map(formatSchemaType).join("\n\n") +
"\n\n";
}
else {
responseText += "No matching types found.\n\n";
}
}
// Add queries section if showing all or queries
if (filter.includes("all") || filter.includes("queries")) {
responseText += "## Matching GraphQL Queries:\n";
if (queriesWereTruncated) {
responseText += `(Results limited to 10 items. Refine your search for more specific results.)\n\n`;
}
if (resultSchema.data.__schema.matchingQueries?.length > 0) {
responseText +=
resultSchema.data.__schema.matchingQueries
.map(formatGraphqlOperation)
.join("\n\n") + "\n\n";
}
else {
responseText += "No matching queries found.\n\n";
}
}
// Add mutations section if showing all or mutations
if (filter.includes("all") || filter.includes("mutations")) {
responseText += "## Matching GraphQL Mutations:\n";
if (mutationsWereTruncated) {
responseText += `(Results limited to 10 items. Refine your search for more specific results.)\n\n`;
}
if (resultSchema.data.__schema.matchingMutations?.length > 0) {
responseText += resultSchema.data.__schema.matchingMutations
.map(formatGraphqlOperation)
.join("\n\n");
}
else {
responseText += "No matching mutations found.";
}
}
return { success: true, responseText };
}
catch (error) {
console.error(`[adobe-commerce-schema-tool] Error processing GraphQL schema: ${error}`);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}