Skip to content

MCP Server — Tool Catalog ​

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 quanAuth & Tenancy / Cơ chế an toàn write

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

Tổng hợp danh sách spec của toàn bộ tool mà MCP Server cung cấp. Cho từng tool sẽ chốt các mục sau:

  • Tên・mô tả
  • Scope cần
  • Schema input (kiểu・bắt buộc/tùy chọn・ràng buộc)
  • Schema output
  • Side effect (read/write/async)
  • Hành vi khi lỗi

Định nghĩa language-independent, không phụ thuộc stack triển khai (Python / TypeScript). Schema viết theo phong cách JSON Schema (Python convert sang Pydantic, TS sang Zod).

2. Quy tắc đặt tên tool ​

<domain>.<action>
PhầnVí dụ
<domain>location / posts / reviews / ranking / insight / report / keyword / group / media / sms / questionnaire / sns / notification / batch
<action>list / get / create / update / delete / summary / status

Ví dụ: location.list, review.reply, report.create, batch.status

3. Spec chung ​

3.1 Tham số input chung ​

Tất cả tool đều chấp nhận các input optional sau:

ParamKiểuMô tả
user_idint?ID user đối tượng (lược đi = user đang auth). Chỉ supervisor admin được chỉ định tùy ý
dry_runbool?true = không thực thi, trả về tóm tắt side effect dự kiến (chỉ write tool)
idempotency_keystring?Khuyến nghị UUID. Cùng key bị từ chối duplicate trong 24h (chỉ write tool)

3.2 Wrapper output chung ​

json
{
  "data": <payload riêng của tool>,
  "meta": {
    "request_id": "01HVABC...",
    "elapsed_ms": 123,
    "next_cursor": "..." // khi paging
  }
}

3.3 Paging ​

Dùng kiểu cursor (không phải offset/limit). Lý do: ổn định ngay cả với bảng lớn (mappy_search_rankings 10 triệu dòng).

ParamKiểuMô tả
cursorstring?next_cursor từ response trang trước
limitint?1〜100, mặc định 50

meta.next_cursor của response không null = còn trang tiếp.

3.4 Format lỗi ​

Trả isError: true của MCP, đặt chi tiết lỗi dạng JSON vào content[0].text:

json
{
  "error_code": "FORBIDDEN_LOCATION",
  "message": "Không có quyền truy cập location đối tượng",
  "details": {
    "location_id": 8802,
    "user_id": 1435
  }
}

4. Ma trận danh sách tool ​

Chú thích:

  • Side effect: 🔵 Read / 🟠 Write đồng bộ / 🔴 Write bất đồng bộ
  • Phase: 1/2/3
  • DB rate: bảng dự kiến tải cao

Phase 1 (MVP) — Auth + Read ​

#Tên toolSide effectscopeDB tham chiếu chínhMô tả
1location.list🔵mappy:location:readmappy_gbp_locations + mappy_user_available_gbp_locationsDanh sách location có thể access
2location.get🔵mappy:location:readmappy_gbp_locationsChi tiết location (bao gồm raw_json)
3review.list🔵mappy:reviews:readmappy_gbp_reviews (280k dòng)Danh sách review
4ranking.list🔵mappy:ranking:readmappy_search_rankings (10M dòng)Xếp hạng tìm kiếm
5insight.summary🔵mappy:insight:readmappy_gbp_insights (4.2M dòng)Tổng hợp insight

Phase 2 — Mở rộng write ​

#Tên toolSide effectscopeMô tả
6post.list🔵mappy:posts:readDanh sách post
7post.create🔴mappy:posts:writeTạo post (support nhiều cửa hàng cùng lúc)
8post.delete🟠mappy:posts:writeXóa post
9review.reply🟠mappy:reviews:writeTrả lời review
10review.template.list🔵mappy:reviews:readDanh sách template reply
11review.template.create🟠mappy:reviews:writeĐăng ký template reply
12media.list🔵mappy:media:readDanh sách media
13media.upload🔴mappy:media:writeUpload media (support hàng loạt)
14media.delete🟠mappy:media:deleteXóa media
15location.update🟠mappy:location:writeUpdate thuộc tính location
16batch.status🔵(Phase 1〜chỉ cần auth)Kiểm tra trạng thái xử lý bất đồng bộ

