Document structuring with MCP
Start one document-structuring job, poll the existing task, and interpret the result.
Document structuring with MCP
Use this guide when an MCP agent imports a document into the Knowledge Base. Structuring and vectorization are separate operations: a successful structuring task does not prove vectorization is complete, and agents should not vectorize the whole workspace after every import.
These contracts were checked against public-gateway develop at bfa1675 (version 0.5.1). This is source verification, not confirmation that every environment has deployed that version.
MCP results currently contain JSON serialized in a text content block. The examples below show the decoded JSON.
Tools
| Tool | Required arguments | Optional arguments | Result |
|---|---|---|---|
get-categories | none | categoryId to inspect a subtree | Available Knowledge Base categories |
create-structuring-task | source: nonempty string | categoryId: nonempty string | { "taskId": "..." } |
get-structuring-task | taskId: nonempty string | none | Status and processing result for the same task |
The authenticated workspace comes from the MCP API key. Do not add tenantId, internal SDS credentials, or internal routes to tool arguments.
Before starting
source must be a URL or storage reference accessible to the processing service. Do not pass a local filesystem path, a file's contents, or a base64 attachment payload as if it were a source URL. Missing or inaccessible sources need to be corrected.
Choose exactly one path:
Known category
Retrieve available categories with get-categories, or reuse a relevant result already obtained. Match the intended category and pass its actual returned ID as categoryId. A category name or policy name is not a category ID.
{"name":"get-categories","arguments":{}}After selecting an actual returned category ID:
{"name":"create-structuring-task","arguments":{"source":"https://example.com/manual.pdf","categoryId":"507f1f77bcf86cd799439012"}}Unknown category
If no matching category is established, pass only source and omit categoryId entirely. This starts pre-parsing and stores the processed document in the general category.
Do not guess an ID, create a category, require category selection just to start this flow, or hard-code a general-category ID.
{"name":"create-structuring-task","arguments":{"source":"https://example.com/manual.pdf"}}The gateway chooses the processing flow from the presence of categoryId. Do not add mode, preParsing, policyId, or tenantId.
Start once and save taskId
Both paths enqueue background work and immediately return a job identifier in taskId, not jobId. This is not the final document or entity ID.
{"taskId":"example-task-id"}Never call create-structuring-task again to check progress. A repeated creation can enqueue duplicate processing. If creation times out without returning an ID, report the uncertain outcome instead of blindly submitting again.
Poll the same task
Wait 30 seconds by default, then call get-structuring-task with the saved taskId.
{"name":"get-structuring-task","arguments":{"taskId":"example-task-id"}}Repeat this status call at the selected interval while work remains in progress. Another positive interval can be selected by the user or agent runtime. The interval is a client-side scheduling choice, not a server guarantee or a pollInterval argument. Use the runtime's timer, wait, or scheduler; do not issue a tight loop.
Use a finite waiting budget. If it expires, retain taskId and report the last observed state as pending or unconfirmed. A client timeout does not prove server-side failure. If the runtime cannot wait or schedule follow-ups, return taskId and explain how to check it later.
Interpret the result
| Field | Meaning |
|---|---|
taskId | The task being checked |
state | Task execution state, passed through from the task service as a string |
applicationStatus | Processing outcome, such as SUCCESS or FAILURE, or null when unavailable |
entityId | Resulting entity ID when supplied by processing, otherwise null |
result | Original processing result, or null |
progress | Currently always null in the gateway; do not promise percentages or an ETA |
Decision logic:
- Confirmed success:
state === "SUCCESS"andapplicationStatus === "SUCCESS". Stop polling. IfentityIdis present, useget-entitywith{"id":"<returned-entity-id>"}when the user needs the processed content. - Failure:
applicationStatus === "FAILURE"or the task reports a terminal failure/cancellation, such asFAILUREorREVOKED. Stop polling and report the relevant returned error without exposing credentials or internal secrets. - Still running: for a nonterminal state such as
PENDING, wait and check the same task again. A missing result is not itself an error. Example state names are not an exhaustive enum. - Finished but inconclusive:
state === "SUCCESS"without confirmed application success is not enough to claim the document was processed. Surface the returned outcome; if it is missing or unrecognized, report that completion is unconfirmed instead of polling forever or inventing a result.
Successful result example:
{
"taskId": "example-task-id",
"state": "SUCCESS",
"progress": null,
"result": {"status":"SUCCESS","result":{"data":{"id":"507f1f77bcf86cd799439013"}}},
"applicationStatus": "SUCCESS",
"entityId": "507f1f77bcf86cd799439013"
}Important failure example: the task executor finished normally, but document processing failed. Treat this as failure, not success.
{
"taskId": "example-task-id",
"state": "SUCCESS",
"progress": null,
"result": {"status":"FAILURE","error":"Document type unsupported"},
"applicationStatus": "FAILURE",
"entityId": null
}On a temporary status-read failure, retry the status read within the runtime's retry/wait budget. On an authentication error or missing task, stop and resolve that issue rather than creating a replacement task.