# GoodGo MCP Server - Service Documentation > Auto-generated from source code audit on 2026-03-20. ## Overview **GoodGo MCP Server** is a TypeScript Model Context Protocol (MCP) server that enables AI assistants (Claude Code, Claude Desktop) to perform F&B shop operations -- catalog management, inventory tracking, recipe handling, and sales analytics -- by proxying requests through the Traefik API gateway to backend .NET microservices. - **Package**: `@goodgo/mcp-server` v1.0.0 - **Runtime**: Node.js 25+ (ESM) - **Language**: TypeScript 5.7+ - **Transport**: stdio (local Claude Code integration) - **SDK**: `@modelcontextprotocol/sdk` 1.12.1 - **Validation**: Zod 3.24 - **HTTP Client**: Axios 1.7 - **Default Shop**: Cobic Coffee (`e1f392af-fe95-4c7f-8656-5b74ad5fd0a9`) - **Auth**: JWT Bearer token (from IAM IdentityServer OAuth2 password grant) --- ## Configuration Environment variables (`.env` file in service root): | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `API_GATEWAY_URL` | No | `http://localhost/api/v1` | Traefik gateway base URL. Docker local uses port 80. Staging: `https://api.staging.goodgo.vn/api/v1` | | `DEFAULT_SHOP_ID` | No | `e1f392af-fe95-4c7f-8656-5b74ad5fd0a9` | Default shop UUID (Cobic Coffee). Used when tools omit `shopId`. | | `API_TOKEN` | **Yes** | _(empty)_ | JWT Bearer token from IAM login. All API calls fail 401 without this. | --- ## Architecture ### Source Structure ``` goodgo-mcp-server/ src/ index.ts # Entry point: McpServer + StdioServerTransport, registers all tool groups services/ api-client.ts # Axios gateway client (single instance, Bearer interceptor, token-leak guard) error-handler.ts # Shared errorResponse() + textResponse() helpers tools/ catalog-tools.ts # 4 tools: list_products, create_product, update_product, delete_product inventory-tools.ts # 4 tools: check_inventory, record_intake, record_usage, low_stock_alerts recipe-tools.ts # 2 tools: list_recipes, create_recipe analytics-tools.ts # 2 tools: popular_items, cost_analysis types/ # (reserved, currently empty) dist/ # Compiled JS output (ESM) .env # Local config (git-ignored) .env.example # Template package.json tsconfig.json ``` ### Gateway Routing All tools call a **single Axios client** pointed at `API_GATEWAY_URL` (default `http://localhost/api/v1`). Traefik routes by path prefix to the appropriate backend microservice: | Path Prefix | Backend Service | Tools Using It | |-------------|----------------|----------------| | `/products` | catalog-service-net (port 5016) | list_products, create_product, update_product, delete_product | | `/inventory`, `/inventory/stock-in`, `/inventory/stock-out`, `/inventory/low-stock` | inventory-service-net (port 5020) | check_inventory, record_intake, record_usage, low_stock_alerts | | `/kitchen/recipes` | fnb-engine-net (port 5018) | list_recipes, create_recipe | | `/orders/dashboard` | order-service-net (port 5014) | popular_items, cost_analysis | ### Error Handling `error-handler.ts` provides a centralized `errorResponse()` function that: 1. Extracts structured error messages from backend `ApiResponse` format (`error.message`, `error`, `Message`, `title`) 2. Maps HTTP status codes to user-friendly messages (401 = token expired, 403 = insufficient permissions, 404 = not found, 400 = validation errors) 3. Handles network errors (ECONNREFUSED, ETIMEDOUT) with actionable guidance 4. **Strips `Authorization` headers** from error objects to prevent token leakage 5. Returns MCP-compliant `{ content: [{ type: "text", text }], isError: true }` format ### Security - Bearer token injected via Axios request interceptor from `API_TOKEN` env var - Authorization header stripped from error responses to prevent token leakage in MCP tool output - Startup warning printed to stderr if `API_TOKEN` is not set --- ## Tools (12 total) ### Catalog Tools (4) #### `list_products` List menu items/products for a shop. Returns name, price, category, stock status. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `categoryId` | string (UUID) | No | - | Filter by category | | `isActive` | boolean | No | - | Filter by active status (omit to show all) | | `page` | number | No | 1 | Page number | | `pageSize` | number | No | 20 | Items per page (max 200) | **Backend**: `GET /products?shopId=&categoryId=&isActive=&page=&pageSize=` **Output**: Formatted table with Name, Price (VND), Category, Active columns. --- #### `create_product` Create a new product/menu item in the shop catalog. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `name` | string | **Yes** | - | Product name (1-200 chars) | | `description` | string | No | - | Description (max 1000 chars) | | `price` | number | **Yes** | - | Price in VND (positive) | | `type` | string | No | `PreparedFood` | Product type: PreparedFood, Beverage, RetailItem | | `categoryId` | string (UUID) | No | - | Category ID | | `sku` | string | No | - | Stock-keeping unit code | | `imageUrl` | string (URL) | No | - | Image URL | **Backend**: `POST /products` **Output**: Created product ID, name, and formatted price. --- #### `update_product` Update an existing product's name, price, description, or category. Fetches current values first to avoid overwriting unchanged fields (merge-patch semantics). | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `productId` | string (UUID) | **Yes** | - | Product ID to update | | `name` | string | No | - | New name (1-200 chars) | | `description` | string | No | - | New description (max 1000 chars) | | `price` | number | No | - | New price in VND | | `categoryId` | string (UUID) | No | - | New category ID | | `imageUrl` | string (URL) | No | - | New image URL | **Backend**: `GET /products/{id}` (fetch current) then `PUT /products/{id}` (full update with merged fields) **Output**: Updated fields summary. --- #### `delete_product` Deactivate a product from the catalog (soft delete). | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `productId` | string (UUID) | **Yes** | - | Product ID to deactivate | **Backend**: `DELETE /products/{id}` **Output**: Confirmation message. --- ### Inventory Tools (4) #### `check_inventory` Check current inventory/stock levels for a shop. Shows item name, quantity, unit, reorder level, and low stock warnings. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `skip` | number | No | 0 | Records to skip | | `take` | number | No | 50 | Records to take (max 200) | **Backend**: `GET /inventory?shopId=&skip=&take=` **Output**: Table with Name, Qty, Unit, Reorder Level, Status (OK/Low). --- #### `record_intake` Record stock intake (nhap kho) -- when goods are received from suppliers. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `productId` | string (UUID) | **Yes** | - | Product ID to stock in | | `amount` | number (int) | **Yes** | - | Quantity to add (positive integer) | | `notes` | string | No | - | Notes for this intake | | `unitCost` | number | No | - | Cost per unit | **Backend**: `POST /inventory/stock-in` **Output**: Intake confirmation with product, amount, cost, and notes. --- #### `record_usage` Record stock usage/outflow (xuat kho) -- when items are consumed, wasted, or adjusted. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `productId` | string (UUID) | **Yes** | - | Product ID to stock out | | `amount` | number (int) | **Yes** | - | Quantity to remove (positive integer) | | `notes` | string | No | - | Reason for usage | **Backend**: `POST /inventory/stock-out` **Output**: Usage confirmation with product, amount, and notes. --- #### `low_stock_alerts` Get items that are at or below their reorder level. Critical for preventing stockouts. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `skip` | number | No | 0 | Records to skip | | `take` | number | No | 50 | Records to take (max 200) | **Backend**: `GET /inventory/low-stock?shopId=&skip=&take=` **Output**: List of low-stock items with current quantity, reorder level, and deficit. --- ### Recipe Tools (2) #### `list_recipes` List all recipes for a shop. Shows recipe name, linked product, prep time, and ingredients. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | **Backend**: `GET /kitchen/recipes?shopId=` **Output**: Numbered list with recipe details and ingredient breakdown (name, quantity, unit, cost). --- #### `create_recipe` Create a new recipe with ingredients list. Links to a product in the catalog. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `productId` | string (UUID) | **Yes** | - | Catalog product this recipe is for | | `name` | string | **Yes** | - | Recipe name (1-200 chars) | | `instructions` | string | No | - | Preparation instructions (max 2000 chars) | | `prepTimeMinutes` | number (int) | No | 5 | Prep time in minutes | | `ingredients` | array | **Yes** | - | Ingredients list (min 1 item) | **Ingredient object schema**: | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `ingredientName` | string | **Yes** | - | Ingredient name | | `quantity` | number | **Yes** | - | Quantity required | | `unit` | string | **Yes** | - | Unit of measurement (g, ml, pcs, etc.) | | `costPerUnit` | number | No | 0 | Cost per unit | | `inventoryItemId` | string (UUID) | No | - | Linked inventory item ID | | `quantityPerServing` | number | No | 0 | Quantity per serving for inventory deduction | **Backend**: `POST /kitchen/recipes` **Output**: Created recipe ID, name, product link, prep time, and ingredient summary. --- ### Analytics Tools (2) #### `popular_items` Get top selling products by analyzing order data. Shows product name, quantity sold, and revenue. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | | `period` | enum | No | `7d` | Time period: `today`, `7d`, `30d` | **Backend**: `GET /orders/dashboard?shopId=&period=` **Output**: Rankings with total revenue, order count, items sold, avg order value, and per-product breakdown. --- #### `cost_analysis` Analyze cost structure by comparing inventory costs with revenue. Shows inventory value and revenue breakdown. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `shopId` | string (UUID) | No | `DEFAULT_SHOP_ID` | Shop ID | **Backend**: `GET /inventory?shopId=&take=200` + `GET /orders/dashboard?shopId=&period=30d` (parallel via `Promise.allSettled`) **Output**: Cost analysis report with 30-day revenue, inventory value, order stats, and top cost items ranked by inventory value. --- ## API Endpoints Consumed Summary of all backend API routes called by MCP tools: | Method | Route | Backend Service | Tool(s) | |--------|-------|----------------|---------| | GET | `/api/v1/products` | catalog-service | list_products | | GET | `/api/v1/products/{id}` | catalog-service | update_product (fetch current) | | POST | `/api/v1/products` | catalog-service | create_product | | PUT | `/api/v1/products/{id}` | catalog-service | update_product | | DELETE | `/api/v1/products/{id}` | catalog-service | delete_product | | GET | `/api/v1/inventory` | inventory-service | check_inventory, cost_analysis | | POST | `/api/v1/inventory/stock-in` | inventory-service | record_intake | | POST | `/api/v1/inventory/stock-out` | inventory-service | record_usage | | GET | `/api/v1/inventory/low-stock` | inventory-service | low_stock_alerts | | GET | `/api/v1/kitchen/recipes` | fnb-engine | list_recipes | | POST | `/api/v1/kitchen/recipes` | fnb-engine | create_recipe | | GET | `/api/v1/orders/dashboard` | order-service | popular_items, cost_analysis | --- ## Setup & Build ### Prerequisites - Node.js 25+ with ESM support - Docker stack running (Traefik + backend services) - Valid JWT token from IAM service ### Install & Build ```bash cd services/goodgo-mcp-server npm install npm run build # tsc -> dist/ ``` ### Run (development) ```bash npm run dev # tsx src/index.ts (hot reload) ``` ### Run (production) ```bash npm start # node dist/index.js ``` --- ## Claude Code Integration ### Register as MCP server ```bash claude mcp add --transport stdio goodgo -- node /absolute/path/to/services/goodgo-mcp-server/dist/index.js ``` Or with environment variables inline: ```bash claude mcp add --transport stdio goodgo -- \ env API_TOKEN=eyJ... DEFAULT_SHOP_ID=e1f392af-... \ node /absolute/path/to/services/goodgo-mcp-server/dist/index.js ``` ### Verify registration ```bash claude mcp list ``` --- ## Token Acquisition The MCP server requires a valid JWT token from the IAM service (Duende IdentityServer password grant). ### Get token via curl ```bash curl -s http://localhost/connect/token \ -d "grant_type=password" \ -d "username=hongochai10@icloud.com" \ -d "password=Velik@2026" \ -d "client_id=password-client" \ -d "client_secret=password-client-secret" \ | jq -r '.access_token' ``` ### Set token in .env ```bash # Copy template cp .env.example .env # Paste the access_token value echo "API_TOKEN=eyJhbGciOiJSUzI1NiIs..." >> .env ``` ### Token refresh Tokens expire (typically 1 hour). Re-run the curl command above and update `API_TOKEN` in `.env`. The MCP server reads `.env` at startup via `dotenv`, so restart the server after updating. --- ## Currency & Locale All prices are in **VND (Vietnamese Dong)**. The server formats prices using `Intl.NumberFormat("vi-VN")` (e.g., `45.000 VND`). When interacting with the AI assistant, prices display as `X.000d` format.