The Model Context Protocol is the plug that connects an AI agent to real capabilities. Instead of every client inventing its own way to call your tools, an MCP server advertises a list of tools, resources, and prompts over a standard JSON-RPC 2.0 interface, and any MCP client (Claude Code, Cursor, an agent built on the Claude Agent SDK) can discover and call them. That standardization is the whole point, and it is also where the danger lives: the moment you expose a tool, you are handing a language model a lever it can pull on your systems. Over three projects I built a server, scoped one down to per-client permissions, and wrote a scanner to catch the mistakes. Here is what actually mattered.
Building a server: the transport is simpler than it looks
My first server, casebook-mcp, turns AgentPostmortem (a public registry of documented AI-agent failures) into something an agent can query mid-investigation. The idea: every team debugging an agent incident is rediscovering failure modes someone already wrote up. So the server exposes four tools: search_cases for ranked full-text search, get_case for full case detail, similar_failures to match an incident description against the corpus, and list_tags.
The lesson here was that you do not need a heavy framework. I implemented the transport directly against the 2025-03-26 streamable HTTP spec in stateless mode: a single POST /mcp endpoint that handles initialize, tools/list, and tools/call. No sessions, no Durable Objects, no auth, because the data is public and read-only. It runs on a Cloudflare Worker with the protocol routing in one file, the pure ranking logic in another (unit tested on its own), and a data layer that hits the live agentpostmortem.com API with a five-minute in-memory cache and falls back to a bundled dataset when offline. A light per-IP rate limit of 60 requests per minute keeps it polite. You can smoke test it with a single curl of tools/list, and add it to Claude Code with one claude mcp add --transport http command.
The separation that paid off: keeping the search and similarity ranking as pure functions meant I could test the interesting logic without standing up the transport at all. MCP protocol handling is boilerplate. Your actual value is in the tool implementations, so isolate them.
Scoping a server: not every client should see every tool
Public read-only data is the easy case. The hard case is a server that fronts a company's real systems. Bridgekit does exactly that: it exposes Shopify, Triple Whale, and Postgres to an AI stack, and three of its four tools are reads (shopify_orders, triplewhale_metrics, db_query against an allowlisted table) while one is a write (shopify_tag_order).
The design decision I care about most is that scope is enforced at discovery, not just at call time. Clients are configured in a secret as JSON, each with a name, a list of allowed tools, and an allowWrite flag. When a client calls tools/list, the server only advertises the tools that client is scoped for. A read-only client never even sees the write tool exists. Callers authenticate with a bearer key (or an x-bridgekit-key header), and every attempt is written to an append-only audit log. When a read-only key tries to call the write tool, the call is denied and the denial is logged.
Two things I would tell anyone building this kind of server. First, per-client tool filtering matters more than you expect, because an agent that cannot see a tool cannot be prompt-injected into calling it. Reducing the advertised surface is a security control, not just tidiness. Second, make it demoable safely: Bridgekit's read tools return clearly-labelled sample data when upstream credentials are not configured, so you can show the whole flow without wiring it to a live store.
Scanning a server: assume you got it wrong
After building two servers, I was convinced I would ship a bad tool eventually, and that most people ship them with no security review at all. So I wrote mcp-audit, a scanner and linter for MCP servers. It connects to a server over stdio or HTTP (or lints a static JSON manifest without executing anything, which is what you want for untrusted servers in code review), enumerates every tool, resource, and prompt, and runs 18 rules over that surface.
The rules cover the failure classes I kept worrying about: arbitrary command or shell execution tools (MCP002, critical), destructive tools with no confirmation argument (MCP001), probable prompt-injection text planted in a tool description (MCP020), secrets or system paths exposed as resources like a .env file (MCP030, critical), caller-controlled URL arguments that invite SSRF (MCP041), HTTP transport with no authentication (MCP040), and unconstrained input schemas that let the model pass anything anywhere. Each finding has a stable MCPxxx id, a severity, and a concrete remediation.
It is built for CI. It runs offline, is fully deterministic, and emits JSON and SARIF 2.1.0 so findings show up as annotations in GitHub code scanning. The process exits non-zero when any finding reaches the --fail-on threshold (default high), so a bad audit breaks the build. You can disable noisy rules, remap severities, or ignore specific locations through a .mcpauditrc file. Running it against my own servers is what turned "I think this is fine" into "the scanner agrees this is fine."
One honest caveat
mcp-audit is a static and structural analyzer. It reasons about the shape of your tools: their names, descriptions, and input schemas. It flags a tool named run_shell and a resource pointing at .env, but it cannot know that your innocently named update_record tool quietly runs raw SQL under the hood, because it never sees the implementation. Pattern-matching on descriptions also means it can miss a cleverly worded injection sink or flag a benign one. It narrows the surface an attacker can reach and catches the obvious, dangerous defaults, but it is a first line of defense, not a substitute for reading the code behind each tool.
The through-line
Building the transport is the least of it. The work that matters is deciding which tools exist, who is allowed to see them, and proving to yourself that none of them are a foot-gun before an agent finds out for you. All three projects are open source under github.com/royalpinto007: casebook-mcp, Bridgekit, and mcp-audit. If you are shipping an MCP server, at minimum run a scanner over it first.
Top comments (0)