# Page tools now return typed results with error and completeness status

> Agno's PageFileSystem.run_command_result returns a PageCommandResult with explicit error, completeness and truncation fields, and Knowledge.get_tools(page_results=True) returns ranked SearchResult objects, including as MCP structured output.

- Published: 2026-09-23
- Author: Ashpreet Bedi
- Categories: Changelog
- Canonical: https://www.agno.com/articles/page-tools-now-return-typed-results-with-error-and-completeness-status
- Markdown: https://www.agno.com/articles/page-tools-now-return-typed-results-with-error-and-completeness-status.md

Agno's `PageFileSystem.run_command_result()` and `arun_command_result()` tell an application whether a page command failed, stopped early or got clipped. The page command tools used to return plain strings. An application that wanted to show a missing page as an error, or report an incomplete grep over MCP, had to guess from the text, and a documentation page that happened to mention `page_unavailable` looked like a failure.

`PageCommandResult` has these fields:

| Field          | Type              | Default |
| -------------- | ----------------- | ------- |
| `text`         | `str`             |         |
| `is_error`     | `bool`            | `False` |
| `errors`       | `Tuple[str, ...]` | `()`    |
| `partial`      | `bool`            | `False` |
| `truncated`    | `bool`            | `False` |
| `continuation` | `Optional[str]`   | `None`  |
| `stop_reason`  | `Optional[str]`   | `None`  |

Agno sets these fields from the way each command ran. It never reads them from the page text. Running three commands against a local page store with both methods gives the same `text`, and only the typed result says which one failed:

```text
run_command("cat /errors.md")
  "==> /errors.md <==\n# Errors\n\nA tool returns page_unavailable when storage is down.\n"
run_command_result("cat /errors.md")
  is_error=false  errors=[]  partial=false  stop_reason=null

run_command("cat /no-such-page")
  "/no-such-page: no such file. Use ls/tree to explore, or rg to search."
run_command_result("cat /no-such-page")
  is_error=true  errors=["page_not_found"]  partial=true  stop_reason="file_error"

run_command_result("rg page_unavailable /")
  is_error=false  errors=[]  text="[1 matching lines in 1 files]\n/errors.md:3:..."
```

A grep that stops early stays successful but reports `partial=true` with a `stop_reason`, so an incomplete result can't pass as proof that nothing matched. `max_output_bytes` (default 32000) bounds the complete UTF-8 JSON of the result, metadata included. When Agno clips the text, it sets `truncated=true` and clears `continuation`, because a line-based continuation command would skip text you never saw. `run_command()` and the default chat tool still return the same strings as before.

### MCP tools that report errors

`PageFileSystem.tools(transport="mcp")` builds a command tool with a native MCP output schema. A failed command comes back to the MCP client with `isError` set.

`Knowledge.get_tools(page_results=True)` returns one page search tool that answers with a ranked `SearchResult`. With the default `transport="chat"` the tool returns `SearchResult` JSON. With `transport="mcp"` it publishes the `SearchResult` output schema and returns the same object as structured content:

```python
from agno.agent import Agent
from agno.os import AgentOS, MCPConfig

search_docs = knowledge.get_tools(page_results=True, tool_name="search_docs", transport="mcp")[0]

agent_os = AgentOS(
    agents=[Agent(id="docs")],
    mcp=MCPConfig(default_tools=False, tools=[search_docs]),
)
```

Calling `search_docs` through an MCP client returned this structured content (hashes shortened, second hit left out):

```json
{
  "schema_version": 1,
  "results": [
    {
      "schema_version": 1,
      "path": "/agent.md",
      "url": "https://docs.example.com/agent",
      "title": "Agent",
      "revision": "99c5aba1...",
      "chunk_id": "5595243a...",
      "content": "Agent: Use Agent with tools.\n\nAgent\n\n# Agent\n\nUse Agent with tools.",
      "score": 0.9374999990686774,
      "rank": 1
    }
  ],
  "partial": false,
  "truncated": false,
  "omitted_count": 0,
  "warnings": []
}
```

The search tool takes a `query` and optional `alternatives`, and you set its name and description with `tool_name` and `tool_description`. Pass `async_mode=True` for an async tool. Each search also records its references on the run when you pass `run_response`. Agno exposes none of these tools on its own. You pass each one to `Agent.tools` or `MCPConfig.tools`. For a worked example of page tools behind chat and MCP, see [how we built the Agno Docs Agent](https://docs.agno.com/use-cases/documentation-agents/how-we-built-it).

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/05_agent_os/27_public_pages/page_tool_results.py), and learn more in the [Published Pages reference](https://docs.agno.com/reference/knowledge/pages).