Phase 3 — Báo cáo・Phân tích ​

#Tên toolSide effectscopeMô tả
17report.list🔵mappy:report:readDanh sách report
18report.get🔵mappy:report:readLấy report (HTML / PDF path)
19report.create🔴mappy:report:writeYêu cầu tạo report
20keyword.list🔵mappy:keyword:readDanh sách từ khóa・lưu lượng
21group.list🔵mappy:group:readDanh sách group
22group.compare🔵mappy:group:read + mappy:ranking:readSo sánh ranking・insight giữa các group
23questionnaire.list🔵mappy:questionnaire:readDanh sách khảo sát
24questionnaire.answers🔵mappy:questionnaire:readLấy câu trả lời
25sms.list🔵mappy:sms:readLog SMS

5. Chi tiết tool Phase 1 ​

5.1 location.list ​

Trả về danh sách location mà user có quyền access.

scope: mappy:location:readSide effect: 🔵 Read DB: mappy_gbp_locations JOIN mappy_user_available_gbp_locations

Schema input:

json
{
  "type": "object",
  "properties": {
    "user_id": { "type": "integer", "description": "ID user đối tượng (lược đi = user auth)" },
    "group_id": { "type": "integer", "description": "Lọc theo group" },
    "store_code": { "type": "string", "description": "Lọc khớp tuyệt đối theo store code" },
    "q": { "type": "string", "description": "Tìm khớp một phần theo tên cửa hàng" },
    "cursor": { "type": "string" },
    "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
  }
}

Schema output:

json
{
  "data": {
    "locations": [
      {
        "id": 8802,
        "name": "locations/8802235673275682965",
        "title": "Shibuya",
        "address": "Tokyo Shibuya-ku...",
        "store_code": "SHIBUYA001",
        "primary_category": "Tiệm tóc",
        "data_source": "places_api_app",
        "new_review_uri": "https://..."
      }
    ]
  },
  "meta": { "next_cursor": "...", "request_id": "...", "elapsed_ms": 123 }
}

Lỗi:

codeĐiều kiện
FORBIDDEN_USERtarget_user_id ngoài phân tầng
INVALID_GROUPgroup_id không thuộc group của user đối tượng

5.2 location.get ​

Trả về thông tin chi tiết location. Bao gồm GBP raw_json (khi yêu cầu).

scope: mappy:location:readSide effect: 🔵 Read

Input:

json
{
  "type": "object",
  "required": ["location_id"],
  "properties": {
    "location_id": { "type": "integer" },
    "include_raw": { "type": "boolean", "default": false, "description": "Bao gồm GBP raw_json (lớn nên chỉ khi yêu cầu rõ ràng)" }
  }
}

Output:

json
{
  "data": {
    "id": 8802,
    "name": "locations/8802235673275682965",
    "title": "Shibuya",
    "address": "...",
    "primary_category": "Tiệm tóc",
    "categories": ["Tiệm tóc", "Hair salon"],
    "phone_numbers": ["03-1234-5678"],
    "website_uri": "https://...",
    "regular_hours": { ... },
    "latlng": { "latitude": 35.65, "longitude": 139.70 },
    "data_source": "places_api_app",
    "raw": { ... }  // chỉ khi include_raw=true
  }
}

Lỗi:

codeĐiều kiện
FORBIDDEN_LOCATIONUser đối tượng không access được location_id
NOT_FOUNDlocation_id không tồn tại

5.3 review.list ​

Trả về danh sách review.

scope: mappy:reviews:readSide effect: 🔵 Read DB: mappy_gbp_reviews (280k dòng, khuyến nghị bắt buộc lọc theo kỳ)

Input:

