Tool calling, without the mess: how Maho ships browser tools
The phrase “tool calling” has been doing a lot of work in 2026. Every product has it. Every demo includes it. Most of the time, it means a chat panel that can sometimes hit an API. Sometimes it means a browser that can pick from a list of nine actions. Sometimes it means an autonomous loop that drains a credit card.
Tool calling is not a feature. It is a contract. Between the model and the host, between the host and the user, between the user and the destination on the other end of the call. Most of what makes tool calling good or bad is whether everyone knows what the contract says.
This post is the long version of how we honor that contract inside Maho’s side panel. Why we ship narrow tools. How we compose them without an orchestrator. The tools we shipped and the ones we cut. The path for power users to author their own. Where tool calling still breaks, on us and elsewhere.

Tool calling is not a feature, it is a contract
Section titled “Tool calling is not a feature, it is a contract”When a model calls a tool, four parties are on the line. The model produces a structured request. The host validates it, runs the permission check, executes the call, and returns the result. The user is the one who granted permission, and is the one who will live with the consequences. The destination, if any, is the third-party service the call lands on.
The contract has three clauses.
First, the inputs are typed. A tool with a vague signature is a tool the model will misuse. The model is good at inferring intent and bad at noticing it has the wrong field. A typed input is a guardrail you do not have to enforce by hand.
Second, the outputs are predictable. The model needs to know what came back so it can plan the next step. A tool that sometimes returns text and sometimes returns a structured object will get reasoned about as if it always returns text, and the structured part will be dropped on the floor.
Third, the permission gate is honest. A tool that asks once and then runs forever is a tool that will eventually run when the user did not mean it to. A tool that asks every time is a tool the user will start clicking through without reading. The middle path is per-tool, per-origin, per-session, with explicit re-prompts on anything destructive. We covered the gate in MCP in the browser.
A tool that breaks any of those three clauses is a liability. The host owes the user a tool surface that does not break them. The model is not responsible for keeping the contract. The host is.
The narrow-tool principle
Section titled “The narrow-tool principle”The default temptation is to ship wide tools. A do_browser_thing tool that takes a freeform string and figures out what to do. A query_database tool that takes any SQL. A run_command tool that takes a shell line. Wide tools are easy to ship and easy to break.
We ship narrow tools instead. Each tool does one thing, with a typed input and a typed output, and refuses anything outside its scope.
A narrow tool has three properties.
It has a name that describes one verb. open_tab, not manage_tabs. get_active_tab, not get_browser_state. The name is the contract surface. A user reading the audit log can tell what happened from the verb.
It takes a small number of explicit fields. open_tab(url) takes a URL, not a freeform “what to open”. The model writes the URL, the host validates the URL, the call either runs or fails for a clear reason. There is no inferred intent inside the tool.
It refuses scope creep. If the model asks get_active_tab for “the tab I was on yesterday”, the tool returns the active tab and nothing else. It does not silently swap to history. If you want history, there is search_history.
The reason narrow tools beat wide tools is not aesthetics. It is reasoning. A model planning across narrow tools writes a sequence of small steps it can verify. A model planning against wide tools writes a paragraph and hopes. The audit log of the first run is readable. The audit log of the second is a story.

