Skip to content

DAN-872: mcp-demo settles over gRPC again — Sui deprecated JSON-RPC on public fullnodes - #7

Open
Danny-Devs wants to merge 5 commits into
mainfrom
agent-a/DAN-872-mcp-demo-grpc-migration
Open

DAN-872: mcp-demo settles over gRPC again — Sui deprecated JSON-RPC on public fullnodes#7
Danny-Devs wants to merge 5 commits into
mainfrom
agent-a/DAN-872-mcp-demo-grpc-migration

Conversation

@Danny-Devs

Copy link
Copy Markdown
Contributor

What this fixes

mcp-demo — the runnable artifact a reviewer reaches for after the README — was dead at the network layer. Sui deprecated JSON-RPC on public fullnodes; every core method answers -32601 on testnet and mainnet alike. Both the agent and the server constructed a SuiClient over that transport.

Stacked on #6. It has to be: the blocker notices this PR removes were added there and do not exist on main. Merge #6 first.

Verified end-to-end, not compiled

The done-bar asks for an observed payment, so:

agent settles       AwKhqdQazAxNdu6d9unPMj7N6wAaAVP5y5G8ENhsy5F
effects.status      success
balanceChanges      receiver +10000000   sender -11997880  (amount + gas)
server verifies     tool result returned
negative control    bogus digest rejected with -32402

The negative control is load-bearing. An earlier run passed against a stale envelope-only server, whose verifier returns true without touching the chain — a false green that looked identical to success. The gate was re-proven able to fail before this was called done.

This needed an SDK major, and that is the part worth knowing

mcp-demo declared ^1.18.0, which had resolved to 1.45.2. On 1.x this migration is not possible, not merely awkward:

Attempt on 1.45.2 Result
core.executeTransaction INVALID_ARGUMENT: invalid read_mask path: transaction.transaction
tx.build({ client: grpc }) Transaction resolution is not supported with the GRPC client
tx.build({ client: graphql }) GraphQL client does not support transaction resolution yet

Reads work on 1.x; settlement cannot. All three clear on 2.24.0.

The endpoint did not change

gRPC is served from the same host and port as the dead JSON-RPC. No config, env var or README URL needed updating. Worth saying plainly, because "migrate to gRPC" reads like an endpoint move and it is not.

Four shape changes that type-check and then misbehave

  1. balanceChanges is present-but-undefined unless requested via include. Omit it and the credit check runs over undefined, returns false, and the demo rejects every real payment — failing closed, which is the safe direction and therefore the one nobody notices.
  2. Responses are protobuf-wrapped, a union discriminated on $kind; the payload is at .Transaction, and the failure arm carries it under a different key.
  3. Balance changes are flat {coinType, address, amount}; JSON-RPC nested the recipient under owner.AddressOwner.
  4. Status is a boolean discriminant, not status.status === 'success'.

A behavioural fix that was not in the ticket

getTransaction returns NOT_FOUND immediately after an execute that already reported success — the public endpoint is load-balanced, so the verifying read can reach a node that has not caught up. Observed: first read NOT_FOUND, second ~4s later succeeds.

Ported literally, the server would intermittently reject settled payments, more often on a fast machine — the worst failure signature for a demo a stranger runs. The verify path now retries on NOT_FOUND within a bounded window.

S7 holds

typescript/src is untouched. The demo is expected to import @mysten/sui — ADR-011 Alternative A rejected promoting it into core for exactly that reason. The S7 boundary test is green.

Verification

typescript   typecheck ✔   vitest 1108/29 ✔   build ✔
mcp-demo     typecheck ✔   end-to-end on testnet ✔ (+ negative control)
python       154 passed ✔

Re-run after the restack, since a rebase invalidates every prior green run.

🤖 Generated with Claude Code

Danny-Devs and others added 5 commits August 16, 2026 00:45
…ee places it appeared

`spec/vectors/` holds 167 vectors across 14 files. The repo said otherwise three
different ways: README.md claimed 133, and docs/specification.md claimed 161 in
two separate places. All three are now the real number.

