Topic: AI agents

MCP Tool Results: Decisions, Not API Dumps

An MCP tool should return decision-ready summaries, verifiable fields, and a path to drill down—not a raw REST response poured into the context window.

The easiest MCP server mistake is not a broken transport or a missing required field. It is wrapping an existing API response and returning it unchanged as a tool result.

That feels faithful. It is usually lazy. The model needs to decide which three records need attention, but it receives a huge nested JSON document. It needs to confirm one state, but first has to guess which field is current. A successful tool call does not make an agent reliable.

My rule is short: a tool result should provide context needed for a decision, not pull an entire backend drawer into the model.

Animated meme (expand/collapse)
A result needs decision-relevant context and a path to drill down; it does not need to pour in an unbounded raw response. · Source: GIPHY

An API response is not an agent’s unit of work

Traditional APIs often serve a human frontend or another program. A complete object, nested relations, presentation fields, audit metadata, and next-page data can all be reasonable there.

An agent rarely needs to own that object. It needs to make the next decision:

  • identify three anomalies for human review;
  • compare two configurations;
  • decide whether a retry is safe;
  • explain what information is still missing.

So renaming GET /orders/:id to get_order is not tool design. Write down the agent’s job first, then return the data that supports that job. A recent implementation discussion on Reddit makes the same practical point: if the model needs one field, an entire JSON payload pollutes later context.

Answer first, then leave handles for verification

For a read-only tool, I prefer four result layers:

  1. Decision summary: answer, counts, filters, and observedAt.
  2. Small evidence set: stable IDs, states, timestamps, and only needed fields.
  3. Clear boundary: truncation, another page, stale data, or insufficient permissions.
  4. Next step: an opaque cursor, an exact follow-up tool, or an action needing user confirmation.

Suppose an agent must find incidents that need attention. It needs a short ranked list, reasons, query time, and nextCursor—not 500 complete event records:

const result = {
  summary: {
    message: "Found 3 incidents needing human review",
    observedAt: "2026-08-17T11:00:00Z",
    totalMatches: 27,
  },
  incidents: [
    { id: "inc_102", severity: "high", state: "open", updatedAt: "2026-08-17T10:42:00Z" },
    { id: "inc_081", severity: "high", state: "open", updatedAt: "2026-08-17T10:19:00Z" },
    { id: "inc_077", severity: "medium", state: "triage", updatedAt: "2026-08-17T09:58:00Z" },
  ],
  nextCursor: "opaque-cursor",
};

return {
  content: [{ type: "text", text: JSON.stringify(result) }],
  structuredContent: result,
  isError: false,
};

The MCP Tools specification defines structuredContent and an optional outputSchema. When a schema exists, the server must return conforming structured results and clients should validate them. That turns requirements such as “a summary always includes a time” and “each record has an ID and state” into a checkable contract instead of a prompt guess.

A cursor is not decoration; it refuses an information flood

For large results, do not add limit: 1000 and hope the model can read it. Make it clear that the result is a slice, whether more data exists, and which opaque cursor belongs in the next request.

MCP’s pagination documentation defines cursors for listing operations such as tools/list, resources/list, and prompts/list; clients must not parse or modify them. That means a business-query tool does not automatically get universal pagination: design cursor and limit in its own input schema, and state the truncation semantics in its result.

Animated meme (expand/collapse)
Sending every record to the model is not transparency when it drowns the signal needed for the next decision. · Source: GIPHY

In practice, a query tool can use three boring, useful arguments:

{
  query: "failed deploys since yesterday",
  limit: 10,
  cursor: "opaque-cursor-or-omitted"
}

limit is a ceiling, not a promise. A cursor is a server-issued capability token, not a page number. When details matter, let the agent call get_incident with a stable ID. That can mean one more tool call, but avoids carrying irrelevant data through every context turn.

Errors must make the next action correct

For a failed read, "Error" is the worst possible response. The model cannot know whether to narrow a query, wait, ask for access, or stop.

MCP distinguishes protocol errors from tool-execution errors. The latter belong in a result with isError: true, so the model can see and correct them. The specification also requires servers to validate inputs, enforce access control and rate limits, and sanitize outputs; clients should validate results before passing them to the LLM.

An error result should minimally include:

  • a safe public code such as RATE_LIMITED, SCOPE_REQUIRED, or CURSOR_EXPIRED;
  • whether retrying is safe and how long to wait;
  • a safe next action instead of an internal stack trace;
  • explicit user confirmation for sensitive operations.

That is not extra copy. It is the shortest path for an agent to recover without leaking internal data into context.

When should intermediate data stay outside the model?

A small, single-step lookup does not need an execution sandbox merely to sound like “context engineering.” A compact complete result is more reliable than an extra orchestration layer.

But when work needs substantial filtering, aggregation, sorting, or three or more dependent calls, process intermediate data in controlled code and return an explainable final conclusion. Anthropic’s advanced tool use guidance draws a useful boundary: large intermediate datasets, repeated queries, and parallel work suit programmatic handling; simple lookups or tasks where the model must inspect every intermediate item do not.

This does not mean every MCP server should copy a vendor feature. The principle is simpler: give deterministic data processing to verifiable code; let the model see the part that needs reasoning and explanation.

Five questions before publishing a result contract

  1. What exact next decision must the agent make, and does the result support it directly?
  2. Can an outputSchema validate it while TextContent carries serialized JSON for compatibility?
  3. Do large results have a clear limit, truncation signal, and opaque cursor?
  4. Does every evidence item have a stable ID, time, and source rather than an impressive but unverifiable shape?
  5. Do errors, permissions, and side effects tell the agent when to stop, retry, or ask for confirmation?

If those answers are clear, the tool reduces guesses. Otherwise, it is only an API endpoint moved into a conversation window, with data volume, permissions, and error handling left to model luck.


External references