The Tool Description Is the Context

The Tool Description Is the Context

Context layer series

MCP tool schema design is the practice of writing a tool’s name, description, parameters, and return schema so an agent picks the right tool and calls it correctly on the first try. The schema is the one artifact the model reads on every single call, which makes it the most direct output of a context layer. A grounded schema carries real enums, disambiguated identifiers, units, scope, and guardrails, generated from your data rather than written by hand. This post shows where each piece comes from and what changes when you fix only the schema.

The evidence first. We have published three benchmarks this summer comparing our task-specific MCP servers with vendor-native ones for BigQuery, Google Ads and HubSpot. The pattern was the same each time. Fewer tokens, fewer calls, fewer turns, same or better answers.

21,682 vs 708,973
Total tokens
HubSpot run, Nexla task-specific server vs HubSpot native MCP
6 vs 18
Tool calls
Same task, same data, same model
2 vs 10
Agent turns
Both servers reached the correct answer

People read those numbers and take away “task-specific beats general-purpose.” That is true. But it hides the mechanism. Task-specific servers win because the tool definitions carry the meaning the agent would otherwise have to discover by trial and error. The 39,000-token schema the HubSpot server resent on every turn was not context. It was a catalog. The agent still had to figure out which object held deals, which property was the close date, and what “closedwon” meant, and it did that with four discovery calls and 13 SQL queries.

The point of a context layer is not a knowledge graph diagram on an architecture slide. Its most direct output is a tool definition that is right. This post is about how that definition gets built, where each piece of it comes from, and what changes when you fix only the schema and nothing else.

Why do agents get tool calls wrong?

Look at any trace where an agent calls a data tool badly and you find the failure in the tool definition before the first call.

"id": { "type": "string" }

Which id? The CRM account ID, the billing account number, and the canonical customer key are all “id” somewhere.

"status": { "type": "string" }

The real values are six enums. One is deprecated. One triggers a workflow.

"amt": 13756172

No unit, no currency, no note that it is net of discounts.

"description": "Gets deals."

The description restates the function name and tells the agent nothing about scope or when to use it.

"close_date": { "type": "string" }

No format, so the agent tries ISO 8601, then epoch, then “last quarter.”

returns first 100 rows

Pagination is not mentioned, so the agent treats the first page as all rows.

None of these are model failures. The model did what the definition allowed. The definition allowed too much.

The general advice for fixing this exists and it is fine: write rich descriptions, use enums, add constraints, keep tool count low. AWS, Anthropic and half a dozen others have written that post. We are not going to repeat it. The question we care about is different. Where does the content of a good description come from at enterprise scale, and how do you keep it right when you have hundreds of sources and nobody is going to hand write 4,000 tool schemas?

What does a good MCP tool schema look like?

The name says what business question the tool answers, not which table it hits. The description states the purpose, the scope (time window, filters already applied), the units of anything numeric, and any consequence. Parameters use business names, carry enums drawn from real values, mark deprecated values, disambiguate identifiers, and state formats. The return schema names every field and says what it means and what unit it is in. Guardrails (read only, row limits, allowed filters) are declared, not implied.

Compare a typical general-purpose CRM tool with one of the tools from our HubSpot benchmark server.

