MCP cho AI agent
Máy chủ MCP (Model Context Protocol) để AI agent (Claude, Cursor, ChatGPT, agent tự viết…) làm việc trực tiếp với dữ liệu cửa hàng: đơn hàng, sản phẩm, danh mục, tồn kho, nhà cung cấp, phiếu nhập hàng (nguyên vật liệu), khách hàng, quy trình, đăng bài, giao hàng và KiotViet.
| URL | https://api.sieutroly.ai/api/mcp |
| Giao thức | Streamable HTTP, không giữ phiên: mỗi yêu cầu JSON-RPC độc lập, phản hồi JSON. Chỉ POST; GET/DELETE → 405 |
| Xác thực | API key của workspace: Authorization: Bearer sk_live_… (hoặc X-API-Key). Tạo ở web → API Key |
| Link MCP | https://api.sieutroly.ai/api/mcp/mcp_… — nút «Link MCP» trên từng API key, cho client không đặt được header (ChatGPT, Claude «Custom connector», n8n…). Link chỉ mở cửa MCP với đúng quyền của key (không gọi được REST). «Đổi link» làm link cũ hết hiệu lực mà API key giữ nguyên; xoay API key cũng đổi link |
| Phạm vi dữ liệu | Đúng một workspace: workspace của API key |
Kết nối
Cách nhanh nhất: trang API Key → bấm «Link MCP» trên key → Copy → dán vào ô MCP server URL của agent.
Claude Code
claude mcp add --transport http sieutroly https://api.sieutroly.ai/api/mcp \
--header "Authorization: Bearer $API_KEY"Cursor / VS Code / Windsurf (mcp.json)
{ "mcpServers": { "sieutroly": { "url": "https://api.sieutroly.ai/api/mcp", "headers": { "Authorization": "Bearer <API_KEY>" } } } }Claude Desktop (qua mcp-remote, cần Node.js)
{ "mcpServers": { "sieutroly": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.sieutroly.ai/api/mcp", "--header", "Authorization:Bearer <API_KEY>"] } } }Quyền → công cụ
Agent chỉ thấy công cụ mà scope của key cho phép (tools/list được lọc theo scope). Muốn agent chỉ xem dữ liệu thì tạo key chỉ có các quyền *:read.
Trần quyền khi tạo key: chỉ cấp được quyền ứng với quyền mà chính người tạo đang có trên web; không xoay, sửa hay xóa được key có quyền cao hơn mình. Chủ workspace không bị giới hạn.
| Scope | Công cụ |
|---|---|
| (mọi key) | whoami |
orders:read | list_orders, get_order, get_order_settings, list_branches, search_address, list_shipping_providers, quote_shipping, get_shipment |
orders:write | create_order, update_order, set_order_status |
orders:payment | set_order_payment, create_order_payment_qr |
products:read | list_products, get_product, list_categories, list_stock, list_stock_movements |
products:write | create_product, update_product, create_category, update_category, stocktake |
purchases:read | list_suppliers, get_supplier, list_banks, list_purchases, get_purchase, list_purchase_approvers |
purchases:write | create_supplier, update_supplier, pay_supplier, create_purchase, update_purchase, submit_purchase, withdraw_purchase, complete_purchase, cancel_purchase |
customers:read | search_customers, get_customer |
customers:write | create_customer, update_customer, add_customer_note |
workflows:read | list_workflows, get_workflow, list_workflow_runs, get_workflow_run |
content:read | list_content_accounts, list_content_posts, get_content_post, refresh_content_stats, get_content_analytics |
content:write | import_content_media, create_content_post, update_content_post, publish_content_post, delete_content_post — publish_content_post / publish_now đăng thật, công khai |
workflows:run | run_workflow — chạy thật mọi bước (gửi tin, tạo đơn, gọi hệ thống ngoài…); chỉ cấp khi cần |
shipping:write | quote_order_shipping, ship_order, cancel_order_shipping, create_shipment, cancel_shipment — gọi tài xế thật qua Ahamove / AllinGo, phát sinh phí (get_shipment thuộc orders:read) |
kiotviet:read | list_kiotviet_operations, kiotviet_read — đọc dữ liệu sống của cửa hàng KiotViet đã kết nối |
kiotviet:write | kiotviet_write — tạo/sửa/xóa thật trên KiotViet (hàng hóa, khách hàng, đặt hàng, hóa đơn, phiếu nhập/chuyển hàng) |
Mỗi công cụ có annotations (readOnlyHint, destructiveHint) để client hỏi người dùng trước khi ghi. Máy chủ gửi kèm instructions hướng dẫn agent: tra product_id trước khi tạo đơn, tìm khách trước khi tạo mới để tránh trùng, v.v.
Quy tắc nghiệp vụ (giống API công khai)
- Tiền là VND (số nguyên), thời gian ISO 8601.
- Đơn hàng: giá lấy từ sản phẩm. Dòng tự nhập dùng
name+unit_price. Muốn gọi lại an toàn thì truyềnexternal_id: đã có đơn cùng mã thì hệ thống trả lại đơn cũ, không tạo đơn mới. Chỉ sửa được đơn còn ở trạng thái đầu. - Địa chỉ giao hàng:
search_address(RESTGET /places?q=, q ≥ 3 ký tự) gợi ý địa chỉ đầy đủ kèm toạ độ bằng bản đồ Ahamove; chỉ chạy khi workspace có kênh Ahamove đang hoạt động, không thì lỗi409 ahamove_not_connected. Dùnglabellàmdelivery_address. - Phí ship:
quote_shipping(RESTPOST /shipping/quote) hỏi giá mọi kênh Ahamove / AllinGo đang hoạt động phục vụ chi nhánh (hoặc chỉprovider/channel_id), trảquotestheo giá tăng dần (hãng, dịch vụ,fee,eta, kênh) +cheapest; kênh lỗi nằm ởchannels[].error, không chặn kênh khác. Truyềnorderđể lấy địa chỉ, SĐT, chi nhánh và tiền thu hộ (phần chưa thanh toán) từ đơn. Điểm lấy hàng là chi nhánh (branch_id, trống = chi nhánh mặc định); chi nhánh thiếu địa chỉ/SĐT thì dùng điểm lấy hàng của kênh. Chưa có SĐT người nhận thì dùng SĐT chi nhánh (chỉ để báo giá). Chỉ báo giá, không tạo đơn giao và không ghi vào đơn — muốn tính vào đơn thì điềndelivery_feekhi tạo/sửa đơn. Chưa có kênh giao hàng →409 shipping_disabled.list_shipping_providers(GET /shipping/options) liệt kê các kênh. - Gọi giao hàng (scope
shipping:write, dùng chính kênh Ahamove / AllinGo đã kết nối):- Cho đơn có sẵn (giống nút «Giao hàng» trên web):
quote_order_shipping(RESTPOST /orders/{ref}/shipping/quote) báo giá từ kênh phục vụ chi nhánh của đơn (nhiều kênh thì truyềnchannel_id), địa chỉ/SĐT mặc định lấy từ đơn, báo giá được GIỮ 15 phút trong đơn →ship_order(POST /orders/{ref}/shipping,quote_id) tạo đơn giao thật, đơn chuyển «Đang giao», mã vận đơn/link theo dõi ởget_order → shipping→cancel_order_shipping(POST /orders/{ref}/shipping/cancel). Đơn đã có đơn giao đang chạy →409 already_shipping; báo giá hết hạn →409 quote_expired. - Giao độc lập (không gắn đơn, như bước «Vận chuyển» của Quy trình):
create_shipment(POST /shipping/shipments:dropoff{SĐT bắt buộc + địa chỉ hoặc lat/lng},pickupriêng tuỳ chọn,service= id báo giá / từ khoá, trống = rẻ nhất,cod_amount) →get_shipment(GET /shipping/shipments/{external_id}, scopeorders:read) →cancel_shipment(POST /shipping/shipments/{external_id}/cancel). Không lưu bản ghi trong SieuTroLy, có nhật ký thao tác (shipping.direct_create/direct_cancel). - Workspace có nhiều kênh giao hàng thì phải truyền
channel_id(400 channel_required); chưa có kênh →409 shipping_disabled.
- Cho đơn có sẵn (giống nút «Giao hàng» trên web):
- KiotViet (scope
kiotviet:read/kiotviet:write, dùng kênh KiotViet đã kết nối): cùng bộ thao tác với các bước «KiotViet» của Quy trình.list_kiotviet_operations(RESTGET /kiotviet/operations) trả kênh KiotViet (channel_id,retailer,ready) + danh mục thao tác (operation,accessread/write/any,params). Chạy bằngkiotviet_read/kiotviet_write(cùng RESTPOST /kiotviet/run{ operation, params, channel_id }): quyền xét theo phương thức thật — GET cầnkiotviet:read, POST/PUT/DELETE cầnkiotviet:write;kiotviet_readgọi thao tác ghi →403 write_operation. Ô*_json(vditems_json,extra_json) nhận luôn object/array. Thao tácrequest(gọi endpoint tuỳ chọn trong danh sách được phép) dùng được với cả hai. Đăng ký/xóa webhook KiotViet KHÔNG mở qua API (404 unknown_operation, đường dẫn/webhooks→403 forbidden_operation) để key không trỏ được dữ liệu cửa hàng ra URL tuỳ ý. Mỗi lần ghi có nhật kýkiotviet.write. Kết quả là dữ liệu sống từ KiotViet (kháclist_productsđọc bản đồng bộ trong SieuTroLy). - Sản phẩm, danh mục: chỉ bản ghi tự tạo (
editable: true) sửa được mọi trường. Bản đồng bộ từ KiotViet hoặc Pancake chỉ đổi đượcis_active,cost_price,min_stock,supplier,kind. Đổi trường khác sẽ trả lỗisynced_product. Danh mục có tối đa 2 cấp. - Nguyên vật liệu, tồn kho: nguyên vật liệu là sản phẩm
kind: "material"(list_products/list_stocklọckind).list_stocktrả tồn theo sản phẩm × chi nhánh:qty,reserved(giữ cho đơn chưa hoàn thành),available,statusout/low/ok. - Kiểm kê:
stocktake(RESTPOST /stock/stocktake, scopeproducts:write) nhậnbranch_id+items: [{ product_id | sku, counted }](≤ 200): tồn của chi nhánh được đặt đúng bằngcounted, chênh lệch ghi thẻ kho loạistocktake, giá vốn không đổi, có nhật ký thao tác.dry_run: truechỉ trảbefore/counted/difftừng dòng, không ghi — agent nên xem trước rồi xác nhận với người dùng. - Phiếu nhập:
create_purchasemặc định lưu nháp (chưa cộng kho); tiền do hệ thống tính (tổng tiền hàng − giảm giá + VAT + chi phí nhập = cần trả NCC; −paid= công nợ). Hoàn thành = cộng kho (giá vốn bình quân gia quyền) + ghi công nợ NCC; còn nợ thì bắt buộcsupplier_id. Phiếu cóapproverschỉ gửi duyệt được (submit_purchase; hoàn thành →approval_required). API key không phải người dùng nên không bao giờ duyệt/không duyệt được — người duyệt xử lý trên web/app. Phiếu chờ duyệt phảiwithdraw_purchasemới sửa được.cancel_purchasechỉ cho phiếu đã hoàn thành chưa trả nợ (trừ lại kho).pay_supplierchỉ GHI NHẬN khoản đã trả (không chuyển tiền), phân bổ phiếu cũ nhất trước. - Khách hàng: tạo khách trùng số điện thoại (mọi dạng
09…/+84…) trả lỗiduplicate_phone, kèmcustomer_idcủa khách đã có. - Quy trình: chỉ chạy được quy trình
runnable: true, tức có Trigger «Chạy thủ công» (nhậninput) hoặc hẹn giờ (chạy ngay, không nhậninput). Quy trình chạy theo sự kiện (tiền vào SePay/Casso, đơn hàng, tin nhắn) bị từ chối với lỗievent_workflow, để không ai giả được sự kiện, ví dụ một giao dịch tiền vào bịa.run_workflowtrả về lần chạy ở trạng tháiqueued/running; xem kết quả bằngget_workflow_run. - Đăng bài đa nền tảng:
list_content_accountstrả các kênh đăng đang hoạt động (platformfacebook= Facebook Page qua kênh «Facebook Video»,youtube,tiktok_video= TikTok API chính thức,tiktok= TikTok cá nhân). Mỗi bài cótargets[], mỗi kênh một nội dung riêng (caption,title, tuỳ chọn YouTube/TikTok), vàmediadùng chung: nhiều ảnh (≤10) HOẶC đúng 1 video. YouTube và TikTok chỉ nhận video. Media phải nằm trong khokeep/của chính workspace:import_content_mediatải từ URL https công khai (chặn địa chỉ nội bộ, không theo chuyển hướng, chỉimage/*/video/*, video ≤100MB, tính vào dung lượng gói); REST còn cóPOST /content/media/presignđể tự PUT. URL ngoài hoặc tệptmp/bị từ chối.create_content_postkhông cóscheduled_at= nháp; có = hẹn giờ. «Đăng ngay» (publish_content_posthoặcpublish_now: true) không chờ upload: bài chuyểnscheduledlúc đó, scheduler đăng trong ~20 giây, xem kết quả từng nền tảng (status,remote_url,error,warning) bằngget_content_post. Bàifailed/partialđăng lại chỉ các nền tảng chưa thành công. Chỉ sửa/xóa được bài nháp/hẹn giờ; xóa không gỡ bài trên nền tảng. - Lỗi: công cụ trả
isError: truekèm{ code, message }, cùng mã lỗi với API công khai:invalid_request,not_found,insufficient_scope,rate_limited… - Giới hạn: mỗi lần gọi công cụ tính là một yêu cầu trong
rate_limit_per_mincủa key. Riêng cửa/api/mcpcó hạn mức thứ hai, gấp 5 lần hạn mức của key, đếm mọi yêu cầu kể cảinitializevàtools/list. Vượt hạn mức trả429. - Nhật ký: mọi thay đổi ghi người thực hiện là
apikey:<id>và phát webhook như khi gọi REST.
Endpoint REST tương ứng
Mỗi công cụ MCP gọi lại API công khai v1 bằng chính key đó, nên dữ liệu, quyền, giới hạn tần suất, nhật ký và webhook giống hệt khi gọi REST. Đối tác không dùng MCP gọi thẳng các endpoint:
- Sản phẩm:
GET/POST /products,GET/PATCH /products/{id} - Danh mục:
GET/POST /categories,PATCH /categories/{id} - Khách hàng:
GET /customers?q=,GET /customers/{id}POST /customers,PATCH /customers/{id}POST /customers/{id}/notes
- Tồn kho:
GET /stock,GET /stock/movements - Nhà cung cấp:
GET/POST /suppliers,GET/PATCH /suppliers/{id},POST /suppliers/{id}/payments,GET /banks - Phiếu nhập:
GET/POST /purchases,GET /purchases/approvers,GET/PATCH /purchases/{id},POST /purchases/{id}/submit|withdraw|complete|cancel - Quy trình:
GET /workflows,GET /workflows/{id},GET /workflows/{id}/runsPOST /workflows/{id}/run,GET /workflow-runs/{id}
Lưu ý an toàn khi cho AI agent dùng
- Chỉ dẫn độc trong dữ liệu (prompt injection): tên khách, ghi chú, nội dung đơn là dữ liệu do khách nhập, và agent đọc được. Nên dùng key chỉ có quyền đọc khi không cần ghi, và để client hỏi xác nhận trước khi dùng công cụ ghi (
readOnlyHint: false,run_workflowcódestructiveHint: true). - Ghi chú nội bộ:
get_customertrả cả lịch sử ghi chú nội bộ của khách cho key cócustomers:read.
