Skip to content

14 min read

Conditional approval and reversal triggers

On this page

Being right is not the same as being useful in a design review. A reviewer who refuses an MCP server until a second consumer is actually building against it is reasoning correctly: expressed interest is not a commitment, and shared infrastructure built on intentions is how a platform team ends up operating something with one user. Leaving it there is the lazy version of being right. It costs nothing to type, it hands the author no path from where they are to where they want to be, and it forecloses on a possibility that might be real.

The predictable result is that the proposal comes back next quarter with the same facts, a director's name on it, and a room less willing to relitigate, which is how the same argument gets had three times and won on the third by attrition rather than by evidence. What replaces the refusal is a decision with stages and triggers: what happens now, what would have to become true for the rest to happen, and what would have to become true for it to be undone.

Argue it from the author's own facts

The strongest case against a proposal is built entirely from what the author already wrote down, because facts they supplied are facts they cannot dispute. Take a typical three: two other teams — call them Growth and Ops, since the proposal names them — have said they want the capability, MCP is the company's standard for agent tooling, and the work is scoped at five weeks.

Two teams expressing interest is a statement about two named teams, and naming them makes the claim checkable in an afternoon. Ask each for an owner, a ticket and an integration date. If both produce them, the deploy-boundary test is satisfied and the proposal's central premise is established rather than asserted — which is a better outcome for the author than approval on the strength of a channel message. If neither produces them, the author has learned something about their own case, and the review has produced information instead of a verdict.

Three questions to each interested team turn the interest into something with a size. What would you call it with, and how often — an answer in calls per week is a different proposition from an answer in "when a customer asks". What would you do if it did not exist? The last one is the most informative, because a team that answers "read the catalogue table ourselves, about a day's work" has described a demand that a published package satisfies, while a team that answers "we would have to reimplement the discount ladder and we would get it wrong" has described an ownership problem and has just strengthened the author's case considerably. Asking is cheap, and it moves the review from two reviewers disagreeing about a forecast to two teams describing their own plans.

The company standard cuts against building now, which is the inversion worth walking the room through. A ratified protocol implies a platform: charts, a gateway that already does token verification, a rotation that covers the existing servers. That is precisely the situation in which the fixed operating cost of an additional server is low, and low fixed cost makes deferral cheap, because the wrap you would build in nine months costs about what it costs today. If the protocol were exotic and the first server at the company, there would be a real argument for starting early to absorb the learning curve. Standardisation removes that argument.

Five weeks is a build estimate for the functionality, and it excludes per-consumer scoping and token verification, two days of client onboarding per consumer, hosting, a rotation for a process that does not exist yet, and the support load of teams outside your management chain. Presenting the revised total is not a rhetorical move if you also present the duplicated side honestly, and at two prospective consumers the duplicated side is one existing function plus one integration.

The three axes beside reuse finish the case, and they have to be read separately or the argument collapses back into adjectives. Where the proposing team owns the rules, consumes them, and deploys them, ownership pushes nothing toward a boundary. Where the capability holds no credential the callers must not hold — because its inputs come from data every consuming team can already read — the security column is empty. And change rate is often the fact the proposal supplies without noticing: a capability that changed nine times last quarter, twice in ways that altered the meaning of a returned field, is one whose interface would be renegotiated before the first two consumers finished integrating.

Answering "we will need this shared in a year"

The author's response to all of the above is that you are optimising for today. Reaching for YAGNI — you aren't gonna need it, the rule of thumb that says build for the requirement in front of you rather than the one you predict — is both weak and unfair here, because the author may well be right about the year, and a heuristic is not an answer to a forecast. The answer that works is about what each direction costs if it turns out to be wrong, and about buying the option for less than five weeks.

The cost of being wrong in one direction is that you build a server and the consumers do not arrive, leaving a service with one caller, a support surface, and a deletion conversation that nobody schedules. The cost of being wrong in the other direction is a migration, and the migration is small for a specific structural reason: the server's tool body is the function. What made the server version expensive in the first place was not the wrapping but everything the function got for free from the process it lived in — the tenant on the request context, the caller identity, the database connection already scoped.

Unpacking that is what turns the claim into something a reviewer can check rather than accept. The wrapper hands the same arguments to the same code, so the protocol layer is new code sitting around an unchanged function, and new code around an unchanged function is the cheap kind of work. What is expensive is every input the function currently receives without asking for it, because a remote caller supplies none of them and each therefore has to become an explicit parameter. That conversion is the only part of the migration that reaches inside the function's own logic. The same reasoning applies on the way out: a remote caller cannot tell whether an answer was complete unless the envelope says so, so the response shape has to gain that field as well. Those two changes are the whole of the irreducible work, and neither of them depends on which way the boundary decision goes.

That gives you something concrete to propose, which is where the conversation stops being a refusal:

python
# Before: depends on ambient request context. Retrofitting this is the 5 weeks.
def pricing_explanation(sku: str) -> dict:
    tenant = request_context.tenant          # ambient
    catalogue = db.session                   # ambient, already scoped
    ...

# After: three days of work, and the boundary becomes a wrap rather than a rewrite.
def pricing_explanation(sku: str, *, tenant: TenantId,
                        catalogue: Catalogue) -> PricingExplanation:
    """Explicit scope and dependencies. Returns a versioned envelope with
    `complete` set, so a future remote caller inherits the completeness
    contract rather than needing one bolted on."""

Three days buys most of the option that five weeks was going to buy, and it is worth doing on its own merits, since a function that takes its scope explicitly is easier to test in the meantime. The remaining question is whether to spend the other four and a half weeks now or when a consumer exists, and waiting improves the artefact rather than merely deferring it. An interface designed against one hypothetical consumer is designed against the author's guess about what Growth and Ops will need; an interface designed when Growth has a branch is designed against a requirement.

Doing the arithmetic behind that split out loud is what keeps the staging honest rather than a way of appearing to agree. Take the author's own estimate and subtract the part being funded now: on a five-day week, five weeks is twenty-five days, three of them are spent in Phase 0, and twenty-two are left — four and a half weeks, which is the figure above. Nothing has been argued away in getting there. The estimate is unchanged and so is the work; what has changed is that the part which is worth doing whether or not the server is ever built has been separated from the part that is worth doing only if it is, and only the first part is being paid for today.

The forecast itself can then be made checkable rather than argued about. If the shared need is real, evidence of it appears within a quarter — a ticket, a roadmap line, a branch — and a scheduled look at that evidence is a much stronger commitment to the author than a rejection is. That is the move that converts the disagreement from one about judgement into one about a date, and it is available to a reviewer who genuinely does not know which of the two forecasts is right.

Review outcomeWhat it costs you nowWhat the author getsWhere it lands next quarter
Flat rejectionnothingno pathsame proposal, more political weight
Approve as proposed5 weeks plus the omitted linesa servera service with one consumer and no trigger to remove it
Conditional approvalthree days and a calendar entrya defined path and a dateeither a server with committed consumers, or a closed question

Those rows are not three strengths of one answer — Rejection, approval as proposed and conditional approval fail in three different directions, and only the last of them fails in a direction somebody checks later. The refusal is the tempting one, and it is tempting for a respectable reason: it is usually correct on the facts, it takes one sentence, and nothing that goes wrong afterwards can be attributed to it. What makes it a poor outcome is that correctness and usefulness are being measured against different questions. A refusal is judged against whether the server should be built today, and on that question it is right. A review outcome is judged against what the organisation does next, and a refusal specifies nothing about that at all, which leaves the next step to be decided by whoever brings the proposal back and by how much weight they bring with them.

The reversal half is the part that is usually missing

The three Phase 1 conditions have to hold together rather than in any combination, and the reason is that each one covers a different way the approval could be wrong. A named owner with a ticket and a date rules out interest wearing a commitment's clothes. A branch that calls the capability rules out a ticket filed to satisfy a trigger. And the change-rate clause rules out freezing an interface that is still moving, which is the failure the other two cannot see, since a committed consumer integrating against a shape that changes twice more is a consumer who will spend the next two quarters in dual-serving windows. Any one of the three on its own is a condition somebody can satisfy without the situation having actually changed.

Read as a set, the three license exactly one thing, and it is worth being precise about what it is. Each condition names an artefact — a ticket carrying an owner and a date, a branch, a count of non-additive changes across a quarter — and an artefact either exists or it does not, so somebody who was not in the review can open the record months later and say whether Phase 1 is due without reconstructing anybody's judgement. What the set does not license is a verdict on the forecast. Conditions that have not occurred are silent about whether they will, the author may still turn out to be right about the year, and a record that treats their non-occurrence as a refutation has converted a scheduled check back into the argument it was written to replace.

Attaching a reversal trigger costs almost nothing at approval time and is nearly impossible to add later. A service with one consumer and no removal criterion becomes permanent by default, because deleting it requires somebody to volunteer for a conversation with a team that has integrated. Two design constraints keep the exit open: the server stays thin, so that the capability is never trapped inside it, and no consumer-specific behaviour is allowed in, since the first special case for one consumer is what makes deletion a negotiation rather than a config change.

The asymmetry between those two moments has a mechanism, and it is a question of who has to agree rather than of what the sentence costs to write. At approval the server has no consumers, so a removal criterion is a clause nobody has yet relied on, and the author accepts it because accepting it is how the approval happens. Once the server is running, the identical clause has to be agreed by every team that has integrated, each of whom would be volunteering for migration work, and none of whom was in the room when the trade was made. Nothing about the sentence has changed. The number of people whose consent it requires has, which is why a trigger written at approval is a condition of the decision and the same words proposed a year later are a request.

Triggers also need an owner and a date on a calendar, because a condition nobody is responsible for checking is a sentence in a document. The review date in a record like ADR-207 is a named person's recurring reminder, and the first thing that person does at the review is look at the call volume, which the pre-launch gate made observable. A trigger that depends on a number nobody emits is not a trigger, which is why the observability lines and the reversal lines have to be written in the same sitting.

The same shape applies to a proposal defending a boundary that already exists, and reviewers are much softer on those. An existing server with one consumer generates the identical evidence a new proposal has to produce — call volume by consumer, non-additive changes per quarter, operating days per year, the fault behaviour it has actually demonstrated — and the reason to run the numbers on it is that the answer is sometimes deletion, which is a decision people take far less often than the data supports.

That softness has a cause worth naming rather than deploring. A new proposal is judged against a baseline everybody can see, since the capability still lives in a function and a reviewer can price the alternative by reading it. An existing server is judged against a migration nobody has costed, so its alternative is unpriced and therefore looks expensive by default — which is a difference in what is visible rather than a difference in the facts. The correction is to make the existing boundary produce the same evidence a new one would have had to produce, and to notice that the reviewer who cannot price the alternative is in the same position as the author who could not name a consumer.

Two of the four options remain unexamined throughout all of this. Skills and subagents cover much of the same ground with a different cost structure, they arrive at stage 12, and 12.16 reruns the two-implementation measurement with four columns instead of two; anything asserted about them before that is a forecast rather than a comparison. If you want the outside view in the meantime, Exploring Skills vs MCP Servers is written by someone who built both for the same job and reports where each one paid off, and it is a better preparation for that comparison than another argument about protocols.

A conditional approval is more work than a refusal and it is the only outcome that ends the argument. It gives the author a path, it gives the reviewer the cheaper option they wanted, and it converts a disagreement about a forecast into a set of conditions that either occur or do not. The three days of Phase 0 are the part that makes this possible at all: an option bought at three days can be left unexercised without anybody having wasted five weeks, and a function that takes its scope explicitly is better code whether or not the server is ever built.

Between now and stage 12 the material in this topic is a review procedure rather than a position on MCP. Build the capability both ways when the decision is close enough to be worth an afternoon, count the deploy boundaries rather than the callers, read ownership, change rate and trust separately because they disagree, write the five fields with facts that could have come out the other way, model fixed against variable over a horizon somebody will still be around for, and prove the four faults before launch. Then attach the triggers in both directions, so the answer you gave has an expiry date and the argument does not have to be had again from the beginning.

When MCP is the wrong answer — read the current version at https://daviddominguez.dev/learn/en/when-mcp-is-the-wrong-answer/conditional-approval-and-reversal-triggers

Back to top