General-purpose representative of what vendor servers ship
{
  "name": "search_crm_objects",
  "description": "Search CRM objects by type with optional filters and properties.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "objectType": { "type": "string" },
      "filterGroups": { "type": "array" },
      "properties": { "type": "array", "items": { "type": "string" } },
      "limit": { "type": "integer" },
      "after": { "type": "string" }
    },
    "required": ["objectType"]
  }
}
Task-specific from the HubSpot benchmark server
{
  "name": "win_loss_summary_90d",
  "description": "Summarize closed deals over the trailing 90 days: count, total and average amount, and win rate, split by won and lost. Amounts are in USD, net of discounts, from the deal amount property at close. Excludes deals in open stages. Use this first for any win/loss question, then drill in with close_reason_breakdown_90d or deal_size_trend_90d.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "pipeline": {
        "type": "string",
        "enum": ["default", "enterprise", "partner"],
        "description": "HubSpot pipeline. Omit for all pipelines."
      },
      "as_of": {
        "type": "string",
        "format": "date",
        "description": "End of the 90 day window, YYYY-MM-DD. Defaults to today in UTC."
      }
    }
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "closed_deals": { "type": "integer", "description": "Deals with a close date inside the window and stage in {closedwon, closedlost}." },
      "won": { "type": "integer" },
      "lost": { "type": "integer" },
      "win_rate": { "type": "number", "description": "won / closed_deals, 0 to 1." },
      "won_amount_usd": { "type": "number" },
      "lost_amount_usd": { "type": "number" },
      "avg_won_amount_usd": { "type": "number" },
      "avg_lost_amount_usd": { "type": "number" }
    }
  },
  "annotations": { "readOnlyHint": true }
}

The first tool is a search API with a JSON wrapper. The second is an answer with a contract. The agent calling the second one does not discover anything. It already knows what “closed” means, what currency the amounts are in, and where to go next.

Now the real question. Who wrote that description, and how do you write 4,000 of them?

Where does each piece of an MCP tool schema come from?

Nobody wrote it. It was assembled. Every element of a grounded tool schema has a source inside the enterprise, and a context layer’s job is to pull from each source and put the result in the right slot. This is what Helix does behind MCP Studio, and it is the part we have described least, so here it is concretely.

Source
What lands in the tool definition
Schemas and samplesSource schema plus a sample of real rows
Types, nullability, formats, and enums built from observed values, with values that stopped appearing marked deprecated. Turns acct_st in {A, I, P, C} into account_status in {ACTIVE, INACTIVE, PENDING, CLOSED}.
LineageHow fields map across connected systems
Which identifier is canonical (“cust_id_v2, not the CRM account ID”), which fields are nightly copies and therefore read only, and where the source of truth lives.
Business docs and glossariesMetrics docs, wikis, slide decks
Definitions of “win rate,” “active customer,” “closed deal,” tied to the fields and tools they govern. When sales ops changes a definition, the description changes with it.
API specsOpenAPI for the long tail of SaaS sources
Parameter constraints, pagination model, rate limits, and which endpoints mutate state. “Returns at most 200 rows per call, use the cursor.”
Execution logsEvery prior tool call, its parameters and outcome
Guidance lines (“filter by close_date for large windows”), tighter enums, renamed parameters that agents kept getting wrong, and the “use this first, then drill in” ordering.
PolicyAccess rules from the platform
Read-only and destructive annotations, row and column scoping, who can call the tool. The description tells the model. The gateway makes it true.

Schemas and samples give you types, formats, and enums

The connector reads the source schema for types and nullability. That is the easy part. The useful part comes from sampling. Sample 10,000 rows of the deals object and you learn that dealstage has exactly nine distinct values, that two of them have not appeared in eighteen months, that closedate is always midnight UTC, and that amount is never negative. The enum in the tool comes from observed values, not from the API docs, because the API docs say “string.”

Lineage tells you which identifier is canonical and which fields are derived

Every enterprise has four customer IDs. Lineage across the connected systems shows which one the others map to. The same graph shows that arr_usd on the CRM account is a nightly copy from billing, so the tool marks it read only and points at the source of truth. Without lineage the tool definition cannot say either of those things and the agent guesses.

Business documents and glossaries give you definitions

“Win rate” means something specific at your company. Those definitions live in a glossary, a metrics doc, a Confluence page, or a slide deck. Helix ingests them, ties each definition to the fields and tools it governs, and the definition lands in the tool description. It does not wait for someone to notice a change and hand edit forty servers.

API specs give you constraints and hard limits

