DEV Community

Cover image for I Built a Fully Local, Autonomous AI Pentesting Agent — Now I’m Teaching It to Speak MCP
auto_majicly
auto_majicly

Posted on

I Built a Fully Local, Autonomous AI Pentesting Agent — Now I’m Teaching It to Speak MCP

⚠️ Everything here is for authorized security testing and research only — systems you own or have explicit written permission to test.

A few months ago I set myself a stubborn goal: build a penetration-testing agent that runs entirely on my own machine — no cloud, no API keys, nothing leaving the laptop — that could plan an engagement, run its own recon, chain attacks based on what it finds, and write up the findings on its own.

That project became HALO (I ship it as GEMMA-by-GOOGLE). It’s driven by a local Gemma model in LM Studio and a Python agent loop that calls out to a server exposing ~29 security tools. It’s fully offline, and it’s the thing I’ve learned the most from this year.
This post is about the part I got wrong first, and the standard I’m now rebuilding it around: the Model Context Protocol (MCP).

What HALO actually does

The design is a small crew of specialist agents passing a shared message format:
• a planner turns a goal into an ordered plan
• an orchestrator routes each task to the right specialist
• a vuln-discovery agent surfaces candidates
• an attacker branches into vuln-class specialists (SQLi, brute force, IDOR, SSRF, XSS, auth)
• a validator confirms findings against real evidence before they count
• a debugger diagnoses failed tool runs and adjusts

One of my favorite pieces is a persistent negative-experience cache. Every tool call gets fingerprinted. Fail once → one retry. Fail twice → it’s blacklisted, and the agent moves on to a better tool for the job. Over an engagement it stops wasting cycles on dead ends it has already proven don’t work — trial-and-error that persists across sessions.

At the end, everything compiles into an HTML report. One command starts the whole thing: engage .

The wall I hit: my tools were bolted on

Here’s the honest part. When I wired those ~29 tools in, I did it the fastest way I knew: a Flask HTTP server with a couple of routes. The agent POSTs {tool, params}; the server runs the tool and returns the result. It works. It’s been working for months.

But it’s bespoke. My agent is the only thing that knows how to talk to it. If I wanted to use those same tools from another client — say, a desktop AI app or an editor — I’d have to reimplement the glue every time. The tools were locked inside my one custom protocol.

That’s exactly the problem MCP was built to solve.

What MCP is (the 60-second version)

The Model Context Protocol is an open standard for connecting LLM applications to external tools and data. Instead of every app inventing its own way to expose “here are my tools, here’s how to call them,” MCP defines a common one:
• a server exposes capabilities — tools, resources, prompts
• a client (the LLM app) connects, calls tools/list to discover what’s available, and tools/call to run them
• it speaks JSON-RPC over a transport like stdio or HTTP/SSE

The payoff is interoperability. Write your tools as an MCP server once, and any MCP-aware client — Claude Desktop, Cursor, Cline, and a growing list of others — can use them without custom glue. Your capability becomes portable.

When it clicked for me, it was obvious: my Flask server is a worse, private version of a thing that already has an open standard.
Why I’m rebuilding HALO’s tool layer as a real MCP server

So that’s the next build. Concretely:
1. Wrap the existing tools with the MCP SDK. Each of my run_* functions becomes a registered MCP tool with a typed schema, exposed via tools/list / tools/call — instead of my ad-hoc POST /.
2. Serve over stdio so any MCP client can launch and introspect it.
3. Ship a Dockerfile so it starts in a clean environment and can be verified.
What this unlocks:
• Plug HALO’s arsenal straight into Claude Desktop / Cursor / Cline — the same recon and analysis tools, now usable from any MCP client, not just my agent loop.
• Keep it local. MCP doesn’t mean cloud. The server still runs on my machine; the model still runs in LM Studio. Nothing about adopting the standard changes the “fully offline” promise — it just standardizes the interface.
• Portability. I do this work on a travel VM that goes with me. A standard interface + a Dockerfile means “it runs the same everywhere” stops being a hope.
Lessons so far
• “It works” and “it’s reusable” are different milestones. My Flask server passed the first bar for months while quietly failing the second. Standards are how you cross the second one without a rewrite every time.
• Adopt the protocol, keep your architecture. My multi-agent crew, the negative-experience cache, the reporting — none of that changes. MCP is about the tool interface, not the brain.
• Local-first and standards-based aren’t in tension. MCP over stdio is arguably more local than my HTTP server was.
• Name things honestly. I’d been loosely calling my Flask layer an “MCP server.” It wasn’t one. Fixing that — making the name true — is half of why this next build matters.

Follow along

HALO is open source (MIT). The multi-agent code, the tool server, and the docs are all in the repo, and the real MCP interface is the next thing landing:
👉 https://github.com/XenoCoreGiger31/GEMMA-by-GOOGLE

If you’re building local-first AI tooling — security or otherwise — I’d love to hear how you’re thinking about MCP. And if you want the follow-up (“here’s exactly how I converted a Flask tool server into a spec-compliant MCP server, with a Dockerfile”), that post is coming once the build lands.
Build for systems you’re allowed to touch. Stay legal, stay curious.

Top comments (0)