Repository navigation
feat(antd-mcp): pointer tools - #342
Conversation
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>
dirvine
left a comment
There was a problem hiding this comment.
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_putpays before storing. Its paid storage path can then return the newer-state conflict, viapointer_put_paid/pointer_put_once(lines 1250–1272 and 1320–1323). - In this ant-sdk revision,
antd/src/pointers.rs:172–174maps that specific error toAntdError::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 areerrors.py/exceptions.pyas 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.MockTransportreproduced 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.
…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
left a comment
There was a problem hiding this comment.
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.
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:
pointer_finality, pointer_controller
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]:
kind and op, counters as strings (u64::MAX included), every finality shape
(open, final, forked, unknown) and the transfer refusal without
confirmation
an httpx MockTransport for the 409 codes, a shortfall's retryable and a
malformed counter, which reads as INTERNAL_ERROR
tools
Live smoke on the dev2 devnet daemon, through the in-memory MCP client
(lifespan, argument validation and ctx injection included):
and finality ("open", counter "1") followed, and retrieve_data of the
resolved target returned the second text.
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.
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