Skip to content

MCP Server — Cơ chế an toàn write

MụcNội dung
Trạng thái🟡 Đang thiết kế
Case liên quan#13 MCP Server (Thiết lập mới)
Tài liệu chaMCP Server (Tổng thể)
Design liên quanTool Catalog / Auth & Tenancy

1. Mục đích tài liệu

Định nghĩa các cơ chế an toàn bắt buộc cho tool write (từ Phase 2) của MCP.

  • Giảm thiểu rủi ro AI thực thi sai
  • Chống duplicate thực thi của cùng một thao tác
  • Xác nhận ý định người dùng khi áp dụng hàng loạt quy mô lớn
  • Theo dõi trạng thái xử lý bất đồng bộ
  • Cân nhắc giới hạn GBP API
  • Đảm bảo khả năng audit

Phase 1 (MVP) toàn bộ là read

Nội dung tài liệu này implement từ Phase 2 trở đi. MVP không cung cấp write tool ngoài batch.status.

2. Nguyên tắc thiết kế

Nguyên tắcNội dung
Đồng ý rõ ràngÁp dụng hàng loạt ≥ 5 cửa hàng bắt buộc confirm_token
An toàn khi thực thi lạiTất cả write tool đều support idempotency_key
Trực quan hóa trạng tháiXử lý bất đồng bộ check hoàn thành bằng batch_id + polling
Có thể thử nghiệmMode xác nhận không side effect bằng dry_run=true
Chấp nhận thất bại một phầnÁp dụng hàng loạt trả về thành công/thất bại theo từng location
Ghi toàn bộ auditWrite・dry_run・từ chối đều ghi vào Audit Log không sót
Cân nhắc GBP APIBảo vệ chống rate limit・token hết hạn

3. 4 cơ chế an toàn

3.1 dry_run

Mô phỏng "nếu thực thi thì sẽ xảy ra gì" mà không tạo side effect.

Hoạt động

Ví dụ response

json
{
  "data": {
    "would_execute": true,
    "operation": "post.create",
    "target_locations": [
      { "id": 8802, "title": "Shibuya", "store_code": "SHIBUYA001" },
      { "id": 8803, "title": "Shinjuku", "store_code": "SHINJUKU001" }
    ],
    "target_count": 2,
    "validation_warnings": [
      {
        "location_id": 8802,
        "code": "RECENT_POST_DETECTED",
        "message": "Chưa đến 1 giờ kể từ post trước (có thể duplicate)"
      }
    ],
    "estimated_gbp_api_calls": 2,
    "estimated_completion_seconds": 10
  }
}

Tool bắt buộc support

toolSupport dry_run
post.create✅ Bắt buộc
post.delete✅ Bắt buộc
review.reply✅ Bắt buộc
media.upload✅ Bắt buộc
media.delete✅ Bắt buộc
location.update✅ Bắt buộc
report.create❌ Không cần (việc tạo không có side effect, nhưng có tính phí)

Kịch bản sử dụng dry_run

Khuyến nghị design: AI khách gọi dry_run trước khi xác nhận "Bạn có muốn đăng không?", tóm tắt kết quả và trình bày cho người dùng.

3.2 idempotency_key

Khi cùng một ý định thao tác được gửi nhiều lần, chỉ thực thi lần đầu.

Spec

MụcNội dung
FormatKhuyến nghị UUIDv4. Tùy ý 8〜64 ký tự ASCII
Thời hạn24 giờ
ScopeTổ hợp (client_id, user_id, tool_name, idempotency_key)
Hành vi khi xung độtTrả về response lần đầu từ cache (bao gồm status code)
Bắt buộc/tùy chọnKhuyến nghị mạnh với write tool, có thể auto gán

Hoạt động

Phát hiện xung đột input

Cùng idempotency_key nhưng input khác → lỗi IDEMPOTENCY_CONFLICT:

json
{
  "error_code": "IDEMPOTENCY_CONFLICT",
  "message": "Request khác đã được gửi với cùng idempotency_key",
  "details": { "key": "KEY1" }
}

Cách phát hiện: lưu hash input của request lần đầu vào Redis, đối chiếu lần 2.

3.3 confirm_token (xác nhận ý định khi áp dụng hàng loạt)

Khi áp dụng hàng loạt ≥ 5 cửa hàng hoặc thao tác có phạm vi ảnh hưởng lớn, bắt buộc đồng ý rõ ràng của người dùng.

