Tools and MCP
One error vocabulary, MCP servers by config, agents as MCP servers.
In short: tools are the things your agent can do — look up an order, issue a credit, open a ticket. agentFast gives every tool the same small set of failure messages so the agent can reason about what went wrong, and lets you plug in tools other people have already built.
Tools are where agents touch the real world, so they're where most production incidents start. Two decisions shape everything here: errors are values, never exceptions, and the error vocabulary is fixed.
The 12-code vocabulary
Every tool in agentFast fails with one of exactly twelve codes. Never free text:
| Code | Means |
|---|---|
| NOT_FOUND | The thing doesn't exist |
| ALREADY_EXISTS | It does, and that's the problem |
| INVALID_INPUT | Schema validation failed |
| FORBIDDEN | Policy said no |
| RATE_LIMITED | Too many calls — includes a retry hint |
| TIMEOUT | Took too long |
| UPSTREAM_ERROR | A dependency failed |
| NETWORK_BLOCKED | Egress denied by the SSRF guard |
| APPROVAL_PENDING | Waiting on a human |
| APPROVAL_REJECTED | A human said no — carries their reason |
| SANDBOX_ERROR | Code execution failed inside the sandbox |
| INTERNAL_ERROR | A tool bug. The backstop |
A model reasons far better about RATE_LIMITED than about Error: 429 Too Many Requests (retry-after: 30). Free-text errors make every tool's failure a novel parsing problem; twelve
codes make it a decision the model has actually seen before.
Writing a tool
One decorator handles validation, timeouts and crashes:
from core.tools import ToolContext, ToolError, tool
@tool(
"Look up an order by id: status, items, total and delivery date.",
group="support",
timeout_s=10,
)
async def order_lookup(tctx: ToolContext, order_id: str) -> dict:
order = await tctx.service("helpdesk").get_order(order_id)
if order is None:
raise ToolError("NOT_FOUND", f"no order {order_id}")
return {"order": order}
The description is the first argument and it is not a comment — the model reads it to decide when to call the tool. See Custom tools for the full walkthrough.
Validation failures become INVALID_INPUT, a timeout becomes TIMEOUT, and an unhandled exception
becomes INTERNAL_ERROR with the traceback logged rather than surfaced. Tools are not supposed to
raise — the wrapper is the backstop, not the mechanism.
Consuming MCP servers
Any MCP server's tools become available by configuration. No code:
mcp_servers:
- name: github
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: ${GITHUB_TOKEN}
Their tools are auto-namespaced (github.create_issue), traced like native tools, rate-limited, and
overridable for HITL — so an MCP tool that opens pull requests can be marked high-risk exactly like
a built-in one:
tool_overrides:
github.create_pull_request:
risk: high
hitl_mode: suspend
Manage them from the CLI or the dashboard's MCP panel:
agentfast mcp add github --command npx --args "-y,@modelcontextprotocol/server-github"
agentfast mcp test github # connects for real and lists the tools
agentfast mcp remove github
Publishing your agent as an MCP server
The other direction: expose your whole agent so Claude Desktop, Cursor or Claude Code can call it.
mcp_provider:
expose_agent: true # the agent itself, as one tool
expose_tools: true # its individual tools
expose_skills: true # playbooks
auto_approve: false # HITL stays enforced across the boundary
agentfast mcp serve
With auto_approve: false, a high-risk tool called through MCP still pauses and waits for a human
in your approval queue — the calling client just sees APPROVAL_PENDING. Setting it to true
removes that gate for every MCP caller. Be deliberate.
What ships built in
54 tools across five groups — core, shared, support, research and code. Notable guards:
- SSRF protection on every outbound fetch — private ranges and metadata endpoints are blocked,
returning
NETWORK_BLOCKED. - Read-only SQL enforcement on database tools.
- A deny-list on
run_shell, plus sandboxed execution for the code tools.
These are defaults, not suggestions. A tool that reaches the network goes through the guard whether it's built in or one you added.