Do AI Agents Really Need MCP Servers? Here's What the Numbers Say
MCP or CLI? A practical look at Jira, GitHub and Playwright, separating tool access, permissions and response shaping from token-cost claims.

Signup Write New Story Do AI Agents Really Need MCP Servers? Here's What the Numbers Say by Kaan Bobaç by Kaan Bobaç | @ kaanbobac Engineering manager and software engineer. I write about coding agents, developer tools, and how engineering teams get work done.
Engineering manager and software engineer. I write about coding agents, developer tools, and how engineering teams get work done.
In my last post, a small CSV button somehow took us through repository instructions, progressive disclosure, skills, and a production incident. Quite a journey for a button. Towards the end, we also arrived at MCP, which raised other questions, but we didn’t go into them then. Today, let’s spend a little more time on it.
Before the deep dive, let’s understand how the pieces fit together at a high level.
This is an illustrative sequence, not measured timing. The interactive diagram in the original article has the same numbered steps, reproduced below.
01 · Application → server: discover the tools.
Before using a tool, the MCP client discovers what the server exposes. This can occur on connection or later on demand; it is not necessarily a complete model-context load at session startup.
02 · Server → application: return tool definitions.
Definitions include tool names, descriptions, and argument schemas. They are blueprints, not execution results. The host decides which details to expose to the model and when.
Your message arrives at the agent application. A task that needs no tool may receive a direct answer without running the tool-execution steps.
04 · Application → model: provide relevant context.
The application supplies the prompt, relevant history, and available tool information. A host can defer some tool definitions and retrieve them through discovery when needed.
05 · Model → application: propose a tool call.
Here the model chooses an operation and supplies arguments. If more tool information is needed, the host can retrieve it first. The model does not directly execute the external API.
06 · Application: check permissions and any approval.
The host may ask you to approve the operation, depending on its policy and settings. A visible approval prompt is not mandatory for every MCP call.
07 · Application → MCP server: call the tool.
The MCP client sends the chosen tool name and arguments to the corresponding server. This is execution, separate from discovering the catalogue.
08 · Server → API: perform the operation.
This example tool calls an external API. A different tool could use local files, a library, or another dependency instead; MCP does not require this API hop.
09 · API → server: return the service result.
The server receives the result of the underlying operation and prepares its tool response. The API is an example dependency, not the language model.
10 · MCP server → application: return the tool result.
The tool result returns through the MCP client to the agent application. A result is evidence to process, not a new instruction overriding the task.
11 · Application → model: provide the relevant result.
The host adds the relevant evidence to the conversation. It may filter or process it rather than passing every byte of output into the model context.
12 · Model → application: answer or request more work.
If another tool is needed, return to the decision and execution steps. Catalogue discovery need not repeat for each call. Here the model has enough evidence to answer.
13 · Application → you: deliver the answer.
This completes the illustrated task. When the animation replays, it restarts the explanation; that does not mean every real tool call repeats startup and discovery.
Let’s start just before our prompt. When we configure an MCP connection, the agent application has a way to contact the server. Through its MCP client, it can discover which tools that server exposes. That can happen when the application connects or starts a session, or later when it needs the tools.
The server returns tool definitions, not the entire details of the tool. Think of a definition as a blueprint: a name, an explanation of what the operation does, and a schema describing the arguments it accepts. For example, an illustrative export-status tool might have this definition:
{ "name": "get_export_status", "description": "Read the status of a CSV export by its ID.", "inputSchema": { "type": "object", "properties": { "export_id": { "type": "string" } }, "required": ["export_id"] } } That tells the application how to request a status. It hasn’t retrieved an export yet. And the information visible to the application isn’t necessarily all shown to the model immediately. Some hosts expose the tool definitions up front; others defer details until a tool search or selection makes them relevant. Before the model generates a valid call, the necessary name and argument contract must be available to it, either from the initial context or through that later discovery step.
Skills are pretty much the same idea. Agents pass definitions to LLM. Then model initially sees a skill’s name and description, then load its instructions when needed. That’s different from an MCP tool definition, which can include the argument schema above. A skill may explain a procedure that uses an MCP tool, but installing the skill doesn’t automatically establish that service connection.
Now we send our first prompt to agent. Then our agent will send to the model two important context: 1) the prompt and 2) available tool definitions. In other word, two important ingredients: what we’re asking for, and what operations the model can request.
The model, LLM, is the “brain”. It does the reasoning itself. It might answer directly, or propose a tool call such as get_export_status with export_id set to the relevant ID. The application handles the execution around that decision: context assembly, tool routing, permissions, and the results passed back to the model. So the model is the decision maker of the response, but the surrounding application, agent, is doing more than blindly forwarding every available piece of context.
If the model proposes a tool call, the agent checks any required permissions or approval and sends it through the appropriate MCP client to the server. The server performs the operation and returns the result. In this example, it calls an API; another implementation could use a command, a library, or a different dependency.
Then the agent sends the relevant result back to the model. The model can request another operation or produce an answer for the user. That’s the agent loop. Discovering the catalogue doesn’t have to happen again for every tool call, and a request that needs no tool can skip the execution part entirely.
The interactive diagram linked above separates those messages so we can see which direction each one travels. It shows one illustrative call through an external API, not a compulsory sequence for every agent task. Approval depends on the host and settings, and discovery timing depends on the implementation.
Let’s zoom in on MCP servers. An MCP server is a program that exposes capabilities to our agent application. That’s the server part. How about the MCP part? MCP stands for Model Context Protocol: a common interface that compatible applications can use to discover and call tools, among other capabilities.
For example, a GitHub MCP server can expose an operation for reading a pull request. Our application discovers its tool definition, and when the model requests that operation with the repository and PR number, the application sends the call to the server. The server retrieves the information through GitHub’s API and returns the result. The model doesn’t need to construct the underlying HTTP request itself.
Put those two parts together, and we have an MCP server. Depending on the implementation, we can configure a connection to a remotely deployed one in our favorite agent application, or run a server locally and let the application communicate with that process. Local doesn’t necessarily mean offline: a local GitHub MCP server still needs to reach GitHub.
At this point, we can ask the question behind the title: if my agent can already run a command or call an API, why would I add an MCP connection for the same job? What does that extra layer give us, and what does it cost?
We’ll dive deeper into this topic, but let’s first return to our previously introduced, realistic-enough software engineering team and continue with a realistic example. We were getting requirements from our product manager. Let’s imagine our tech landscape looks like this: GitHub for source control and pull requests, Jira for tracking the work, and Playwright for testing the user interface. These three give us a good starting point.
Our goal is to make ourselves lazy. Lazier than today, preferably 🙂
If I currently have to write five sentences, copy a ticket description, explain which PR I mean, and paste a screenshot, I’d quite like to reduce the repeated part of that work. Not by removing the actual requirement, of course. “Do the thing” is still a fairly ambitious prompt.
In the previous post, we dealt with instructions and procedures: how our project works, and how to carry out a workflow. Now we need access to information and actions outside that conversation. A skill can describe how to review a ticket or investigate a failure, but it doesn’t create a Jira connection or a browser by itself. It uses the tools the environment provides. So skills and external tools can work together; they aren’t competing alternatives.
We can supply the context by hand. Copy the Jira ticket into the prompt, add the relevant PR discussion, click through the browser ourselves, and describe the result. That’s a valid way to work. It also gives us several copies to refresh when the source changes.
Alternatively, we could ask, “Read DEMO-2 and implement the export option.” That only helps if the agent has a working way to retrieve the issue and enough guidance to read the relevant details. The short prompt isn’t magic; there is access and setup behind it.
The shorter request still needs the right context. ‘Review PR 1240’ needs a repository, and inspecting a local diff won’t automatically include the PR discussion or check results. Likewise, seeing the export button in a screenshot isn’t proof that the downloaded CSV is correct.
The convenience I’m looking for is less manual transfer of the right evidence, not less need to understand the requirement. Let’s look at what each connection can supply.
Long story short, even with good skills, a tidy AGENTS.md, and carefully managed project context, some work still needs capabilities outside the conversation. The need is clear enough. The next question is how to provide them without installing another connection just because it exists.
The product manager has put the request in a Jira issue. We ask the agent to read it before doing the work.
For this example, we want the issue key, summary, and status. We don’t need every custom field, attachment, and historical comment just to find the active export request.
Jira provides a REST API, so a command-line client can make a bounded request and return a compact result. There is also official Atlassian CLI tooling, ACLI. That is not the same product as the community tool often called jira-cli.
In the documented Cloud-oriented ACLI workflow, after configuring the supported access, we could search and then open the relevant work item like this:
acli jira workitem search \ --jql 'project = DEMO AND text ~ "CSV"' \ --fields key,summary,status \ --limit 5 --json acli jira workitem view DEMO-2 \ --fields key,summary,status,description --json The first command gives a small search result; the second loads the description before we act on the request. If the decision is in a comment, we need the relevant discussion too. These are documented ACLI examples, not commands executed against a live tenant in our experiment.
Now compare that with the official Atlassian Rovo MCP connection. Its documented Jira tools include JQL search, reading issues, reading comments, adding or editing comments, and transitions. So the choice isn’t “CLI can work with Jira, MCP can only search it,” or the other way round. The host, available tools, permissions, and deployment decide what we can actually do.
ACLI has JQL search and work-item reads with selected fields and JSON; REST clients use the supported API contract
Documented search/read tool groups expose JQL and issue retrieval
ACLI documents comment and transition commands; APIs can expose corresponding operations
Documented write tools include comments and transitions, subject to granted permissions
Select fields and project locally; own the wrapper if we write one
Check the operation’s arguments and returned structure; results can also be processed in host-side code
Needs an execution environment or some other supported connection
A supported hosted connection can supply operations directly to the agent application
Verify that specific CLI/API and login path; don’t assume Cloud examples work on Data Center
Verify the official hosted integration’s product support and organization settings; community MCP servers are separate products
The official connector supports its documented authentication methods and organization controls. We don’t need to add a broad credential to a shell just to make the comparison look symmetric. Equally, a community server isn’t evidence of what the official hosted server exposes.
Before implementing the export option, let’s see the recent pull requests. Perhaps someone already changed the API, or there is a related fix we should understand first.
If the terminal already has gh configured, an agent can ask for exactly the fields it needs:
gh pr list \ --repo microsoft/playwright \ --state open \ --limit 5 \ --json number,title \ --jq '.[] | {number,title}' That’s the public-repository example used later for the measurement, not the repository of our imaginary reporting tool. In our own project, we would point the command at the relevant repository.
There’s no new MCP connection in that path. The agent calls the terminal tool, gh handles its configured GitHub access, and the result contains the PR numbers and titles. If we then need a particular PR’s body, checks, or diff, we can request those next.
gh pr view PR_NUMBER --repo OWNER/REPOSITORY \ --json number,title,body,baseRefName,headRefName gh pr diff PR_NUMBER --repo OWNER/REPOSITORY gh pr checks PR_NUMBER --repo OWNER/REPOSITORY These aren’t commands I would run blindly on every request. If all I need is the status of a check, I don’t need the entire diff. If I’m reviewing a change, numbers and titles won’t be enough. The information we ask for should follow the task.
So what changes functionally if we choose the official GitHub MCP server instead?
Read PR details, inspect a diff, check status
Native commands such as pr view, pr diff, and pr checks
Use the corresponding operations exposed by the enabled toolsets; check the server and client versions
--json selects supported fields; --jq projects them
Available arguments and output shape depend on the tool; host-side code can also filter results
Work in a host without local shell execution
Not directly available just because gh exists on my laptop
A supported remote connection can expose GitHub operations without giving that host my local shell
Use appropriate credentials and execution permissions
Use appropriate credentials plus supported read-only/toolset restrictions; the protocol itself doesn’t reduce account permissions
The MCP server and gh are different clients with different command surfaces. I wouldn’t assume every feature or option has a one-to-one equivalent. I would check the operations our workflow needs, rather than declaring the entire server a replacement for the CLI or the other way round.
For our five-PR question, if gh is already configured, the terminal route is straightforward. If the application we’re using needs GitHub operations through its supported tool interface, the MCP route may fit better. Neither should get broader permissions just because it is convenient.
The Jira requirement is clear, and we’ve looked at the GitHub context. Now we need to check the browser behavior. Open the reporting page, choose the visible sales metrics, create the CSV, and inspect what it contains.
The official Playwright MCP server exposes browser operations to a compatible agent application: navigation, page snapshots, clicks, form interactions, and other supported capabilities. That lets the application run an interactive loop against the page rather than asking us to describe every screen.
The alternative isn’t necessarily clicking everything by hand. The official Playwright CLI exposes browser commands, and we can also write Playwright code directly. If the agent can execute that code in an appropriate environment, it can carry out the same sort of work without an MCP connection.
A CLI doesn’t have to forget the browser after each command either. The CLI version used in this experiment supports named sessions. MCP also keeps browser context through its server. Both can support interactive work, and both offer ways to run known flows as code. We need to look at the particular capabilities and configuration, not assume that “CLI” means stateless or that “MCP” means a full-page dump on every action.
Navigate, select, click, inspect page state
Separate commands in a named browser session
Separate tools in the server’s browser context
Scoped/depth-limited snapshots, snapshot search, file output
Scoped/depth-limited snapshots, snapshot search, file-output parameters
run-code; --raw can return only the result
browser_run_code_unsafe in the measured toolset; review the server-process execution capability
Keep a browser session across interactions
Yes; the server manages its browser context
Supported attachment modes, including a CDP connection
Supported extension/endpoint modes; these are not identical configuration paths
Requires screenshots and actual visual checks, not just a passing CSV assertion
The same limitation: accessibility snapshots and text assertions aren’t visual-design proof
For a concrete CLI example, here’s the local sales-export demo used in the measurements later in this post. The e4 and e5 references come from this fixture’s snapshot; on another page, or after the page changes, read the current snapshot rather than assuming those references still identify the same controls.
playwright-cli -s=export-demo open \ http://127.0.0.1:8876/ --browser=chrome playwright-cli -s=export-demo snapshot --depth=2 playwright-cli -s=export-demo select e4 visible playwright-cli -s=export-demo click e5 playwright-cli -s=export-demo eval \ '() => ({columns:window.lastExport.columns, rows:window.lastExport.rows, csv:window.lastExport.csv})' playwright-cli -s=export-demo close The equivalent MCP path uses the corresponding navigation, snapshot, select, click, and evaluate tools. The final evaluation reads export state deliberately exposed by our local demo, not a universal variable that appears on real websites. The close operation is cleanup and isn’t included in either measured five-call segment.
For a page we don’t know yet, inspecting the structure and deciding what to do next can be useful. Once we understand a repeatable flow, a script can capture it. Neither approach removes the need to verify the result: the button appearing, a click succeeding, and the correct CSV arriving are three different claims.
For this task, the output we ultimately care about is the exported file. The experiments below distinguish checking the application’s export state from checking the actual download. A successful click alone proves neither.
We have a clearer idea of what the three connections can provide and how the alternatives work. Now let’s look at how much text those paths return. The evidence comes in two forms: output-shaping experiments for GitHub and synthetic Jira data, followed by a direct Playwright interface comparison. They answer different questions, so let’s keep them separate.
I wanted some numbers rather than another argument about which setup looks nicer. But there are several different things people call “token usage,” and putting them in one bar chart can make almost any tool look good.
First, there is the information available for choosing a tool: names, descriptions, argument schemas, CLI help, or a skill explaining the commands. Then there are the calls themselves and whatever the tools return. Around all of that sits the model conversation, its reasoning, retries, system instructions, and the host’s context management.
The measurements here count the returned text and, separately, some serialized tool and instruction surfaces, using tiktoken with the o200k_base encoding. They are reproducible counts of specific text artifacts. No model inference ran in this experiment: command arguments, model reasoning, accumulated input across turns, host wrappers, caching, and billed usage are outside the returned-text figures.
That means I can say, “This response contains 744 returned-text tokens under this encoding.” To know whether it makes an agent cheaper overall, we’d also need the model’s actual usage, including the work needed to choose, write, and fix the calls.
Size of richer versus selected output, preserving the requested facts
Five synthetic issues served by a local Cloud-shaped API fixture
Size of field selection and projection for that fixture; no live Jira or Rovo performance claim
Official MCP and CLI against the same local application, three repetitions per path
Returned text for two separately labelled verification methods; no total-model-cost claim
Those are the boundaries behind the graphs. Within them, we can still ask useful questions about unnecessary fields, response formats, and what happens when we batch work into code.
The versions, examples, correctness checks, and measurement limits are described alongside the results below. The tables show the values behind each chart.
Let’s start with the five synthetic Jira issues. We want their keys, summaries, and statuses. The fixture lets us inspect both the full and reduced responses without using a company account.
curl --fail --silent --get \ 'http://127.0.0.1:8876/rest/api/3/search/jql' \ --data-urlencode 'jql=project = DEMO' \ --data-urlencode 'fields=summary,status' \ | jq -cj '[.issues[] | { key, summary:.fields.summary, status:.fields.status.name }]' Against the actual Jira Cloud API, the hostname, authentication, permissions, pagination, and supported parameters must come from that deployment’s contract. There are no credentials in this example. A production client should obtain them through the approved environment, not a token pasted into the article or the agent’s chat.
There are two useful steps in that command. Requesting selected fields reduces what comes back from the service. Projecting the response with jq reduces what we then put in front of the model. Those are related, but they aren’t the same operation.
In the fixture, the selected status field still contains structured data. We only need its name for this particular answer. Keeping the key and summary and extracting that name gives us a smaller output again.
Final key, summary, and status projection
The fixture’s fields are fixed and inspectable. Real issues will have different sizes, but the mechanism is visible here: request fewer fields, then return only the parts needed for the answer.
For our CSV change, this compact result helps identify the issue. We’d still open its relevant description and discussion before implementing it, as in the earlier ACLI example. If a tool returns a large response, a reviewed wrapper can project it too. Field selection and projection aren’t exclusive to a CLI; an MCP-backed workflow can apply them as well.
For the little list task, I compared a richer JSON response with a projected response containing only the five PR numbers and titles. The richer one included fields such as the body, branch names, and timestamps. Both came from the CLI.
That’s an 89% smaller returned-text response for the question we actually asked. Nice. Both outputs came from gh, so the saving belongs to field selection and projection, not to choosing CLI over MCP. We’ve preserved the requested numbers and titles while removing fields we weren’t going to use.
For the experiment, the local application displays twelve synthetic sales rows. The export can include all fields, or only the visible date and revenue columns. The correct result is known in advance: twelve rows with those two columns and the expected values.
I used the official packages, pinned rather than silently following latest:
For the step-by-step paths, both tools did five things: navigate to the page, request a shallow accessibility snapshot to find the controls, select the visible fields, click the export button, and read the exported CSV data from the page’s recorded export state.
The shallow snapshot was important. We weren’t asking the model to read every table cell just to locate a dropdown and a button. We also weren’t withholding needed page information from one path while giving it to the other.
Then I tried code execution on both sides. The same Playwright function navigated, selected the fields, clicked the button, waited for the download, and read the downloaded CSV stream. The code paths therefore also checked the actual downloaded file, rather than relying only on the application’s export state.
In this experiment, both code paths execute the browser work locally, then return the evidence needed for the answer. MCP servers can run remotely too; local execution describes this setup, not a requirement of the protocol. Both code paths read the downloaded CSV stream and check its columns, row count, and content.
All paths returned the expected columns, row count, and CSV content, but from different evidence sources. The stepwise pair checks the application’s recorded export state; the code pair additionally reads the downloaded bytes. If download delivery failed while the app state remained correct, the first check could miss it. That’s why the results below are two experiments, not one five-way ranking.
In the pinned version, a normal MCP navigation response linked to a saved YAML snapshot. The CLI did the same sort of thing. I explicitly requested a depth-limited snapshot afterward in both stepwise paths.
That matters because a comparison built on “MCP always sends the whole accessibility tree after every click” doesn’t describe what this server actually returned in this setup. Tool versions and options matter. A confident article about an older behavior doesn’t make that behavior permanent.
Here are the returned-text counts for each verification method. Within each method, MCP and CLI use the same check. Counts were identical across the three repetitions of each path.
Experiment A — verify application export state, five stepwise calls
Returned-text tokens ( o200k_base ), median of 3
Experiment B — verify downloaded CSV bytes, one code call in a warm browser
Within experiment A, the returned text is practically the same size. In experiment B, the normal code responses are also close. Raw CLI mode returns only the result value, which explains its smaller payload. To compare stepwise versus code execution fairly as complete workflows, I’d first give them matching evidence collection and startup conditions.
The useful result is specific: these two interfaces returned similarly sized text for equivalent checks within each experiment. Changing the response format changed the code-path output more noticeably than switching interfaces.
There is another difference we need to keep visible. The stepwise measurements include their initial navigation or browser opening. The one-call code measurements use an already-running browser. They’re useful as examples of batching and output control, but not a fair cold-start speed contest. I haven’t made a latency leaderboard out of them.
The MCP code tool in this version is called browser_run_code_unsafe. The name is telling us something useful: it executes arbitrary code in the server process. In this isolated local demonstration, the test code is controlled. I would not treat that capability as equivalent in permissions to a narrowly defined “click this button” tool on some remote service.
The CLI’s ability to execute code comes with its own execution permissions too. Smaller output isn’t an access-control argument.
It can be part of the cost, yes. But first we have to establish what the host actually loads.
The official Playwright server exposed twenty-five default tools in this run. Tokenizing the compact serialized tools/list result gave 4,398 tokens. Restricting that same serialized catalogue to the five stepwise tools used by the task gave 848. That second number is a projection, not a claim that our measured host automatically selected only those five definitions at startup.
The CLI’s help text was 1,598 tokens, and its bundled SKILL.md was 3,688. We didn’t silently assume those were free just because they live on disk. On the other hand, a model familiar with a command may not need to read all the help, and a host may load skill metadata first rather than the complete instructions.
This is an inventory of possible context inputs, rather than a cost ranking. A host may filter tools, defer loading definitions until discovery, cache prompt prefixes, or expose only a restricted toolset. Some configurations will eagerly include more; others won’t.
This is why I don’t want to take a large tools/list response, multiply it by every turn, and call the result the actual bill. We need the host’s real usage trace for that. We also need to account for cached input differently from uncached input when talking about money.
Anthropic’s work on code execution and deferred tool use is relevant here because it improves tool-driven workflows without requiring us to abandon MCP. I found that distinction useful: efficient code execution, filtering, and delayed loading can exist on either side of this comparison.
Imagine we’ve looked at the graphs and picked the smaller response. Then we discover that the chosen route can’t read the ticket discussion, loses the browser session, or requires an authentication setup our agent environment doesn’t support.
We haven’t really finished the comparison, have we?
For our Jira request, finding a summary isn’t the same as reading the acceptance criteria. For the GitHub review, seeing that checks are green isn’t the same as inspecting the change. For Playwright, finding the button isn’t the same as verifying the downloaded CSV.
I would write down the operations the workflow actually needs, then try them. Not everything called a Jira integration supports every Jira product, custom field, or workflow. Not every GitHub toolset exposes every operation. We can have a very compact tool that simply doesn’t cover our job.
On my laptop, running gh may be completely ordinary. In an agent application without shell execution, that same command isn’t available just because I know it exists.
A supported MCP connection can expose operations to that host without giving it my local shell. Conversely, inside a coding agent that already has an appropriate terminal and configured tools, adding a server for one known API call may be extra work rather than less work.
Even in a terminal-enabled agent, a maintained connector can be useful if it handles the service interaction well. I would inspect what it provides instead of assuming either a terminal or an MCP connection must always be simpler.
The Jira account still needs permission to read the issue. The GitHub credential still needs access to the repository. The browser session may contain a logged-in user and access to actions that aren’t part of our test.
MCP doesn’t make those permissions disappear. A shell with a broad credential is powerful. A tool that executes arbitrary code inside an MCP server is powerful too. A read-only hint describes intended behavior; actual permissions enforce the restriction.
For our comparison, I want to know which identity is used, which operations are allowed, and what information can enter the conversation. I’d also treat ticket text, comments, and web-page content as evidence, not as new instructions that can override the task.
Suppose the Jira search returns only the first page. Or gh exits with an error but a wrapper ignores it. Or the browser tool reports that it clicked the button while the download never finishes.
These problems aren’t unique to one route. We need bounded pagination, meaningful errors, sensible timeouts, and a clear check for completion. A smaller result isn’t useful if it quietly drops the evidence explaining why the task failed.
A known browser workflow may be easier to verify with a script and explicit assertions. An unfamiliar page may need an interactive loop. Both the measured CLI and MCP support persistent browser interactions and code execution, so we should inspect the specific workflow rather than decide from the acronym.
Armin Ronacher’s discussion of code-oriented tools helped me think about that distinction: exploration can benefit from interaction, while a workflow we understand can become code we run repeatedly. That’s a useful question to ask before buying into a permanent rule about which interface is best.
Tokens aren’t elapsed time. One path might need several model round trips; another might batch the work into code. A CLI can launch a process, a remote server adds a connection, and both may ultimately wait on the same service API. We didn’t run a fair cold-start latency comparison here, so I wouldn’t invent a speed winner from the payload graph.
Then there is the maintenance. If we write the API wrapper, we own the authentication handling, pagination, response changes, and tests. If we install an MCP server, someone still owns its deployment, updates, and compatibility with the host. Using an official tool can reduce some of that work, but it doesn’t remove the need to check whether an update changed the behavior we rely on.
A shared integration may be valuable across several agent applications. A small local command may be enough for one workflow. I would consider both the work saved today and the thing we’re agreeing to maintain tomorrow.
Anthropic’s code-execution discussion is useful here because it improves workflows that still use MCP: execute and filter intermediate work in code, then return the information the model needs. Efficient execution isn’t something we can only get by removing MCP.
So, after the token comparison, my checklist would be quite practical:
Can it read the necessary Jira context, inspect the PR, and verify the CSV?
Is shell execution available, or does the application need a supported connector?
Which account and permissions are used, and what data reaches the model?
Are missing pages, failed requests, and unfinished downloads made explicit?
Does it preserve the required session, and can we inspect or reset it safely?
How many round trips, processes, connections, and maintenance steps are involved?
Is this a one-off command, a repeatable script, or an integration used across hosts?
None of those questions makes the token measurements irrelevant. They help explain whether the smaller response is actually the better choice for this piece of work.
Our product manager still wants the export option. They haven’t asked us to win a transport argument. After all this, here’s where I’d start in the environment we’ve described: a coding agent with a terminal.
For Jira, I’d use the supported CLI or a small API client if it’s already configured and can retrieve the relevant description and discussion. I wouldn’t replace a working, maintained MCP connection just to remove the acronym. If the host has no shell, or the organization provides the supported Jira connection through MCP, I’d use that route instead.
For GitHub, gh is a straightforward starting point for our PR reads, diffs, and checks. I’d request the fields the current question needs, then load more when the task calls for it. If we move to a host that exposes GitHub through MCP, I’d check its enabled operations and permissions rather than assume we need to reproduce our laptop’s shell there.
For Playwright, I’d keep an interactive session while exploring the page, then capture a known flow as code when repeating it becomes useful. Both pinned interfaces can support those patterns. Our returned-text measurements don’t give me a reason to migrate between them by themselves. Host support, permissions, how we collect evidence, and how we reuse the test matter more to that choice.
Those are starting decisions, not permanent commitments. I’d reconsider if a route couldn’t retrieve the necessary ticket context, dropped PR evidence, lost the browser state we needed, or made errors hard to detect. I’d also measure the actual host and model usage if cost became the problem, rather than infer the bill from these text artifacts.
The next experiment would compare complete agent runs: the same task, equivalent verification, model and host versions, initial context, cached and uncached tokens, retries, completion rate, and final-result quality. This post’s measurements give us inspectable pieces of that question, not the whole answer.
For now, we can make specific improvements. A huge response? Request or return less irrelevant material. Repeated browser work? Try a tested script. Too many definitions loaded up front? Inspect discovery and loading in the host. Each change has something we can check.
I still like having a useful setup. I just don’t want to keep improving the setup while the actual task is still waiting.