Skip to content

Build1 publisher3 min readPublished

Keeping one tool description in two files forked this MCP contract in two days

In a dev.to sample the same restaurant search tool is described once in a C# attribute and twice in Bicep, and two of the copies already disagree after two days of editing. The author's argument is that the description is the API contract now.

The Engineer · Build desk

Illustration accompanying Keeping one tool description in two files forked this MCP contract in two days

What happened

  • A dev.to sample restaurant backend describes the same search behaviour in prose three times: in a C# McpToolTrigger attribute, in the APIM REST door's Bicep, and in the Bicep for a gateway-manufactured MCP server.
  • Two of those copies, written two days apart, already differ: "Both filters are optional" became "Both cuisine and city filters are optional", and "call it without" became "call without".
  • A commenter on the series warned about granularity drift in tool definitions and asked how anyone validates tool descriptions at all.
  • Auth and idempotency sit in the shared service that both doors call, so neither door can drift on what is allowed or on what happens when a call is retried.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost Description prose is a recurring cost, not a one-time one: whoever pays the inference bill funds the writing style of whoever authored the tool, on every call, for as long as the server runs.
  • constraint When a description forks, the symptom is an agent picking the wrong tool or guessing a parameter it was told not to guess, and that shows up in production traffic instead of in review.
  • decision The gateway-manufactured MCP door creates a second copy of every description by design, so a team adopting that pattern has to name one file as the owner of the sentence.
  • contradiction The reader's test asks both doors to preserve the same error behaviour, and the sample keeps two error dialects while counting itself as passing, so parity has to be judged at the decision the shared service makes.

The post rates one kind of sentence above the rest: say where a value comes from, as in "the restaurant id, as returned by search_restaurants (for example 'r1')" [17]. The JSON schema for that field says restaurantId is a required string [18]. Any string satisfies that, including one the model made up. Only the prose says the value has to come from a previous search_restaurants call and must not be guessed [18]. The author puts that at the top of the contract, calling it the difference between an agent that chains tools and one that hallucinates identifiers [19].

The drift happened in a three-tool repo maintained by one person for one week [10]. The REST door tolerates prose drift because its real contract sits somewhere a tool can read: routes, status codes, an OpenAPI document that can be diffed [12]. The MCP doors have no second artifact, so the description is the interface [12], and the author's claim is that the contract forks the moment it exists in two files [11].

The cost side comes from an earlier measurement in the same series: about 111 tokens for a realistic description and 208 for a verbose one, paid on every model call [13]. That is 97 extra tokens per tool per call at the verbose tier [15]. At the fifty tools the post uses in its scaling example [14], the realistic tier costs 5,550 tokens of description per call and the verbose tier 10,400, a gap of 4,850 [16]. Those figures transfer only if your descriptions run about as long as the sample's and your whole tool list goes into every call. The author's spending rule is that the verbose tier doubles the bill mostly with ceremony the model would infer anyway, so pay for disambiguation and provenance and cut the rest [20].

The two doors report a bad restaurant id differently on purpose. The REST door returns a 4xx status with a small JSON body, because its caller is code and code branches on status [22]. The MCP door returns "Restaurant '{restaurantId}' was not found. Use search_restaurants first to get a valid id." [23]. The shared service is what decides the id is bad [25].

The commenter's question about how anyone validates tool descriptions is still open [3]. The post does not propose a check. Its three rules are at least scoreable: does the sentence distinguish the tool from its neighbours, does it state optionality, does it state provenance [17]. In my view the cheap half is deduplication, with one exported string that both doors consume, so a wording change is one edit. Whether the sentence steers the model to the right tool is a separate question, and answering it takes an eval.

What to watch

  • Whether the Functions or APIM tooling gains a way to diff tool descriptions across doors, which would turn this from a review habit into a build failure.
  • Whether the series publishes token measurements for the gateway-manufactured MCP door, which carries its own copy of every description.
  • Whether anyone answers the commenter's validation question with a scoring harness for description quality.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories