MCP tool calling under the hood

jdauriemma1 pts0 comments

MCP tool calling under the hood - Jeff Auriemma<br>Jeff Auriemma<br>MCP tool calling under the hood<br>Jeff Auriemma

July 22, 2026

Share

When you ask an agent to create a Jira ticket, and a few seconds later the ticket exists, we usually say that the model called a tool. That's useful shorthand, and I use it too, but it can make the process sound more direct than it really is. The model almost certainly didn't interact with Jira's services directly. What it most likely did was generate a structured request asking the harness, which is the application wrapped around it, to do those things on its behalf. I want to walk through what actually happens during a tool call, using a simple example prompt:<br>Create a Jira ticket for the login bug we just discussed.<br>We'll get into this, but FYI there's a video version of this article here if you prefer that medium.<br>Before the model is ever invoked, the application has to build a request for it. That request bundles together everything the model needs in order to act sensibly: the user's message, whatever conversation history the model needs to understand what "the login bug" refers to, any relevant system instructions, and descriptions of the tools that are available to it. A Jira tool might be described with a name like create_jira_ticket, a short explanation of what it does, and a schema for its arguments, maybe a title, a description, and a project key. There usually isn't a separate search step here. The conversation and the available tool definitions travel together in the same inference request, and the model chooses among the tools it's been given based on their names, descriptions, and schemas. Those definitions might be cached by the provider to reduce cost or latency, but logically they're still part of what the model sees when it decides what to do next.

Here's what that tool definition might actually look like on the wire:<br>"name": "create_jira_ticket",<br>"description": "Create a new Jira issue",<br>"parameters": {<br>"type": "object",<br>"properties": {<br>"title": { "type": "string" },<br>"description": { "type": "string" },<br>"project": { "type": "string" }<br>},<br>"required": ["title", "project"]<br>Notice everything that's missing from it: an API client, credentials, retry logic, any code that actually creates a ticket. The implementation is abstracted away from the model. It only needs to know that this capability exists and what a valid request for it looks like, which is a lot like a function signature with no body behind it.<br>declare function create_jira_ticket(args: {<br>title: string;<br>description: string;<br>project: string;<br>}): { key: string; status: string };<br>That's the right frame for the whole exchange. It's like a function or RPC call. The model is invoked with the conversation, the instructions, and that signature, and during inference it decides that the Jira tool is appropriate and produces a tool-use response. Conceptually, that response might look something like this:<br>"name": "create_jira_ticket",<br>"arguments": {<br>"title": "Login fails after session expiration",<br>"description": "Users are redirected to an error page after their session expires.",<br>"project": "WEB"<br>Depending on the model API, the arguments may be constrained to the tool's schema while they're being generated, which means the model can be prevented from omitting required arguments, using the wrong types, or producing malformed JSON. But that only tells us the request has the correct shape. Knowing WEB is a real Jira project, and that the current user has permission to create a ticket there, and whether the application should allow the operation at all are all out of scope for the model. Those decisions still belong to the application.

At this point, the model has not created anything. It has produced a structured request indicating which action it wants the application to take, and it's the harness that receives that request and decides whether to execute it. It might check the user's permissions. It might apply an organizational policy. It might require human confirmation before proceeding. Or it might reject the request entirely. This distinction matters more than it might seem to on first read, because a model being able to request an action is not the same thing as a model having the authority to perform it.

If the request is allowed, the harness invokes the actual tool implementation, and this part is mostly ordinary software. Some code authenticates with Jira. It constructs an API request. It handles network failures, rate limits, retries, logging, and whatever else the integration requires, none of which the model has any visibility into.<br>def create_jira_ticket(title, description, project):<br>token = jira_auth.get_token()<br>resp = http.post(f"{JIRA_URL}/issue",<br>headers={"Authorization": token},<br>json={"title": title, "description": description, "project": project})<br>return with_retries(resp)<br>The model generally doesn't know or care whether the tool is implemented as a local function, an HTTP call, a shell...

model tool request project jira title

Related Articles