MCP Server — Tool Catalog
| Mục | Nộ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 cha | MCP Server (Tổng thể) |
| Design liên quan | Auth & 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ần | Ví 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:
| Param | Kiểu | Mô tả |
|---|---|---|
user_id | int? | ID user đối tượng (lược đi = user đang auth). Chỉ supervisor admin được chỉ định tùy ý |
dry_run | bool? | true = không thực thi, trả về tóm tắt side effect dự kiến (chỉ write tool) |
idempotency_key | string? | Khuyến nghị UUID. Cùng key bị từ chối duplicate trong 24h (chỉ write tool) |
3.2 Wrapper output chung
{
"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).
| Param | Kiểu | Mô tả |
|---|---|---|
cursor | string? | next_cursor từ response trang trước |
limit | int? | 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:
{
"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 tool | Side effect | scope | DB tham chiếu chính | Mô tả |
|---|---|---|---|---|---|
| 1 | location.list | 🔵 | mappy:location:read | mappy_gbp_locations + mappy_user_available_gbp_locations | Danh sách location có thể access |
| 2 | location.get | 🔵 | mappy:location:read | mappy_gbp_locations | Chi tiết location (bao gồm raw_json) |
| 3 | review.list | 🔵 | mappy:reviews:read | mappy_gbp_reviews (280k dòng) | Danh sách review |
| 4 | ranking.list | 🔵 | mappy:ranking:read | mappy_search_rankings (10M dòng) | Xếp hạng tìm kiếm |
| 5 | insight.summary | 🔵 | mappy:insight:read | mappy_gbp_insights (4.2M dòng) | Tổng hợp insight |
Phase 2 — Mở rộng write
| # | Tên tool | Side effect | scope | Mô tả |
|---|---|---|---|---|
| 6 | post.list | 🔵 | mappy:posts:read | Danh sách post |
| 7 | post.create | 🔴 | mappy:posts:write | Tạo post (support nhiều cửa hàng cùng lúc) |
| 8 | post.delete | 🟠 | mappy:posts:write | Xóa post |
| 9 | review.reply | 🟠 | mappy:reviews:write | Trả lời review |
| 10 | review.template.list | 🔵 | mappy:reviews:read | Danh sách template reply |
| 11 | review.template.create | 🟠 | mappy:reviews:write | Đăng ký template reply |
| 12 | media.list | 🔵 | mappy:media:read | Danh sách media |
| 13 | media.upload | 🔴 | mappy:media:write | Upload media (support hàng loạt) |
| 14 | media.delete | 🟠 | mappy:media:delete | Xóa media |
| 15 | location.update | 🟠 | mappy:location:write | Update thuộc tính location |
| 16 | batch.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 tool | Side effect | scope | Mô tả |
|---|---|---|---|---|
| 17 | report.list | 🔵 | mappy:report:read | Danh sách report |
| 18 | report.get | 🔵 | mappy:report:read | Lấy report (HTML / PDF path) |
| 19 | report.create | 🔴 | mappy:report:write | Yêu cầu tạo report |
| 20 | keyword.list | 🔵 | mappy:keyword:read | Danh sách từ khóa・lưu lượng |
| 21 | group.list | 🔵 | mappy:group:read | Danh sách group |
| 22 | group.compare | 🔵 | mappy:group:read + mappy:ranking:read | So sánh ranking・insight giữa các group |
| 23 | questionnaire.list | 🔵 | mappy:questionnaire:read | Danh sách khảo sát |
| 24 | questionnaire.answers | 🔵 | mappy:questionnaire:read | Lấy câu trả lời |
| 25 | sms.list | 🔵 | mappy:sms:read | Log 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:
{
"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:
{
"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_USER | target_user_id ngoài phân tầng |
INVALID_GROUP | group_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:
{
"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:
{
"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_LOCATION | User đối tượng không access được location_id |
NOT_FOUND | location_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:
{
"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:
{
"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:
{
"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/untilbắ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:
{
"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:
{
"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:
{
"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:
{
"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):
{
"data": {
"batch_id": "01HVABC...",
"status": "processing",
"queued_at": "2026-05-25T10:00:00Z",
"target_count": 12,
"estimated_completion_seconds": 60
}
}Output (dry_run=true):
{
"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
location_idsdài >= 5 → bắt buộcconfirm_token- Chi tiết xem Cơ chế an toàn write
6.2 review.reply
Trả lời review.
scope: mappy:reviews:writeSide effect: 🟠 Write đồng bộ (qua GBP API) DB: mappy_gbp_reviews
Input:
{
"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:
{
"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:
{
"type": "object",
"required": ["batch_id"],
"properties": {
"batch_id": { "type": "string" }
}
}Output:
{
"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:
{
"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:
{
"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:
{
"type": "object",
"required": ["report_id"],
"properties": {
"report_id": { "type": "integer" }
}
}Output:
{
"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:
{
"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
| tool | Giới hạn đặc biệt |
|---|---|
ranking.list | Từ chối kỳ > 365 ngày |
review.list | Kỳ > 90 ngày cần xác nhận rõ ràng |
post.create | location_ids >= 5 thì bắt buộc confirm_token |
media.upload | Tối đa 10 media / request |
report.create | Tối đa 5 lần / giờ / user |
batch.status | Khoảng polling khuyến nghị 5 giây, tối đa 1 req/sec |
9. Giới hạn payload
| Loại | Giới hạn |
|---|---|
| Kích thước JSON input | 256 KB |
| Kích thước JSON output | 1 MB (vượt thì paging) |
Output bao gồm raw_json | 5 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:
- Làm gì (1 dòng)
- Khi nào dùng (1〜2 dòng, kịch bản sử dụng)
- Điểm chính của input bắt buộc・tùy chọn
- 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ục | Phương án |
|---|---|---|
| 1 | Đặt tên tool: dot vs underscore | location.list / location_list (spec MCP cho phép cả 2) |
| 2 | Có mã hóa cursor không | base64(JSON) / mã hóa / opaque hoàn toàn |
| 3 | Filter khi output raw_json | Có filter loại trừ thông tin nhạy cảm không |
| 4 | Ngôn ngữ tool description | Tiếng Nhật / Tiếng Anh / cả 2 |
| 5 | Kích thước tối đa xử lý hàng loạt (100 vs 500) | Tùy tải DB / GBP API |
| 6 | Hạn signed URL của report.get | 1h / 24h / 7d |
| 7 | Giới hạn group_ids của group.compare | 5 / 10 / 20 |
| 8 | Thêm metrics (CTR v.v.) | Thêm từ Phase 3 trở đi |
12. Tài liệu liên quan
- MCP Server (Tổng thể)
- Auth & Tenancy — Cấu trúc phân tầng scope
- Cơ chế an toàn write — Chi tiết dry_run / idempotency / confirm_token
- DB (audit log) — Ghi tool call