The cost of narrow tools is more tools. Nine where you could have shipped two. We accept that. It is the right cost.
Composing across tools, without an orchestrator
Section titled “Composing across tools, without an orchestrator”Once tools are narrow, you compose. The model writes a plan, calls tool A, reads the result, calls tool B. The composition is the model’s job. There is no orchestrator on top.
This decision is contested. Some agentic products ship a planner-orchestrator layer that decomposes a goal into a tool chain. We do not. The reason is that the orchestrator becomes the bug surface. When the chain runs wrong, you cannot tell whether the model picked the wrong step or the orchestrator picked the wrong tool. The audit log gets two layers and no clear attribution.
Composing without an orchestrator means three things hold.
The model is responsible for sequencing. It writes the plan as part of its reasoning. The host runs each step the moment it is approved. There is no separate state machine on top of the plan.
Each tool is independent. get_page_content does not need get_active_tab to have run first. The model can start anywhere, and the host can run any tool the user has granted permission for. Tools do not assume each other.
The audit log is one stream. Every call, with timestamp, input, output, and permission check, lives on a single timeline. The user can replay the agent’s day from that timeline without crossing layers.
The cost is that the model has to be smart enough to plan. The 2026 frontier models are. Smaller models compose less reliably, which is one of the failure modes we cover later.
The tools we shipped, and the ones we cut
Section titled “The tools we shipped, and the ones we cut”Maho ships nine built-in tools. We started with twenty-three.
The nine that shipped are the ones that act on browser state. They are listed in MCP in the browser. All nine pass the narrow-tool test, all nine are typed, all nine respect the permission gate.
The fourteen we cut are worth naming. The pattern of why we cut them is more useful than the list.
We cut three search tools. web_search, image_search, shopping_search. Each one would have hardcoded a search engine and a key arrangement. The right answer was an MCP server the user can register, not a built-in we have to maintain.
We cut four service-specific tools. create_linear_issue, post_slack_message, send_email, add_to_notion. Each one would have shipped with vendor SDKs, vendor auth flows, and a vendor lock-in we did not want in the browser. The right answer is the public MCP server for each service, registered by the user when they want it.
We cut three “smart” tools. summarize_page, extract_entities, classify_tab. These are not tools. They are model calls that do not need to leave the panel. The model already has the page content via get_page_content. Wrapping a model call in a tool was a category error.
We cut three composite tools. research_topic, prepare_meeting, triage_inbox. Each one was three or four steps glued together. We unglued them. The model chains the narrow tools when asked to research a topic. The chain is visible. The composite was a hidden orchestrator and we said we do not ship one of those.
We cut one tool because it was dangerous. eval_javascript. A tool that runs arbitrary JS in the page is a tool that the model will eventually use to do something the user did not approve. The cost-benefit was not close.
The pattern is that built-in tools should act on the browser surface and stop. Anything else is an MCP server.
Tool authorship for power users
Section titled “Tool authorship for power users”If you want a tool we did not ship, you write an MCP server. The browser is the host. Your server is the source.
The shape is small.
You scaffold a server in any language with an MCP client library. Most of them are a few hundred lines. The server lists its tools at startup, validates the inputs the host sends, executes the work, and returns a structured result.
You register the server in Maho with a URL or a local command. Maho lists the server’s tools alongside the built-ins. You grant permission per tool, per origin, per session. The audit log records every call from your server the same way it records calls into the built-ins.
You can revoke the server at any time. Maho stops listing its tools immediately. Any granted permissions are released. The Keychain entries the server may have used go through the Keychain’s normal lifecycle.
The thing this enables is the version of agentic browsing that holds up after a month of use. Your model has access to the surface you picked, and nothing else. The browser stays small. The user gets large.

The reference docs for registration, permission scopes, and the audit log are in the browser AI docs. The shape on screen matches the shape in this post. There is no second permission system underneath.
Where tool calling will keep failing
Section titled “Where tool calling will keep failing”Tool calling in browsers is not finished. Three problems are visible from here, and we do not have full answers for any of them.
Prompt injection from page content. A page can contain text that, when read by get_page_content, asks the model to call a different tool. The model is sometimes wise enough to ignore. Sometimes not. We default mutating tools to per-call confirmation specifically because of this, but a confirmed call can still be the wrong one if the user is not paying attention. The mitigation is a strong audit log and a habit of reading the prompt before clicking allow.
Loops that do not stop. A model that calls a tool, reads the result, decides to call another, and so on, can run for a hundred steps and burn money on the way. We expose a step counter and a hard stop. We have not solved the case where the model ten steps in is doing the wrong thing in a way that looks superficially right.
Smaller models compose poorly. The 2026 frontier models can plan across narrow tools. The 7B class often cannot. They miss steps, they invent fields, they call the wrong tool. If you point Maho at a small local model, the AI panel works for chat and falls down on multi-step tool chains. We do not paper over this with a planner-orchestrator. We tell you.
The honest read is that tool calling will keep getting better and will keep failing in new ways. The contract is the part we can control. The model is the part we cannot. We try to make sure that when something goes wrong, the audit log is enough to tell you what happened.
Get notified
Section titled “Get notified”Maho is in pre-release for macOS. The nine built-in tools and the BYO MCP path ship in the first build. Get notified.