fix update Section Block
This commit is contained in:
1 parent
996dfdabd0
commit
6f67ee6077
47 files changed
+1792
-197
No files matched your search
@@ -1,15 +1,35 @@
|
||||
# mcp-server
|
||||
# HomAroma MCP Server
|
||||
|
||||
To install dependencies:
|
||||
MCP server สำหรับเชื่อม AI tools เข้ากับ backend ของ HomAroma ผ่าน stdio transport
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run --cwd apps/mcp-server dev
|
||||
```
|
||||
|
||||
To run:
|
||||
หรือรันตรงจาก package:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
cd apps/mcp-server
|
||||
bun run dev
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||
## Environment
|
||||
|
||||
รองรับตัวแปรต่อไปนี้:
|
||||
|
||||
- `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`
|
||||
|
||||
## Scripts
|
||||
|
||||
- `bun run dev`
|
||||
- `bun run start`
|
||||
- `bun run check`
|
||||
+224
-7
@@ -40,9 +40,196 @@ function findNodeAndParent(tree: any[], targetId: string, parent: any = null, li
|
||||
return null;
|
||||
}
|
||||
|
||||
let BACKEND_URL = "http://localhost:3051/api";
|
||||
const ADMIN_USERNAME = process.env.ADMIN_USERNAME || "admin";
|
||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "homaroma123"; // เปลี่ยนรหัสผ่านตรงนี้ให้ตรงกับ Production ด้วยนะครับ
|
||||
const localizedStringSchema = {
|
||||
oneOf: [
|
||||
{ type: "string" },
|
||||
{
|
||||
type: "object",
|
||||
additionalProperties: { type: "string" }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const localizedStringListSchema = {
|
||||
type: "array",
|
||||
items: localizedStringSchema
|
||||
};
|
||||
|
||||
const SECTION_TOOL_CONFIGS = [
|
||||
{
|
||||
name: "update_hero_article",
|
||||
sectionType: "HERO_ARTICLE",
|
||||
description: "Update a HERO_ARTICLE section with structured editorial fields.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized article title" },
|
||||
subtitle: { ...localizedStringSchema, description: "Localized subtitle" },
|
||||
author: { ...localizedStringSchema, description: "Localized author name" },
|
||||
category: { ...localizedStringSchema, description: "Localized category or tag" },
|
||||
publishDate: { type: "string", description: "Publish date or display date" },
|
||||
date: { type: "string", description: "Alternative date field accepted by backend" },
|
||||
imageUrl: { type: "string", description: "Main image URL" },
|
||||
backgroundImageUrl: { type: "string", description: "Background image URL" },
|
||||
overlayColor: { type: "string", description: "Overlay color in hex or rgba" }
|
||||
},
|
||||
required: ["sectionId"]
|
||||
},
|
||||
{
|
||||
name: "update_rich_text",
|
||||
sectionType: "RICH_TEXT",
|
||||
description: "Update a RICH_TEXT section with localized title and content.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized title displayed above the content" },
|
||||
content: { ...localizedStringSchema, description: "Localized HTML or plain text content" }
|
||||
},
|
||||
required: ["sectionId", "content"]
|
||||
},
|
||||
{
|
||||
name: "update_scent_notes_breakdown",
|
||||
sectionType: "SCENT_NOTES_BREAKDOWN",
|
||||
description: "Update a SCENT_NOTES_BREAKDOWN section with localized title, description, and note groups.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized title" },
|
||||
description: { ...localizedStringSchema, description: "Localized supporting description" },
|
||||
topNotes: { type: "array", items: localizedStringSchema, description: "Top note values" },
|
||||
heartNotes: { type: "array", items: localizedStringSchema, description: "Heart note values" },
|
||||
middleNotes: { type: "array", items: localizedStringSchema, description: "Middle note values" },
|
||||
baseNotes: { type: "array", items: localizedStringSchema, description: "Base note values" }
|
||||
},
|
||||
required: ["sectionId"]
|
||||
},
|
||||
{
|
||||
name: "update_quote_callout",
|
||||
sectionType: "QUOTE_CALLOUT",
|
||||
description: "Update a QUOTE_CALLOUT section with a localized quote, author, and role.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
quote: { ...localizedStringSchema, description: "Localized quote text" },
|
||||
author: { ...localizedStringSchema, description: "Localized author name" },
|
||||
role: { ...localizedStringSchema, description: "Localized author role or title" }
|
||||
},
|
||||
required: ["sectionId"]
|
||||
},
|
||||
{
|
||||
name: "update_product_carousel",
|
||||
sectionType: "PRODUCT_CAROUSEL",
|
||||
description: "Update a PRODUCT_CAROUSEL section with localized copy and carousel options.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized title" },
|
||||
description: { ...localizedStringSchema, description: "Localized supporting description" },
|
||||
collectionId: { type: "string", description: "Optional collection identifier" },
|
||||
limit: { type: "number", description: "Maximum number of products to show" },
|
||||
showPrice: { type: "boolean", description: "Show product price" },
|
||||
showAddToCart: { type: "boolean", description: "Show add to cart button" }
|
||||
},
|
||||
required: ["sectionId"]
|
||||
},
|
||||
{
|
||||
name: "update_accordion",
|
||||
sectionType: "ACCORDION",
|
||||
description: "Update an ACCORDION section with categories, questions, and localized answers.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized section title" },
|
||||
categories: {
|
||||
type: "array",
|
||||
description: "Accordion categories",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { ...localizedStringSchema, description: "Localized category name" },
|
||||
icon: { type: "string", description: "Optional icon or emoji" },
|
||||
questions: {
|
||||
type: "array",
|
||||
description: "Questions under the category",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
q: { ...localizedStringSchema, description: "Localized question" },
|
||||
a: { ...localizedStringSchema, description: "Localized answer" }
|
||||
},
|
||||
required: ["q", "a"]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ["name", "questions"]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ["sectionId", "categories"]
|
||||
},
|
||||
{
|
||||
name: "update_contact_info",
|
||||
sectionType: "CONTACT_INFO",
|
||||
description: "Update a CONTACT_INFO section with localized address, email, line, and hours.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized section title" },
|
||||
address: { ...localizedStringSchema, description: "Localized address" },
|
||||
email: { ...localizedStringSchema, description: "Localized email" },
|
||||
line: { ...localizedStringSchema, description: "Localized Line ID or contact" },
|
||||
hours: { ...localizedStringSchema, description: "Localized business hours" }
|
||||
},
|
||||
required: ["sectionId", "address", "email", "line", "hours"]
|
||||
},
|
||||
{
|
||||
name: "update_contact_form",
|
||||
sectionType: "CONTACT_FORM",
|
||||
description: "Update a CONTACT_FORM section with localized title and subtitle.",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to update" },
|
||||
title: { ...localizedStringSchema, description: "Localized form title" },
|
||||
subtitle: { ...localizedStringSchema, description: "Localized form subtitle" }
|
||||
},
|
||||
required: ["sectionId"]
|
||||
}
|
||||
] as const;
|
||||
|
||||
const SECTION_TOOL_CONFIG_BY_NAME = new Map(
|
||||
SECTION_TOOL_CONFIGS.map((config) => [config.name, config] as const)
|
||||
);
|
||||
|
||||
function buildSectionPayload(params: any, fields: readonly string[]) {
|
||||
const payload: Record<string, any> = {};
|
||||
for (const field of fields) {
|
||||
if (params[field] !== undefined) {
|
||||
payload[field] = params[field];
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildSectionUpdateResult(toolName: string, sectionType: string, updatedSection: any, payload: Record<string, any>) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
tool: toolName,
|
||||
sectionType,
|
||||
sectionId: updatedSection?.id,
|
||||
updatedFields: Object.keys(payload),
|
||||
section: updatedSection
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const LOCAL_BACKEND_URL = process.env.MCP_BACKEND_URL_LOCAL || "http://localhost:3051/api";
|
||||
const PRODUCTION_BACKEND_URL = process.env.MCP_BACKEND_URL_PRODUCTION || "https://api.homaroma.shop/api";
|
||||
|
||||
let BACKEND_URL = process.env.MCP_BACKEND_URL || LOCAL_BACKEND_URL;
|
||||
const ADMIN_USERNAME = process.env.MCP_ADMIN_USERNAME || process.env.ADMIN_USERNAME || "admin";
|
||||
const ADMIN_PASSWORD = process.env.MCP_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD || "homaroma123";
|
||||
|
||||
let authToken = "";
|
||||
|
||||
@@ -442,6 +629,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
required: ["sectionId"]
|
||||
}
|
||||
},
|
||||
...SECTION_TOOL_CONFIGS.map((config) => ({
|
||||
name: config.name,
|
||||
description: config.description,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: config.properties,
|
||||
required: config.required
|
||||
}
|
||||
})),
|
||||
{
|
||||
name: "delete_page_section",
|
||||
description: "Delete a block section from a dynamic page layout",
|
||||
@@ -569,9 +765,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
case "set_target_environment": {
|
||||
const env = params.env;
|
||||
if (env === "production") {
|
||||
BACKEND_URL = "https://api.homaroma.shop/api";
|
||||
BACKEND_URL = PRODUCTION_BACKEND_URL;
|
||||
} else {
|
||||
BACKEND_URL = "http://localhost:3051/api";
|
||||
BACKEND_URL = LOCAL_BACKEND_URL;
|
||||
}
|
||||
api.defaults.baseURL = BACKEND_URL;
|
||||
authToken = ""; // reset token so it logs in again on next request
|
||||
@@ -807,7 +1003,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (params.payload) payload.payload = params.payload;
|
||||
if (params.isActive !== undefined) payload.isActive = params.isActive;
|
||||
const res = await api.put(`/pages/sections/${params.sectionId}`, payload);
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
return buildSectionUpdateResult("update_page_section", res.data?.type ?? params.type ?? "UNKNOWN", res.data, payload);
|
||||
}
|
||||
case "update_hero_article":
|
||||
case "update_rich_text":
|
||||
case "update_scent_notes_breakdown":
|
||||
case "update_quote_callout":
|
||||
case "update_product_carousel":
|
||||
case "update_accordion":
|
||||
case "update_contact_info":
|
||||
case "update_contact_form": {
|
||||
const config = SECTION_TOOL_CONFIG_BY_NAME.get(request.params.name);
|
||||
if (!config) {
|
||||
throw new Error(`Unknown tool config for: ${request.params.name}`);
|
||||
}
|
||||
|
||||
const payload = buildSectionPayload(params, Object.keys(config.properties).filter((key) => key !== "sectionId"));
|
||||
const res = await api.put(`/pages/sections/${params.sectionId}`, {
|
||||
type: config.sectionType,
|
||||
...payload
|
||||
});
|
||||
|
||||
return buildSectionUpdateResult(request.params.name, config.sectionType, res.data, payload);
|
||||
}
|
||||
case "delete_page_section": {
|
||||
const res = await api.delete(`/pages/sections/${params.sectionId}`);
|
||||
@@ -1043,4 +1260,4 @@ async function main() {
|
||||
console.error("HomAroma MCP Server running on stdio");
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
main().catch(console.error);
|
||||
@@ -2,6 +2,11 @@
|
||||
"name": "mcp-server",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun --watch index.ts",
|
||||
"start": "bun index.ts",
|
||||
"check": "bunx tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd /home/kitisak/Projects/homaroma/apps/mcp-server
|
||||
/home/kitisak/.bun/bin/bun run index.ts
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
bun run index.ts
|
||||
Reference in new issue
Block a user