Two of those are cosmetic. The third is not. §13 "Conformance" criterion 5 is the
NORMATIVE definition of what makes an implementation s402-conformant, so an
implementer in Go, Python or Rust was told they conformed at 161 vectors — leaving
six shipped vectors outside the definition of conformance. That clause now binds
conformance to *every* shipped vector and carries the count as a version-anchored
parenthetical rather than as the operative term.

Also fixed: the README told readers to find the vectors by cloning the repo and
running `ls test/conformance/vectors/`. That directory is generated by
scripts/prepare-publish.sh at publish time and is git-ignored, so following the
instructions in a fresh clone produced "No such file or directory". Clones are now
pointed at `spec/vectors/`, and the relationship between the two paths is stated
so the npm path stays right for package consumers.

A count written into prose is a derived value maintained by hand — it rots
silently on every vector added, and nothing fails when it does. Three independent
wrong numbers is what that looks like once it has been running a while. So the fix
is not only the numbers: test/spec-doc-counts.test.ts counts spec/vectors/ and
compares it against every claim in both documents.

The test is deliberately built to refuse a vacuous pass. If the wording changes so
that no claim matches, it fails rather than silently verifying nothing — a check
that cannot fail is not a check. It was watched to fail against four mutations
before being kept: a wrong count in the spec, a wrong count in the README, a
reworded claim, and a vector added without a doc update.

It reads `spec/vectors/` and not `typescript/test/conformance/vectors/` on
purpose. The latter is git-ignored build output; a check keyed there passes on any
machine that has ever packed and is red on its first CI run.

Also adds the README's missing "who this is for / who this is not for" and a
falsification section naming four observable conditions that would retire the
project — per the README convention's third question, which the doc set had
nowhere to put.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…was claiming a CI check that never existed

`Status: Accepted` has only ever meant DECIDED. It has never said BUILT — so an ADR
that shipped and an ADR that was ratified and quietly never built were byte-identical
on the page. All twelve records now carry an `Implementation:` field, each determined
against the code rather than against the prose.

The ratio this makes countable: 7 of 12 shipped, 1 upheld, 2 in-progress, 2 not-started.

ADR-006 was the only record already carrying an `Implementation:` field, and it was
false. It stated in the present tense that scripts/compute-scheme-digests.mjs "runs in
CI and writes spec/scheme-digests.json", failing any PR that changed a scheme doc
without updating the digests. Neither file appears in any commit on any branch, and
CI's five jobs contain no such gate. So the project's single answer to "was this built?"
was a claim that read as verified precisely because it sat in the slot reserved for
verified things. The paragraph is now marked as an unbuilt plan.

Two findings fell out of doing this honestly:

ADR-004's extensions framework ships as the `s402/extensions` subpath in package.json
while its Status still reads Proposed — built without the decision ever being accepted.
Status is left untouched: flipping a governance field is a decision, not a cleanup.

ADR-010's S16 is half-built. Searching for the label found one narrative mention;
searching for the MECHANISM found the protocol version genuinely bound into the signed
envelope and required at envelope.ts:268. Its scheme-digest half is blocked on ADR-006,
which is not-started. Grepping for a rule's name finds where someone wrote the name;
grepping for its mechanism finds where it is enforced.

Where the evidence is a search rather than a proof, the field says so — the negatives
are recorded as "no witness found", not as "proven absent".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tranger meets it

Sui has deprecated JSON-RPC on public fullnodes. Every core method returns
-32601 on testnet AND mainnet, verified directly on both rather than taken from
a report. mcp-demo/src/server.ts and src/agent.ts both construct a SuiClient
over that transport, and docs/guide/tutorial.md's "Making It Real" snippet tells
readers to do the same.

This is a hole in the done-bar item I had just called satisfied. Item 1 is "a
stranger can run the one demo in 10 minutes", and I had verified the README's
CLAIMS — paths, counts — without ever running the demo. The grant-ready standard
I authored says to enumerate every shipped surface; I applied that to test suites
and missed the runnable artifact, which is the surface the stranger test is
actually about.

The fix here is honesty, not repair: both surfaces now state the blocker above
the instructions, so nobody follows a dead path. The migration to gRPC/GraphQL
is tracked separately and is not attempted inside a documentation pass. Both
notices are to be removed by that work — a stale blocker warning is its own
defect, so the removal is written into that ticket's done-bar.

