One weekend in early June I went looking for my MCP servers and found an archaeology dig. A FreshBooks server living inside ~/.claude/mcp-servers/, no git history, born in a single Claude session in May. A Sumo Logic server that was a straight clone of a repo I found on GitHub, still carrying a Dockerfile and an HTTP transport I never used. A GitHub server written in untyped JavaScript. Each one wired up by hand-editing whichever JSON file that particular host wanted: one path for Claude Desktop, another for Claude Code, a third for Cowork. Every new machine, every new host, the same twenty minutes of config spelunking.

The cleanup is the boring part of this story: a pnpm and Turborepo monorepo, everything ported to strict TypeScript, a conventions document, CI, and npm publishing. That took the weekend. The part worth writing down is what I noticed while moving the servers in: Claude used some of them constantly and ignored others, and the difference had nothing to do with how many tools each one exposed. The servers that earned their keep were the ones that knew things.

Watching Claude order off the menu

The Sumo Logic API is honest about what it is: searches are jobs, so you start one, poll until it finishes, then page through the results. My first server exposed exactly that, one tool per endpoint, and watching Claude use it was like watching an intern with a curl cheatsheet. Five tool calls to answer one question, and the first query was usually wrong anyway, because the query language is the easy half. The hard half is knowing that the checkout service logs under a source category like prd/ecommerce/checkout, that its noisy health checks need filtering out, and that nobody has ever written any of this down anywhere a model could read it.

The fix was already sitting in the server I had cloned. Grey Perez, a developer I have never met whose mcp-sumologic this package descends from, had built it around a context file, and it is still the best idea in the whole monorepo. It maps the organization the way a senior engineer carries it in their head (environments, then applications and infrastructure, each with its source category, common filters, and a couple of known-good sample queries), plus shortcuts: parameterized query templates for the searches you run every week. On top of that map the server grows tools shaped like questions. discover_sources tells the model what exists. search_by_context takes an application name and a time range and builds the query itself. The raw job-polling tools are still there for the odd bespoke search, but the model almost never needs them, because the tribal knowledge lives in the server now.

An adapter exposes the API. A facade answers the question.
Matthew Purdon

The FreshBooks server is the purest version of the idea. The API has endpoints for time entries, clients, and invoices; nothing in the API knows that it is invoicing day. So the tools are the chores themselves. generate_timesheet pulls a date range of time entries and writes the exact bi-weekly Excel file my client expects, signature image and all. generate_invoice groups the same entries by project and service, looks up each billable rate, and creates the draft invoice. What used to be a Python script and an evening of copy-paste is one sentence in a chat window.

One question, forty API calls

My Monday-morning question is "what happened in the org while I wasn't looking?" GitHub does not have an endpoint for that. It has a search endpoint that returns thin results, and a detail endpoint you must hit once per PR to learn anything useful, and a secondary rate limit that bans you for asking too enthusiastically (I found that one the empirical way). So get_org_recent_prs does the whole dance in one call: search the org, then enrich every hit with its size, CI state, and whether it is a dependabot PR, in batches of eight to stay under the ban hammer. One tool call, one rolled-up answer, org-wide.

The deepest tool in the set is get_pr_ci_failures, which exists because "why is CI red?" is never one API call. It resolves the PR to its head commit, finds the failed workflow runs for that commit, keeps only the newest run per workflow, walks each one down through its failing jobs to the failing steps, downloads the job log, locates that step's section by its log markers, strips the timestamps, and returns the last fifty lines. That is the part of code review nobody enjoys (four phases of clicking through the Actions UI), and the model gets it as a single tool whose answer is exactly the fifty lines that matter. Its sibling get_pr_for_review categorizes every changed file as code, CI, infrastructure, or dependencies, so a review can open with the blast radius instead of discovering it on file nine of twelve.

Production is a different planet

The scariest sentence in agent tooling is "the model has your production connection string". The MongoDB server is built around taking that sentence seriously. It keeps three connection strings (dev, staging, production) in a chmod 600 config file, always knows which one is loaded, and reports it through current_environment along with a writesRestricted flag. In production, write tools do not execute. They return a refusal that describes exactly what would have run (the operation, the environment, the host, the documents or filters involved) and ask to be re-invoked with confirmed: true. The model cannot forget to ask permission, because asking permission is the return value. drop_collection requires confirmation in every environment, including dev, because there is no environment where I want that to be a surprise.

The same paranoia covers what comes back over the wire. The server will happily tell you which host it is talking to; it derives a sanitized hostname from the connection string and never returns the string itself, because a credential that enters the context window gets re-sent with every turn that follows (the tcc story's secret shields fight the same battle one layer up). And the private work server, a MySQL database with fifteen years of history in it, applies the philosophy to schemas: its best tool is get_schema_wiki, which returns the entire schema as one markdown document, cached for a day. The model reads it once at the start of a session instead of dribbling out twenty describe_table calls, and its other tools are named after the domain objects, not the tables.

Five servers, one shape

A week after the consolidation, dependabot bumped every dependency in the repo and handed me the best war story in it. The dotenv library, as of version 17, prints a friendly tip banner when it loads. To stdout. A stdio MCP server is its stdout; the host expects nothing on that stream but JSON-RPC frames, so one library being helpful is a corrupted stream and a dead server. The fix is a quiet: true flag and a new respect for how fragile the transport is: the conventions document now opens with the rule that diagnostics go to stderr, and ESLint enforces it by banning console.log outright.

That document (CONVENTIONS.md, distilled from the MongoDB package as the reference implementation) is what makes five servers feel like one. Config is validated with zod at startup, and a bad config fails with a message that names the exact file and shows sample contents to paste. Servers shut down cleanly on SIGINT and guard against late-resolving promises crashing the process mid-session. And the installer ties it together: npx @mpurdon/mcp-servers configure detects which Claude hosts are on the machine, prompts for each server's credentials (masked, never echoed), and writes the right entry into the right config file for Desktop, Code, and Cowork; idempotently, with a --dry-run flag. The twenty minutes of config spelunking is now a single command.

The installer also solves the problem that not everything can be public. Work servers stay in private repos, but each one ships a register command that writes a small versioned descriptor into ~/.mpurdon-mcp/servers.d/. The configurator discovers those at runtime and offers them in the same list as the npm packages, marked (private) and launched from local disk. The public CLI ships no knowledge of them at all; one install experience, two tiers of secrecy.

TypeScriptMCP SDKzodTurboreponpm
Source on GitHub ↗