When I started exploring the Model Context Protocol (MCP), the question I kept asking myself wasn't "what can an LLM do with my data?" but the opposite one: what shouldn't it be able to do?
This post documents the technical decisions behind growth-copilots-mcp, an MCP server I built for a B2B insurance growth case — designed from the ground up to answer business questions, never to write them.
Transparency note: the business data in this project is synthetic from the ground up (the structure and business rules are real, the numbers are not). I'm stating this upfront because it's a non-negotiable condition of how I build these pieces.
The problem I wanted to solve
Growth and account teams constantly face the same question: "why is this account underperforming compared to last month?" or "where's the growth opportunity we're not seeing?" Usually that means manually cross-referencing several sources — CRM, media dashboards, spreadsheets — before reaching an answer.
The hypothesis: if an LLM can query that data in a structured way and without being able to modify it, it can speed up that diagnosis without introducing the risk of it deciding, on its own, to write back into the business system.
Architecture decisions
Stack: Python + uv as package/environment manager, mcp[cli]==2.0.0 as the protocol SDK, stdio transport.
MCPServer, not FastMCP: the first non-obvious decision. FastMCP doesn't exist in mcp[cli]==2.0.0 — it's easy to assume it does, depending on which protocol guide you're following, and lose time debugging an import that was never going to work. Always install with --with-editable ., never as a closed package.
100% read-only, by design, not by configuration: none of the server's tools has a write path. This isn't a permission restriction that can be toggled off with a flag — the ability to write simply doesn't exist in the code. If you want an AI system to never decide, on its own, to modify business data, the strongest guarantee isn't "telling it not to" — it's making sure the code doesn't allow it.
The tools: V1 vs. V2
The server has two generations of tools:
5 V1 tools: direct queries — portfolio status, campaign performance, historical risk quality. They answer concrete questions with concrete data.
3 V2 tools, "Decision Orchestration": instead of just returning data, they combine multiple sources to flag the gap between current and target portfolio, or generate a channel recommendation by cross-referencing cost-per-policy and conversion. The difference from V1 isn't technical complexity — it's the level of question being answered: from "give me the number" to "tell me where to look."
Testing: 44/44, and why that was the hardest part
It's not enough for a tool to "work" when called correctly. In a server designed for an LLM to decide when and how to invoke each tool, you also have to test the cases where it calls things wrong: ambiguous parameters, inconsistent date ranges, filter combinations that don't exist in the data.
The 44 tests cover both the happy path across all 8 tools and those edge cases — because the real risk with an MCP server isn't a failed query, it's a response that looks correct and isn't, with the LLM using it to reason about the business.
What I deliberately left out
This server doesn't generate content, doesn't decide what to publish, and has no tool that writes to any system. That's intentional: "building with AI" and "deciding with my own judgment" are separate things to me, and I don't want them blurred in how I present this work.
What's next
I'm still iterating on the Decision Orchestration tools and documenting the process on a case-study blog (with real interactive widgets, not just screenshots). If you're more interested in the business side of this same case, or have feedback on the read-only approach, I'd love to hear it in the comments.
Have you built something similar with MCP? I'm especially curious how others have handled testing for "bad invocations" from the LLM side — it's the part I've found least documented so far.
Top comments (0)