205 lines
15 KiB
Markdown
205 lines
15 KiB
Markdown
# DBK Platform MCP Server
|
|
|
|
> HomAroma is the reference tenant. This server exposes shared MCP capabilities; a tool call must use the active tenant's data and configuration.
|
|
|
|
MCP server สำหรับเชื่อม AI tools เข้ากับ backend ของ DBK Platform ผ่าน stdio transport โดยใช้ข้อมูลและการตั้งค่าของ tenant ที่กำลังเชื่อมต่อ
|
|
|
|
## Run
|
|
|
|
```bash
|
|
bun install
|
|
bun run --cwd apps/mcp-server dev
|
|
```
|
|
|
|
หรือรันตรงจาก package:
|
|
|
|
```bash
|
|
cd apps/mcp-server
|
|
bun run dev
|
|
```
|
|
|
|
## Environment
|
|
|
|
### Multi-customer target profiles (recommended)
|
|
|
|
MCP process เดียวสลับ backend ลูกค้าหลายรายได้ด้วย allowlist ใน private JSON registry ตัวอย่างโครงสร้างอยู่ที่ [target-profiles.example.json](./target-profiles.example.json) ให้คัดลอกไปยัง absolute path **นอก Git** แล้วกำหนด:
|
|
|
|
- `MCP_TARGET_PROFILES_FILE` = absolute path ของ registry
|
|
- `MCP_DEFAULT_TARGET_PROFILE` = profile ID ที่เริ่มใช้งานทุกครั้งหลัง restart
|
|
- ตัวแปร username/password ที่แต่ละ profile ระบุใน `usernameEnv` และ `passwordEnv` ผ่าน secret store หรือ environment ของ MCP client
|
|
|
|
Registry เก็บเพียง `id`, `displayName`, `kind` (`local`/`staging`/`production`), `baseUrl`, และ **ชื่อตัวแปร** credential; ห้ามใส่รหัสผ่านจริงใน JSON หรือ Git แต่ละ profile ต้องใช้ชื่อตัวแปร credential แยกกัน ไม่รับ URL ซ้ำกัน และตรวจว่าทุก secret มีค่าก่อนเริ่ม server URL ระยะไกลต้องเป็น HTTPS; HTTP อนุญาตเฉพาะ loopback หาก config ผิด server จะไม่เริ่ม
|
|
|
|
ใน profile mode ใช้ `list_target_profiles` → `select_target_profile` ด้วย ID → `get_target_environment` เพื่อยืนยัน URL ก่อนเขียน ทุก backend-facing tool ต้องส่ง `expectedProfileId` ให้ตรงกับ active profile; หากแชตอื่นสลับเป้าหมายระหว่างนั้น คำขอจะถูกปฏิเสธก่อนแตะ backend คำขอ MCP ถูกประมวลผลแบบเรียงลำดับเพื่อไม่ให้การสลับ target ตัดกลางคำขอ; auth token เก็บแยกตาม profile การเรียก `set_target_environment` รวมทั้ง `customUrl` จะถูกปฏิเสธ และไม่ fallback ไป URL หรือ admin credential ชุด legacy การเปลี่ยน active profile มีผลเฉพาะ process ปัจจุบัน; restart กลับไป `MCP_DEFAULT_TARGET_PROFILE`
|
|
|
|
Registry เป็น allowlist ของปลายทาง **ไม่ใช่ระบบ authorization แยกตามแชต/ผู้ใช้** ทุก client ที่เข้าถึง MCP process เดียวกันสามารถเลือกทุก profile ใน registry ได้ และ `expectedProfileId` ป้องกันการส่งคำขอไปผิดเป้าหมายโดยไม่ตั้งใจเท่านั้น หากลูกค้าอยู่คนละ trust group ให้ใช้ MCP process และ registry แยกกัน
|
|
|
|
`upload_product_image_from_url` ถูกซ่อนและปฏิเสธใน profile mode เพราะตัวดาวน์โหลดเดิมรับ URL ภายนอกโดยไม่มี outbound allowlist หรือ resource limits; legacy mode ยังคง behavior เดิม ระหว่างนี้ให้ใช้ workflow อัปโหลดที่ผ่านการตรวจความปลอดภัยจากระบบอื่น
|
|
|
|
**Migration:** ตั้ง registry และ secret environment ทุก profile ให้ครบก่อนเปิด `MCP_TARGET_PROFILES_FILE` แล้ว restart MCP client ตรวจ `get_target_environment` ว่า mode เป็น `profiles` และ ID/URL ถูกต้องก่อนใช้ tool เขียนข้อมูล อย่าคัดลอก URL หรือ credential ของลูกค้าจริงลงตัวอย่างใน repository
|
|
|
|
### Legacy mode (no registry)
|
|
|
|
รองรับตัวแปรต่อไปนี้เพื่อความเข้ากันได้:
|
|
|
|
- `MCP_BACKEND_URL` สำหรับกำหนด backend target ตรง ๆ
|
|
- `MCP_BACKEND_URL_LOCAL` ค่าเริ่มต้นสำหรับ local
|
|
- `MCP_BACKEND_URL_PRODUCTION` ค่าเริ่มต้นสำหรับ production
|
|
- `MCP_ADMIN_USERNAME`
|
|
- `MCP_ADMIN_PASSWORD`
|
|
|
|
ถ้าไม่ตั้งค่า `MCP_BACKEND_URL` server จะเริ่มที่ local backend และสลับเป็น production ได้ด้วย tool `set_target_environment`
|
|
|
|
ชื่อ `homaroma` ที่ยังอยู่ใน package, server identity, config key หรือ URL เป็น legacy runtime identifier เพื่อความเข้ากันได้ ไม่ใช่ชื่อแพลตฟอร์มหรือค่าแบรนด์สำหรับทุก tenant การเปลี่ยน identifier เหล่านี้ต้องทำเป็น migration แยกต่างหาก
|
|
|
|
ข้อควรระวังใน legacy mode: `set_target_environment` ยังใช้ admin credential ชุดเดียวกับทุก backend; `production` ยังมี HomAroma URL เป็น fallback หากไม่ได้กำหนด `MCP_BACKEND_URL_PRODUCTION` และ `customUrl` รับ URL อิสระ จึงไม่ควรใช้ legacy mode เพื่อสลับหลายลูกค้า
|
|
|
|
legacy mode ยังมีค่า admin สำรองในโค้ดตามการตัดสินใจชั่วคราวของเจ้าของโปรเจกต์ ควรกำหนด `MCP_ADMIN_USERNAME` และ `MCP_ADMIN_PASSWORD` ในสภาพแวดล้อมของ MCP client และย้ายออกจากค่าสำรองในรอบ migration ถัดไป อย่าใส่รหัสผ่านในแชตหรือ commit ลง repository ค่า fallback นี้ **ไม่ถูกใช้ใน profile mode**
|
|
|
|
## Available tools
|
|
|
|
- `upload_cms_asset` อัปโหลด JPEG/PNG/WebP/GIF จากไฟล์ในเครื่องเข้า Media Library ของ backend ปัจจุบัน แล้วคืน `url` และ `path` สำหรับ `ARTICLE_MEDIA` / `ARTICLE_GALLERY` โดยไม่ผูกกับสินค้า ต้องตั้ง `MCP_ASSET_ROOT` เป็น absolute directory ของไฟล์ที่อนุญาตก่อน restart; ส่ง `filePath` แบบ absolute ภายใน directory นั้น ขนาดไม่เกิน 10 MiB และใน profile mode ต้องส่ง `expectedProfileId` ด้วย Tool ตรวจ real path, ชนิดไฟล์จากนามสกุลและ magic bytes; ไม่รับ URL ภายนอก, SVG หรือ base64 การอัปโหลดสร้าง Media record แม้ยังไม่เพิ่มภาพลงหน้า CMS ให้ตรวจ target ด้วย `get_target_environment` ก่อนเสมอ
|
|
- `update_brand_name` เปลี่ยนชื่อแบรนด์กลางที่ใช้ใน header/footer ของ `apps/spa` และ `apps/website`
|
|
- `update_store_settings_translation` อัปเดตข้อความแปลของ store settings
|
|
- `update_footer_content` อัปเดตคำอธิบายแบรนด์และ social links เท่านั้น
|
|
- `get_footer_brand` / `update_footer_brand` อ่านและแก้ Footer Brand block โดยตรง (brand name, logo, คำอธิบายแยกภาษา, social links) พร้อม `dryRun` และ `expectedVersion`
|
|
- `get_footer_layout` / `patch_footer_layout` อ่านและแก้โครง Footer พร้อม preview และการตรวจ version ก่อนเขียน
|
|
- `get_category_sections` / `seed_category_sections` / `add_category_section` / `update_category_section` / `reorder_category_sections` / `delete_category_section` จัดการ section ของหน้า Category
|
|
|
|
Tool definitions มี annotation บอกการอ่านอย่างเดียวหรือการลบ และผลลัพธ์ JSON มี `structuredContent` เพิ่มจากข้อความเดิมเพื่อให้ LLM ใช้ง่ายขึ้น โดยยังเข้ากันได้กับ client ที่อ่าน `content` อยู่
|
|
|
|
ตัวอย่างจาก `get_page_block_schema` เป็นโครง payload สำหรับเริ่มต้น ไม่ใช่เนื้อหาที่ควรเผยแพร่ทันที ให้แทน URL ตัวอย่าง ข้อความ ผู้เขียน และช่องทางติดต่อด้วยข้อมูลที่ตรวจสอบแล้วของ tenant ปัจจุบันก่อนเขียนจริง โดยเฉพาะ `CONTACT_INFO` ที่มี email และ LINE ID เป็น placeholder
|
|
|
|
## Safe editing workflow
|
|
|
|
1. เรียก `get_target_environment` ก่อนแก้ข้อมูล โดยเฉพาะหลัง restart; ใน profile mode ยืนยันทั้ง profile ID และ URL อย่าเขียน production ถ้าผู้ใช้ไม่ได้ขอชัดเจน
|
|
2. อ่านข้อมูลเป้าหมายก่อน แล้วเลือก tool ที่เฉพาะงาน
|
|
3. ใช้ `dryRun: true` เมื่อมี, ตรวจผล preview แล้วค่อยเขียนจริง
|
|
4. ส่ง `expectedVersion` สำหรับ Footer layout และงานอื่นที่รองรับ version
|
|
5. อ่านข้อมูลซ้ำหลังเขียนเพื่อตรวจผล
|
|
|
|
Category section tools ทำ preview ฝั่ง MCP เท่านั้น เพราะ backend ปัจจุบันยังไม่มี `dryRun` หรือ version check แบบ atomic สำหรับ endpoint กลุ่มนี้ ข้อมูลอาจเปลี่ยนระหว่าง preview กับการเขียนจริง; ให้ตรวจรายการล่าสุดก่อนเขียนเสมอ
|
|
|
|
## Footer Menu LLM Workflow
|
|
|
|
Footer Explore and Footer Support are flat item menus, not Mega Menu payload trees. Supported menu names are:
|
|
|
|
```text
|
|
footer-explore
|
|
footer-support
|
|
```
|
|
|
|
Use this workflow for every Footer menu edit:
|
|
|
|
1. Call `get_footer_menu` with the target language.
|
|
2. Keep the returned `version`.
|
|
3. Call one item-level write tool with `dryRun: true` and `expectedVersion`.
|
|
4. Review the compact preview.
|
|
5. Apply the same operation with `dryRun: false` and the same version.
|
|
6. Call `get_footer_menu` again to verify and obtain the next version.
|
|
|
|
If an allowed Footer menu does not exist yet, `get_footer_menu` returns `exists: false`, `version: null`, and an empty item list. Previewing the first add returns `willCreateMenu: true`; applying that add creates the menu as active and inserts the first item atomically.
|
|
|
|
Available item-level tools:
|
|
|
|
- `add_footer_menu_item`
|
|
- `update_footer_menu_item`
|
|
- `delete_footer_menu_item`
|
|
- `reorder_footer_menu_items`
|
|
- `update_footer_menu_item_translation`
|
|
|
|
Every reorder request must include every current item ID exactly once. A `MENU_VERSION_CONFLICT` response means Admin or another agent changed the menu after it was read; fetch it again before retrying.
|
|
|
|
Do not pass `exploreLinks`, `collectionLinks`, or `supportLinks` to legacy `update_footer_content`. Full menu-array replacement is rejected because it can delete item IDs and translations. Website Collections are populated from Categories rather than a `footer-collections` menu.
|
|
|
|
## Mega Menu LLM Workflow
|
|
|
|
Header Mega Menu uses the `main` menu payload tree. The website renders this structure:
|
|
|
|
```text
|
|
Root -> Column -> Link -> Child Link
|
|
```
|
|
|
|
Supported localized text keys are:
|
|
|
|
```text
|
|
th, en, ja, zh, vi, ko, lo, it
|
|
```
|
|
|
|
LLM agents should use this workflow for every menu edit:
|
|
|
|
1. `capture_menu_snapshot` for `main`
|
|
2. `get_mega_menu_tree`
|
|
3. `validate_mega_menu_tree`
|
|
4. Plan operations from diagnostics and current `nodeId` values
|
|
5. `bulk_menu_operations` with `dryRun: true`
|
|
6. Review `preview.diff`, `preview.before`, `preview.after`, `issues`, and `warnings`
|
|
7. Apply only after approval
|
|
8. Run `validate_mega_menu_tree` again
|
|
9. Use `restore_menu_snapshot` if the applied result is wrong
|
|
|
|
Do not add a Link directly under root. Add a Column under Root first, then add Links under that Column.
|
|
|
|
Dry-run responses are expected to include path-based diffs such as:
|
|
|
|
```json
|
|
{
|
|
"path": "payload[0].columns[0].links[0].label.it",
|
|
"before": null,
|
|
"after": "Candele"
|
|
}
|
|
```
|
|
|
|
`validate_mega_menu_tree` reports:
|
|
|
|
- missing locale keys, especially `lo` and `it`
|
|
- unsupported translation keys
|
|
- duplicate or missing `nodeId`
|
|
- invalid URL shape
|
|
- placeholder text
|
|
- empty columns
|
|
- featured card issues
|
|
- desktop render limits such as hidden columns or links
|
|
|
|
## Category Management LLM Workflow
|
|
|
|
Product categories are managed by backend `/categories` APIs through MCP. Supported localized text keys are:
|
|
|
|
```text
|
|
th, en, ja, zh, vi, ko, lo, it
|
|
```
|
|
|
|
LLM agents should use this workflow for category edits:
|
|
|
|
1. `list_categories` with `lang: "all"` to discover category IDs and current translations
|
|
2. `create_category`, `update_category`, `update_category_media`, `update_category_translations`, or `delete_category` with `dryRun: true`
|
|
3. Review the preview and product counts
|
|
4. Apply only after the intended change is clear
|
|
5. Run `list_categories` again to verify
|
|
|
|
Category media uses semantic roles: `thumbnail`, `hero_desktop`, `hero_mobile`,
|
|
`card_portrait`, and `social_share`. `update_category_media` replaces the complete
|
|
media set, so omitted roles are removed. `update_category_image` remains available
|
|
only as a legacy thumbnail compatibility tool.
|
|
|
|
Backend category support currently covers:
|
|
|
|
- base `name`
|
|
- `imageUrl`
|
|
- localized names
|
|
- product-count guarded soft delete (`deletedAt`)
|
|
|
|
Backend category support does not currently cover:
|
|
|
|
- slug
|
|
- nested parent/child hierarchy
|
|
- sort order
|
|
- active/inactive state
|
|
- restore/undelete workflow
|
|
|
|
## Scripts
|
|
|
|
- `bun run dev`
|
|
- `bun run start`
|
|
- `bun run check`
|