Kvmzen Blog
← Back to Tech in practice

Why AI Agents Can't Live Without JSON: A Complete Data Flow Analysis from Tool Calling and Function Calling to MCP

AIAgent ·~13 min read

Why AI Agents Can't Live Without JSON: A Complete Data Flow Analysis from Tool Calling and Function Calling to MCP

The model returns valid JSON, but the API rejects the request or the MCP client cannot use the result.

Fastest fix: treat JSON as transport data, not proof of correctness. Validate syntax, schema, tool choice, permissions, call identity, execution status, and business rules as separate gates in the AI Agent JSON data flow.

This guide is for:

  • Agent developers who need to understand where JSON belongs in a tool-calling loop.
  • Troubleshooting engineers tracking bad parameters, missing state, or unusable tool results.
  • Platform architects designing shared events, schema versions, and reproducible test environments.

The real failure is usually between layers

Consider this failure chain:

The model selects deploy_build, produces parseable arguments, your executor sends the request, the API returns 403, the error is converted to plain text, the tool-call ID is dropped, and the model receives a result it cannot associate with the original action.

At first glance, this looks like a JSON problem. It is not one problem. It is a sequence of responsibility boundaries:

  1. Model generation — selects a tool and proposes arguments.
  2. Application orchestration — validates, authorizes, and decides whether execution is allowed.
  3. Tool or API execution — performs the requested operation.
  4. Protocol transport — carries calls and results between components.
  5. Business validation — checks whether the result is usable for the actual workflow.

JSON connects these layers because machines can inspect it, log it, compare it, and validate it. But JSON does not perform reasoning, grant permissions, execute APIs, or guarantee that a result is useful.

That distinction is the foundation of reliable Agent operations.

Why do AI Agents commonly use JSON? Because JSON is a compact machine-readable envelope for names, arguments, IDs, statuses, errors, and structured results. Its value is not that the model “thinks in JSON.” Its value is that the surrounding software can verify and route the model’s output before taking action.

OpenAI’s current tool definitions use function names and JSON Schema parameters, while Google’s Function Calling flow returns a function name and arguments for your application to execute. Both documents make the same operational point: the model proposes the call; your code performs it. See the OpenAI function tool reference and Google’s official Function Calling workflow.

Why valid JSON still fails validation

A parser answers one narrow question: “Can this text be decoded as JSON?”

A schema validator answers a different question: “Does the decoded value have the required shape?”

For example, this payload is valid JSON:

{
  "repository": "mobile-app",
  "branch": 42
}

It can still violate a schema requiring branch to be a string. Other common failures include:

  • A required field is missing.
  • A number is returned where an enum value is required.
  • An object is returned where an array is expected.
  • A field is nested under the wrong parent.
  • An unexpected property appears when additional properties are forbidden.
  • A nullable field is treated as mandatory, or the reverse.
  • A date is syntactically a string but does not match the expected format.

The practical consequence is that “JSON parsed successfully” should never be your final acceptance test.

Syntax validation and schema validation are different gates

Use separate events for both checks:

{
  "event": "tool_call_received",
  "json_parse": "passed",
  "schema_validation": "failed",
  "schema_version": "deploy.v3",
  "error_code": "type_mismatch",
  "field": "branch"
}

This makes the failure searchable. Without the distinction, a dashboard may label every rejection as invalid_json, sending engineers toward parsers when the actual issue is a type mismatch.

Schema support is also not identical across providers. Google’s Structured Output documentation lists supported types and properties, including objects, arrays, strings, numbers, integers, booleans, required fields, enums, and selected numeric constraints. It explicitly describes this as a subset of JSON Schema rather than the entire specification. See Google’s Structured Output documentation.

OpenAI likewise documents strict schema adherence as supporting only a subset of JSON Schema for strict mode. This means a schema that works in one provider or validator may be rejected, ignored, or interpreted differently in another. Keep a provider-specific compatibility test instead of assuming that one schema file is portable without adaptation. See the OpenAI schema and evaluation reference.

Why can an Agent fail even when the JSON is legal? Because legality only proves encoding. It does not prove field types, required properties, supported schema features, or business meaning.

A useful validation sequence is:

  • Parse the raw payload.
  • Validate against the provider-compatible input schema.
  • Normalize field names and formats.
  • Apply application-level constraints.
  • Record the schema version and validation result.
  • Reject before execution if any gate fails.

Do not “fix” every issue by making the schema stricter. Strict fields cannot correct a wrong tool choice.

When the schema passes but the Agent chooses the wrong tool

A model may produce perfectly valid arguments for an irrelevant function. For example, it can call get_build_status when the user asked to restart a failed build. The arguments may match the declared schema exactly, yet the workflow is still wrong.

This is a tool-selection problem, not a field-validation problem.

