Back to Composable
In 2013 Neal Ford wrote about two types of extensibility abstractions: composable and contextual. Contextual systems like Maven and Eclipse provide enormous scaffolding through plug-in APIs, making the first 80% fast, but the last 10% impossible because you can’t get underneath the built-in abstractions. Composable systems like Unix shells consist of finer-grained parts wired together in ways that often surprise even their designers. Ford’s observation was that composable build tools scale better than contextual ones over the lifetime of a project, and that every project eventually hates Maven because what starts as helpful rigidity becomes a cage as complexity grows. He called this Dietzler’s Law Redux.
That essay gave me a vocabulary for something I already felt. For over a decade I’ve been using Legos versus Lincoln Logs as my go-to analogy when explaining this to people. Lincoln Logs let you rapidly prototype a log cabin, but you’ll never build a robot. Legos require more effort upfront but the parts compose into anything. I’d gravitated toward Linux, toward CLI tools, toward piping things together, without being able to articulate why beyond that toy analogy. Ford’s framing made it legible: I preferred composable systems because they let me reach underneath, and contextual systems kept boxing me in at exactly the moment I needed to do something the designer hadn’t anticipated. I’ve carried {composable, contextual} as a diagnostic pair ever since. It is the first lens I reach for when evaluating a new tool or protocol.
MCP as the new Maven
Anthropic’s Model Context Protocol landed in late 2024 and was adopted faster than any standard RedMonk had ever tracked. Every major foundation model lab endorsed it. The contextual appeal is obvious. MCP provides a standardized plug-in API for connecting LLMs to external tools, with discovery, schema negotiation, and authentication built in.
MCP isn’t Maven in the sense of being rigid or opinionated about structure. The analogy is about the contextual pattern itself: providing a large amount of scaffolding that makes the first 80% fast while consuming resources (context window tokens, in this case) that become precious as complexity grows. The movement away from MCP isn’t primarily about flexibility. It’s motivated by two things: preserving context, which is the scarcest resource an agent has, and giving models direct “hands and feet” through interfaces they already know how to use from training data. The friction arrives not because MCP constrains what you can do, but because it costs too much context to explain what it can do.
The structural issue is that MCP dumps entire tool catalogs as JSON schemas into context at session start. Every connected server loads all of its tools before any work begins, and each tool consumes tokens just to describe itself. Stack a few servers together and a meaningful fraction of the context window is spent on tool definitions before the model reasons about anything. Armin Ronacher, creator of Flask, demonstrated that the GitHub CLI consumed far less context and completed tasks faster than the GitHub MCP. David Cramer, who built Sentry’s MCP server, summarized: “MCP does a lot of things. It’s bloated, complicated, and brittle. I’m not going to defend the protocol.” Sentry subsequently shipped a dedicated CLI explicitly labeled “for developers and agents.”
The other friction is non-composability. MCP tool outputs cannot be piped between each other. All data must pass through the agent’s context window, preventing Unix-style composition like az vm list | jq '.[] | .name' | xargs kubectl get pod. Anthropic’s own engineering blog acknowledged that code execution patterns achieve 98.7% token efficiency improvement over standard MCP tool calling, effectively validating the critics’ core argument from inside the house.
Every connected server loads its whole catalog before the agent starts, leaving a thin band to work in and no way for one server’s output to reach another; the same work is one bash call where each tool hands its output to the next and only the final result costs context.
What we actually built instead
I’ve been working on two second brains recently: this digital garden series and a professional one for my work at GitLab. The RAG pipeline for searching customer meeting notes, the harness explorer for static analysis of agent instruction files, the content capture and publishing workflows that drive this site. Nearly all of it is CLI tools composed together. We did not build a single MCP server for any of it.
The RAG pipeline strings together sentence-transformers for embedding, LanceDB for vector search, and GitLab Duo for synthesis. Each piece is a CLI invocation. The harness explorer is a single Go binary with an embedded ONNX model. The content workflows are Make targets calling shell scripts. The agent interacts with all of it through bash.
I’ll be honest: I’ve felt guilty about this. The plan was always to build an MCP server once the protocol stabilized, to give these tools a proper structured interface that other agents and integrations could discover. Instead we kept iterating on CLI tooling because the feedback loop was tight and the composition was natural, and the MCP specification kept feeling like a thing we’d get to next month. Each tool already had a CLI, the agent already understood how to call CLIs from training data, and composition happened through pipes and exit codes rather than through a protocol layer that would have consumed context explaining itself. The MCP server remained a backlog item that never quite earned its place in the sprint.
Discovering that there’s a growing movement arriving at the same conclusion independently is a strange kind of validation. The guilt was misplaced. What felt like cutting corners was actually the composable instinct doing what it’s always done: reaching for the finer-grained parts that wire together naturally, and resisting the contextual scaffolding until the scaffolding proves it’s worth the cost. This post is largely about tracking that phenomenon and acknowledging that we’re likely to keep following it.
The paradox at the center
Every major foundation model lab has formally adopted MCP. Their flagship agent products all use bash as the primary tool-calling mechanism. Claude Code spawns a persistent bash session with the tool schema baked into model weights. OpenAI Codex CLI executes shell commands as its core operation, built in Rust with Landlock/seccomp sandboxing. Gemini CLI does the same. Labs adopt MCP for ecosystem standardization and enterprise sales while their engineering teams build agents that call bash.
The research validates the practice. Wang et al.’s CodeAct paper (ICML 2024) demonstrated that using executable Python code as a unified action space outperforms JSON-based tool calling by up to 20% in success rate. The insight is that code is inherently composable in ways that structured tool schemas are not: variables carry state, loops handle iteration, and existing libraries provide the tool surface. OpenHands built an entire agent development platform on this premise, where the primary interface is a bash shell inside a sandboxed container. The most radical version of this argument reduces the entire tool-calling surface to a single schema: {"name":"bash","input_schema":{"command":"string"}}. One tool replaces an entire protocol stack.
Meanwhile, the labs are also exploring interfaces beyond both CLI and MCP. OpenAI’s Operator uses a Computer-Using Agent (CUA) model trained to interact with GUIs directly, clicking buttons and filling forms through screenshots. Anthropic has a parallel computer use capability. Google’s Agent-to-Agent protocol (A2A) addresses a different seam entirely: not agent-to-tool but agent-to-agent, where opaque agents built on different frameworks discover each other and collaborate as peers without exposing internal state. These aren’t competing with CLI so much as exploring orthogonal surfaces: GUIs for applications that have no API at all, agent coordination for multi-party workflows. The CLI rebellion is specifically about the tool-calling layer, where the shell already provides everything MCP reinvents.
Skills, not schemas
An emerging pattern sits between full MCP and raw bash: the self-contained skill drop. Instead of loading full tool catalogs as JSON schemas at session start, you give the agent a markdown file describing available CLI tools in a few hundred tokens. The agent reads the skill, runs --help on specific tools when needed, and discovers capabilities progressively rather than consuming them all upfront. Alastair McClelland’s python-tool-skills pattern formalizes this: Python CLI tools built with Click, distributed via uvx, wrapped in markdown skill files. The tools work independently outside any agent context.
The UTCP specification takes the “death of the wrapper” argument to its logical conclusion. Rather than requiring wrapper servers, UTCP provides a JSON “manual” that tells agents how to call existing APIs directly using their native protocols. No MCP server running in the background, no protocol translation layer. If a human can call the API, the agent calls it the same way. The philosophy is explicitly anti-contextual: don’t build scaffolding around the tool, describe the tool as it already exists.
I think there’s a next step beyond skill dropping that I’d call harness dropping. Consider that a CLI tool could scan the site it’s operating in, inventory what utilities are available, and shape the skill drop to be domain-specific to what’s actually present. Need to validate YAML? If yq is available, the skill says use yq. It’s not, but Python and uv are? Then the skill describes a uvx-invoked package instead. The harness drop becomes environment-aware: not a static instruction file but a generated one that reflects the composable surface actually accessible. This is what Ford’s composable systems do naturally (you work with what’s on the PATH) but made explicit as a pattern for agent workflows. The harness explorer project is adjacent to this: if you can measure the properties of an instruction set, you can start reasoning about whether the instructions match the environment they’re operating in.
Each rung down hands the agent less at session start and leaves more to be discovered at call time, which is the space the skill drop and the harness drop occupy between a full MCP catalog and raw bash.
Where contextual still wins (and where it’s struggling)
We’ve also been spending a lot of time building an MBSE stack to manage context when working with MBSE languages, where SysML v2 is the first target. Nearly all of this work lives in an MCP server, and it has been illuminating in a different way. The MCP server provides structured access to model elements, relationships, and validation, things that genuinely benefit from the contextual scaffolding because there is no pre-existing CLI that LLMs were trained on. SysML v2 is new enough that the models have limited training data for it, and the schema-rich MCP approach should theoretically help bridge that gap.
What we’ve noticed is that the LLMs don’t seem to use the tools well. They have the schemas, they can see the available operations, but they frequently call tools with wrong parameters, ignore available tools in favor of hallucinating answers, or sequence operations in ways that don’t reflect the domain’s constraints. We’ve been varying the tooling explanations, rewording descriptions, adding examples to schemas, restructuring the tool surface, trying to find the incantation that makes the models reach for the right tool at the right time. It’s the classic contextual problem Ford described: the scaffolding is there, the API surface is enormous, but the cost of understanding what that context provides is high, and the model is paying that cost at inference time with every token of schema it has to parse.
This maps onto the CodeAct finding in an interesting way. The models perform better when they can write code against a library than when they select from pre-defined tool schemas. Code is a composable medium. Tool schemas are a contextual one. The same model that struggles to pick the right MCP tool from a list of 30 options might do better if you handed it a Python library with docstrings and let it compose calls in code. We haven’t tested that yet, but the MBSE work is making me wonder whether the right answer for novel domains isn’t a better MCP server but a well-documented SDK with a skill file that says “here’s the library, here’s what it does, write code against it.”
The composable turn
Ford observed that many contextual systems eventually become more composable by redesigning them as DSLs. Ruby on Rails succeeded where 4GLs failed because it was implemented as an internal DSL atop a general-purpose language. When developers hit Dietzler’s Law, they could drop below the framework. Rake and Gradle replaced Ant and Maven by being languages rather than plug-in systems.
Something analogous is happening with agent tooling. The protocol layer (MCP) is being supplemented and in many cases replaced by composable alternatives: CLI tools that agents already know how to call, code execution that lets agents compose operations in a general-purpose language, skill files that describe capabilities in natural language rather than JSON schemas, and specifications like UTCP that eliminate the wrapper server entirely. The bridging tools (CLIHub, mcporter, mcp-cli) let teams maintain MCP compatibility while executing through CLI, which is the Gradle move: understand the Maven stuff you already have, but give you a composable language underneath.
The practical heuristic emerging from all of this is straightforward. Use CLI for any tool that already has a command-line interface. Use code execution for novel domains where the agent needs to compose operations. MCP likely still makes sense when you need to serve context over HTTP or other network protocols, when the consumer isn’t a local agent with shell access but a remote client that needs discovery, auth, and structured transport. OAuth-dependent integrations, enterprise multi-tenant access control, applications that genuinely have no CLI: these are the cases where the protocol’s scaffolding earns its context cost. The harness is the thing that encodes this knowledge for each project, and measuring its quality over time is how you keep it from drifting into the same rigidity that makes contextual systems fail.
Ford ended his essay by noting that he preferred composable tools because they delivered more power and scalability over time, even at the cost of a steeper learning curve. Thirteen years later, the agent tooling ecosystem is rediscovering the same tradeoff. The terminal was the universal agent protocol all along. I suspect we’ll forget this the next time someone offers a contextual shortcut, and then rediscover it again. That seems to be the cycle.