Hoạt động

Điều kiện bắt buộc

Thao tácĐiều kiện
post.createlocation_ids length ≥ 5
post.deletelocation_ids length ≥ 3
media.uploadlocation_ids length ≥ 5 HOẶC tổng size media ≥ 50 MB
media.deletemedia_ids length ≥ 5
location.updateSố đích update đồng thời ≥ 3
review.replyThường không cần (1 đối tượng)

Chi tiết confirm_token

MụcNội dung
Nguồn phátMCP Server
FormatASCII random ≥ 32 ký tự (lưu Redis)
Thời hạn5 phút
Scope(client_id, user_id, tool_name, input_hash)
Tái sử dụngKhông (tiêu thụ 1 lần)
Thay đổi inputinput_hash thay đổi thì vô hiệu

Ví dụ response 412

json
{
  "error_code": "CONFIRMATION_REQUIRED",
  "message": "Áp dụng hàng loạt ≥ 5 cửa hàng cần confirm_token",
  "details": {
    "confirm_token": "abc123def456...",
    "expires_in": 300,
    "target_summary": {
      "operation": "post.create",
      "target_count": 10,
      "locations": [
        { "id": 8801, "title": "Shibuya" },
        { "id": 8802, "title": "Shinjuku" }
      ],
      "post_summary": "Thông tin chiến dịch hôm nay..."
    },
    "human_readable_message": "Sẽ đăng cùng nội dung hàng loạt vào 10 cửa hàng. Bạn có chắc không?"
  }
}

3.4 batch_id + polling

Write tới GBP API tốn thời gian (hàng loạt nhiều cửa hàng = vài chục giây ~ vài phút), nên bắt buộc thực thi bất đồng bộ + check trạng thái.

Hoạt động

Chuyển trạng thái batch

Khoảng polling khuyến nghị

Trạng tháiKhoảng khuyến nghị
queued5 giây
processing (target_count ≤ 5)5 giây
processing (target_count > 5)10 giây
done / failed / partialDừng polling

Khuyến nghị exponential backoff phía client.

Chấp nhận thất bại một phần

Trường hợp 1 trong 10 cửa hàng bị lỗi GBP API:

json
{
  "data": {
    "batch_id": "01HVABC...",
    "status": "partial",
    "target_count": 10,
    "success_count": 9,
    "failure_count": 1,
    "failures": [
      {
        "location_id": 8802,
        "error_code": "GBP_PERMISSION_DENIED",
        "message": "Không có quyền đăng vào location này",
        "retry_possible": false
      }
    ]
  }
}

4. Cân nhắc rate limit của GBP API

4.1 Tận dụng implement hiện có

Mappy đã có cơ chế xử lý rate limit của GBP API (spatie/guzzle-rate-limiter-middleware). MCP access qua Laravel API, cơ chế này tự động áp dụng.

4.2 Quản lý access token

  • Token quản lý ở bảng google_access_tokens / mappy_google_access_tokens của Mappy DB
  • Phương thức token ngắn hạn 60 phút + refresh token
  • Xung đột update token khi parallel đã được kiểm soát phía Laravel (SMARTMEO-47 đã xử lý)

4.3 Bảo vệ bổ sung phía MCP

Bảo vệNội dung
Giới hạn số thực thi đồng thời5 parallel / user
Tối đa 100 cửa hàng / batchTheo kết quả check DB, cân nhắc số cửa hàng của khách lớn nhất
Kiểm soát loop pageSize=100Dùng logic hiện có của Laravel
Rate limit qua Redis10 request/phút (write)

5. Hành vi khi thất bại và retry

5.1 Pattern thất bại và đối phó

Pattern thất bạiĐối phó
Lỗi giao tiếp (MCP ↔ Laravel)Client có thể resend bằng idempotency_key
GBP API lỗi tạm thời (5xx)Worker auto retry (tối đa 3 lần, exponential backoff)
GBP API lỗi vĩnh viễn (4xx)Không retry, ghi vào failures
GBP token hết hạnAuto refresh rồi retry
Worker crashCó thể re-run qua bảng failed_jobs hiện có
Restart MCP Serverbatch_id lưu persistent trong DB, có thể tiếp tục theo dõi

5.2 Cancel từ user (AI khách)