Improve selection at the semantic layer:

  • Use action-oriented names such as restart_build instead of vague names such as build_action.
  • Describe what the tool does, what it does not do, and when it should be used.
  • State destructive or irreversible effects explicitly.
  • Remove unrelated tools from the candidate set for narrow workflows.
  • Include task context such as repository, environment, user intent, and approval state.
  • Use an explicit tool-choice policy when the workflow requires or forbids a tool.

OpenAI’s tool configuration includes none, auto, and required selection modes, while its tool reference also supports restricting the allowed tools. Google documents sequential and parallel function calling, which makes candidate control important when several functions can be selected in one turn. See the OpenAI tool selection reference.

Function Calling JSON has separate generation and execution roles

In a Function Calling loop, the model generates the proposed function name and arguments. The application receives that proposal, checks it, and executes the corresponding code. The API or local executor—not the model—performs the side effect.

Your orchestration layer should therefore preserve three separate records:

{
  "model_proposal": {
    "name": "restart_build",
    "arguments": {
      "build_id": "bld_742"
    }
  },
  "authorization": {
    "decision": "approved",
    "policy": "build-restart-on-staging"
  },
  "execution": {
    "status": "queued",
    "job_id": "job_913"
  }
}

This prevents a dangerous ambiguity: a model proposal must not be logged as if the action already happened.

A common implementation mistake is to directly deserialize arguments into an SDK call without checking ownership, environment, resource state, or authorization. Even with strict schemas, you still need application validation before execution. The OpenAI function-calling guidance also places responsibility for validating generated arguments on your application before the function is called.

Correct parameters can still produce a rejected API call

Once syntax and schema checks pass, the next failure layer is execution.

Typical causes include:

  • Missing or expired authentication.
  • Valid authentication without the required permission.
  • A resource that has been deleted, paused, locked, or already completed.
  • Network timeout, DNS failure, or unavailable dependency.
  • A business rule such as an invalid deployment window.
  • An idempotency conflict caused by a previous retry.
  • A regional or environment mismatch.

The executor should return structured errors rather than a vague sentence such as “The request failed.”

Prefer an error envelope like this:

{
  "ok": false,
  "error": {
    "layer": "authorization",
    "code": "permission_denied",
    "retryable": false,
    "message": "The service account cannot restart production builds.",
    "required_action": "Request production-build:restart permission."
  }
}

The layer, code, and retryable fields help both automation and operators. The model can explain a permission failure or ask for approval. It should not have to infer whether a timeout, denied permission, or missing resource caused the failure.

Operational warning: Never convert every executor exception into a successful-looking tool result. A result with status: "failed" is different from a transport failure where no trustworthy result exists.

For API execution, separate retry policy from model behavior. A timeout may be retryable by the executor. A permission denial usually is not. A resource-state conflict may require a fresh lookup before another action. Let the application decide; do not ask the model to guess.

Why call IDs and conversation state matter

A tool call is not just a function name and arguments. It also needs identity.

The ID connects:

  • The model’s proposed call.
  • The executor’s authorization decision.
  • The outbound API request.
  • The returned tool result.
  • The follow-up model turn.
  • The audit record.

If that identity disappears, several errors become possible:

  • A result is attached to the wrong parallel call.
  • A timeout is retried as a new action.
  • The model receives a result for a call it no longer remembers.
  • A loop waits for a result that the orchestrator already delivered.
  • The same side effect executes twice because idempotency cannot be checked.

What happens when a tool-call ID is lost? The system loses correlation. Even if the result content is correct, the orchestration layer may not know which request it answers. Treat the ID as a required state field, not optional metadata.

Google’s current interaction examples use a function-call identifier when sending a function result back with previous_interaction_id. Its stateless mode requires the client to preserve the original user input, all model-generated steps, and the function result in the next request. See Google’s interaction-state documentation.

Another provider’s multi-turn API may expose a different parent-response field and a separate tool-call ID. The exact field names differ by API, so do not copy one provider’s message format into another provider’s adapter. Map them into your own internal event model.

A robust internal event should contain:

{
  "trace_id": "trace_001",
  "interaction_id": "provider_interaction_id",
  "tool_call_id": "call_abc",
  "parent_event_id": "event_014",
  "sequence": 3,
  "provider": "provider-name",
  "schema_version": "tool.input.v2"
}

Your provider adapter can translate these fields into the platform-specific request shape while keeping observability consistent.

MCP result handling requires a compatibility layer

Model Context Protocol adds another contract layer. An MCP server exposes tools with an inputSchema, may expose an outputSchema, and returns tool results through protocol-defined content fields.

The important distinction is between:

  • Text content — human-readable or backward-compatible output.
  • Structured content — machine-readable JSON object for clients.
  • Output schema — the declared structure the structured result should follow.
  • Error status — whether the tool completed with an application-level error.

