Công cụ MCP
Các công cụ AgentFlow mở cho trợ lý AI bên ngoài qua MCP — đầu vào, kết quả và mã lỗi của từng công cụ.
Điểm cuối
AgentFlow là một máy chủ MCP tại:
POST {origin}/api/v1/mcpĐiểm cuối nói JSON-RPC 2.0, giao thức MCP phiên bản 2025-06-18, qua HTTP với một phản hồi cho mỗi yêu cầu. Client MCP tự tìm cách đăng nhập qua GET {origin}/api/v1/.well-known/oauth-protected-resource.
Mỗi yêu cầu mang Authorization: Bearer …, là một trong hai:
- khóa của một chủ thể dịch vụ (
afk_…) — xem Xác thực; - token OAuth của một người, cấp cho chính điểm cuối MCP này. Trợ lý khi đó hành động với đúng quyền của người đó.
Phía quản trị được cấu hình ở Máy chủ MCP. Một quy trình được bật cho MCP ở Chia sẻ quy trình qua MCP.
Các công cụ
Chỉ những quy trình đã xuất bản và đã bật chia sẻ qua MCP mới hiện ra và khởi chạy được qua list_workflows, get_workflow_form, start_workflow và batch_run_workflow. Người gọi còn phải được phép khởi chạy chính quy trình đó.
Các đối số và kết quả đều viết theo kiểu snake_case.
list_workflows
Tìm các quy trình mà trợ lý được dùng.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
search | chuỗi | không | Từ khóa, không phân biệt hoa thường, lọc theo tên và mô tả. |
{ "workflows": [{ "id": "…", "name": "Đề xuất mua sắm", "description": "…" }] }get_workflow_form
Những gì người dùng phải cung cấp để khởi chạy một quy trình.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
workflow_id | chuỗi (id) | có | Id lấy từ list_workflows. |
Khi quy trình có biểu mẫu khởi đầu:
{ "has_start_form": true, "version_id": "…", "fields": [ … ] }fields là một danh sách phẳng: mỗi trường có tên, kiểu, có bắt buộc hay không, tiêu đề, mô tả và các lựa chọn.
Khi quy trình không thu thập gì lúc khởi chạy:
{ "has_start_form": false, "version_id": "…", "note": "…" }Quy trình đó khởi chạy được với input_payload rỗng; người dùng điền bước đầu tiên trong hộp công việc của mình.
start_workflow
Khởi chạy một lượt chạy thay cho người dùng. Lệnh này tạo một lượt chạy thật, nên trợ lý cần xác nhận với người dùng trước.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
workflow_id | chuỗi (id) | có | |
name | chuỗi | có | Tên ngắn cho lượt chạy. |
input_payload | đối tượng | có | Giá trị các trường, theo tên trường từ get_workflow_form. |
record_id | chuỗi (id) | không | Với quy trình gắn với hồ sơ: hồ sơ mà lượt chạy nói về. Bắt buộc khi quy trình khai báo chủ thể là một hồ sơ đơn lẻ. Quy trình nhận nhiều hồ sơ cùng lúc không khởi chạy ở đây mà qua batch_run_workflow. |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
{ "instance_id": "…", "link": "/instances/…", "status": "running" }batch_run_workflow
Chạy một quy trình gắn với hồ sơ trên nhiều hồ sơ của một thực thể cùng lúc. Bắt buộc với quy trình nhận một tập hồ sơ; cũng dùng được để chạy một quy trình một-hồ-sơ lần lượt trên từng hồ sơ đã chọn. Các lượt chạy được khởi động ở chế độ nền, nên trợ lý cần xác nhận danh sách hồ sơ với người dùng trước.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
entity_id | chuỗi (id) | có | Thực thể chứa các hồ sơ. |
workflow_id | chuỗi (id) | có | |
record_ids | mảng chuỗi (id) | có | Tối đa 200 id hồ sơ của thực thể đó. |
{ "batch_id": "…", "arity": "single", "status": "…" }arity là single hoặc multiple. Tiến độ được theo dõi qua API REST — xem ví dụ về hồ sơ.
get_instance_status
Trạng thái của một lượt chạy đã khởi động, ví dụ để báo người dùng rằng nó đang chờ phê duyệt.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
instance_id | chuỗi (id) | có | run_id được chấp nhận như tên gọi khác. Gửi một trong hai; gửi cả hai mà trỏ tới hai lượt chạy khác nhau thì bị từ chối. |
{ "instance_id": "…", "name": "…", "status": "running", "outcome_label": null }Người gọi chỉ đọc được lượt chạy mà họ xem được trong AgentFlow: lượt chạy do chính họ khởi động (với một khóa: do các khóa của chủ thể đó khởi động), hoặc mọi lượt chạy khi có instance.read.any hay instance.admin. Mọi lượt chạy khác được trả lời y như không tồn tại.
find_runs
Các lượt chạy mà người gọi xem được.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
status | chuỗi | không | Một trong running, paused, completed, failed, cancelled. Giá trị lạ bị từ chối, không bao giờ bị bỏ qua. |
q | chuỗi | không | Từ khóa khớp với tên lượt chạy. |
limit | số nguyên | không | Tối đa 50. |
{ "runs": [{ "run_id": "…", "name": "…", "status": "running", "started_at": "…" }], "complete": true }complete: false nghĩa là câu trả lời đã bị cắt tại limit; hãy thu hẹp câu hỏi thay vì mô tả trang này như toàn bộ.
list_entities
Các loại bản ghi mà bản triển khai này lưu, và loại nào người gọi được tạo.
{ "entities": [{ "entity_id": "…", "key": "account", "name": "Account", "description": null, "can_create": true }], "complete": true }describe_entity
Các trường của một thực thể: kiểu dữ liệu, trường nào lọc và sắp xếp được, và tham chiếu trỏ tới đâu. Hãy đọc trước khi gọi search_records, để bộ lọc nêu đúng tên trường có thật.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
entity_id | chuỗi (id) | có | Từ list_entities. |
Trường mà người gọi không được đọc thì không hề được liệt kê — cũng không liệt kê như "đã giữ lại", vì chính tên trường cũng là thứ họ không được đọc.
search_records
Bản ghi của một thực thể, thu hẹp bằng bộ lọc.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
entity_id | chuỗi (id) | có | |
q | chuỗi | không | Từ khóa tự do, tìm trên các trường tìm kiếm được. |
filter | đối tượng | không | {"field_filters": [{"field": "tier", "op": "eq", "value": "paid"}], "ref_filters": [...]}. Khóa lạ bị từ chối. |
sort | chuỗi | không | Một khóa trường từ describe_entity. |
dir | chuỗi | không | asc hoặc desc. |
limit | số nguyên | không | Tối đa 50. |
{
"entity_name": "Account",
"records": [{ "record_id": "…", "entity_id": "…", "fields": { … }, "updated_at": "…", "omitted_field_count": 0 }],
"total": 2,
"total_capped": false,
"note": null,
"complete": true
}Việc từ chối khóa lạ chính là lý do filter là một đối tượng. Các kiểu bộ lọc không tự từ chối được khóa lạ, nên fieldFilters gõ sai sẽ được đọc thành không có bộ lọc nào cả, và cuộc tìm kiếm trả lời một câu hỏi đã thu hẹp bằng toàn bộ bản ghi của thực thể — rồi báo đó là câu trả lời.
Mỗi dòng mang tối đa tám trường. omitted_field_count lớn hơn không nghĩa là dòng đó chưa phải cả bản ghi; phần còn lại nằm ở get_record. Không có con trỏ phân trang: complete: false nghĩa là còn nữa, và cách lấy tiếp là thu hẹp bộ lọc.
get_record
Một bản ghi, trọn vẹn.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | Từ search_records. |
{
"record_id": "…", "entity_id": "…", "entity_name": "Account", "label": "Northwind",
"fields": { … }, "updated_at": "…", "redacted_field_count": 1, "omissions": [], "omitted_count": 0
}Thứ trả về là thứ người gọi này được đọc: trường họ không được xem thì vắng mặt chứ không để trống, và redacted_field_count cho biết đã giữ lại bao nhiêu — nên một bản ghi có số này lớn hơn không thì chưa đầy đủ. Một id bản ghi người gọi không với tới được trả lời y hệt một id không tồn tại.
Giới hạn 64 KiB. Xem Giới hạn dung lượng.
get_entity_create_form
Những gì cần có để tạo một bản ghi của thực thể này.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
entity_id | chuỗi (id) | có |
{ "entity_id": "…", "entity_name": "Account", "fields": [ … ] }create_record
Tạo một bản ghi, có hiệu lực ngay.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
entity_id | chuỗi (id) | có | |
values | đối tượng | có | Theo khóa trường lấy từ get_entity_create_form. |
unit_id | chuỗi (id) hoặc null | không | Khi thực thể có phạm vi theo đơn vị. |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
Trả về bản ghi như get_record trả về, đã che các trường nhạy cảm theo quyền người gọi.
Bị từ chối đúng như POST /entities/{entity_id}/records từ chối chính người gọi đó — vì nó chính là thao tác ấy.
update_record
Sửa từng trường của một bản ghi, có hiệu lực ngay.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | |
changes | đối tượng | có | Chỉ những trường cần đổi. null tường minh sẽ xóa trường đó. |
expected_updated_at | chuỗi (date-time) | có | Giá trị updated_at ở lần đọc gần nhất. |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
Nếu người khác đã đổi bản ghi trong lúc đó, lệnh bị từ chối với STALE_VERSION thay vì ghi đè lên công việc của họ. Hãy đọc lại bản ghi rồi thử lại.
find_work
Công việc trong My Work của người gọi, mọi loại: nhiệm vụ quy trình đang chờ họ, và các bản ghi như phiếu hỗ trợ có theo dõi công việc. Chỉ dành cho một con người — xem Công cụ mà chủ thể dịch vụ không gọi được.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
view | chuỗi | không | all (mọi thứ bạn được xem), mine (việc bạn đang giữ) hoặc claimable (việc chưa ai nhận mà bạn nhận được). |
overdue | boolean | không | Chỉ việc còn mở đã quá hạn. |
signs | boolean | không | true cho việc có bước quy trình đang chờ chữ ký của bạn. |
q | chuỗi | không | Từ cần tìm trong tiêu đề của việc. |
sort | chuỗi | không | due_at, started_at, updated_at hoặc priority. |
limit | số nguyên | không | Tối đa 50. |
{
"work": [
{
"record_id": "…", "entity_id": "…", "entity": "Workflow task", "title": "Duyệt ngân sách",
"work_state": "active", "due_at": null, "claimed_by_user_id": null, "held_by_you": false,
"step": { "workflow": { … }, "step": { … }, "kind": "human", "signs": false, "subject": { … } },
"finishes": "…"
}
],
"partition": "…", "complete": true
}Dòng có step là một nhiệm vụ quy trình: thao tác với get_task, claim_task và submit_task, truyền record_id của nó. Mọi dòng khác kết thúc khi bản ghi chuyển sang trạng thái hoàn tất, tức là sửa bản ghi chứ không phải nộp gì; finishes cho biết là loại nào. partition mô tả đúng tập hợp đã trả về. Hãy thuật lại điều đó, đừng khẳng định rộng hơn.
get_task
Một nhiệm vụ quy trình và biểu mẫu nó yêu cầu.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | record_id của một dòng có step từ find_work. |
{
"record_id": "…", "entity_id": "…", "run_id": "…", "status": "open", "workflow_name": "…", "run_name": "…",
"step_label": "Review", "claimed_by": null, "held_by_you": false, "due_at": null, "can_claim": true,
"contents_readable": true,
"step_description": "…", "form": { … },
"saved_values": { … }, "prefill": null,
"actions": [{ "id": "approve", "label": "Phê duyệt" }], "signed_action_id": null
}saved_values là những gì đã được nhập vào nhiệm vụ này — không phải prefill, thứ luôn do AI tạo ra. Khi nộp, hệ thống THAY THẾ giá trị đã lưu chứ không trộn vào, nên muốn hoàn tất một nhiệm vụ mới điền dở thì phải gửi lại các giá trị đó kèm giá trị mới.
actions là các nút quyết định mà nhiệm vụ này đưa ra. Khi danh sách không rỗng, submit_task bắt buộc payload._action mang một trong các id đó: thiếu thì bị từ chối với HUMAN_ACTION_REQUIRED, id lạ thì HUMAN_ACTION_UNKNOWN. Hành động bị ẩn với người gọi này không được liệt kê. signed_action_id cho biết hành động nào (nếu có) đi theo luồng ký thay vì nộp thường.
Mở cho những người làm nhiệm vụ — người đang giữ và những người được đề nghị nó — và cho bất kỳ ai, con người hay chủ thể dịch vụ, có quyền giao việc (assign) chạm tới bản ghi của nhiệm vụ, hoặc instance.admin. Mọi người gọi khác nhận câu trả lời của một nhiệm vụ không tồn tại, kể cả người có quyền đọc bản ghi.
contents_readable là true với mọi người gọi được trả lời, vì quy tắc bảng điều khiển của bước cho người đọc vào xem trọn vẹn; người không được cho vào nhận câu trả lời của một nhiệm vụ không tồn tại và không có nội dung, nên không có biểu mẫu bị ẩn nào để phân biệt.
claim_task
Nhận một nhiệm vụ về cho mình, có hiệu lực ngay. Chỉ dành cho một con người.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
release_task
Trả lại một nhiệm vụ đang giữ, có hiệu lực ngay. Chỉ dành cho một con người. Trả lại nhiệm vụ do người khác giữ cần quyền giao việc (assign) trên thực thể Workflow task.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
Gọi lại kèm khóa thì thành công và trả về nhiệm vụ như người gọi đọc được lúc này, dù họ không còn giữ nó — vì yêu cầu đang được lặp lại đã thành công thật — hoặc IDEMPOTENT_RESOURCE_GONE khi họ không còn mở được nó. Gọi lại không kèm khóa thì bị từ chối, đúng với lý do thật sự: người gọi không giữ nhiệm vụ đó.
submit_task
Hoàn tất một nhiệm vụ bằng dữ liệu biểu mẫu, có hiệu lực ngay. Lượt chạy đi tiếp. Chỉ dành cho một con người.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
record_id | chuỗi (id) | có | |
payload | đối tượng | có | Theo tên trường trong biểu mẫu của nhiệm vụ. |
idempotency_key | chuỗi | không | Xem Thử lại an toàn. |
Lỗi kiểm tra dữ liệu trả về dưới mã VALIDATION_ERROR, nêu tên các trường không hợp lệ ngay trong thông điệp.
list_dashboards
Các bảng thông tin người gọi mở được.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
q | chuỗi | không | Từ khóa khớp với tên. |
limit | số nguyên | không | Tối đa 50. |
{ "dashboards": [{ "dashboard_id": "…", "name": "…", "description": null, "updated_at": "…" }], "complete": true }get_dashboard
Các con số của một bảng thông tin, tính theo phạm vi của chính người gọi.
| Đầu vào | Kiểu | Bắt buộc | Ý nghĩa |
|---|---|---|---|
dashboard_id | chuỗi (id) | có | |
filter_values | đối tượng | không | Giá trị cho các bộ lọc của chính bảng, theo tên điều khiển. |
{ "dashboard_id": "…", "name": "…", "description": null, "filter_values": {}, "tiles": [ … ], "omissions": [], "omitted_count": 0 }Chia sẻ một bảng thông tin là chia sẻ đặc tả của nó, không bao giờ là con số: ô dựa trên dữ liệu người gọi không được đọc sẽ trả về "không khả dụng", chứ không lấp đầy bằng quyền của người khác.
Giới hạn 16 KiB. Xem Giới hạn dung lượng.
Công cụ mà chủ thể dịch vụ không gọi được
find_work, claim_task, release_task và submit_task từ chối chủ thể dịch vụ với mã USER_IDENTITY_REQUIRED.
Đây không phải một tùy chọn cấu hình: theo thiết kế, những người được đề nghị một việc chỉ gồm con người. Không có danh sách việc nào để trả lời, và không có nhiệm vụ nào để một khóa nắm giữ. get_task là ngoại lệ, vì đọc một nhiệm vụ được nêu đích danh đi theo quy tắc của chính bước đó chứ không theo đối tượng nhận: chủ thể dịch vụ được trả lời khi có quyền giao việc (assign) chạm tới bản ghi của nhiệm vụ, hoặc instance.admin, còn lại nhận câu trả lời của một nhiệm vụ không tồn tại.
Thử lại an toàn
Mọi công cụ làm thay đổi dữ liệu đều nhận idempotency_key tùy chọn, ghi vào cùng sổ cái với header Idempotency-Key của REST API — xem Thử lại an toàn.
- Gửi lại cùng một khóa để thử lại an toàn. Việc chỉ được làm một lần. Lần lặp trả về tài nguyên ở trạng thái hiện tại, đọc lại chứ không phát lại bản ghi âm: một trường trở thành nhạy cảm sau lần gọi đầu cũng bị giữ lại ở lần lặp.
- Tài nguyên đã bị xóa trả về
IDEMPOTENT_RESOURCE_GONE. - Cùng khóa nhưng khác tham số bị từ chối với
IDEMPOTENCY_KEY_MISMATCH. Khóa định danh cho yêu cầu, và tham số là một phần của yêu cầu. - Khóa thuộc về người gọi, không thuộc về công cụ. Dùng lại một khóa ở công cụ khác cũng bị từ chối như vậy. Điều này là cố ý: phương án còn lại là
claim_tasktrả lời bằng một bản ghi mà nó chưa hề đụng tới. - Khóa gửi cho MCP và cho REST API không lẫn sang nhau. Sổ cái ghi một lệnh gọi công cụ dưới
mcp.<tên_công_cụ>.
Các công cụ nhận khóa: start_workflow, batch_run_workflow, create_record, update_record, claim_task, release_task, submit_task.
Giới hạn dung lượng
get_record giới hạn 64 KiB và get_dashboard giới hạn 16 KiB.
Khi vượt giới hạn, hệ thống bỏ nguyên cả thành phần và báo lại bằng hai trường. omitted_count luôn là số lượng đã bỏ. omissions nêu tên chúng — chỉ tới một phần ngân sách, sau đó trở thành mẫu đại diện, vì với payload gồm nhiều thành phần nhỏ thì danh sách những thứ thiếu sẽ lấn hết phần còn lại.
Nó không bao giờ cắt ngắn một giá trị: một con số đọc dở vẫn trông như một con số, và không cách nào biết nó đã mất ba chữ số cuối.
omitted_count lớn hơn không nghĩa là câu trả lời chưa trọn vẹn. Hãy nói rõ điều đó thay vì mô tả nó như đầy đủ, kể cả khi omissions rỗng — rỗng mà count dương nghĩa là tên không đủ chỗ, không phải là không bỏ gì.
Kết quả của một lệnh gọi công cụ
Thành công:
{
"content": [{ "type": "text", "text": "…" }],
"structuredContent": { … },
"isError": false
}structuredContent là dữ liệu kết quả; content chứa cùng dữ liệu đó dưới dạng văn bản JSON.
Bị từ chối hoặc thất bại:
{
"content": [{ "type": "text", "text": "workflow not found or not available to you" }],
"structuredContent": {
"error": {
"code": "NOT_FOUND",
"message": "workflow not found or not available to you",
"request_id": "…"
}
},
"isError": true
}codelấy từ danh mục mã lỗi.messagelà câu mô hình đọc, giữ nguyên văn tiếng Anh.request_idlà giá trị cần gửi kèm khi báo lỗi cho bộ phận hỗ trợ.
| Từ chối | code | message |
|---|---|---|
| Thiếu hoặc sai một đối số | VALIDATION_ERROR | Ví dụ `workflow_id` must be a valid id hoặc `name` is required. |
| Quy trình không tồn tại, hoặc người gọi không được dùng | NOT_FOUND | workflow not found or not available to you |
Thực thể không tồn tại, hoặc người gọi không được dùng (batch_run_workflow) | NOT_FOUND | entity not found or not available to you |
| Lượt chạy không tồn tại, hoặc người gọi không được xem | NOT_FOUND | workflow run not found |
| Quy trình chưa bật chia sẻ qua MCP | WORKFLOW_NOT_EXPOSED | this workflow is not available via MCP |
| Quy trình chưa có phiên bản xuất bản | WORKFLOW_NOT_PUBLISHED | this workflow has no published version |
| Quy trình khởi chạy từ một trigger | WORKFLOW_STARTS_FROM_TRIGGER | this workflow starts from a trigger, not from a caller |
| Dữ liệu khởi chạy không hợp lệ | VALIDATION_ERROR | Bắt đầu bằng validation failed: |
| Lượt khởi chạy hàng loạt bị từ chối | Mã riêng của lần từ chối đó | |
| Lỗi nội bộ | INTERNAL_ERROR | internal error (request <id>) — nguyên nhân được ghi log, không bao giờ trả về. |
Lỗi xác thực và lỗi JSON-RPC
Khi xác thực bị từ chối, điểm cuối trả về một lỗi JSON-RPC có error.data là {code, request_id}. code trùng với mã mà API REST gửi cho cùng lý do.
| HTTP | JSON-RPC | Khi nào |
|---|---|---|
401 | -32001 | Token hoặc khóa không hợp lệ, đã thu hồi hay hết hạn, token cấp cho nơi khác, token do một ứng dụng khác lấy thay mặt người dùng, hoặc phiên đã bị thu hồi (INVALID_TOKEN, TOKEN_EXPIRED, DELEGATED_TOKEN_NOT_ACCEPTED, SESSION_REVOKED). Phản hồi mang header WWW-Authenticate để client đăng nhập lại. |
403 | -32003 | WRONG_ORG, USER_DEACTIVATED, và USER_NOT_PROJECTED khi người đó chưa có tài khoản AgentFlow. Đăng nhập lại không giải quyết được, nên không có WWW-Authenticate. |
503 | -32002 | Kho xác thực không trả lời. Phản hồi mang header Retry-After. |
Các lỗi JSON-RPC còn lại:
| JSON-RPC | Khi nào |
|---|---|
-32700 | Không đọc được thân yêu cầu (parse error). |
-32601 | Phương thức không tồn tại (method not found). |
-32602 | Công cụ không tồn tại (unknown tool: …). |