Блог AI / Agent

Стек AI Agent 2026: как сочетать LLM, MCP, Function Calling и JSON Schema?

You are wiring an AI Agent and the docs mention LLM, MCP, Function Calling, and JSON Schema at once. All four look like they “let the model call tools,” so the choice feels like pick-one.

They are not four substitutes. The LLM reasons, Function Calling serializes the decision into executable JSON, MCP reaches external services and returns structured results, and JSON Schema is the contract language across those three layers.

В этой статье:

Запомните это: The 2026 Agent stack no longer argues about “whether to use JSON.” It argues about which layer owns the decision, the execution, and the contract. The model thinks, Function Calling serializes, MCP executes, JSON Schema checks every hop. Below: responsibilities → data flow → each layer → composition → misplacement.

What the four layers do

Draw the boundaries first. When they blur, debugging becomes “the model is bad” or “MCP is broken”—usually neither is. The seam was never drawn.

Layer Owns Input Output
LLM Reason, plan, decide the next step Conversation, tool results, system prompt Keep talking, or “call this tool”
Function Calling Turn “which tool, which args” into an API object tools array + conversation tool_calls[].arguments JSON
MCP Discover tools, run the call, return structured results tools/call + arguments structuredContent JSON
JSON Schema Describe and validate JSON at every hop Schema document Pass / failure reason

One line: the LLM thinks, Function Calling says, MCP does, JSON Schema checks.

How to write JSON Schema, and how Tool Calling differs from Structured Output, is in Why AI Agents need JSON Schema. This piece only covers how the four layers connect.

Data flow of one tool call

Take “look up a ticket by id.” The user says “check T-1042.” The Runtime does not throw that sentence at the ticket system. It walks these six steps.

  1. 1
    Runtime builds the request

    Send the conversation, system prompt, and tools array to the LLM API. Each tool’s parameters field is a JSON Schema.

  2. 2
    The model does Function Calling

    It returns name=get_ticket and arguments as a JSON string—not prose like “please look that up for me.”

  3. 3
    Runtime validates input

    After json.loads, validate against the same schema. Retry or reject on missing fields or wrong types. Do not send dirty args downstream.

  4. 4
    MCP executes

    The Runtime puts the validated arguments into MCP tools/call. The Server queries the store and returns structuredContent.

  5. 5
    Runtime validates output

    structuredContent must match the MCP outputSchema. Only then is it fed back to the model.

  6. 6
    The model gives the final answer

    That can be natural language, or another Structured Output pass with a separate schema for the final JSON.

Note the two validates: one stops bad model args, one stops a bad Server payload. Skip either and the next layer treats dirty data as fact.

LLM: reasoning and decisions only

The LLM is the only layer that “thinks.” It reads context and decides to answer, ask, or call a tool. It should not touch databases, payment APIs, or the filesystem.

By 2026, vendor model APIs share the same semantics: you send tools, the model returns tool_calls or final text.

OpenAI uses Function Calling / Responses API, Anthropic uses Tool Use, and DeepSeek keeps OpenAI field names. The gaps are field names and Strict support—not whether output should be structured.

Treat the LLM as an executor and two things happen: secrets enter the prompt, and side effects cannot be audited. Leave execution to MCP. The model only outputs “who I want to call, with which args.”

Function Calling: tool choice at the model API

Function Calling is not a protocol. It is the shape of a model API response. The next step must be a machine-readable function name plus arguments—not a paragraph.

tools definition sent to the model (parameters is JSON Schema)
{
  "model": "gpt-5",
  "messages": [
    {"role": "user", "content": "Look up ticket T-1042"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_ticket",
        "description": "Fetch a support ticket by id",
        "parameters": {
          "type": "object",
          "properties": {
            "ticketId": {
              "type": "string",
              "pattern": "^T-[0-9]+$"
            }
          },
          "required": ["ticketId"],
          "additionalProperties": false
        }
      }
    }
  ]
}

The model returns arguments as a string, not a parsed object. The Runtime must loads + validate itself. Strict Schema does not change that—API-level conformance is not business-layer validation. It is the same class of problem as 2026 Structured Output’s response_format, except the constrained object is tool arguments instead of the final answer.

Function Calling ends here. It does not care how the tool is implemented or what the result looks like. Who receives the arguments is the Runtime’s job.

MCP: the protocol between Agent and the outside world

MCP (Model Context Protocol) is the standard conversation between the Agent Runtime and external services: list tools, call tools, take structured results. The 2026-07-28 spec dropped the session handshake. Each request is self-describing, so a Server can sit behind ordinary HTTP load balancing. Background: AI Conference 2026 highlights.

MCP tool definition: inputSchema / outputSchema use JSON Schema 2020-12
{
  "name": "get_ticket",
  "description": "Fetch a support ticket by id",
  "inputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "ticketId": {
        "type": "string",
        "pattern": "^T-[0-9]+$"
      }
    },
    "required": ["ticketId"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "ticketId": { "type": "string" },
      "status": { "type": "string", "enum": ["open", "pending", "closed"] },
      "assignee": { "type": ["string", "null"] }
    },
    "required": ["ticketId", "status"]
  }
}

The Runtime puts Function Calling arguments that already passed validation into tools/call as-is. The Server’s structuredContent must pass outputSchema. The Client validates again—same engineering problem, data now comes from the Server instead of the model.