http
POST /tools/call
{
  "name": "batch.cancel",
  "arguments": { "batch_id": "..." }
}
  • Chỉ cancel được khi đang queued (trước khi Job được pull)
  • Sau khi chuyển sang processing thì không cancel được (tránh không nhất quán giữa chừng)

5.3 Auto rollback

Khi write gặp lỗi nghiêm trọng giữa chừng (vi phạm constraint DB v.v.), rollback bằng transaction phía Laravel. Trả về error response từ MCP.

GBP API là external, không thể đảm bảo tuyệt đối nhất quán Mappy DB - GBP

  • Case DB thành công + GBP thất bại: Laravel retry queue gửi lại tới GBP
  • Case DB thất bại + GBP thành công: cần sửa thủ công từ audit log
  • Thao tác quan trọng bảo vệ 3 lớp: dry_run + idempotency + audit log

6. Rate limit (write)

ScopeGiới hạn
Theo access_token10 request/phút
Theo user_id30 request/phút
Theo user_id500 lần/ngày (tổng gọi write tool)
Theo client_id1000 request/giờ
Cùng idempotency_key24 giờ 1 lần
Cùng confirm_token5 phút 1 lần

Khi hit rate limit:

json
{
  "error_code": "RATE_LIMITED",
  "message": "Vượt rate limit thao tác write",
  "details": {
    "scope": "user_per_minute",
    "limit": 30,
    "retry_after_seconds": 45
  }
}

7. Quan hệ giữa confirm_token và idempotency_key

Hai cái vai trò khác nhau nên cùng tồn tại.

Điểmconfirm_tokenidempotency_key
Mục đíchXác nhận ý định userChống duplicate thực thi
Nguồn phátMCP ServerClient (AI khách)
Thời hạn5 phút24 giờ
Điều kiện bắt buộcHàng loạt ≥ 5 v.v.Khuyến nghị (toàn bộ write)
Tái sử dụngKhông (tiêu thụ 1 lần)Có (cùng input = cùng response)

Flow sử dụng đồng thời

8. Liên kết với audit log

Ghi toàn bộ thao tác write (bao gồm dry_run・từ chối) vào Audit Log.

EventMục ghi
Thực thi dry_runtool_name, target_count, warnings
Phát confirm_tokentool_name, target_summary, expires_at
Verify confirm_token OKtool_name, expired/valid
Verify confirm_token NGreason (expired/conflict)
batch dispatchbatch_id, target_locations
batch hoàn thànhsuccess_count, failure_count, failures
Hit rate limitscope, limit, retry_after
Yêu cầu cancelbatch_id, allowed/denied

Schema chi tiết xem DB (audit log).

9. Hướng dẫn implement client (phía AI khách)

9.1 Flow khuyến nghị

1. Preview kết quả bằng dry_run

2. Nếu ≥ 5 đối tượng hoặc thao tác quan trọng, xác nhận ý định với người dùng

3. Thực thi chính thức kèm confirm_token

4. Nhận batch_id thì polling

5. Báo cáo trạng thái hoàn thành cho người dùng

9.2 Ví dụ system prompt khuyến nghị cho AI khách

Khi bạn dùng write tool của Mappy MCP, hãy tuân thủ các điều sau:

1. Luôn xác nhận trước với dry_run=true
2. Tóm tắt kết quả dry_run, hỏi user "có muốn thực thi không"
3. Áp dụng hàng loạt ≥ 5 cửa hàng bắt buộc confirm_token. Phải giữ token server trả về và gửi kèm khi resend
4. idempotency_key gán UUID dựa trên conversation ID mỗi lần
5. Khi nhận batch_id thì polling batch.status và chờ hoàn thành
6. Khi thất bại đọc mảng failures và giải thích cụ thể cho user

10. Điểm tranh luận tại review (chưa chốt)

#MụcPhương án
1Ngưỡng số lượng bắt buộc confirm_token3 / 5 / 10
2Phạm vi bắt buộc support dry_runToàn bộ write / chỉ write hàng loạt
3TTL của idempotency_key24h / 7d
4Giá trị rate limit cuối cùngTrên là tạm
5Phạm vi cho phép batch.cancelChỉ queued / cả processing
6Số lần auto retry khi GBP fail3 / 5
7Chiến lược chấp nhận thành công một phầnChấp nhận partial / Bắt buộc tất cả / Có thể setting
8Client polling vs WebhookPhase 2 polling, Phase 3 xem xét Webhook

11. Tài liệu liên quan