Skip to content

feat(antd-mcp): pointer tools - #342

Merged
Nic-dorman merged 4 commits into
mainfrom
nic/v2-1568-pointers-mcp
Oct 19, 2026
Merged

Nic-dorman merged 4 commits into
mainfrom
nic/v2-1568-pointers-mcp

Conversation

@Nic-dorman

@Nic-dorman Nic-dorman commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Part of the pointer API fan-out. Stacked on #335; review that first.

feat(antd-mcp): pointer tools

The MCP server gains ten tools over the antd SDK's pointer API, registered
next to the existing 19:

  • reads, all free: pointer_keys, pointer_get, pointer_resolve,
    pointer_finality, pointer_controller
  • daemon-wallet writes: pointer_create, pointer_update, pointer_transfer
  • external signer, mirroring prepare_chunk_upload / finalize_chunk_upload:
    pointer_prepare and pointer_finalize

Every tool returns a JSON dict like the others. Counters go out as decimal
strings, never JSON numbers: a final pointer sits at 2^64-1, which clients
that read numbers as doubles, and LLMs, round. The tool descriptions say so.
pointer_get leaves out the owner's 1,952-byte public key unless asked
(include_owner_key), so a read does not spend about 3,900 characters of an
agent's context.

The writes keep the posture of store_data, upload_file and chunk_put: no
confirmation step, no cost pre-check, no retry. Their descriptions now say
that each call pays from the daemon wallet and that every repeat pays again.
A transfer is irreversible, so pointer_transfer goes further. Its
description opens with IRREVERSIBLE and it is annotated destructive and not
idempotent. Unless confirm_irreversible is true, it sends and pays nothing
and returns CONFIRMATION_REQUIRED, with a message naming both addresses.
Invalid kind or op strings are refused before anything is sent, as
payment_mode strings already are.

errors.format_error maps the pointer-era errors by exact type:
PointerFinalError to POINTER_FINAL, PointerForkedError to POINTER_FORKED
(not the VERSION_CONFLICT of its ForkError parent), ConflictError to
CONFLICT and CloseGroupShortfallError to CLOSE_GROUP_SHORTFALL with a
retryable flag. Each carries a hint saying what to do next. A retryable
shortfall reads "Retry the finalize, the payment was kept" and says to call
pointer_finalize again with the same upload_id. A non-retryable one says
not to repeat a paid write automatically. ALREADY_EXISTS on create is
unchanged.

The tools need an antd SDK with the pointer API, which is newer than 0.2.0.
The imports are guarded the same way as PartialUploadError's, and the tools
are registered only when the SDK's AsyncRestClient has pointer_get. Against
antd 0.2.0 from PyPI the server imports and lists its 19 tools as before,
and a 409 still reads as ALREADY_EXISTS. The dependency pin is unchanged;
pyproject notes what the pointer tools need.

Tests: 50 new tests, 94 in all, passing on mcp 2.3.0 and on the mcp 2.0.0
floor, with antd-py[rest] and with [all]:

  • every tool over a mocked client, including argument forwarding, the typed
    kind and op, counters as strings (u64::MAX included), every finality shape
    (open, final, forked, unknown) and the transfer refusal without
    confirmation
  • error mapping on mocked errors, and through the real AsyncRestClient over
    an httpx MockTransport for the 409 codes, a shortfall's retryable and a
    malformed counter, which reads as INTERNAL_ERROR
  • the tool registry and annotations
  • an import with the pointer API stripped from the SDK, which serves 19
    tools

Live smoke on the dev2 devnet daemon, through the in-memory MCP client
(lifespan, argument validation and ctx injection included):

  • create and update on one owner gave counters "0" and "1". Get, resolve
    and finality ("open", counter "1") followed, and retrieve_data of the
    resolved target returned the second text.
  • A second owner was created and transferred to the first. Without
    confirmation the call was refused and the pointer stayed untouched. With
    confirmation it went final 7/7, reading counter "18446744073709551615"
    with transferred_to set. Controller and resolve followed it.
  • Updating or transferring it again returned POINTER_FINAL with its hint.
  • pointer_prepare quoted an update (counter "2", wave-batch payments), and
    a write during it returned CONFLICT. An unknown upload_id finalized to
    NOT_FOUND.

