Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions magento/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
"title": "Base URL",
"description": "Magento store base URL, e.g. https://loja.granado.com.br (no trailing slash, no /rest)"
},
"apiToken": {
"type": "string",
"title": "API Token",
"description": "Magento integration access token (sent as Authorization: Bearer)",
"format": "password"
},
"storeCode": {
"type": "string",
"title": "Store Code",
Expand Down Expand Up @@ -49,7 +43,7 @@
}
}
},
"required": ["baseUrl", "apiToken"]
"required": ["baseUrl"]
}
},
"description": "MCP for Magento 2 REST APIs — live sales dashboard widgets (orders per hour, sales cards, cancellation rate, top products, status breakdown) plus curated tools for orders, catalog, customers, inventory and CMS.",
Expand Down
30 changes: 20 additions & 10 deletions magento/server/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const INITIAL_RETRY_DELAY_MS = 500;
export const DEFAULT_STORE_CODE = "all";
export const DEFAULT_CURRENCY = "BRL";

type CredentialSource = "state" | "env" | "missing";
type CredentialSource = "authorization" | "state" | "env" | "missing";

export interface ResolvedCredentials extends MagentoCredentials {
/** Where each credential value came from — useful for diagnosing missing config. */
Expand All @@ -35,15 +35,26 @@ function pickSource(
return { value: "", source: "missing" };
}

export interface MeshRequestContext {
authorization?: string;
state?: Partial<MagentoCredentials>;
}

export function resolveCredentials(
state: Partial<MagentoCredentials> | undefined,
ctx: MeshRequestContext | undefined,
): ResolvedCredentials {
const safeState = state ?? {};
const safeState = ctx?.state ?? {};

const baseUrl = pickSource(safeState.baseUrl, process.env.MAGENTO_BASE_URL);
const apiToken = pickSource(
safeState.apiToken,
process.env.MAGENTO_API_TOKEN,
);

const rawAuth = ctx?.authorization;
const tokenFromAuth = rawAuth
? rawAuth.replace(/^Bearer\s+/i, "")
: undefined;
const apiToken: { value: string; source: CredentialSource } = tokenFromAuth
? { value: tokenFromAuth, source: "authorization" }
: pickSource(undefined, process.env.MAGENTO_API_TOKEN);

const storeCode = pickSource(
safeState.storeCode,
process.env.MAGENTO_STORE_CODE,
Expand All @@ -54,11 +65,10 @@ export function resolveCredentials(
);

if (baseUrl.source === "missing") {
const stateKeys = state ? Object.keys(state) : [];
const stateKeys = safeState ? Object.keys(safeState) : [];
console.warn(
"[Magento] resolveCredentials: baseUrl is missing — neither MESH_REQUEST_CONTEXT.state.baseUrl nor process.env.MAGENTO_BASE_URL is set.",
JSON.stringify({
receivedStateType: state === undefined ? "undefined" : typeof state,
receivedStateKeys: stateKeys,
envMagentoBaseUrlSet: Boolean(process.env.MAGENTO_BASE_URL),
}),
Expand Down Expand Up @@ -94,7 +104,7 @@ export function assertValidCredentials(
}
if (!creds.apiToken) {
throw new Error(
`Magento apiToken is missing${where} — set MESH_REQUEST_CONTEXT.state.apiToken or the MAGENTO_API_TOKEN env var (integration access token).`,
`Magento apiToken is missing${where} — set the Token field in the MCP connection (Authorization: Bearer) or the MAGENTO_API_TOKEN env var (integration access token).`,
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion magento/server/tools/custom/cancellation-rate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const cancellationRate = (_env: Env) =>
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, TOOL_ID);

const timezone = creds.timezone ?? DEFAULT_STORE_TIMEZONE;
Expand Down
2 changes: 1 addition & 1 deletion magento/server/tools/custom/orders-sales-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const ordersSalesCard = (_env: Env) =>
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, TOOL_ID);

const timezone = creds.timezone ?? DEFAULT_STORE_TIMEZONE;
Expand Down
2 changes: 1 addition & 1 deletion magento/server/tools/custom/orders-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const ordersTimeline = (_env: Env) =>
annotations: { readOnlyHint: true },
execute: async ({ runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, TOOL_ID);

const timezone = creds.timezone ?? DEFAULT_STORE_TIMEZONE;
Expand Down
2 changes: 1 addition & 1 deletion magento/server/tools/custom/status-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const statusBreakdown = (_env: Env) =>
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, TOOL_ID);

const timezone = creds.timezone ?? DEFAULT_STORE_TIMEZONE;
Expand Down
2 changes: 1 addition & 1 deletion magento/server/tools/custom/top-products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const topProducts = (_env: Env) =>
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, TOOL_ID);

const timezone = creds.timezone ?? DEFAULT_STORE_TIMEZONE;
Expand Down
4 changes: 2 additions & 2 deletions magento/server/tools/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function createSearchTool(config: {
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, config.id);

const params = buildSearchCriteriaParams({
Expand Down Expand Up @@ -115,7 +115,7 @@ function createGetTool<TSchema extends z.ZodObject<z.ZodRawShape>>(config: {
annotations: { readOnlyHint: true },
execute: async ({ context, runtimeContext }) => {
const env = runtimeContext.env as Env;
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT?.state);
const creds = resolveCredentials(env.MESH_REQUEST_CONTEXT);
assertValidCredentials(creds, config.id);

const input = context as z.infer<TSchema>;
Expand Down
5 changes: 0 additions & 5 deletions magento/server/types/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ export const StateSchema = z.object({
.describe(
"Magento store base URL, e.g. https://loja.granado.com.br (no trailing slash, no /rest)",
),
apiToken: z
.string()
.describe(
"Magento integration access token, sent as Authorization: Bearer",
),
storeCode: z
.string()
.optional()
Expand Down
Loading