Official tools spec: MCP Tools. The inputSchema root is still an object; after 2026-07-28, outputSchema may be any JSON value.

JSON Schema: the contract across all three layers

JSON Schema is not a fourth “feature module.” It is the shared description language of the other three layers. The current mainstream is 2020-12.

Where it appears Field Constrains Who validates
Function Calling tools[].function.parameters arguments the model generated Runtime (before calling MCP)
Structured Output response_format.json_schema The model’s final answer Runtime (before business code)
MCP input inputSchema arguments on tools/call Runtime + Server
MCP output outputSchema structuredContent Runtime (before feeding the model)

The most common composition bug: Function Calling parameters and MCP inputSchema are written twice, and field names or enums disagree. The model generates against A, the Server validates against B, and it looks like “the model always fails the tool.”

The fix is one contract, two mounts: generate parameters and inputSchema from the same schema object. If the final answer also goes downstream, keep a separate Structured Output schema. Do not reuse the tool-input schema for that.

Reference architecture: how to compose them

In production the default wiring looks like this. A local function or your own HTTP API can stand in for MCP at first—the boundaries must not move.

Component Does Does not
LLM API Reason; return tool_calls or final JSON Hold secrets or hit internal systems directly
Agent Runtime Assemble tools, validate, route to MCP, feed back, loop Treat natural language as args, or skip validate
MCP Server Run side effects, return JSON that matches outputSchema Parse the prompt or plan for the model
Schema catalog Single source; feeds both API tools and MCP Hand-write two “almost the same” JSON files
Runtime wiring Function Calling to MCP (pseudocode)
schema = catalog.get("get_ticket")          # single source
req.tools = [{ "type": "function",
               "function": { "name": "get_ticket",
                             "parameters": schema.input } }]

resp = llm.chat(req)
args = json.loads(resp.tool_calls[0].arguments)
validate(args, schema.input)                # stop bad model args

content = mcp.call("get_ticket", args)      # MCP executes
validate(content.structuredContent, schema.output)

req.messages.append(tool_result(content))   # feed back, next turn

The loop can run for many turns: the model sees structuredContent and then picks the next tool or the final answer. The schema catalog stays put. What grows is the pile of tool results in messages.

What happens when a layer is misplaced

The four layers compose because each does one job. Slide a duty to the neighbor, and the outage looks like a model-quality problem.

Mistake Looks like Actually causes
Let the LLM hit HTTP itself One less framework, ships faster Secrets in context; no audit trail; swapping models rewrites every call
Use MCP as if it were Function Calling The protocol also has name / arguments The model never sees a tools list, so it will not pick tools reliably
Ask for JSON in the prompt, skip schema The demo runs No fixtures; fields drift; downstream parse breaks daily
Write parameters and inputSchema separately Both sides “work” Enums / required keys disagree → random tool failures
Trust the model output, skip validate Strict / JSON Mode is on Missing fields, truncation, and type errors hit business code

Rule of thumb: the model only emits decision JSON; execution happens only in MCP (or a backend you name); both directions need a schema.

Debug this path with JSONNote

This path has at least three JSON documents: model arguments, MCP structuredContent, and the final Structured Output. All three should format / validate / diff locally. Keys and samples never need to leave the machine.

  1. 1
    Format the arguments

    Paste tool_calls.arguments into JSON Formatter first, to catch broken syntax and truncation.

  2. 2
    Validate against the same schema

    Paste parameters / inputSchema and the arguments into JSON Schema. The failure path is exactly where the Runtime should reject.

  3. 3
    Diff two calls

    After a prompt or schema change, use JSON Diff to see which fields moved in arguments or structuredContent.

  4. 4
    Share the debug scene

    Use URL Hash sharing to put a tool call in the link. A teammate opens it and reproduces—nothing hits a server.

FAQ

Do Function Calling and MCP have to be used together?

No. Local functions and HTTP APIs can back a tool. MCP standardizes discovery, invocation, and structuredContent; Function Calling only lets the model pick a name and arguments. They often combine, but each can stand alone.

How many JSON Schemas should I write?

At least two: one for tool input (Function Calling parameters must match MCP inputSchema) and one for tool output or the final answer (MCP outputSchema or Structured Output response_format). Do not let the model side and the MCP side drift apart.

Is Function Calling enough without MCP?

Enough for a single-process demo. Once tools are reused by multiple agents or runtimes, or you need to scale out, skipping MCP means inventing tools/list, auth, and structured returns yourself. The 2026 default is Function Calling to choose, MCP to execute.

The model already returned JSON—do I still validate in the Runtime?

Yes. Function Calling arguments and MCP structuredContent can still miss fields, have wrong types, or be syntactically broken. API-level conformance is not business correctness. Validation is the Runtime’s job, not a guarantee from the model or the Server.

Итог

The 2026 Agent stack fits in three sentences:

The LLM decides, Function Calling serializes, MCP executes. JSON Schema is the shared contract—not a fourth optional extra.

Freeze one input schema and one output schema. Mount the same objects on the model API and the MCP Server. Validate both hops in the Runtime. Models and protocols will keep changing. The contract and the validation layer are what take an Agent from demo to production—JSONNote’s local format / schema / diff tools are there when you debug that JSON.

← Назад к блогу