The official MCP tools specification defines structuredContent for structured results and recommends also returning serialized JSON in a text content block for backward compatibility. If an output schema is provided, the server must produce structured results that conform to it, while clients should validate them.

How should an MCP tool result be returned to the model? The MCP client should preserve the protocol result, validate structuredContent against outputSchema when present, retain compatible text content, and then translate the result into the model provider’s expected tool-result format.

Do not assume every client supports every field equally. A safe result may look like:

{
  "content": [
    {
      "type": "text",
      "text": "{\"build_id\":\"bld_742\",\"status\":\"queued\"}"
    }
  ],
  "structuredContent": {
    "build_id": "bld_742",
    "status": "queued"
  },
  "isError": false
}

If the tool itself encounters a domain failure, MCP defines isError for the tool result. Tool-originated errors should be reported inside the result so the model can see the failure and potentially self-correct. Protocol-level errors are reserved for cases such as an unknown tool or unsupported operation. See the MCP schema reference.

This boundary prevents a frequent integration bug: the server returns valid structured data, but the client only forwards an unparsed text string; or the client expects structuredContent even though the server only provides text.

Build a unified event log before changing prompts

When multiple providers, MCP servers, remote APIs, and macOS tools are involved, prompt edits alone will not give you a reliable diagnosis. You need a cross-layer event log.

Record these fields for every attempted action:

  • Request and response timestamps.
  • Provider and model identifier.
  • Tool catalog or candidate-tool hash.
  • Input schema version.
  • Raw model proposal, with secrets redacted.
  • Parsed arguments.
  • Schema validation result.
  • Tool-selection decision.
  • Authentication and authorization decision.
  • API request ID or executor job ID.
  • Tool-call ID and parent interaction ID.
  • MCP outputSchema validation result.
  • Retry count and idempotency key.
  • Final business validation result.
  • Runtime environment, dependency versions, and remote host state.

For Agent workflows that depend on macOS commands, Xcode, simulators, signing tools, or local filesystem state, correlate the Agent trace with the remote Mac environment. A tool may be correct while the host is wrong: the expected SDK may be missing, a keychain may be locked, or a simulator may not exist in that environment.

If you need a temporary and isolated macOS environment to reproduce a cross-layer failure, review Mac rental use cases for development and testing. Keep the application trace and host-state trace under the same trace_id; otherwise you will see the API error without knowing what the executor actually found.

A decision checklist for production triage

Use this checklist in order. Stop at the first failed gate and fix that layer before changing the next one.

  • [ ] Capture the raw model output before normalization.
  • [ ] Confirm the payload parses as JSON.
  • [ ] Validate the payload against the provider-compatible input schema.
  • [ ] Record the schema name and version used for that turn.
  • [ ] Confirm that the selected tool matches the user’s requested action.
  • [ ] Confirm that only relevant candidate tools were exposed.
  • [ ] Check authentication, authorization, environment, and resource state.
  • [ ] Return a structured execution error with code, layer, and retryable.
  • [ ] Preserve the provider’s tool-call ID across every adapter.
  • [ ] Match every result to its parent interaction and call ID.
  • [ ] For parallel calls, store results by ID rather than arrival order.
  • [ ] Validate MCP structuredContent against outputSchema when available.
  • [ ] Preserve compatible text content for clients that do not consume structured fields.
  • [ ] Run business validation after technical execution succeeds.
  • [ ] Attach all events to one trace and record the runtime environment.

This sequence prevents the most expensive mistake in Agent operations: tightening JSON types when the actual problem is authorization, state correlation, or an incompatible MCP result.

When a remote Mac environment is the better troubleshooting choice

Your current setup may be a local Mac shared by several developers, a Windows or Linux host reached through multiple remote layers, or a cloud VM with incomplete macOS dependencies. Those approaches can work for ordinary API testing, but they often create three operational weaknesses: environment drift, hidden local state, and poor reproduction of macOS-specific tools.

A dedicated remote Mac environment gives you a cleaner comparison point when you need to reproduce signing, simulator, filesystem, shell, or CI-related Agent actions. You can pin the dependency set, isolate logs, and rerun the same call chain without asking whether another user changed the host.

That does not mean renting is always the right answer. Buy or maintain your own Mac when you need continuous heavy workloads, permanent physical access, attached hardware, or long-term predictable utilization. For temporary debugging, cross-provider testing, and isolated macOS reproduction, however, a Kvmzen rental can be easier to control than a shared workstation or a non-macOS cloud substitute. You can start with the Kvmzen Mac rental overview and contact the team through the Kvmzen support page if your test requires a specific remote execution setup.

Limited-time offer

More than a Mac — your development base in the cloud

Dedicated compute · Global nodes · Monthly subscription · No hardware to buy

Back to home
Limited-time offer View plans