Skip to content
Get Started

③ Answers

Tools & Agent Overview

How agents, runtime-discovered Python tools, skills, and the MCP endpoint fit together

Four moving parts do the work in a chat turn, and this page is the map between them.

Mirobody ships two agent runtimes and picks one per chat turn. Both read from the same tool registry; what differs is who runs the tool loop.

AgentShape of a turn
DeepAgentLangChain create_agent plus the deepagents middleware stack, over a PostgreSQL-backed virtual filesystem, with an in-process JavaScript REPL. The loop runs here.
BaseAgentNo LangChain. It hands /mcp to the provider and streams the result — the loop runs in the provider.

Which one answers, which providers each can use, and how their prompts are configured is the subject of Agent Types.

There is no registration macro and no build step. When the server starts, the engine walks every directory named in MCP_TOOL_DIRS, imports each module it finds, and turns the eligible callables into MCP tools.

  1. Scan the directories in MCP_TOOL_DIRS subdirectories, non-.py files and files whose name starts with _ are skipped
  2. Import each module an import error is logged and that module is skipped — the server still starts
  3. Register top-level functions and methods of classes whose name ends in Service
  4. Build the JSON schema from the type hints str int float bool list[...] dict[...]
  5. Build the description from the docstring first block → tool description; the Args: block → per-parameter descriptions

The rules, in full:

  • Directories come from the MCP_TOOL_DIRS config key. The shipped default is the single packaged directory mirobody/agent/tools; add your own and list it first.
  • Files must end in .py. A leading underscore means “not a tool module” — which is how __init__.py and private helpers stay out of the registry.
  • Functions at module level are registered as tools. A leading underscore excludes them.
  • Classes are only considered when the class name ends in Service. Inside such a class, public methods become tools; _-prefixed methods, inherited methods and methods imported from another module are ignored.
  • A class may opt out by defining a static _enabled() -> bool. Return False and the whole class is skipped, so none of its tools exist. This is how optional integrations disappear cleanly when their API key is not configured.

A tool that needs to know who is asking declares a user_info parameter. It is stripped from the schema the model sees, and filled in by the server from the authenticated caller before the function runs:

{"success": True, "user_id": "...", "session_id": "..."}

So the model never supplies — and cannot forge — an identity; a tool reads it with user_info.get("user_id") and scopes its query to that user.

Skills are the other half of “what an agent knows how to do”, and they are not tools. A skill is a directory containing one SKILL.md: its frontmatter says when to reach for it and is injected into every prompt, its body is read only when a task calls for it. The content becomes context, not a function call. See Agent Skills.

The same registry is served at /mcp over JSON-RPC 2.0, so Claude Desktop, Cursor and any custom client can call exactly the tools your own agents call. tools/list is unauthenticated — and honest per account: the two tools bound to user data are listed only when that account actually holds that kind of data.

Terminal window
curl -X POST http://localhost:18080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The endpoint faces both ways: it serves tools outward, and Mirobody also keeps a per-user registry of external MCP servers. Mirobody MCP Server covers client setup, remote HTTPS access and OAuth.

Two config keys per agent narrow the registry, using the agent name in upper case as the suffix — DEEP and BASE for the two shipped agents:

KeyEffect
ALLOWED_TOOLS_{NAME}Whitelist. When set, only these tools are offered.
DISALLOWED_TOOLS_{NAME}Blacklist. Applied after the whitelist, so it always wins.

Leave both empty — as the shipped config.yaml does — and every discovered tool is available to that agent.