We built MCP’s tool architecture in 2015 — by accident | countinghouse
countinghouse
We built MCP’s tool architecture in 2015 — by accident
countinghouse — a multi-tenant MCP runtime. Apache-2.0, on npm.
In 2015 I wrote a Node.js framework for a problem nobody framed this way back then: let a client that has never seen your service discover it, understand it, and call it — without integrating an SDK.
In 2024, Anthropic shipped the Model Context Protocol. When I read the spec carefully, I had a strange afternoon: the tool architecture — a discovery endpoint returning machine-readable descriptions, a uniform invocation endpoint, every tool taking exactly one schema-defined JSON object in and returning one out, validated on the server — was, piece for piece, what I had been running in production for a decade.
This is not a priority claim. MCP’s designers solved their problem correctly, and so did gRPC’s, and — mostly by luck — so did I. What makes it worth writing up is the second accident, five years later, which turned out to matter more.
The first accident
The framework was called CDIF. The setting was the early IoT wave: heterogeneous devices speaking Z-Wave, ZigBee and UPnP dialects, plus ordinary web services, and clients that had to talk to all of them. Everything is a device exposing services containing actions, and a client’s entire lifecycle is four HTTP calls: list what’s here, fetch a thing’s JSON description, fetch the dereferenced JSON Schema for its arguments, invoke an action with a JSON payload. The server validates every call against the schema before implementation code sees it. Implementations are plain npm packages loaded into the runtime.
If you know MCP you’ve already done the mapping: get-spec is tools/list, invoke-action is tools/call, the schema endpoint is inputSchema, server-side validation is server-side validation.
The deepest correspondence came from the least visionary decision. Around 2016 I added a flag called allowSimpleType. Unless you set it, the framework enforced a rule: every action takes exactly one argument, of type object, and returns exactly one output, of type object — both schema-defined. The motivation was mundane. UPnP’s argument lists carried primitive types, the right shape for a temperature sensor. Web service inputs are documents: nested, optional-field-heavy, list-carrying. Squeezing documents into scalar argument lists was ugly, so I split the type system.
That is precisely MCP’s tool shape. It is also gRPC’s. Three designs, three unrelated pressures: gRPC wanted evolvability (add a field without breaking a signature); CDIF wanted type hygiene; MCP wants generation reliability, because a model filling one named, schema-constrained object fails far less often than one emitting positional arguments. Different reasons, same shape. Positional parameters are an artifact of same-process function calls; across a boundary between parties that don’t share code, single-object-in/single-object-out looks less like a convention than a finding.
Why it went nowhere in 2015
CDIF’s README promised that clients needn’t integrate any SDK to reach any device or service. Technically true; commercially, it answered a question nobody was asking. The promise only pays off if a universal consumer exists — a client that talks to arbitrary services it has never seen. In 2015 those were schema-driven form generators and visual flow editors. Useful, niche. Human developers, given the choice, just integrated the SDK.
Then LLMs arrived — among other things, the best schema-driven form fillers ever built. One reads a description, fills the object, handles the response, for any tool it has never seen before. Self-describing interfaces stopped being a nicety and became load-bearing. The design didn’t get better; the world grew into it. If you built service infrastructure before 2023 — integration platforms, ESB-ish things, device abstraction layers — your old code may be closer to MCP-ready than most greenfield projects. The hard part was never the transport. It was the discipline of machine-readable contracts, which some of us adopted for reasons that had nothing to do with AI.
What ten years of production actually buys you
The revived project is called countinghouse — the room where a trading firm recorded and settled its accounts. Reviving it wasn’t free, and specifics matter, because “we were doing MCP before MCP” earns zero credibility without them: JSON Schema draft-04 → 2020-12 with a migration for every module spec; MCP’s stateless Streamable HTTP transport; a decade of ES5 rewritten; production npm audit from 31 high/critical findings to zero, with four moderate left and a dev toolchain that carries its own, which I won’t pretend otherwise about.
One change closes the loop on the 2016 accident. Back then, single-object-in/out was a flag you could turn off, sitting next to a pile of UPnP scaffolding: a state table that arguments pointed into by name, a direction...