For the long tail of SaaS sources, the OpenAPI spec is often the only documentation. It carries the parameter constraints, the pagination model, the rate limits, and sometimes the enums. It also tells you which endpoints mutate state, which is how a read-only server knows what to exclude.

Execution logs tell you what actually works

This is the source most people skip. Every tool call an agent makes, with its parameters and its outcome, is data. Over a few hundred runs you learn that filtering by close_date and stage together returns in 200ms while filtering by owner alone times out, that agents keep passing the CRM ID where the canonical ID is expected, and that a particular parameter combination always returns zero rows. Each of those becomes a line in a description, a tighter enum, or a renamed parameter. The tool gets better because it was used, and the improvement is written back into the definition, not left as tribal knowledge in a Slack thread.

Policy gives you the guardrails

Who is allowed to call the tool, which rows they see, which columns are masked, whether the tool is read only, and what happens on a write. These come from the platform’s access policies and land in the tool as annotations and in the gateway as enforcement.

Put those six together and you get the second schema above. Pick any slot in the schema below and it tells you which source filled it.











Source: schema plus samples

The connector sampled the pipeline property across 10,000 deals and found exactly three values in use. The API docs say “string.” The enum comes from what is actually there, and a value that stops appearing gets marked deprecated on the next regeneration.

Source: lineage

Four systems have a customer identifier. Lineage shows the CRM account ID and the billing account number both map to cust_id_v2. The tool names that one and warns the agent away from the other two in the parameter description.

Source: business docs and glossary

The sales ops glossary defines “closed” as stage in {closedwon, closedlost} with a close date inside the window. That sentence is tied to the dealstage and closedate fields, so it lands in the description and in the output field definition. Change the glossary and the description changes.

Source: API spec

The OpenAPI spec says closedate is an ISO date, results page with a cursor, and a page holds at most 200 rows. Those become the format, the pagination note, and the row limit in guardrails. The spec also marks which endpoints write, which is how a read-only server knows what to leave out.

Source: execution logs

Across a few hundred runs, agents that called the summary first answered in two turns. Agents that started with the raw rows tool took six. That pattern becomes one sentence in the description, and the sentence is checked again the next time the logs are reviewed.

Source: policy

The platform’s access policy marks this tool read only and scopes rows by the caller’s team. The annotation tells the model. The gateway enforces it. The model cannot talk its way past a policy it can only read.

The generation runs in one direction and the feedback runs in the other. Watch the flow.

Sources

Schemas, samples, lineage, docs, specs, logs, policy

Helix

Ties each fact to a field and a tool slot

Tool definition

Name, description, params, output, guardrails, version

Agent

Reads it on every call. Its calls become the next logs.

Purple dot: generation, sources to agent. Green dot: execution history flowing back into Helix.

How much context belongs in a tool description?

There is a tension here. If every tool description carries the full data dictionary, you are back to the 39,000-token schema. The description has to carry meaning, not the whole catalog.

The way we handle it is progressive disclosure. The tool description carries what the agent needs to choose the tool and call it correctly: purpose, scope, units, the enums that matter, the identifier disambiguation, the guardrails. Anything deeper (the full column list, the sample values, the lineage graph) is available through the Agentic Probe when the agent asks for it.

Schema tokens sent to the model, HubSpot benchmark

Nexla task-specific
~1,200
HubSpot native, resent every turn
~39,000

Same information reachable. Only the relevant part sent by default. A good test: read the description without seeing the backend and ask whether you, a human analyst, would know what you were about to get. If yes, it is grounded. If you would need to run it once to find out, it is not.

What happens when you fix only the schema

The claim in this post is that the schema carries the win, not the backend. So we tested it that way. We took [TASK FROM THE HUBSPOT OR GOOGLE ADS BENCHMARK] and held the backend fixed. Same connector, same query macros, same data. The only variable was the tool definitions. In one run the agent saw the bare definitions: minimal descriptions, no enums, no units, no output schema. In the other it saw the grounded ones generated as described above.

Metric Bare schema Grounded schema
Tool calls fill in fill in
Agent turns fill in fill in
Total tokens fill in fill in
Correct answer fill in fill in
Clarifying questions asked fill in fill in
Wrong parameter attempts fill in fill in

[Two to three sentences on the result. Expected direction: fewer calls, fewer wrong-parameter attempts, fewer clarifying questions, same or better accuracy, on the same backend. Report honestly if any metric did not move.]

The backend did not get smarter. The agent did not get smarter. The tool told the truth about itself and the agent stopped guessing.

Five mistakes teams make when they generate schemas

Generating definitions from sources beats hand writing them. It also fails in predictable ways. We have made every one of these.

1

Enums from the docs instead of the data

The API doc lists eleven status values. Nine of them have never appeared in your tenant. Ship all eleven and the agent spends turns filtering on values that return nothing. Build enums from observed values, keep the doc list as a fallback for new values.

2

Over describing

If the description is the data dictionary, you are back to the 39,000-token schema, just formatted nicely. A description is for choosing the tool and calling it correctly. Put the rest behind the probe.

3

Generating once

A definition generated in March and never touched is a hand written definition with extra steps. Regeneration has to be tied to source change, which means schema fingerprints, glossary hooks, and a version on every tool. That is the next post.

4

Business names that nobody uses

Renaming acct_st to account_status is right. Renaming it to customer_lifecycle_state_indicator because the glossary says so is wrong. Use the name people say out loud in meetings. Execution logs tell you which names agents and users actually reach for.

5

No output schema

Teams spend all their effort on parameters and return an untyped blob. The agent then has to guess what won_amount_usd means, which is the same failure in the other direction. Every output field gets a name, a type, a unit, and one sentence.

What about tools that write

Everything above is about read tools, because that is what the benchmarks measured. The same six sources produce write tools, with two additions: application validation rules become preconditions, and consequence metadata (what downstream systems react to this field) becomes part of the description and the dry run response. The first post in this series, Read Context Is Solved. Write Context Is Not., walks through one write tool built both ways. The short version: a write tool whose schema does not carry the enum, the precondition, and the consequence is a bug waiting for a retry.

Audit your top twenty tools tomorrow

You do not need a context layer to start. You need an afternoon and your tool list.

  1. Pull the tool definitions from every MCP server your agents use. Sort by call volume. Take the top twenty.
  2. For each tool, run the ten-point review below. Score it out of ten. Do not fix anything yet.
  3. Pull a week of traces for the five lowest scoring tools. Count wrong-parameter attempts and clarifying questions per call.
  4. Fix the worst tool by hand using only your schema, your samples, and your glossary. Rerun the same traces. Measure again.
  5. If the numbers moved, you have proved the claim in this post on your own data. Now decide whether you want to hand fix the other 3,980 tools or generate them.

Most teams that do this find the same thing we did. The fix was never the model. It was a JSON schema that told the truth.

A grounded tool schema template

Copy this. Fill it from your sources, not from your head.

Template every sentence traceable to a source
{
  "name": "<business_question_in_snake_case>",
  "description": "<What question this answers>. <Scope: window, filters, exclusions>. <Units and currency for anything numeric>. <Consequence if it writes>. <When to use it versus its siblings>.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "<param>": {
        "type": "<type>",
        "enum": ["<observed values only>"],
        "format": "<date | date-time | uuid | ...>",
        "description": "<Business meaning. Which identifier. Deprecated values. Default.>"
      }
    },
    "required": ["<only what is truly required>"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "<field>": { "type": "<type>", "description": "<Meaning. Unit. How computed.>" }
    }
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "idempotentHint": true
  },
  "x-context": {
    "source_of_truth": "<system.object.field>",
    "canonical_ids": { "<param>": "<id system>" },
    "definitions": ["<glossary term ids>"],
    "limits": { "max_rows": 200, "pagination": "cursor" },
    "generated_from": ["schema", "samples", "lineage", "glossary", "openapi", "execution_logs"],
    "version": "<semver>"
  }
}

