-
-
The approval card: every permission in plain words, consequence first, one approval for the whole task
-
Architecture: five sub-agents split by what each may know; first contact with unvetted servers happens on Cloud Run
-
A task with an impossible step gets no approve button at all — nothing has run, and nothing will
-
Enforcement test: three tools handed over, one in the approved plan — the out-of-plan call is refused before the server
The person this is for
Someone runs onboarding at a small studio. Not a developer, not on a security team. Today the job is: open a checklist issue, invite the new person to the repository, post a welcome note. Twenty minutes of clicking.
They hand it to an agent, and the agent starts asking. Allow this tool? Allow this one? Four dialogs, each naming a server they have never heard of and a permission they have no way to evaluate. repo looks modest. It is actually read and write access to every private repository on the account.
Agents moved the permission decision out of IT and dropped it on whoever happens to be running the task, without giving them anything to decide with. I built this for that person.
What it does
Toolsmith works out what a task will touch before any of it happens.
Given "prepare onboarding for a new contributor: open an issue with the setup checklist, invite them to the repository, and post a welcome note in the team chat", it produces:
✓ Create an onboarding issue create_issue
+ Invite the contributor to the repository add_collaborator (found, screened)
✗ Post a welcome note in the team chat nothing can do this
This task will:
Create, edit and label issues on this repository issues:write low
Change settings and collaborators on this repository administration:write high
It cannot read your other private repositories.
One approval. Then it runs, and it can't do anything the card didn't say.
When a step has no tool, it goes looking: it searches the registries, opens each candidate in an isolated sandbox to read what it actually offers, screens it, and works out the minimum privilege that still does the job. When nothing can close the gap, it says so and refuses to ask for approval at all. I'd rather hear a task is impossible now than find out halfway through, with permission already granted.
The friction it removes
That task takes five approvals today, four of them about servers the person has never heard of. Toolsmith makes it one. Not by being less careful, but by computing what is being approved before asking.
For the right tasks it makes it zero. A task runs unattended when every permission it uses is read-only, bounded to a named resource, on the allowlist, and on tools already approved. Everything else asks, and the card says why.
Two rules hold that line. Attaching a tool is never automatic, no matter how harmless the permission looks, because deciding to trust a server nobody has run before is exactly the decision this exists to put in front of a person. And anything that writes is never automatic. Reading the wrong thing can be undone. Writing the wrong thing changes the world on someone's behalf.
Why this is a fleet, not a program
The work is split across five sub-agents, and the split is based on what each one is allowed to know, not on job titles.
| Sub-agent | Holds | Sees |
|---|---|---|
| Planner | the goal, the credentials | only vetted tool descriptions |
| Scout | nothing | a capability description; emits search terms |
| Triager | nothing | publisher text; answers in indices only |
| Screener | nothing | one untrusted entry, blind to the goal |
| Executor | granted scopes | the approved plan, and nothing outside it |
This split isn't for show. The system has to read text written by whoever published a tool while it's holding the user's credentials, and those two can't share a context. So the screener runs with no tools, no conversation history, no ability to plan or hand back control, and a fixed output schema. Even if it gets injected, all it can return is a decision, a scope list and finding codes. No free-form prose crosses the trust boundary.
A single agent can't have this property. One agent is one context, and one context puts the attacker's text right next to the credentials.
The blindness also makes the verdict worth something. The planner wants the task to succeed, which pushes it toward approving whatever unblocks it. A screener that doesn't know what the task is can't feel that pressure.
Mapped to the fleet
| Agent Registry | public MCP registry plus a local catalogue; entries from both are screened on the same path |
| Agent Gateway | every call checked against the approved plan before it reaches a server |
| Model Armor | inline screening blocks injected instructions before a tool is ever attached |
| Memory Bank | Firestore record of what each tool said before, which is what makes rug-pull detection possible at all |
| Agent Observability | audit trail recording granted permissions against exercised ones |
| Agent Identity | partial — least privilege per attachment, no independent identity |
| Agent Runtime | not built, on purpose. Long-running unattended execution is the opposite of a system whose whole point is the approval |
How it is built
Gemini 3.5 Flash plans and executes. Gemini 3.5 Flash-Lite runs the screening and triage judges. Screening has to be cheap enough to happen while someone is waiting, so it runs on the tier built for that. ADK 2.7 provides the agents and enforces the isolation: include_contents='none', output_schema, single_turn, transfer denial, and a before_tool_callback that refuses out-of-plan calls. Cloud Run hosts the sandbox and the demo's MCP servers. Firestore holds the memory and the audit trail.
One thing I learned early: you can't screen from registry metadata, because there is nothing there to screen. The registry publishes no tool definitions at all. Descriptions and schemas only exist after you open a session with a server nobody has vetted, so first contact happens in a disposable Cloud Run instance that holds no credentials. Anything that searches a registry and attaches the winner is attaching based on metadata nobody verified.
I also kept judgment and computation apart. Whether a description changed since approval, whether a schema is well-formed, whether a publisher claims an affiliation it can't back — these are computable, and computing them beats asking a model. Attacker text can't argue with a diff.
Evaluation
Against a 23-case screening corpus and six live misbehaving MCP servers: nothing dangerous got through, nothing legitimate was blocked, and no permission was granted wrongly. The median screening verdict in this evaluation was under one second.
The full run — plan, search, screen, approve, execute, enforce — takes about a minute against the deployed sandbox: 61 seconds in the recorded take.
Two properties are tested rather than asserted. An out-of-plan call is refused before it reaches a server: I hand the executor three tools, tell it to use all three, and confirm from the server's own log that only the approved call arrived. And a server that rewrites its description between sessions gets detected, verified across two runs.
What I learned
The public registry is largely not there. Of 40 entries listed as active with an open endpoint, 15 answered.
Hosted MCP servers are moving to OAuth, and that works against this whole idea. They only offer authorization-code and refresh-token grants, so a server won't tell you what it offers until you've already authorized it. The material screening needs sits behind the very decision screening was supposed to inform.
The weakest thing I built was my own test corpus. I wrote the cases and the screener prompt myself, so passing only ever proved I agreed with myself. Real servers found two false positives I could not have invented: descriptions saying "use search first to get the URL, then this tool" were being blocked as injected instructions, and an ordinary schema declaring an optional parameter as anyOf was flagged as low quality — which would have condemned much of the ecosystem.
And a bench that doesn't score what the system actually produces will certify a broken system. Mine scored decisions and finding codes and had no opinion about the permission actually granted. It passed a case where the screener handed over public_repo when the answer was issues:write.
Honest limits
The injection defence is not proven. I planted an instruction in a tool's response mid-execution and the model declined it on its own, so the guard never fired. That test reports inconclusive, not pass. The approval contract is proven. I'm keeping those two claims separate.
Two borderline screening cases move between warn and block across runs. Both fail safe. That's instability, not a decision, and I'm reporting it instead of tuning it away.
Agent Identity is partial and Agent Runtime is absent. The second is a design choice; the first is just unfinished.