fix(antd-mcp): count the pointer tools in CI and word the shortfall hint for any store

The pointer tools take the server from 19 to 29 tools, and python-ci.yml's
registration step asserted exactly 19, so it would fail on this branch. It
now expects 29, message included.

The hint for a non-retryable CLOSE_GROUP_SHORTFALL told the agent to "check
the pointer" before paying again. The code also comes from a data or chunk
store whose close group fell short, where that reads wrong. The pointer
advice is now scoped ("For a pointer, check it with pointer_get or
pointer_finality first"). The rest keeps its meaning: a read is free to
retry, a paid write is not repeated automatically, and the user is asked
before paying again.

Tests: the non-retryable shortfall test asserts the scoped wording, and a new
test checks a chunk_put shortfall gets the same code and hint without the
pointer phrasing. 95 pass, in a fresh venv built with python-ci.yml's
install recipe (-e ../antd-py[rest] -e .[test]) and in the antd-py[all] dev
venv. The registration step, run verbatim from the workflow, reports 29
tools.

Version bumps happen at release time; nothing here bumps a package version.

🤖 Generated with Claude Code

Nic-dorman and others added 2 commits October 15, 2026 12:23
The MCP server gains ten tools over the antd SDK's pointer API, registered
next to the existing 19:
- reads, all free: pointer_keys, pointer_get, pointer_resolve,
  pointer_finality, pointer_controller
- daemon-wallet writes: pointer_create, pointer_update, pointer_transfer
- external signer, mirroring prepare_chunk_upload / finalize_chunk_upload:
  pointer_prepare and pointer_finalize

Every tool returns a JSON dict like the others. Counters go out as decimal
strings, never JSON numbers: a final pointer sits at 2^64-1, which clients
that read numbers as doubles, and LLMs, round. The tool descriptions say so.
pointer_get leaves out the owner's 1,952-byte public key unless asked
(include_owner_key), so a read does not spend about 3,900 characters of an
agent's context.

The writes keep the posture of store_data, upload_file and chunk_put: no
confirmation step, no cost pre-check, no retry. Their descriptions now say
that each call pays from the daemon wallet and that every repeat pays again.
A transfer is irreversible, so pointer_transfer goes further. Its
description opens with IRREVERSIBLE and it is annotated destructive and not
idempotent. Unless confirm_irreversible is true, it sends and pays nothing
and returns CONFIRMATION_REQUIRED, with a message naming both addresses.
Invalid kind or op strings are refused before anything is sent, as
payment_mode strings already are.

errors.format_error maps the pointer-era errors by exact type:
PointerFinalError to POINTER_FINAL, PointerForkedError to POINTER_FORKED
(not the VERSION_CONFLICT of its ForkError parent), ConflictError to
CONFLICT and CloseGroupShortfallError to CLOSE_GROUP_SHORTFALL with a
retryable flag. Each carries a hint saying what to do next. A retryable
shortfall reads "Retry the finalize, the payment was kept" and says to call
pointer_finalize again with the same upload_id. A non-retryable one says
not to repeat a paid write automatically. ALREADY_EXISTS on create is
unchanged.

The tools need an antd SDK with the pointer API, which is newer than 0.2.0.
The imports are guarded the same way as PartialUploadError's, and the tools
are registered only when the SDK's AsyncRestClient has pointer_get. Against
antd 0.2.0 from PyPI the server imports and lists its 19 tools as before,
and a 409 still reads as ALREADY_EXISTS. The dependency pin is unchanged;
pyproject notes what the pointer tools need.

Tests: 50 new tests, 94 in all, passing on mcp 2.3.0 and on the mcp 2.0.0
floor, with antd-py[rest] and with [all]:
- every tool over a mocked client, including argument forwarding, the typed
  kind and op, counters as strings (u64::MAX included), every finality shape
  (open, final, forked, unknown) and the transfer refusal without
  confirmation
- error mapping on mocked errors, and through the real AsyncRestClient over
  an httpx MockTransport for the 409 codes, a shortfall's retryable and a
  malformed counter, which reads as INTERNAL_ERROR
- the tool registry and annotations
- an import with the pointer API stripped from the SDK, which serves 19
  tools

Live smoke on the dev2 devnet daemon, through the in-memory MCP client
(lifespan, argument validation and ctx injection included):
- create and update on one owner gave counters "0" and "1". Get, resolve
  and finality ("open", counter "1") followed, and retrieve_data of the
  resolved target returned the second text.
- A second owner was created and transferred to the first. Without
  confirmation the call was refused and the pointer stayed untouched. With
  confirmation it went final 7/7, reading counter "18446744073709551615"
  with transferred_to set. Controller and resolve followed it.
- Updating or transferring it again returned POINTER_FINAL with its hint.
- pointer_prepare quoted an update (counter "2", wave-batch payments), and
  a write during it returned CONFLICT. An unknown upload_id finalized to
  NOT_FOUND.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int for any store

The pointer tools take the server from 19 to 29 tools, and python-ci.yml's
registration step asserted exactly 19, so it would fail on this branch. It
now expects 29, message included.

The hint for a non-retryable CLOSE_GROUP_SHORTFALL told the agent to "check
the pointer" before paying again. The code also comes from a data or chunk
store whose close group fell short, where that reads wrong. The pointer
advice is now scoped ("For a pointer, check it with pointer_get or
pointer_finality first"). The rest keeps its meaning: a read is free to
retry, a paid write is not repeated automatically, and the user is asked
before paying again.

Tests: the non-retryable shortfall test asserts the scoped wording, and a new
test checks a chunk_put shortfall gets the same code and hint without the
pointer phrasing. 95 pass, in a fresh venv built with python-ci.yml's
install recipe (-e ../antd-py[rest] -e .[test]) and in the antd-py[all] dev
venv. The registration step, run verbatim from the workflow, reports 29
tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman
Nic-dorman marked this pull request as ready for review October 16, 2026 09:07

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — incorrect payment guarantee on pointer CONFLICT

Reviewed head: b4a70f9f7a9413401d84ff20d12fbe9f419b6358. Frozen base: 8a4938650722e93f27fb504bd2428884ed0ea601. Head and base were rechecked before publication.

P2 — do not tell an agent that every conflict was unpaid

antd-mcp/src/antd_mcp/errors.py:78–82 says Nothing was paid both when a prepared write blocks admission and when this update loses to a newer state. antd-mcp/README.md:213 repeats the guarantee.

The latter path can return CONFLICT after payment. An agent can therefore receive false cost information before deciding whether to submit another paid update. The hint already says to read first, and it does not retry automatically; these are mitigations, not proof of no charge.

Minimum fix: remove the unconditional no-payment guarantee from the hint and README. Distinguish the unpaid admission conflict from a superseded write that may already be paid. Keep the read-before-write advice. Add an offline regression for a moved-while-in-flight CONFLICT which rejects the false no-payment guarantee. Do not replace it with an unconditional paid guarantee. The companion Python docstring is reported separately on #335.

Confirmed causal path

  • At the pinned ant-core revision, pointer_put pays before storing. Its paid storage path can then return the newer-state conflict, via pointer_put_paid / pointer_put_once (lines 1250–1272 and 1320–1323).
  • In this ant-sdk revision, antd/src/pointers.rs:172–174 maps that specific error to AntdError::Conflict. This daemon source is byte-identical in #335 and #342.
  • A separate admission conflict for a prepared write awaiting payment is genuinely pre-payment. The same error code therefore does not prove that nothing was paid.

Evidence limits and participation

  • Focused two-role panel: 4/6 substantive opinions — 2/3 named models reviewed; provisional team coverage. Qwen 3.8 and DS4 completed correctness and adversarial reviews; all four requested changes. GLM 5.3 returned no complete verdict in either role after one same-model retry. There is no full-team consensus claim.
  • The parent checked the source chain and the exact locations below. Some panel outputs used old path labels (server.py / _rest.py); the actual targets are errors.py / exceptions.py as linked here.
  • No live daemon payment, wallet access or paid E2E was run. The source establishes payment ordering. The offline mock proves only REST parsing and user-visible hint formatting. It does not prove an actual financial loss or a second payment.

Other review coverage and checks

  • The earlier broad source panel completed 8/12 scoped opinions across the two source bundles: GLM 5.3 and Qwen completed both roles in each bundle; DS4 timed out in both roles in each bundle after retry. Those eight initial opinions were APPROVE. The later focused evidence establishes the specific blocker above and supersedes that initial no-blocker conclusion at this seam; the earlier results remain preserved.
  • Parent-run mocked MCP/unit suite: 95 passed, pytest exit 0. The real SDK REST parser plus MCP formatter over httpx.MockTransport reproduced HTTP 409 CONFLICT with the false hint, exit 0.
  • GitHub CI at preflight: 3 SUCCESS. This is not a live-network test.

No merge, deployment, payment or Linear ticket-state change was performed. Re-review the fix at its new exact head.

Nic-dorman and others added 2 commits October 16, 2026 17:28
…ls (#342)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…L went unpaid

ant-core pays for a pointer write before storing it, and the store can
still fail. An update that loses to a newer state while in flight comes
back as CONFLICT after paying, and a pointer that became final in flight is
refused as POINTER_FINAL after paying. The hints said "Nothing was paid" for
both, which could lead an agent to pay for another write on false cost
information.

- CONFLICT's hint now follows the daemon's message: a write refused because
  a prepared write awaits payment says it was refused before paying; an
  update that lost to a newer state says it may already be paid for and to
  ask the user before paying again; anything else claims neither.
- POINTER_FINAL's hint says it is usually refused before paying, but a
  write already paid for keeps that payment.
- README and the tool docstrings say the same.
- Tests: the newer-state CONFLICT must not claim nothing was paid; the
  prepared-write CONFLICT says it was refused before paying; an unrecognised
  CONFLICT message claims neither.

Found by the review of #342.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — MCP pointer tools — payment-warning fix verified

Reviewed #342 at b82fe05be9d93e25a37cd1946275054b65c76acb against frozen base bd87bc809bcfda16535337f6cefb01f653c41823. No material unresolved code defect remains in this reviewed scope. This is not merge authorisation.

Participation

4/6 reviewed — partial panel (2/3 named models). GLM 5.3/OpenRouter (z-ai/glm-5.3) and local Qwen 3.8 (qwen38-27b) completed correctness and adversarial reviews. Qwen receipts are reused only where exact-head/evidence hashes match. Follow-ups reconcile the same model's opinion; they are not extra votes. DS4 correctness and adversarial have no substantive recovered opinion at publication; bounded same-model recovery is separate and is not counted as agreement. This is not full-team consensus.

Findings

The corrected CONFLICT hints distinguish a prepared write awaiting payment (refused before payment), an update overtaken in flight (possibly paid), and an unknown message (no claim that payment was avoided). POINTER_FINAL now warns that an already-paid refused write keeps its payment. Tests cover these cases. Counters remain decimal strings; pointer_transfer requires confirm_irreversible=True; paid-finalize retries reuse the same upload_id. The older-SDK capability gate retains the 19-tool surface.

Executed checks and CI distinction

Offline Python tests passed at this checkout: 97 tests. The pointer-capable SDK floor exposes 29 tools; the legacy 0.2.0 floor exposes 19 tools and preserves HTTP 409 as ALREADY_EXISTS. These are offline/mocked tests, not paid network or wallet tests.

Limits / watch-outs

The hint router depends on two daemon substrings; wording drift falls back to the conservative generic hint. External prepare/finalize delegates transfer consent to the external payer; it is not the same explicit flag as daemon-wallet pointer_transfer. Signed quotes are not exposed by these MCP tools. Keep the #335 SDK dependency/merge ordering; this approval does not approve dependency publication.

Coverage receipts: ant-sdk-review-20261019/342/ready-review-ledger.json. Head/base and live checks are revalidated immediately before submission. No merge, release, deployment or payment was performed.

@Nic-dorman
Nic-dorman changed the base branch from nic/v2-1568-pointers-py to main October 19, 2026 12:16
@Nic-dorman
Nic-dorman merged commit ea4f694 into main Oct 19, 2026
3 checks passed
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.

2 participants