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.

Built With

Share this project:

Updates