The x-context block is not part of the MCP spec. It is where we record provenance for the schema itself: what fed it and which version it is. That matters later, when the source changes and the tool has to change with it. That is the subject of the next post in this series.

Ten-point review for any data tool

  1. The name states the business question, not the table.
  2. The description gives scope, units, and consequence in the first two sentences.
  3. Every identifier parameter names which identifier it is.
  4. Every finite-valued parameter is an enum built from observed values, with deprecated values marked.
  5. Every date or time parameter has a format and a timezone.
  6. Every numeric output has a unit or currency.
  7. The output schema exists and names every field.
  8. Pagination and row limits are stated.
  9. Read-only or destructive is declared in annotations and enforced at the gateway.
  10. You can name the source of every sentence in the description.

Number ten is the one that separates a hand-written schema from a generated one. If you cannot say where a sentence came from, you cannot keep it true.

MCP Studio Edit tool panel showing the generated description for list_avoma_calls with its source reference avoma_api.list_calls, the backing Nexset, and locked runtime config
MCP Studio’s tool editor. The generated description carries purpose, inputs, filters, and limits, and the panel shows where each piece came from: the source reference (avoma_api.list_calls) and the governed Nexset behind the tool. Runtime config stays locked on existing tools.

Frequently asked questions

What is MCP tool schema design?

It is the practice of writing a tool’s name, description, input parameters, and output schema so an agent selects the right tool and calls it correctly. Good schemas carry business names, real enums, identifier disambiguation, units, formats, and declared guardrails.

What makes a good MCP tool description?

The first two sentences state what question the tool answers, its scope, and the units of anything numeric. Then it says when to use this tool versus its siblings. If a human analyst can read it and know what they are about to get, it is grounded.

Should MCP tool parameters use enums?

Yes, wherever the set of valid values is finite. Build the enum from observed values in your data, not from the API documentation, and mark deprecated values. A parameter typed as string invites the agent to guess.

How many tokens should a tool schema use?

Enough to choose the tool and call it correctly, no more. In our HubSpot benchmark the task-specific server sent about 1,200 schema tokens against roughly 39,000 for the native server, and reached the same answer in a third of the calls. Deeper detail belongs behind on-demand lookup, not in every description.

Can MCP tool schemas be generated automatically?

Yes. Every element has a source: types and enums from schema plus samples, canonical identifiers and derived fields from lineage, definitions from the business glossary, limits from API specs, usage guidance from execution logs, and guardrails from policy. That is what Helix does behind MCP Studio, and it is what keeps 4,000 tool definitions true.

What is progressive disclosure in MCP?

The description carries only what the agent needs to choose and call the tool. Anything deeper, like the full column list or sample values, stays available through a probe the agent queries on demand. Same information reachable, only the relevant part sent by default.

Closing

The benchmarks showed that task-specific servers win. This is why. The tool definition is where enterprise semantics meet the model, and it is the one artifact the model reads on every single call. A context layer that cannot produce a correct tool definition from your schemas, samples, lineage, docs, specs and history is a context layer that leaves the last mile to the model. That mile is where the errors live.

See how MCP Studio generates these for your own sources

Request early access. Bring the tool that your agent keeps calling wrong.

Try out MCP Studio

Read next in this series: Read Context Is Solved. Write Context Is Not., Schema Drift Reaches the Tool Definition, How to Evaluate a Context Layer, and Whose Identity Is Calling? Evidence: the HubSpot benchmark and task-specific vs system-specific MCP servers.


You May Also Like

A Guide to AI Readiness
Intercompany Integration Overview

Join Our Newsletter

Share

Related Blogs

The Data Layer Your AI Is Missing

Connect, contextualize, and govern enterprise
data across 1000+ systems in real time.