~/writing/building-mcp-servers.md

Building MCP servers engineers actually use

Aug 20266 min readmcpllmstypescript

I've shipped two MCP (Model Context Protocol) servers in the last year: an internal research agent that does cross-source research across Jira, Confluence, and internal knowledge bases — now used by about 50 engineers across 4 teams — and github-intel-mcp, an open-source server that exposes GitHub repository intelligence to AI agents. Different domains, same lesson: the hard part isn't the protocol. It's designing tools an LLM can actually wield.

Design tools around questions, not endpoints

The first instinct is to wrap your existing API one endpoint per tool. It produces terrible agents. An LLM given list_issues, get_issue, and list_comments will burn ten tool calls reassembling context a human would get from one page.

The tools that work map to the questions people ask. In github-intel-mcp there are three: repo health scoring (activity metrics, contributor churn), PR risk classification (file sensitivity, diff size, author history, review coverage), and workflow bottleneck detection (stale PRs, reviewer overload). Each one does the multi-call aggregation server-side and hands the model a finished answer to reason over.

Schemas are the UX

The model reads your schema the way a developer reads docs — it's the only interface it has. Every input in my servers goes through Zod, and the descriptions are written for the model, not for humans:

server.tool(
  "classify_pr_risk",
  "Classify a pull request's merge risk (LOW to CRITICAL). " +
  "Use when asked whether a PR is safe to merge or needs review.",
  {
    repo: z.string().describe("owner/name, e.g. 'vercel/next.js'"),
    pr_number: z.number().int().positive(),
  },
  async ({ repo, pr_number }) => { /* ... */ }
)
  • Say when to use the tool, not just what it does — that's what tool selection runs on.
  • Put format examples in field descriptions; they eliminate an entire class of malformed calls.
  • Return structured results with stable keys. Prose responses rot; JSON composes.

Adoption is a product problem

The internal research agent taught me the other half. It got to 50 engineers not because MCP is novel, but because it removed a real chore: gathering context for incident triage and design reviews, which used to mean fifteen minutes of tab-hopping across Jira and Confluence. The pitch was never "we have an AI agent" — it was "ask one question, get the cross-source summary."

What moved adoption: fast first answers (nobody retries a slow tool twice), citations back to source documents (trust), and graceful degradation when a backend source is down (a partial answer beats an error). None of that is in the MCP spec. All of it decides whether anyone uses your server.

If you're starting one: pick the three questions your users ask most, build one tool per question, write the schema like documentation, and measure time-to-first-useful-answer. The protocol is the easy 20%.