Skip to content

Maintaining an MCP server people actually use

Published: at 10:30 AM

In March 2025 I wrote MCP servers the way some people write notes. Strava on the 11th. n8n on the 18th. Formula 1 on the 22nd. ComfyUI and TMDB a couple of days later, FreshRSS on the 25th, ERPNext on the 28th. Most of them were a day’s work: wrap an API, describe the tools, add a config snippet for Claude Desktop and Cline, push.

Almost all of them stayed exactly as I left them. One didn’t. The ERPNext MCP Server has been picking up stars at a steady few a month ever since. It is now my most-used open-source work by a wide margin, and it has taught me more than the other nine combined.

The one nobody planned for

The F1 server was the fun one. ERPNext was the boring one: an open-source ERP, the Frappe API, documents and doctypes. Eleven commits in the first week. The day after I published it I removed username and password login in favour of API keys, wrote the docs, and added a licence. Then I didn’t touch it for eleven months.

It kept growing anyway. There was no launch and no spike in the star history, just a slow line going up while I was busy elsewhere. My guess is that it got used because it connects an assistant to the place where a business’s real data lives. A server that tells you lap times is a demo. A server that can read your invoices is a tool.

What people sent me

The interesting part was not the stars. It was the pull requests.

November 2025: minimal responses. A contributor opened a PR that made create and update calls return a minimal response instead of the whole document. They had measured it: a bill of materials with its child tables would flood the model’s context every time it wrote something. They closed it the same day, saying they would track it in a fork instead.

I didn’t reply for three months. When I did, in February, I agreed with every point and implemented it properly: get_document for a single record with its child tables, submit_document and cancel_document using Frappe’s own lifecycle endpoints, delete_document, a generic call_method, and minimal write responses with an opt-in verbose flag. I also took the MCP SDK from 0.6.0 to 1.27.1. That landed in April.

December 2025: accounting tools. A different contributor sent fourteen accounting and banking tools: trial balance, bank accounts, account trees, journal entry validation, bank import. The ERPNext knowledge behind it was solid, and every endpoint was used correctly.

I didn’t merge it. I wrote a long review instead, and it is the clearest statement I have of how I think MCP servers should be built.

Generic tools, not more tools

The server had six generic tools: get_documents, create_document, update_document, run_report and so on. They work with any doctype, because ERPNext already knows its own schema. The PR would have added fourteen tools for one module.

ERPNext has more than fifteen modules: HR, manufacturing, stock, CRM, projects. Fourteen tools per module is two hundred tools. Models get noticeably worse at choosing a tool as the list grows. A lean server with a few composable tools does better than a big one with a tool for everything.

Most of the new tools were also things the generic ones could already do:

And every domain-specific tool is something to maintain as ERPNext changes. Generic tools survive upgrades because they hand the schema back to ERPNext.

So I suggested splitting it up: the SDK bump and the docs as easy separate merges, journal-entry validation as the one piece that adds real logic beyond CRUD, and prompt recipes or optional tool packs for the rest.

The reply that taught me something

Last week, the contributor replied. They had ended up not using the MCP server at all. They built a set of skills with small scripts that call the ERPNext web API directly, and for their accounting work that performed better and more reliably.

I think that’s a real finding, not a defeat. MCP is a good way to give a general assistant access to a system. For a narrow, repeated workflow, a skill that already knows the steps can beat a server that exposes primitives and hopes the model composes them well. It’s the same argument I made in the review, taken one step further than I took it.

What I’d tell myself in March 2025

The server still has open threads: a request for a Dockerfile for a directory listing, and a pull request for HTTP/SSE transport and multi-site support that deserves a proper review. I maintain it in bursts. If you use it, that’s the honest picture, and issues are welcome.


Previous Post
Working with a team of agents
Next Post
The sensor was in the airflow