Worth recording what did NOT break: typescript/package.json has no Sui
dependency and never had one (ADR-002, invariant S7, enforced by the S7
Chain-Agnostic Boundary CI job). The entire protocol layer — encoding,
validation, conformance vectors, all 1108 tests — runs offline and is untouched.
The blast radius is exactly one demo and one snippet. That containment is the
pure-protocol decision paying out, and it is the strongest evidence for S7 the
repo has produced so far.

Raised cross-lane by agent-b g11 from the SweeFi pass; verified independently
before acting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… rejects payments that landed

Sui deprecated JSON-RPC on public fullnodes, which left mcp-demo dead at the
network layer. Both touchpoints now use SuiGrpcClient.

The endpoint is unchanged. gRPC is served from the same host and port, so this
is a client swap and not an endpoint move.

Requires @mysten/sui 2.x, and that is not incidental. On the 1.45.2 the caret
had resolved to, executeTransaction is rejected by current fullnodes with
"invalid read_mask path: transaction.transaction", and neither the gRPC nor the
GraphQL client can resolve a transaction for building. The write path cannot be
migrated on 1.x at all.

Four shape changes, each of which type-checks and then misbehaves:

- balanceChanges is present-but-undefined unless requested via `include`. Left
  out, the credit check runs over undefined, returns false, and the demo
  rejects every real payment while looking like a working payment gate.
- Responses are protobuf-wrapped as a union discriminated on $kind; the payload
  is at .Transaction, and the failure arm carries it under another key.
- Balance changes are flat {coinType,address,amount}; JSON-RPC nested the
  recipient under owner.AddressOwner.
- Execution status is a boolean discriminant, not status.status === 'success'.

Also adds a bounded retry to the verifying read. getTransaction returns
NOT_FOUND immediately after an execute that already reported success, because
the public endpoint is load-balanced and the read can reach a node that has not
caught up. Observed: first read NOT_FOUND, second read ~4s later succeeds.
Without the retry the demo intermittently rejects settled payments, and does so
more often on a fast machine.

Verified end-to-end on testnet, not compiled:
  agent settles      7cVD9Z7SirgNhJYQMgaUcjnsbivfGcWFp5Fnozxg4WpX
  effects.status     success
  balanceChanges     receiver +10000000, sender -11997880 (amount + gas)
  server verifies    tool result returned
  negative control   bogus digest rejected with -32402

The negative control matters: an earlier run passed against a stale
envelope-only server, whose verifier returns true without touching the chain.
That was a false green. The gate was re-proven able to fail before this was
called done.

typescript/src is untouched, so S7 holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…out is fixed

The notices in mcp-demo/README.md and docs/guide/tutorial.md said the demo could
not reach the network and that they would be removed when the migration landed.
It landed in the previous commit, so they go in the same PR — a stale blocker
warning is its own defect, and this one would now be actively false.

The tutorial snippet is updated to the transport that works: SuiGrpcClient,
an explicit tx.setSender (the gRPC client has no signAndExecuteTransaction to
infer it from), and a note that this needs @mysten/sui 2.x.

Two README claims the migration made untrue are corrected: the declared
dependency (^1.18.0 -> ^2.24.0) and the verification path, which is no longer
SuiClient.getTransactionBlock. The retry on the verifying read is documented
where someone reading the settlement flow will meet it.

Deliberately NOT changed: "Demo returns a 501 JSON-RPC error" further down that
README. That JSON-RPC is MCP's own wire protocol, not Sui's node RPC, and it is
still correct. Only Sui's transport was deprecated.

docs/guide/quickstart.md also mentions @mysten/sui and still carries no notice.
That is correct rather than an oversight: it names no transport, constructs no
client and points at no fullnode, so it stayed true throughout. The SDK did not
die, the JSON-RPC transport did.

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

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
s402-docs Ready Ready Preview Aug 16, 2026 8:41am

Base automatically changed from agent-a/DAN-855-spec-vector-count-and-stranger-test to main August 16, 2026 23:19
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