A plugfest for four different answers

Walk into a protocol-interoperability lab during a plugfest and the layout tells you something before any demonstration starts. The stations are not arranged as better and worse versions of one device. They are arranged side by side because they answer different questions about how two systems agree to talk: one station shows a single fixed lead that only ever reaches one adapter; another shows a frame of screw terminals wired by hand against a paper specification; another shows a hub with a ring of identical ports that any conforming device can join; another shows a block clamped over several mismatched shapes to present one face to whatever sits behind it. None of these makes the others obsolete. Each is the right answer to a different part of “how do two things that were built separately manage to work together.”

The same is true, and stated far less carefully, about how a language model gets access to anything outside its own context. A model on its own emits tokens. Everything it does to a database, a calendar, a filesystem or another API happens because some piece of software agreed, in advance, to treat certain tokens as a request. There are, as documented across the vendors and standards bodies that actually build these systems, roughly four distinct patterns for making that agreement, and they solve different layers of the same overall problem rather than competing solutions to one layer. Native vendor function-calling — OpenAI’s and Anthropic’s own tool-use APIs — governs how a model itself, inside one vendor’s inference call, signals an intended action. Wrapping an existing REST API in an OpenAPI document is an older pattern for how a tool describes its own capabilities to any client, model or otherwise. LangChain-style abstractions operate at a third layer entirely: client-side application code that targets many models and many tools through one interface. And the Model Context Protocol (MCP) is a newer attempt at the second layer — a standardized, cross-vendor way for a tool to describe, expose and authenticate itself — built with two years of the plugin and prompt-glue era’s documented friction already visible.

Two research threads set up why any of this needed solving at all. Schick and colleagues motivated the general problem plainly: large language models are remarkably good at few-shot learning of new tasks, yet “struggle with basic functionality, such as arithmetic or factual lookup, where much simpler and smaller models excel” [11]. Patil and colleagues, building Gorilla against a large corpus of real machine-learning APIs, documented the specific failure mode that follows from asking a model to call something it was never shown: “their inability to generate accurate input arguments and their tendency to hallucinate the wrong usage of an API call” [12]. Both problems are about getting the model to propose a well-formed, applicable action. Neither paper says anything about how the tool on the other end should be discovered, held in a session, or authenticated — and that gap between “the model proposed a call” and “the call reached an authorized, discoverable service” is exactly where the four patterns below diverge.

ADVERTISEMENT

This article works through each in turn, from primary documentation rather than from any vendor’s framing of its competitors, and ends with what changes and what does not when they are placed side by side on the same four axes: portability, discovery, statefulness, and authentication.

A squat aluminium appliance with a single captive lead caught mid-dressing into its one fixed adapter block, the strain-relief boot pushed only halfway up the cable and no other socket anywhere beside it
Figure 1. Native function calling wires a model straight to one vendor's own request format; portable is not a word that describes a molded-in lead.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

The model’s own contract: native function calling

The first layer is the narrowest and the oldest of the four in its current form. It answers one question only: inside a single vendor’s inference API, how does the model signal “call this, with these arguments” in a way a program can parse without guessing?

OpenAI shipped the pattern in June 2023, adding to gpt-4-0613 and gpt-3.5-turbo-0613 the ability for a developer to describe functions and have the model “intelligently choose to output a JSON object containing arguments to call” them [2]. The current guide states the design goal in the vendor’s own terms: function calling gives OpenAI models “a powerful and flexible way to interface with external systems and access data outside their training data,” with each function described by a name, a description and parameters “defined by a JSON schema” [1]. Notably, the documentation is silent on how a call, once emitted, should authenticate against whatever backend it targets — that is left entirely to the integrator’s own code, outside the scope of what the API defines.

Anthropic’s Messages API implements the same underlying idea with a different vocabulary and one structural distinction the OpenAI docs do not draw as sharply: where code executes. “Client tools… run in your application,” while “server tools… run on Anthropic’s infrastructure,” meaning the caller sees results directly for the latter without writing any handler at all [4]. Which tool Claude reaches for on a given turn is steerable rather than fixed — the documentation notes that with the default auto tool choice, a light system-prompt instruction shifts triggering behavior toward or away from calling a tool, and that this boundary can be forced explicitly through tool_choice [4]. Tool definitions are not free: the same documentation publishes, per model, the token cost of simply enabling tool use, from roughly 286 tokens for Claude Opus 5 at an auto choice up to several hundred more depending on model and mode [4] — a cost that scales with how many tools are described, independent of whether any of them is ever called.

Both vendors have separately grown a mechanism for coping with very large tool sets without dumping every schema into context at once — OpenAI’s guide describes pairing function calling with a tool_search capability to “defer rarely used tools and load them only when the model needs them” [1], and Anthropic’s tool catalogue lists a comparable tool-search capability to “work with thousands of tools by discovering and loading them on demand” [4]. This is worth flagging precisely because it looks like the next layer’s job — discovery — arriving inside the first layer instead. It is not the same thing: both mechanisms discover among tools the same vendor’s array already contains, in that vendor’s own schema dialect. Nothing here lets a client discover a tool it does not already know how to describe, and nothing here is portable to the other vendor’s array without a translation step, even though both happen to use JSON Schema as the underlying description language. That translation gap — same schema language, incompatible field names and calling conventions — is the specific, documented shape of the portability problem at this layer.

ADVERTISEMENT

None of the above says anything about whether a well-formed call is also a correct one, and that is a separate, measured question rather than a structural one. The Berkeley Function Calling Leaderboard states its own purpose plainly: it “evaluates the LLM’s ability to call functions (aka tools) accurately,” through what its authors describe as “holistic agentic evaluation” built on “real-world data” [13]. That distinction matters for everything in this article — a model proposing a schema-valid call has only cleared the first layer discussed here. Whether the call was the right one to make is a property of the model’s judgment, not of which of the four patterns below carried the call to its destination.

Wrapping an existing API in a document: REST and OpenAPI

The older of the two “expose a tool to any client” patterns predates function calling as an LLM feature entirely, because it was never designed for language models in the first place. The OpenAPI Specification describes its own purpose without reference to AI at all: it “defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic,” and its stated ambition is to remove “guesswork in calling a service,” the way interface descriptions long did for lower-level programming [9].

The concrete instance of this pattern applied to language models is now itself a documented historical artifact. OpenAI’s own now-retired ChatGPT plugin architecture worked by having a developer host two things at a well-known path: “a manifest (JSON file) that defines relevant metadata for the plugin” and “an API schema (OpenAPI JSON or YAML format)” describing the actual endpoints [3]. The model was handed the OpenAPI document, generated a call shaped by it, and the plugin runtime performed the real HTTP request. That page’s own heading now reads “Plugins (deprecated)” [3] — a documented deprecation, not a claim this article is making on the pattern’s behalf.

Reading the tradeoffs directly off that design: portability was genuinely strong in principle, since OpenAPI is vendor-neutral and any client capable of parsing the spec could in theory use it — the model vendor is incidental to the description format. Discovery, by contrast, was static rather than live: a document fetched once from a fixed path, authored primarily for human developers rather than trimmed to fit a model’s limited context, with every distinct API bringing its own separately maintained document and no shared listing mechanism across arbitrary services. Authentication was, if anything, the format’s strongest documented feature — OpenAPI defines a dedicated Security Scheme Object covering basic auth, API keys, OAuth2 and OpenID Connect — but it is a static declaration attached to one API description, not a live handshake a client can use to discover, at the moment of connecting, which scheme currently applies. And statefulness was simply outside the format’s concern: OpenAPI describes endpoints, not sessions, so whether a given API behaved statelessly per request was a property of that API’s own design, invisible at the schema layer.

A screw-terminal frame on a lab bench with one terminal caught mid-tightening, a flat screwdriver blade still resting in its slot, a closed paper reference binder lying beside it and one further terminal along the row still bare
Figure 2. The plugin-era pattern wrapped an existing REST API in a document and left a person to wire the rest by hand, one terminal and one manifest at a time.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Gluing it together from the application side: LangChain-style abstraction

The third pattern operates at neither the model’s inference contract nor the tool’s wire description. It is client-side application code, and it exists because the first layer is vendor-specific by construction: a tool schema written for one vendor’s input_schema field is not interchangeable with another vendor’s parameters field without a translation step, and an application that wants to run against several models needs that translation written somewhere.

LangChain’s own framing states the goal directly, structured as an equation of sorts: “Agent = Model + Harness,” where the harness is deliberately “minimal” and “highly configurable” so a team can “compose exactly what your use case needs” [10]. The specific claim relevant here is portability: the framework offers “one interface for chat models, embeddings, and more across providers,” explicitly naming “OpenAI, Anthropic, Google, and more,” so that a team can “switch models with minimal code changes and keep your application portable” [10]. Mechanically, a tool in this pattern is close to the simplest thing that could work — an ordinary function with a docstring, handed to an agent constructor that manages invocation on the caller’s behalf [10].

ADVERTISEMENT

This is the same portability problem named at the end of the function-calling section, attacked from the opposite side. Native function calling does not become vendor-neutral because a framework sits above it; the framework instead maintains, and must keep current, a mapping from one uniform object model to each vendor’s own native call format, translating a request out and a response back at call time. The documented tradeoff follows directly from where the translation lives: the abstraction is only as complete as its maintainers’ mapping to each vendor’s evolving native format, which means a newly shipped vendor feature is not available through the abstraction until someone updates the mapping. It is also, categorically, unlike the two protocol-layer patterns in one respect worth stating plainly: it is not itself a wire protocol or an independently running service. It exists inside the calling application’s own process. There is no separate “server” a different, unrelated client could point at the same tool definitions the way a client can point at an OpenAPI document or an MCP server — and, following from that, the pattern says nothing new about authentication to the underlying data source. That remains exactly as much the tool author’s job as it would be writing a plain function with no framework at all.

The standardized cross-vendor layer: the Model Context Protocol

MCP sits at the same layer as OpenAPI — describing a tool to any client, independent of which model consumes it — but it was built specifically for this scenario, after the plugin era’s documented friction was already visible, rather than borrowed from general API description.

Anthropic’s announcement states the problem in terms close to the ones above: “even the most sophisticated models are constrained by their isolation from data — trapped behind information silos and legacy systems,” because “every new data source requires its own custom implementation, making truly connected systems difficult to scale,” historically forcing developers to maintain “separate connectors for each data source” [5]. The stated remedy is “a universal, open standard for connecting AI systems with data sources, replacing fragmented integrations with a single protocol,” intended to give “a simpler, more reliable way to give AI systems access to the data they need” [5]. The specification names its own lineage plainly, describing MCP as taking “some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools” [6] — the same combinatorial problem LSP solved for editors and language backends, applied here to model clients and tool servers.

That combinatorial framing is worth making explicit, as a stylized model rather than a measured cost curve. Call the number of distinct model-facing client applications MM and the number of distinct external tools or data sources NN. Absent any shared protocol, a connector is needed for each pairing that is actually wanted, so the number of bespoke integrations that must be built and separately kept current scales as

IbespokeM×N. I_{\mathrm{bespoke}} \approx M \times N.

A protocol that both sides target once, instead, lets each side write a single adapter to the shared standard, so the count becomes

IstandardisedM+N. I_{\mathrm{standardised}} \approx M + N.

This is an illustrative accounting, not a verified cost function — real integrations are not uniform in effort, and a bespoke connector written once is not literally rebuilt MM separate times in practice. What the two expressions capture correctly is the shape of the argument both LSP and MCP’s own announcement make: the saving claimed is structural, in how many distinct things must exist and be maintained, not a claim that any single connector becomes individually cheaper to write [5, 6].

Discovery: a live call instead of a fetched document

Where an OpenAPI-based integration hands the model a document fetched once from a fixed path, MCP makes discovery a protocol operation. A client sends a tools/list request and a server “MUST respond… with the set of tools currently available to the requesting client,” a set that “MAY change over time” but “MUST NOT vary per-connection or as a side effect of other requests on the connection,” though it “MAY vary by the authorization presented on the request” [7]. Servers are additionally directed to return that list in a deterministic order specifically because doing so “improves LLM prompt cache hit rates when tools are included in model context” [7] — a wire-format rule justified by the economics of the model consuming it, which has no analogue in a document meant primarily for a human developer to read once. Base MCP messages are, by the specification’s own description, “stateless, self-contained requests” with “per-request capability negotiation” [6], and the same base protocol supports a live change notification so a client’s list of available tools can update without a fresh fetch of an entire document.

A compact hub appliance with a ring of uniform push-fit ports, most already carrying matched slim cables out to small server boxes, one new spoke cable caught half into an open port with its collar standing proud and not yet clicked home
Figure 3. A standardized protocol turns each new source into one more identical spoke on the same ring, discovered the same way as every other.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Statefulness: declined at the protocol level, not solved

It would be a mistake to read MCP’s newer design as having quietly solved the session problem that OpenAPI left to each API’s own convention. It has not, and says so about itself. Tool design guidance in the specification opens by conceding that “MCP has no protocol-level session, so a server cannot rely on implicit per-connection state to relate one tool call to the next” [7]. The documented pattern for anything that needs to persist across calls — a shopping basket, an open transaction — is an explicit handle: a creation tool returns an identifier, and the model is responsible for passing that identifier back on every subsequent call, with the server treating it as “a name, not a capability” that must be revalidated on each use [7]. That is architecturally the same move REST-convention APIs already made under OpenAPI: push session state out of the description layer and into ordinary request-and-response data that the calling side must carry forward correctly. MCP standardizes how the workaround is documented and shaped; it does not remove the workaround.

Authentication: optional, and different depending on how the tool is reached

This is the axis where MCP’s documentation is most specific, and where the tradeoff is sharpest. Authorization is “OPTIONAL for MCP implementations” in the first place [8], and where it is used, an MCP server acts as “an OAuth 2.1 resource server,” with clients required to implement OAuth 2.0 Protected Resource Metadata for discovering where the corresponding authorization server lives [8]. But that entire OAuth-based flow is scoped explicitly to one class of deployment: “implementations using an HTTP-based transport SHOULD conform to this specification,” while “implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment” [8]. In plain terms, the same protocol answers “who is allowed to call this tool” in two unrelated ways depending on how the tool is reached: a full authorization-server handshake with bearer tokens, audience validation and step-up scope challenges for a networked server, and nothing more elaborate than an environment variable for a tool running as a local subprocess on the same machine as the client — which is, at the time of writing, a very common way MCP servers are actually run. Neither answer is wrong for its transport. But a team auditing “how does this agent authenticate to its tools” cannot get a single answer for an MCP-based deployment without first asking which transport each server uses.

A small matte badge reader beside one server box with a badge on a retractable reel swinging a finger's width short of the plate, and a second server box beside it linked by a bare wire jumper already landed on a wall credential panel
Figure 4. MCP's own authorization flow covers the networked case and explicitly steps aside for the local one, so the same protocol answers who is allowed two different ways depending on how the tool is reached.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Composition, not competition, is the last documented fact worth naming here. Anthropic’s own tool-use documentation treats MCP as something its native tool-calling API connects to, not as a rival to it — pointing developers toward “the MCP connector” to “connect to remote MCP servers from the Messages API without a separate MCP client” [4]. An MCP server solves discovery and description; the model still needs the first layer’s contract — a tool_use block, a parameters field — to actually propose a call. The two are stacked, not substituted.

A pale-metal interposer block caught mid-descent above three visibly mismatched plugs on a lab bench, its underside cavity shaped to swallow all three at once and its retaining screws still standing proud and undriven
Figure 5. A model-agnostic tool abstraction does not replace the underlying wire formats; it clamps one uniform face over whatever shapes they already are.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Four answers to three different questions

Laid out on the same axes, without any of the four gaining a “winner” column, the documented differences read as follows.