json
{
  "type": "object",
  "properties": {
    "user_id": { "type": "integer" },
    "location_id": { "type": "integer", "description": "Không chỉ định = tất cả cửa hàng có quyền" },
    "since": { "type": "string", "format": "date-time", "description": "ISO8601, mặc định 30 ngày trước" },
    "until": { "type": "string", "format": "date-time" },
    "min_rating": { "type": "integer", "minimum": 1, "maximum": 5 },
    "max_rating": { "type": "integer", "minimum": 1, "maximum": 5 },
    "has_reply": { "type": "boolean" },
    "cursor": { "type": "string" },
    "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
  }
}

Output:

json
{
  "data": {
    "reviews": [
      {
        "id": 12345,
        "location_id": 8802,
        "reviewer_name": "Yamada Taro",
        "rating": 5,
        "comment": "...",
        "reply_comment": "...",
        "create_time": "2026-05-01T10:00:00Z",
        "update_time": "2026-05-02T09:00:00Z"
      }
    ]
  },
  "meta": { "next_cursor": "...", ... }
}

Ràng buộc kỳ

Kỳ quá 90 ngày cần xác nhận rõ ràng (đối phó tải). Không chỉ định = 30 ngày trước.

5.4 ranking.list ​

Trả về dữ liệu xếp hạng tìm kiếm.

scope: mappy:ranking:read + mappy_users.search_ranking_enabled = 1Side effect: 🔵 Read DB: mappy_search_rankings (10M dòng, bắt buộc lọc theo kỳ)

Input:

json
{
  "type": "object",
  "required": ["since", "until"],
  "properties": {
    "user_id": { "type": "integer" },
    "location_id": { "type": "integer" },
    "keyword": { "type": "string" },
    "since": { "type": "string", "format": "date-time" },
    "until": { "type": "string", "format": "date-time" },
    "aggregate": { "type": "string", "enum": ["daily", "weekly", "raw"], "default": "daily" },
    "cursor": { "type": "string" },
    "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
  }
}

Ràng buộc bắt buộc

  • since / until bắt buộc
  • Kỳ tối đa 365 ngày
  • Khi không chỉ định location_id, trả tất cả cửa hàng của user, nhưng tối đa 100 cửa hàng

Output:

json
{
  "data": {
    "rankings": [
      {
        "location_id": 8802,
        "keyword": "tiệm tóc Shibuya",
        "date": "2026-05-01",
        "rank": 3,
        "search_volume": 1234
      }
    ],
    "summary": {
      "avg_rank": 3.2,
      "best_rank": 1,
      "worst_rank": 8,
      "total_records": 1234
    }
  },
  "meta": { ... }
}

5.5 insight.summary ​

Trả về tổng hợp GBP insight.

scope: mappy:insight:readSide effect: 🔵 Read DB: mappy_gbp_insights (4.2M dòng)

Input:

json
{
  "type": "object",
  "required": ["since", "until"],
  "properties": {
    "user_id": { "type": "integer" },
    "location_id": { "type": "integer" },
    "group_id": { "type": "integer", "description": "Tổng hợp theo group" },
    "since": { "type": "string", "format": "date" },
    "until": { "type": "string", "format": "date" },
    "metrics": {
      "type": "array",
      "items": { "type": "string", "enum": ["views", "actions_website", "actions_phone", "actions_directions", "search_count"] },
      "description": "Không chỉ định = tất cả metric"
    },
    "aggregate": { "type": "string", "enum": ["daily", "weekly", "monthly", "total"], "default": "monthly" }
  }
}

Output:

json
{
  "data": {
    "summary": {
      "period": { "since": "2026-04-01", "until": "2026-04-30" },
      "metrics": {
        "views": 15234,
        "actions_website": 1234,
        "actions_phone": 456,
        "actions_directions": 789,
        "search_count": 2345
      },
      "previous_period_diff": {
        "views": { "value": 1820, "percent": 13.6 }
      }
    },
    "series": [
      { "date": "2026-04-01", "views": 500, "actions_website": 40 }
    ]
  }
}

6. Chi tiết tool Phase 2 (trích) ​

6.1 post.create ​

Tạo post (hỗ trợ nhiều cửa hàng cùng lúc, bất đồng bộ).

