Project-level docs:
- README.md: rewrite with correct tech stack (.NET 10/Blazor, not Node.js/Flutter)
- ROADMAP.md: add MCP server, shop lifecycle, onboarding redesign, POS nav fix
- CLAUDE.md: add goodgo-mcp-server to project structure
- CTO_REPORT_SHOP_DELETE.md: status OPEN → RESOLVED (implemented in 6263eeb)
MCP Server docs:
- SERVICE_DOCS.md: new file — 12 tools reference, architecture, setup guide
Frontend docs:
- web-client-tpos-net README: fix wrong paths (web-client-base-net → web-client-tpos-net)
- web-client-tpos-net docs/en/README: same path fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
384 lines
15 KiB
Markdown
384 lines
15 KiB
Markdown
# 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.
|