Native function calling REST + OpenAPI wrapping LangChain-style abstraction MCP
Layer addressed Model’s own call-signaling contract Tool’s self-description, any client Application code targeting many models/tools Tool’s self-description, any client
Discovery Vendor-scoped array; tool_search-style lazy loading within one vendor Static document fetched once, per API None of its own; discovers via whichever layers it wraps Live tools/list call; deterministic order, change notifications
Statefulness Stateless per call; conversation state kept by caller Not addressed by the format; per-API convention Not addressed; inherits whatever the wrapped layer does No protocol session; explicit application-level handle pattern
Authentication Out of scope; integrator’s own responsibility Rich static declaration (API key, OAuth2, OIDC) per document Out of scope; unchanged from writing a plain function Optional OAuth 2.1 for networked transports; environment credentials for local stdio
Portability Low across vendors; JSON Schema shared, field conventions are not High in principle; vendor-neutral description format The stated purpose of the layer itself High across model vendors; server side is model-agnostic by design

Reading the table as a set of documented facts rather than a scoreboard, the pattern that emerges is that each column is strong exactly where the others are silent. Native calling is the only layer that says anything at all about how the model itself proposes an action — none of the other three touches that. OpenAPI and MCP both standardize the tool’s side of the conversation, at different points in that problem’s history, with MCP trading a static document for a live, cacheable, change-aware listing and paying for it with a genuinely bifurcated authentication story. LangChain-style frameworks solve neither of those; they solve the orthogonal problem of an application wanting to write its tool-calling logic once and run it against several vendors’ versions of the first layer.

Predictions, and what would falsify them

These are forecasts, separated from the documentation above. Horizon: 12 August 2029. Assumption throughout: tool-mediated agents continue to be deployed against real external systems, and no single vendor’s proprietary format becomes the sole way models call tools.

One. MCP’s authorization split by transport will narrow rather than persist as a permanent two-tier model, with local-transport deployments gaining an optional, lighter-weight credential-negotiation mechanism closer in spirit to the networked OAuth flow. Disconfirmed if the 2029 specification still directs stdio implementations to rely solely on ambient environment credentials with no negotiated alternative.

Two. Application-level abstraction frameworks will increasingly treat MCP as a first-class tool source rather than translating it into their own proprietary tool object at the boundary, narrowing the number of independent “harness” abstractions doing overlapping work. Disconfirmed if the leading frameworks in 2029 still require a separate, framework-specific wrapper around every MCP server rather than consuming its tool listing natively.

Three. The documented gap between vendors’ native function-calling schemas will not close into a single shared field-level convention, because each vendor’s format is entangled with other API-specific behavior (parallel calls, strict-mode validation, server-executed tools) that a shared standard would have to constrain. Disconfirmed if two or more major model vendors ship a wire-compatible tool-calling request format by the horizon.

Four. Static, document-based API description (OpenAPI-style) will persist alongside live protocol discovery rather than being displaced by it, because a large share of existing enterprise APIs will still be described that way and re-platforming them onto a live discovery protocol has a cost that many operators will not pay. Disconfirmed if OpenAPI-described integrations become a minority of documented tool sources in production agent deployments surveyed near the horizon.

None of these requires a capability discontinuity in the underlying models. Each follows from constraints already visible in the specifications and the one documented deprecation examined above.

What to take away

Four patterns, three layers, one problem asked as if it had a single best answer when it does not. Native function calling governs how a model, inside one vendor’s own inference call, signals an intended action — and neither older nor newer patterns touch that layer at all. REST wrapped in an OpenAPI document and the Model Context Protocol both address the layer below it, a tool describing itself to any client, at two different points in that problem’s twenty-year history, trading a static document for a live, cacheable listing and inheriting a genuinely split authentication story in the process. LangChain-style abstractions sit above both, solving the orthogonal problem of application code that wants to target many models and many tool sources through one interface, at the cost of only ever being as current as its own maintained mapping to each vendor’s native format.

Asking which of the four is best is asking a category question as if it were a comparison question. The useful question is narrower and answerable from documentation alone: which layer does this decision actually sit at — model contract, tool description, or application glue — and what does the chosen pattern’s own specification say it does and does not promise on portability, discovery, statefulness and authentication. Every one of those four answers is already written down by the people who built the thing. The mistake is not reading it before choosing.