scope: mappy:posts:writeSide effect: 🔴 Write bất đồng bộ DB: qua Jobs queue hiện có Giá trị trả: batch_id → check status bằng batch.status

Input:

json
{
  "type": "object",
  "required": ["location_ids", "post"],
  "properties": {
    "location_ids": {
      "type": "array",
      "items": { "type": "integer" },
      "minItems": 1,
      "maxItems": 100,
      "description": "Cửa hàng đối tượng hàng loạt"
    },
    "post": {
      "type": "object",
      "required": ["summary"],
      "properties": {
        "summary": { "type": "string", "maxLength": 1500 },
        "topic_type": { "type": "string", "enum": ["STANDARD", "EVENT", "OFFER"] },
        "event": {
          "type": "object",
          "properties": {
            "title": { "type": "string", "maxLength": 58 },
            "start_time": { "type": "string", "format": "date-time" },
            "end_time": { "type": "string", "format": "date-time" }
          }
        },
        "call_to_action": {
          "type": "object",
          "properties": {
            "action_type": { "type": "string", "enum": ["BOOK", "ORDER", "SHOP", "LEARN_MORE", "SIGN_UP", "CALL"] },
            "url": { "type": "string", "format": "uri" }
          }
        },
        "media_urls": { "type": "array", "items": { "type": "string", "format": "uri" } }
      }
    },
    "dry_run": { "type": "boolean", "default": false },
    "idempotency_key": { "type": "string" },
    "confirm_token": { "type": "string", "description": "Bắt buộc khi đăng hàng loạt > 5 cửa hàng (xem cơ chế an toàn write)" }
  }
}

Output (dry_run=false):

json
{
  "data": {
    "batch_id": "01HVABC...",
    "status": "processing",
    "queued_at": "2026-05-25T10:00:00Z",
    "target_count": 12,
    "estimated_completion_seconds": 60
  }
}

Output (dry_run=true):

json
{
  "data": {
    "would_execute": true,
    "target_locations": [...],
    "validation_warnings": [
      { "location_id": 8802, "warning": "Chưa đến 1 giờ kể từ post trước" }
    ]
  }
}

Trường hợp bắt buộc confirm_token

6.2 review.reply ​

Trả lời review.

scope: mappy:reviews:writeSide effect: 🟠 Write đồng bộ (qua GBP API) DB: mappy_gbp_reviews

Input:

json
{
  "type": "object",
  "required": ["review_id", "reply"],
  "properties": {
    "review_id": { "type": "integer" },
    "reply": { "type": "string", "maxLength": 4000 },
    "dry_run": { "type": "boolean", "default": false },
    "idempotency_key": { "type": "string" }
  }
}

Output:

json
{
  "data": {
    "review_id": 12345,
    "reply_comment": "...",
    "reply_at": "2026-05-25T10:00:01Z"
  }
}

6.3 batch.status ​

Kiểm tra trạng thái xử lý bất đồng bộ (post.create / media.upload v.v.).

scope: Chỉ cần auth cơ bản (không cần scope riêng, nhưng phải khớp user phát hành batch) Side effect: 🔵 Read

Input:

json
{
  "type": "object",
  "required": ["batch_id"],
  "properties": {
    "batch_id": { "type": "string" }
  }
}

Output:

json
{
  "data": {
    "batch_id": "01HVABC...",
    "status": "done", // queued / processing / done / failed / partial
    "queued_at": "...",
    "started_at": "...",
    "completed_at": "...",
    "target_count": 12,
    "success_count": 11,
    "failure_count": 1,
    "failures": [
      { "location_id": 8802, "error_code": "GBP_PERMISSION_DENIED", "message": "..." }
    ]
  }
}

7. Chi tiết tool Phase 3 (trích) ​

7.1 report.create ​

Yêu cầu tạo report.

scope: mappy:report:writeSide effect: 🔴 Write bất đồng bộ DB: Sử dụng pattern status/progress nội tại của mappy_reports

Input:

json
{
  "type": "object",
  "required": ["template", "period"],
  "properties": {
    "user_id": { "type": "integer" },
    "location_id": { "type": "integer" },
    "group_id": { "type": "integer" },
    "template": { "type": "string", "enum": ["monthly", "yearly"] },
    "period": {
      "type": "object",
      "required": ["since", "until"],
      "properties": {
        "since": { "type": "string", "format": "date" },
        "until": { "type": "string", "format": "date" }
      }
    },
    "include_ai_advice": { "type": "boolean", "default": true },
    "output_format": { "type": "string", "enum": ["html", "pdf"], "default": "html" }
  }
}

Output:

json
{
  "data": {
    "report_id": 59,
    "status": "queued",
    "estimated_completion_seconds": 30
  }
}

Check trạng thái hoàn thành bằng report.get.

7.2 report.get ​

Lấy report.

scope: mappy:report:readSide effect: 🔵 Read

Input:

json
{
  "type": "object",
  "required": ["report_id"],
  "properties": {
    "report_id": { "type": "integer" }
  }
}

Output:

json
{
  "data": {
    "report_id": 59,
    "status": "done", // pending / processing / done / failed
    "progress": 100,
    "file_url": "https://...?signed=...",
    "format": "html",
    "generated_at": "2026-05-25T11:00:00Z",
    "ai_advice": "..."
  }
}

7.3 group.compare ​

Trả về dữ liệu so sánh giữa các group.

scope: mappy:group:read + mappy:ranking:read + mappy:insight:readSide effect: 🔵 Read

Input:

json
{
  "type": "object",
  "required": ["group_ids", "since", "until"],
  "properties": {
    "group_ids": { "type": "array", "items": { "type": "integer" }, "minItems": 2, "maxItems": 10 },
    "since": { "type": "string", "format": "date" },
    "until": { "type": "string", "format": "date" },
    "metrics": { "type": "array", "items": { "type": "string", "enum": ["avg_rank", "views", "actions"] } }
  }
}

8. Rate limit・Bảo vệ resource ​

toolGiới hạn đặc biệt
ranking.listTừ chối kỳ > 365 ngày
review.listKỳ > 90 ngày cần xác nhận rõ ràng
post.createlocation_ids >= 5 thì bắt buộc confirm_token
media.uploadTối đa 10 media / request
report.createTối đa 5 lần / giờ / user
batch.statusKhoảng polling khuyến nghị 5 giây, tối đa 1 req/sec

9. Giới hạn payload ​

LoạiGiới hạn
Kích thước JSON input256 KB
Kích thước JSON output1 MB (vượt thì paging)
Output bao gồm raw_json5 MB
Số lượng đối tượng hàng loạt (location_ids)100

10. Cách viết tool description ​

Để LLM dễ chọn tool, description nên bao gồm:

  1. Làm gì (1 dòng)
  2. Khi nào dùng (1〜2 dòng, kịch bản sử dụng)
  3. Điểm chính của input bắt buộc・tùy chọn
  4. Có side effect không

Ví dụ:

location.list: Trả về danh sách cửa hàng mà user có quyền access.
Dùng để tìm kiếm cửa hàng, lọc, xem theo group.
Không side effect. Lược user_id = trả cửa hàng của user auth.

11. Điểm chưa chốt (quyết định tại review design) ​

#MụcPhương án
1Đặt tên tool: dot vs underscorelocation.list / location_list (spec MCP cho phép cả 2)
2Có mã hóa cursor khôngbase64(JSON) / mã hóa / opaque hoàn toàn
3Filter khi output raw_jsonCó filter loại trừ thông tin nhạy cảm không
4Ngôn ngữ tool descriptionTiếng Nhật / Tiếng Anh / cả 2
5Kích thước tối đa xử lý hàng loạt (100 vs 500)Tùy tải DB / GBP API
6Hạn signed URL của report.get1h / 24h / 7d
7Giới hạn group_ids của group.compare5 / 10 / 20
8Thêm metrics (CTR v.v.)Thêm từ Phase 3 trở đi

12. Tài liệu liên quan ​