Hướng dẫn AgentFlow

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àoKiểuBắt buộcÝ nghĩa
searchchuỗikhôngTừ 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àoKiểuBắt buộcÝ nghĩa
workflow_idchuỗ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àoKiểuBắt buộcÝ nghĩa
workflow_idchuỗi (id)có
namechuỗicóTên ngắn cho lượt chạy.
input_payloadđối tượngcóGiá trị các trường, theo tên trường từ get_workflow_form.
record_idchuỗi (id)khôngVớ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_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
entity_idchuỗi (id)cóThực thể chứa các hồ sơ.
workflow_idchuỗi (id)có
record_idsmả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àoKiểuBắt buộcÝ nghĩa
instance_idchuỗ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àoKiểuBắt buộcÝ nghĩa
statuschuỗikhôngMột trong running, paused, completed, failed, cancelled. Giá trị lạ bị từ chối, không bao giờ bị bỏ qua.
qchuỗikhôngTừ khóa khớp với tên lượt chạy.
limitsố nguyênkhôngTố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àoKiểuBắt buộcÝ nghĩa
entity_idchuỗ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àoKiểuBắt buộcÝ nghĩa
entity_idchuỗi (id)có
qchuỗikhôngTừ khóa tự do, tìm trên các trường tìm kiếm được.
filterđối tượngkhông{"field_filters": [{"field": "tier", "op": "eq", "value": "paid"}], "ref_filters": [...]}. Khóa lạ bị từ chối.
sortchuỗikhôngMột khóa trường từ describe_entity.
dirchuỗikhôngasc hoặc desc.
limitsố nguyênkhôngTố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àoKiểuBắt buộcÝ nghĩa
record_idchuỗ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àoKiểuBắt buộcÝ nghĩa
entity_idchuỗ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àoKiểuBắt buộcÝ nghĩa
entity_idchuỗi (id)có
valuesđối tượngcóTheo khóa trường lấy từ get_entity_create_form.
unit_idchuỗi (id) hoặc nullkhôngKhi thực thể có phạm vi theo đơn vị.
idempotency_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
record_idchuỗi (id)có
changesđối tượngcóChỉ những trường cần đổi. null tường minh sẽ xóa trường đó.
expected_updated_atchuỗi (date-time)cóGiá trị updated_at ở lần đọc gần nhất.
idempotency_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
viewchuỗikhôngall (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).
overduebooleankhôngChỉ việc còn mở đã quá hạn.
signsbooleankhôngtrue cho việc có bước quy trình đang chờ chữ ký của bạn.
qchuỗikhôngTừ cần tìm trong tiêu đề của việc.
sortchuỗikhôngdue_at, started_at, updated_at hoặc priority.
limitsố nguyênkhôngTố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àoKiểuBắt buộcÝ nghĩa
record_idchuỗ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àoKiểuBắt buộcÝ nghĩa
record_idchuỗi (id)có
idempotency_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
record_idchuỗi (id)có
idempotency_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
record_idchuỗi (id)có
payloadđối tượngcóTheo tên trường trong biểu mẫu của nhiệm vụ.
idempotency_keychuỗikhôngXem 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àoKiểuBắt buộcÝ nghĩa
qchuỗikhôngTừ khóa khớp với tên.
limitsố nguyênkhôngTố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àoKiểuBắt buộcÝ nghĩa
dashboard_idchuỗi (id)có
filter_valuesđối tượngkhôngGiá 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_task trả 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
}
  • code lấy từ danh mục mã lỗi.
  • message là câu mô hình đọc, giữ nguyên văn tiếng Anh.
  • request_id là giá trị cần gửi kèm khi báo lỗi cho bộ phận hỗ trợ.
Từ chốicodemessage
Thiếu hoặc sai một đối sốVALIDATION_ERRORVí 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ùngNOT_FOUNDworkflow 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_FOUNDentity 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 xemNOT_FOUNDworkflow run not found
Quy trình chưa bật chia sẻ qua MCPWORKFLOW_NOT_EXPOSEDthis workflow is not available via MCP
Quy trình chưa có phiên bản xuất bảnWORKFLOW_NOT_PUBLISHEDthis workflow has no published version
Quy trình khởi chạy từ một triggerWORKFLOW_STARTS_FROM_TRIGGERthis workflow starts from a trigger, not from a caller
Dữ liệu khởi chạy không hợp lệVALIDATION_ERRORBắt đầu bằng validation failed:
Lượt khởi chạy hàng loạt bị từ chốiMã riêng của lần từ chối đó
Lỗi nội bộINTERNAL_ERRORinternal 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.

HTTPJSON-RPCKhi nào
401-32001Token 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-32003WRONG_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-32002Kho 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-RPCKhi nào
-32700Không đọc được thân yêu cầu (parse error).
-32601Phương thức không tồn tại (method not found).
-32602Công cụ không tồn tại (unknown tool: …).

On this page