Skip to content

feat(mcp): pre-label plan parity - #704

Merged
JArmandoAnaya merged 4 commits into
mainfrom
feat/mcp-prelabel-plan
Aug 19, 2026
Merged

feat(mcp): pre-label plan parity#704
JArmandoAnaya merged 4 commits into
mainfrom
feat/mcp-prelabel-plan

Conversation

@JArmandoAnaya

@JArmandoAnaya JArmandoAnaya commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

An agent could not see a pre-labeling run's prompt at all. A run asks only about the classes a bare
box prediction can be written as, and the work that made that narrowing visible in the browser, over
HTTP and at a terminal (#703) left the MCP surface out. pre_label_batch blocks for minutes — one
forward pass per untouched asset — and answered seven counters, none of which say which classes it
asked about, so an agent reading assets_labeled: 0 against a schema mixing boxes and polygons could
not tell a narrow prompt from a model that found nothing. That silence is worse here than in a
browser, because the wait is longer and the caller is the one least able to go and read the schema
itself.

get_pre_label_plan is a read that answers the prompt and everything left out of it, with no
connection involved, so an agent can decide whether the wait is worth it before committing to it.
It refuses on the run's own terms, so both tools give one answer rather than an empty list from one
and a refusal from the other. pre_label_batch's result now carries the same shape under plan,
captured from the run's own callback rather than derived beside the call — that the reported prompt
is the one the run actually used is the whole reason for reporting it.

The payload is a projection in visionset.wire rather than a dict written out in visionset.mcp.
That package exists because a second hand-written spelling of a shape the REST API already publishes
is what "promoted, not copied" is meant to prevent, and registering the projection in
tests/cli/test_json_contract.py's pair list puts a machine gate under the claim that the MCP result
and PreLabelPlanOut agree key for key. Prose alone would have let a rename on either side ship in
silence. Carrying the pinned version on PreLabelPlan itself is what makes that projection possible:
the run's outcome has no other honest way to name the version its prompt came from.

Two names differ from the first draft. The tool is get_pre_label_plan rather than pre_label_plan,
because beside pre_label_batch the shorter name parses as a verb phrase and reads as two things a
run does rather than a read and a write. The result key is plan rather than prompt, because it
holds excluded_classes, which by definition are not in the prompt. The FastAPI route function keeps
its own name: that name is the published operationId, so changing it would move openapi.json and
the generated client for a cosmetic gain.

PreLabelPlanOut's published field set is unchanged, and openapi.json and
frontend/ui-core/src/generated/ are byte-identical to main.

Found, not fixed

Issue-number references in prose in docs/batches.md, docs/mcp.md and a comment in
tests/mcp/test_batch_tools.py predate this branch and are left alone.

Test plan

bash scripts/check.sh run by group on the rebased branch, every group exiting 0:

check.sh python      3986 passed, 24 skipped in 33.17s
                     ruff (lint), ruff (format), mypy, import contracts all clean
check.sh frontend    annotator 1062 passed (37 files); ui-core 1155 passed (53 files)
check.sh generated   frontend/ui-core/src/generated/api.ts matches openapi.json.
                     frontend/ui-core/src/generated/checks.ts matches openapi.json.
                     docs/mcp-tools.md matches the served tool listing.
check.sh docs        All checks passed.
check.sh browser     annotator + app e2e: 278 passed (40.6s)
                     browser cycle, real server: 1 passed (25.2s)

The branch was rebased onto main after #703 was squash-merged, which is what resolved the
conflict: the squash replaced four commits with one, so the originals had to be dropped rather than
replayed. The rebase applied with no conflicts, and the resulting tree is byte-identical to the
reviewed state.

#677 was closed by #703, not by this pull request.

Base automatically changed from feat/prelabel-prompt-classes to main August 19, 2026 07:09
pre_label_batch blocks for minutes and reports counters, none of which say
which classes it asked about, so an agent reading assets_labeled 0 has
nothing to reason from. pre_label_plan is the read that decides whether the
wait is worth it: the prompt, and every class left out of it with every
reason that holds.

No connection, because the prompt is a property of the pinned schema alone.
A run that asked about two of a schema's five classes labels nothing under
the other three, and the counters cannot say so — assets_labeled 0 reads
identically to a model that found nothing. The result now carries the plan
beside them.

Captured from pre_label's on_plan rather than derived beside the call, so
the reported prompt is the one the run actually used.
…reads

`PreLabelPlan` now carries the schema version both its halves were derived
from, which lets `visionset.wire` publish the whole shape: the hand-written
payload in `mcp/batches.py` is gone, the read tool and the run's result both
project through `wire.pre_label_plan`, and the json-contract gate holds it
key-for-key against `PreLabelPlanOut`. The published REST shape is unchanged.

The read tool is `get_pre_label_plan`, since beside `pre_label_batch` the old
name parsed as a second thing a run does rather than a read. The run's result
names its plan `plan`, because the container holds `excluded_classes`, which
are by definition not in the prompt.

`docs/batches.md` names the tool, stops claiming the narrowing is invisible in
every run's outcome, and lists the GET route beside the POST.
@JArmandoAnaya
JArmandoAnaya force-pushed the feat/mcp-prelabel-plan branch from 44c2418 to c5b7930 Compare August 19, 2026 07:14
@JArmandoAnaya
JArmandoAnaya merged commit 15fc76a into main Aug 19, 2026
15 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/mcp-prelabel-plan branch August 19, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant