From 20cf8781b811c47716979f67eb1bff30693fe169 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sun, 15 Mar 2026 17:03:18 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20MCP=20server=20audit=20=E2=80=94=20corre?= =?UTF-8?q?ct=20API=20routing=20through=20Traefik=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CTO Audit findings and fixes: - Port config: all 4 services were using wrong localhost ports (5002/5003/5004/5019). All services run behind Traefik on port 80 — consolidated to single gateway client. - Route fix: /shops/{shopId}/products → /products?shopId= (Traefik routes /shops to merchant-service) - Response parsing: dashboard API uses "revenue"/"popularItems" (not "totalRevenue"/"topItems") - Added .gitignore to prevent .env with JWT tokens from being committed Verified all 12 tools against live Docker services via Traefik gateway. Co-Authored-By: Claude Opus 4.6 --- services/goodgo-mcp-server/.env.example | 9 +++-- services/goodgo-mcp-server/.gitignore | 3 ++ .../src/services/api-client.ts | 31 +++++++++------- .../src/tools/analytics-tools.ts | 37 +++++++++++-------- .../src/tools/catalog-tools.ts | 3 +- 5 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 services/goodgo-mcp-server/.gitignore diff --git a/services/goodgo-mcp-server/.env.example b/services/goodgo-mcp-server/.env.example index 3ed48770..9f8e12eb 100644 --- a/services/goodgo-mcp-server/.env.example +++ b/services/goodgo-mcp-server/.env.example @@ -1,8 +1,9 @@ # GoodGo MCP Server Configuration -CATALOG_API_URL=http://localhost:5002/api/v1 -INVENTORY_API_URL=http://localhost:5003/api/v1 -FNB_API_URL=http://localhost:5019/api/v1 -ORDER_API_URL=http://localhost:5004/api/v1 + +# API Gateway URL (Traefik routes all services by path prefix) +# Docker local: http://localhost/api/v1 (port 80 via Traefik) +# Staging: https://api.staging.goodgo.vn/api/v1 +API_GATEWAY_URL=http://localhost/api/v1 # Default shop for convenience (Cobic Coffee) DEFAULT_SHOP_ID=e1f392af-fe95-4c7f-8656-5b74ad5fd0a9 diff --git a/services/goodgo-mcp-server/.gitignore b/services/goodgo-mcp-server/.gitignore new file mode 100644 index 00000000..deed335b --- /dev/null +++ b/services/goodgo-mcp-server/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.env diff --git a/services/goodgo-mcp-server/src/services/api-client.ts b/services/goodgo-mcp-server/src/services/api-client.ts index ceb8fef3..86a73542 100644 --- a/services/goodgo-mcp-server/src/services/api-client.ts +++ b/services/goodgo-mcp-server/src/services/api-client.ts @@ -3,6 +3,16 @@ import dotenv from "dotenv"; dotenv.config(); +/** + * All microservices run behind Traefik API Gateway on port 80. + * Traefik routes by path prefix: + * /api/v1/products, /api/v1/categories → catalog-service + * /api/v1/inventory, /api/v1/stock → inventory-service + * /api/v1/kitchen, /api/v1/tables → fnb-engine + * /api/v1/orders → order-service + */ +const GATEWAY_URL = process.env.API_GATEWAY_URL || "http://localhost/api/v1"; + function createClient(baseURL: string): AxiosInstance { const client = axios.create({ baseURL, @@ -21,21 +31,14 @@ function createClient(baseURL: string): AxiosInstance { return client; } -export const catalogApi = createClient( - process.env.CATALOG_API_URL || "http://localhost:5002/api/v1" -); +// Single gateway client — Traefik routes by path prefix +const gateway = createClient(GATEWAY_URL); -export const inventoryApi = createClient( - process.env.INVENTORY_API_URL || "http://localhost:5003/api/v1" -); - -export const fnbApi = createClient( - process.env.FNB_API_URL || "http://localhost:5019/api/v1" -); - -export const orderApi = createClient( - process.env.ORDER_API_URL || "http://localhost:5004/api/v1" -); +// Export aliases for backward compatibility (all point to same gateway) +export const catalogApi = gateway; +export const inventoryApi = gateway; +export const fnbApi = gateway; +export const orderApi = gateway; export const DEFAULT_SHOP_ID = process.env.DEFAULT_SHOP_ID || "e1f392af-fe95-4c7f-8656-5b74ad5fd0a9"; diff --git a/services/goodgo-mcp-server/src/tools/analytics-tools.ts b/services/goodgo-mcp-server/src/tools/analytics-tools.ts index da467106..cf195af3 100644 --- a/services/goodgo-mcp-server/src/tools/analytics-tools.ts +++ b/services/goodgo-mcp-server/src/tools/analytics-tools.ts @@ -9,12 +9,17 @@ interface TopItem { } interface DashboardData { - totalRevenue: number; + // API returns "revenue" (not "totalRevenue") + revenue: number; + totalRevenue?: number; orderCount: number; itemsSold: number; avgOrderValue: number; - topItems: TopItem[]; - revenueByHour: any[]; + // API returns "popularItems" (not "topItems") + popularItems?: TopItem[]; + topItems?: TopItem[]; + hourlyRevenue?: any[]; + revenueByHour?: any[]; } interface InventoryItem { @@ -47,12 +52,14 @@ export function registerAnalyticsTools(server: McpServer): void { params: { shopId: resolvedShopId, period }, }); - const dashboard: DashboardData = - response.data?.data ?? response.data ?? {}; + // Dashboard API may or may not wrap in {data: ...} + const raw = response.data?.data ?? response.data ?? {}; + const dashboard: DashboardData = raw; - const topItems: TopItem[] = dashboard.topItems ?? []; + const topItems: TopItem[] = dashboard.popularItems ?? dashboard.topItems ?? []; + const totalRevenue = dashboard.revenue ?? dashboard.totalRevenue ?? 0; - if (topItems.length === 0) { + if (topItems.length === 0 && totalRevenue === 0) { return { content: [ { @@ -65,19 +72,19 @@ export function registerAnalyticsTools(server: McpServer): void { const lines = topItems.map((item, i) => { const rank = `#${i + 1}`; - const revenue = item.revenue.toLocaleString(); - return `${rank} ${item.productName} — ${item.quantitySold} sold (${revenue} VND revenue)`; + const rev = (item.revenue ?? 0).toLocaleString(); + return `${rank} ${item.productName} — ${item.quantitySold} sold (${rev} VND revenue)`; }); const summary = [ `Top Selling Products (${period})`, `${"—".repeat(40)}`, - `Total Revenue: ${dashboard.totalRevenue.toLocaleString()} VND`, - `Total Orders: ${dashboard.orderCount}`, - `Items Sold: ${dashboard.itemsSold}`, - `Avg Order Value: ${dashboard.avgOrderValue.toLocaleString()} VND`, + `Total Revenue: ${totalRevenue.toLocaleString()} VND`, + `Total Orders: ${dashboard.orderCount ?? 0}`, + `Items Sold: ${dashboard.itemsSold ?? 0}`, + `Avg Order Value: ${(dashboard.avgOrderValue ?? 0).toLocaleString()} VND`, ``, - `Rankings:`, + topItems.length > 0 ? `Rankings:` : `(No product rankings available)`, ...lines, ].join("\n"); @@ -134,7 +141,7 @@ export function registerAnalyticsTools(server: McpServer): void { ) : 0; - const totalRevenue = dashboard.totalRevenue ?? 0; + const totalRevenue = dashboard.revenue ?? dashboard.totalRevenue ?? 0; const grossMargin = totalRevenue > 0 ? ((totalRevenue - totalInventoryCost) / totalRevenue) * 100 diff --git a/services/goodgo-mcp-server/src/tools/catalog-tools.ts b/services/goodgo-mcp-server/src/tools/catalog-tools.ts index d9348234..c33a9060 100644 --- a/services/goodgo-mcp-server/src/tools/catalog-tools.ts +++ b/services/goodgo-mcp-server/src/tools/catalog-tools.ts @@ -65,6 +65,7 @@ export function registerCatalogTools(server: McpServer): void { const resolvedShopId = shopId ?? DEFAULT_SHOP_ID; const params: Record = { + shopId: resolvedShopId, isActive, page, pageSize, @@ -72,7 +73,7 @@ export function registerCatalogTools(server: McpServer): void { if (categoryId) params.categoryId = categoryId; const { data: res } = await catalogApi.get( - `/shops/${resolvedShopId}/products`, + `/products`, { params }, );