cascrow-mcp 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## 3.0.0 — unreleased (owner publishes)
3
+ ## 3.1.0 — 2026-10-20
4
+
5
+ Not published yet when the second half of these changes landed; everything below ships in one 3.1.0.
6
+
7
+ - **New tools `cascrow_request_human_review` and `cascrow_human_reviews`.** Either party of a contract can ask for a person at cascrow to review a verification outcome (Art. 22(3) GDPR), with an optional `reason`; a person decides within one month and both parties receive the decision with its reasons (`human_review.decided`). `cascrow_human_reviews` lists the requests on a contract with their status, deadline, outcome and the reviewer's statement. A second request while one is open comes back as `status: "ALREADY_OPEN"` with its `dueAt`, not as an error.
8
+ - **New tool `cascrow_rotate_callback_secret`.** Callbacks to your `callbackUrl` are now signed with a secret of your own account (`X-Cascrow-Signature: sha256=HMAC-SHA256(secret, "t=" + <unix ms of X-Cascrow-Timestamp> + "." + <raw body>)`). Two steps: without `confirm` the tool only returns the warning; with `confirm: true` it rotates and returns the new secret — the previous one stops verifying at once, and the new one enters the conversation. `cascrow_set_discoverable` never relays the secret into the model's context (it says a secret exists and points to the rotate tool).
9
+ - **`cascrow_create_contract` takes `reviewWindowDays` (2–30, default 14; under 7 only between two businesses) and `panelDecidesAlone`**, and returns the server's `reviewTerms` — what the builder sees before joining. The `agentReviewMode` description says what happens since 2026-10-20: without the funder's decision a milestone completes only when the panel's own verdict opened the review with a YES; any other review goes to cascrow's human review.
10
+ - **`cascrow_get_reputation` reads `stats: null`**: a builder who has not consented to publishing their figures shows "Figures: not published" and only the milestones they published one by one — never read as "no track record". An address that publishes nothing at all answers like an unknown one.
11
+ - **`cascrow_v3_acceptance_get` names the free veto** of an escrow without an arbiter — in its description and in its result whenever the signed typed data names the zero address as arbiter: the payer can object to every claim at no cost, and an objected claim ends in a refund after the deadline.
12
+ - New hint for **`SCREENING_UNAVAILABLE`**: the new-money sanctions screen fails closed — nothing was created, retry in a few minutes; existing escrows are not affected.
13
+ - **`cascrow_set_discoverable` takes `showNameOnCertificates` and `consentVersion`.** `showNameOnCertificates` shows the company name (or name) as "Delivered by" on the public check page of the account's certificates. Turning it or `discoverable` ON publishes the account holder's name and needs their consent: without `consentVersion` nothing changes, and the tool returns `code: "CONSENT_REQUIRED"` with the server's exact consent text and the `consentVersion` to send once they agree. The text is passed on as the server sends it, never kept in this package. Turning either off needs nothing. The reply also says whether the name is shown on certificate check pages.
14
+ - **Wallet linking follows cascrow's Sign-In with Ethereum challenge:** `GET /api/user/wallet/challenge?address=<your wallet>`, sign the returned message with that wallet, `POST /api/user/wallet`. The old one-line challenge is refused by the server. Every `cascrow-signer` pin is now `cascrow-signer@0.2.0` — 0.1.0 can no longer link a wallet (its other commands keep working).
15
+ - `cascrow_verify`'s description names the panel's OpenAI model correctly (GPT-4o-mini).
16
+ - `@modelcontextprotocol/sdk` `^1.32.1` (was `^1.15.0`, a range that admitted versions affected by GHSA-6qxp-vccf-f47h).
17
+ - The package now carries its MIT `LICENSE` and ships only `index.js`, `README.md`, `CHANGELOG.md` and `LICENSE`; `server.json`, `smithery.yaml` and `REMOTE_HTTP_SERVER.md` stay in the repository, where the MCP registry and Smithery read them.
18
+
19
+ ## 3.0.0 — 2026-09-27
4
20
 
5
21
  - **`cascrow_join_contract` says what the builder can do now.** The server's answer carries `status` and `activated`, returned as well as rendered: a $0 verification contract is active at once — submit proof; a contract with an amount waits for the payer's MilestoneEscrowV3 offer, which you accept with your own key (`npx cascrow-signer@0.1.0 accept --milestone <id>`). It used to say "Contract is now AWAITING_ESCROW — ready to receive proof once funded" for both, so a $0 builder waited for money that never comes.
6
22
  - **New outcome `NOT_COMPLETABLE_WITHOUT_ESCROW`.** When the panel approves a milestone that carries an amount without a MilestoneEscrowV3 escrow, nothing can complete it: `cascrow_verify` renders "⛔ NOT COMPLETED — … no money moved; contact support", `cascrow_mcp_submit` surfaces the server's 409 with the same hint, and `cascrow_get_proof_status` / `cascrow_watch_verification` read the new `notCompletableWithoutEscrow` field and stop polling. Such a milestone used to read as "approved, the automatic payout did not go through — the release can be retried", a retry that no longer exists.
@@ -12,13 +28,13 @@
12
28
  - `cascrow_review_queue` / `cascrow_review_contract` describe what a funder verdict does now: it completes or hands back a verification-only milestone and moves no money. A COMPLETED milestone no longer reads "funds released" — it says whether no money was held or a MilestoneEscrowV3 escrow paid out on chain.
13
29
  - `cascrow_get_reputation` prints `totalReleasedUsd` (and the XRP released by MilestoneEscrowV3 escrows) instead of "RLUSD released". `totalRlusdReleased` is still in the response, deprecated.
14
30
 
15
- ## 2.1.0 — unreleased (owner publishes)
31
+ ## 2.1.0 — not published on its own, released as part of 3.0.0
16
32
 
17
33
  - **MilestoneEscrowV3, remote half.** New tools: `cascrow_v3_offer` (payer offers terms), `cascrow_v3_acceptance_get` / `cascrow_v3_acceptance_submit` (builder reads the frozen terms and hands over the acceptance signature), `cascrow_v3_prepare` (an UNSIGNED transaction — rebuild `data` and check `to`/`chainId` yourself), `cascrow_v3_report_tx` (a transaction HASH only; a signed transaction is refused before anything is sent) and `cascrow_v3_attestation` (the non-binding AI recommendation for a claim). cascrow never signs, sends or relays: signing happens in the separate, pinned local signer, `npx cascrow-signer@0.1.0`.
18
34
  - `cascrow_fund_milestone` refuses a MilestoneEscrowV3 milestone with the way to fund it (`npx cascrow-signer@0.1.0 fund`), and every V3 refusal code (`V3_DISABLED`, `RATE_UNAVAILABLE`, `V3_NOT_OFFERED`, `OFFER_EXISTS`, `V3_OFFER_OPEN`, `ATTESTATION_PENDING`, `CLAIM_PROOF_UNKNOWN`, `V3_ESCROW`, `INVALID_SIGNATURE`, `V3_INDEX_STALE`, …) comes with what the recipient can do.
19
35
  - A verification of a V3 milestone reads as `V3_RECOMMENDATION` — "moves NO money" — in `cascrow_verify` and `cascrow_mcp_submit`, never as approved/paid or rejected.
20
36
 
21
- ## 2.0.0 — unreleased (owner publishes)
37
+ ## 2.0.0 — not published on its own, released as part of 3.0.0
22
38
 
23
39
  - **Breaking: `cascrow_escrow_fund` is removed.** It sent the agent's EVM private key to cascrow, which signed the deposit server-side; cascrow no longer accepts, stores or signs with anyone's key. A host that still calls the tool by name gets an error that says so, and nothing it passed is read. Fund a v2 escrow by signing locally: `GET /api/agent/escrow-prepare` returns the two unsigned transactions, you sign and broadcast them with your own wallet, then `POST /api/escrow/sync`. Funding a MilestoneEscrowV3 contract from an API client is not available yet. **If you ever passed a private key to this tool, treat that key as exposed and move its funds to a new wallet.**
24
40
  - The anonymous-mode message states the real number of tools (it said 18).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cascrow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MCP server for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy: AI verification, on-chain reputation, and — for work that is paid — a non-custodial milestone escrow (MilestoneEscrowV3, native XRP; testnet for now).
4
4
 
5
- AI agents use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote**, with every verdict anchored on the XRP Ledger. For a paid milestone the AI result is a signed, non-binding recommendation: the money sits in a MilestoneEscrowV3 smart contract that only the payer, the builder or an arbiter they chose can move — cascrow holds no key and never signs.
5
+ AI agents use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote**, with every verdict anchored on the XRP Ledger. For a paid milestone the AI result is a signed, non-binding recommendation: the money sits in a MilestoneEscrowV3 smart contract that only the payer, the builder or an arbiter they chose can move — cascrow holds no key that can move escrowed funds and never signs an escrow transaction.
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -130,7 +130,7 @@ Rate limit: 3 registrations per IP per hour.
130
130
  | `cascrow_verify_work` | Verify AI work with no escrow contract, from a GitHub PR, pasted code, or a base64-encoded screenshot. Returns a public report URL. With `CASCROW_API_KEY` set, the verdict is also anchored on the XRP Ledger mainnet — anonymous calls are not. |
131
131
  | `cascrow_get_contract` | Get contract status and all milestone states. |
132
132
  | `cascrow_list_my_contracts` | List all contracts for this agent, as Requester and/or Builder. |
133
- | `cascrow_review_queue` | As funder of a verification-only contract: list milestones still waiting for your verdict. Poll it — an undecided milestone is completed automatically after 48 h under review mode `MANUAL_AUTO` or `AUTO`, 14 days otherwise. |
133
+ | `cascrow_review_queue` | As funder of a verification-only contract: list milestones still waiting for your verdict. Poll it — an undecided milestone is completed automatically when its review window closes only when the panel's own verdict opened the review with a YES — any other review goes to cascrow's human review instead; the window is the contract's `reviewWindowDays` (default 14 days; 48h on an agent contract created before 2026-10-20 in review mode `MANUAL_AUTO` or `AUTO`). |
134
134
  | `cascrow_v3_claims` | As payer of a MilestoneEscrowV3 escrow: list claims that release it unless your own wallet calls `object()` before `objectionDeadline`. cascrow cannot object for you. |
135
135
  | `cascrow_v3_offer` | As payer: offer MilestoneEscrowV3 terms (native XRP) for a milestone. The builder accepts with their own key. |
136
136
  | `cascrow_v3_acceptance_get` / `cascrow_v3_acceptance_submit` | As builder: read the frozen terms, hand over your EIP-712 acceptance signature (binding until `acceptBy`). |
@@ -138,15 +138,18 @@ Rate limit: 3 registrations per IP per hour.
138
138
  | `cascrow_v3_report_tx` | After you broadcast: the transaction **hash** (never a signed transaction), so the index re-reads now. |
139
139
  | `cascrow_v3_attestation` | cascrow's signed, non-binding AI recommendation for a claim. |
140
140
 
141
- **Signing is never done here.** cascrow holds no key of yours and relays nothing. For MilestoneEscrowV3 use the separate local signer — `npx cascrow-signer@0.1.0` (CLI or its own MCP server, tools `cascrow_signer_*`) — which checks every prepared transaction against its own pinned chain and contract.
141
+ **Signing is never done here.** cascrow holds no key of yours and relays nothing. For MilestoneEscrowV3 use the separate local signer — `npx cascrow-signer@0.2.0` (CLI or its own MCP server, tools `cascrow_signer_*`) — which checks every prepared transaction against its own pinned chain and contract.
142
142
  | `cascrow_review_contract` | As funder of a verification-only contract: approve (completes it) or reject (hands it back) a milestone the AI panel scored 60–85. Moves no money. |
143
- | `cascrow_join_contract` | Join a contract as Builder agent via invite code. The answer says whether it is active now (`activated: true`, a $0 contract — submit proof) or waits for the payer's MilestoneEscrowV3 offer (accept it with your own key: `npx cascrow-signer@0.1.0 accept --milestone <id>`). |
143
+ | `cascrow_request_human_review` | As funder or builder: ask for a person at cascrow to review a verification outcome (Art. 22(3) GDPR). Decided within one month, sent to both parties with reasons. |
144
+ | `cascrow_human_reviews` | The human-review requests on a contract: status, deadline, outcome and the reviewer's statement. |
145
+ | `cascrow_join_contract` | Join a contract as Builder agent via invite code. The answer says whether it is active now (`activated: true`, a $0 contract — submit proof) or waits for the payer's MilestoneEscrowV3 offer (accept it with your own key: `npx cascrow-signer@0.2.0 accept --milestone <id>`). |
144
146
  | `cascrow_handoff` | Send a contract to another agent by Agent ID. |
145
147
  | `cascrow_get_agent_id` | Get this agent's own Agent ID. |
146
148
  | `cascrow_check_invites` | Check pending contract invites. |
147
149
  | `cascrow_get_reputation` | Look up any agent's on-chain track record by wallet address — completed, failed, success rate and value released (`totalReleasedUsd`, plus XRP for MilestoneEscrowV3; `totalRlusdReleased` is deprecated) over every eligible milestone, plus the milestone records the builder has published (opt-in, default private) with their proof hashes. No auth required. |
148
150
  | `cascrow_discover_agents` | Search discoverable Builder agents by skill tag and minimum success rate. |
149
- | `cascrow_set_discoverable` | Opt into the agent marketplace, set skill tags, register a callback URL. |
151
+ | `cascrow_set_discoverable` | Opt into the agent marketplace or into being named on certificate check pages, set skill tags, register a callback URL. Turning a name on needs the account holder's consent: the first call changes nothing and returns the text to show and the `consentVersion` to send. |
152
+ | `cascrow_rotate_callback_secret` | Replace the callback signing secret and return the new one (callbacks carry `X-Cascrow-Timestamp` and `X-Cascrow-Signature: sha256=HMAC-SHA256(secret, "t=" + <unix ms of the timestamp> + "." + <raw body>)`; reject a timestamp more than 5 minutes off). The first call changes nothing and returns a warning — the old secret stops verifying and the new one enters the conversation; call again with `confirm: true`. `cascrow_set_discoverable` never returns a secret. |
150
153
 
151
154
  ## How Verification Works
152
155
 
@@ -192,12 +195,12 @@ Claude will:
192
195
 
193
196
  1. **Requester (payer)** creates the contract (`cascrow_create_contract`) with an `amountUSD` above 0 and hands it to the **Builder** (`cascrow_handoff`); the builder joins (`cascrow_check_invites` + `cascrow_join_contract`).
194
197
  2. The payer offers the escrow terms — amount in native XRP at a frozen XRP/USD rate, review period, optional arbiter — with `cascrow_v3_offer`.
195
- 3. The builder reads the frozen terms (`cascrow_v3_acceptance_get`), checks them and signs the acceptance with its own key (`npx cascrow-signer@0.1.0 accept`, or `cascrow_v3_acceptance_submit` with a signature made elsewhere). The signature binds until `acceptBy`.
196
- 4. The payer funds the escrow with **one transaction from its own machine**: `npx cascrow-signer@0.1.0 fund --milestone <id>` (or the signer's MCP tool `cascrow_signer_fund`).
198
+ 3. The builder reads the frozen terms (`cascrow_v3_acceptance_get`), checks them and signs the acceptance with its own key (`npx cascrow-signer@0.2.0 accept`, or `cascrow_v3_acceptance_submit` with a signature made elsewhere). The signature binds until `acceptBy`.
199
+ 4. The payer funds the escrow with **one transaction from its own machine**: `npx cascrow-signer@0.2.0 fund --milestone <id>` (or the signer's MCP tool `cascrow_signer_fund`).
197
200
  5. The builder delivers → `cascrow_submit_proof` → `cascrow_watch_verification`. The AI result is a signed, non-binding recommendation (`cascrow_v3_attestation`).
198
201
  6. The builder claims on chain with the submission's `manifestHash`. The payer releases, or objects before the objection deadline (`cascrow_v3_claims` lists open claims); without an objection the builder can take the money after the review period.
199
202
 
200
- cascrow never accepts, creates, stores or signs with a private key and is never the arbiter (`cascrow_escrow_fund`, which took a key, was removed in 2.0.0 — if you ever passed a key to it, treat that key as exposed and move its funds). Each agent links its own wallet once — `GET /api/user/wallet/challenge` → sign → `POST /api/user/wallet`. A contract without an amount needs none of this: it is active once the builder has joined.
203
+ cascrow never accepts, creates, stores or signs with a private key and is never the arbiter (`cascrow_escrow_fund`, which took a key, was removed in 2.0.0 — if you ever passed a key to it, treat that key as exposed and move its funds). Each agent links its own wallet once — `GET /api/user/wallet/challenge?address=<your wallet>` → sign the Sign-In with Ethereum message → `POST /api/user/wallet` (or `npx cascrow-signer@0.2.0 link`). Replacing a linked wallet by API key also needs the current wallet's signature (`cascrow-signer link --old-home …`). A contract without an amount needs none of this: it is active once the builder has joined.
201
204
 
202
205
  ## Blockchain Architecture
203
206
 
package/index.js CHANGED
@@ -40,8 +40,8 @@ const CUSTODY_TOOL_REMOVED =
40
40
  "Error: This way of funding has been removed: cascrow never accepts, creates, stores or signs with private keys. " +
41
41
  "A $0 verification contract needs no funding — it is active once the builder has joined. " +
42
42
  "A milestone with an amount is a MilestoneEscrowV3 escrow in native XRP, funded by the payer with one transaction " +
43
- "from their own machine: first link the wallet you will fund from (GET /api/user/wallet/challenge, sign the message, " +
44
- "POST /api/user/wallet), then run the local signer: npx cascrow-signer@0.1.0 fund --milestone <milestoneId>. " +
43
+ "from their own machine: first link the wallet you will fund from (GET /api/user/wallet/challenge?address=<your wallet>, sign the message with that wallet, " +
44
+ "POST /api/user/wallet), then run the local signer: npx cascrow-signer@0.2.0 fund --milestone <milestoneId>. " +
45
45
  "If your request contained a private key, treat it as exposed and move its funds to a new wallet. " +
46
46
  "[CUSTODY_PATH_REMOVED]";
47
47
 
@@ -50,7 +50,7 @@ const CUSTODY_TOOL_REMOVED =
50
50
  // app). v3-client-text-parity.test.ts holds the copy equal to the owner.
51
51
  // BEGIN v3-client-text
52
52
  const V3_CLIENT_TEXT = {
53
- "signerPin": "cascrow-signer@0.1.0",
53
+ "signerPin": "cascrow-signer@0.2.0",
54
54
  "remoteTools": [
55
55
  "cascrow_v3_offer",
56
56
  "cascrow_v3_acceptance_get",
@@ -61,17 +61,18 @@ const V3_CLIENT_TEXT = {
61
61
  "cascrow_v3_claims"
62
62
  ],
63
63
  "codeHints": {
64
- "V3_DISABLED": "MilestoneEscrowV3 is switched off for NEW money right now (offers, acceptances, funding). An escrow that already exists is never locked: object, release, refund, claim and withdraw keep working (cascrow_v3_prepare, or npx cascrow-signer@0.1.0 without cascrow at all).",
64
+ "V3_DISABLED": "MilestoneEscrowV3 is switched off for NEW money right now (offers, acceptances, funding). An escrow that already exists is never locked: object, release, refund, claim and withdraw keep working (cascrow_v3_prepare, or npx cascrow-signer@0.2.0 without cascrow at all).",
65
65
  "RATE_UNAVAILABLE": "cascrow could not get a consistent XRP/USD rate from its two sources, so no offer can be priced. Nothing was created; retry in a minute.",
66
+ "SCREENING_UNAVAILABLE": "cascrow could not complete the sanctions screening this new-escrow step needs (offer, acceptance or funding), so nothing was created or prepared. This is a condition on cascrow's side, not a finding about you or your counterparty — retry in a few minutes. It does not touch escrows that already exist: their actions keep working (cascrow_v3_prepare, or npx cascrow-signer@0.2.0).",
66
67
  "V3_NOT_OFFERED": "The payer has not offered MilestoneEscrowV3 terms for this milestone yet — only the payer can (cascrow_v3_offer). The payer has been notified.",
67
68
  "OFFER_EXISTS": "An open offer already exists for this milestone and stays valid until it expires. Wait for the payee's acceptance or for the offer to lapse; sending the same inputs again returns the same offer.",
68
- "V3_OFFER_OPEN": "An open MilestoneEscrowV3 offer, or a signed acceptance that is not funded yet, is bound to this milestone or wallet, so it cannot change now. Wait until the payer funds the escrow from their own machine (npx cascrow-signer@0.1.0 fund --milestone <milestoneId>) or the offer or acceptance lapses.",
69
+ "V3_OFFER_OPEN": "An open MilestoneEscrowV3 offer, or a signed acceptance that is not funded yet, is bound to this milestone or wallet, so it cannot change now. Wait until the payer funds the escrow from their own machine (npx cascrow-signer@0.2.0 fund --milestone <milestoneId>) or the offer or acceptance lapses.",
69
70
  "ATTESTATION_PENDING": "The AI panel has not finished on this claim. Its result is a non-binding recommendation — as payer you can object NOW, before the objection deadline, without waiting for it.",
70
71
  "CLAIM_PROOF_UNKNOWN": "This claim's proofHash matches no evidence submitted to cascrow, so there is no AI recommendation for it. As payer, judge the delivery yourself and object before the objection deadline if it is not what was agreed.",
71
- "V3_ESCROW": "This milestone is held in a MilestoneEscrowV3 escrow: only its payer, payee or arbiter moves that money, on chain, with their own key. Get the unsigned transaction with cascrow_v3_prepare, or sign locally with npx cascrow-signer@0.1.0.",
72
+ "V3_ESCROW": "This milestone is held in a MilestoneEscrowV3 escrow: only its payer, payee or arbiter moves that money, on chain, with their own key. Get the unsigned transaction with cascrow_v3_prepare, or sign locally with npx cascrow-signer@0.2.0.",
72
73
  "V2_ESCROW_PRESENT": "This milestone or its contract still carries data of the retired v2 (RLUSD) escrow, so it cannot become a MilestoneEscrowV3 escrow. Nothing was changed; contact support.",
73
- "INVALID_SIGNATURE": "The acceptance signature does not verify for your linked wallet over these exact terms (EIP-712, low-s, from an ordinary account). Sign the terms returned by cascrow_v3_acceptance_get again — npx cascrow-signer@0.1.0 accept does it and checks them first.",
74
- "V3_INDEX_STALE": "cascrow's index of the chain is behind. Read the escrow from the chain directly (npx cascrow-signer@0.1.0 claims, or getEscrow / ClaimSubmitted); every deadline, above all an objection window, runs on chain regardless.",
74
+ "INVALID_SIGNATURE": "The acceptance signature does not verify for your linked wallet over these exact terms (EIP-712, low-s, from an ordinary account). Sign the terms returned by cascrow_v3_acceptance_get again — npx cascrow-signer@0.2.0 accept does it and checks them first.",
75
+ "V3_INDEX_STALE": "cascrow's index of the chain is behind. Read the escrow from the chain directly (npx cascrow-signer@0.2.0 claims, or getEscrow / ClaimSubmitted); every deadline, above all an objection window, runs on chain regardless.",
75
76
  "ACCEPTANCE_LAPSED": "The acceptance expired without funding, so the milestone is closed — this is not a failure of either party. A new attempt is a new milestone.",
76
77
  "NOT_COMPLETABLE_WITHOUT_ESCROW": "The work was approved, but this milestone carries an amount without a MilestoneEscrowV3 escrow, and the previous escrow system has been removed — nothing can complete or pay it, so it was left as it was and NO money moved. cascrow has opened a case for it; contact support@cascrow.com with the contract ID. Do not resubmit: your evidence is recorded.",
77
78
  "RELEASE_HOLD_REMOVED": "releaseHoldHours was removed with the previous escrow system; send the contract without it. A MilestoneEscrowV3 escrow's review period is agreed in the payer's offer (cascrow_v3_offer), and a $0 verification contract has no payout to delay.",
@@ -81,14 +82,15 @@ const V3_CLIENT_TEXT = {
81
82
  },
82
83
  "joined": {
83
84
  "active": "Joined as Builder. This is a $0 verification contract, so it is active now — submit proof for a milestone (cascrow_submit_proof). No money is held or moved.",
84
- "awaitingOffer": "Joined as Builder. The milestones carry an amount, so nothing is funded yet: wait for the payer's MilestoneEscrowV3 offer, then accept it with your own wallet key — npx cascrow-signer@0.1.0 accept --milestone <milestoneId>, or on the contract page. cascrow never signs for you. Submit proof once the payer has funded the milestone.",
85
+ "awaitingOffer": "Joined as Builder. The milestones carry an amount, so nothing is funded yet: wait for the payer's MilestoneEscrowV3 offer, then accept it with your own wallet key — npx cascrow-signer@0.2.0 accept --milestone <milestoneId>, or on the contract page. cascrow never signs for you. Submit proof once the payer has funded the milestone.",
85
86
  "unknown": "Joined as Builder. The server did not say whether the contract is active — check it with cascrow_get_contract before submitting proof."
86
87
  },
87
88
  "payeeHints": {
88
- "CLAIM_PROOF_UNKNOWN": "This manifest is not a sealed submission of this milestone, so cascrow will not confirm it and nothing was signed. As payee, claim with the manifestHash the evidence door returned (the upload's answer, or cascrow_get_proof_status): npx cascrow-signer@0.1.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>."
89
+ "CLAIM_PROOF_UNKNOWN": "This manifest is not a sealed submission of this milestone, so cascrow will not confirm it and nothing was signed. As payee, claim with the manifestHash the evidence door returned (the upload's answer, or cascrow_get_proof_status): npx cascrow-signer@0.2.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>."
89
90
  },
90
- "fundRemoved": "cascrow_fund_milestone was removed (and with it fundMilestone in cascrow-sdk and `cascrow fund`). A $0 verification contract is active by itself once the builder has joined — there is nothing to fund. A milestone with an amount is a MilestoneEscrowV3 escrow in native XRP: the payer offers terms (cascrow_v3_offer), the builder accepts with their own key, and the payer funds it with one transaction from their own machine — npx cascrow-signer@0.1.0 fund --milestone <milestoneId>. cascrow never signs or sends for you.",
91
- "recommendation": "🧭 AI RECOMMENDATION {verdict} for this MilestoneEscrowV3 submission ({confidence}% confidence on this file) — this verdict moves NO money. The submission is YES only when every file in it has a definite AI yes; only then is it claim-ready. As payee: claim on chain with the submission's manifestHash (npx cascrow-signer@0.1.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>). As payer: release, or object before the objection deadline.",
91
+ "fundRemoved": "cascrow_fund_milestone was removed (and with it fundMilestone in cascrow-sdk and `cascrow fund`). A $0 verification contract is active by itself once the builder has joined — there is nothing to fund. A milestone with an amount is a MilestoneEscrowV3 escrow in native XRP: the payer offers terms (cascrow_v3_offer), the builder accepts with their own key, and the payer funds it with one transaction from their own machine — npx cascrow-signer@0.2.0 fund --milestone <milestoneId>. cascrow never signs or sends for you.",
92
+ "recommendation": "🧭 AI RECOMMENDATION {verdict} for this MilestoneEscrowV3 submission ({confidence}% confidence on this file) — this verdict moves NO money. The submission is YES only when every file in it has a definite AI yes; only then is it claim-ready. As payee: claim on chain with the submission's manifestHash (npx cascrow-signer@0.2.0 submit-claim --milestone <milestoneId> --proof-hash <manifestHash>). As payer: release, or object before the objection deadline.",
93
+ "noArbiterVeto": "No arbiter is named. The payer can object to every claim at no cost, and after the deadline an objected claim ends in a refund to the payer. This escrow protects you only against an inactive payer, not a dishonest one.",
92
94
  "reportTxOnlyHash": "cascrow_v3_report_tx takes a transaction HASH (0x + 64 hex characters) and nothing else. cascrow never accepts a signed transaction — broadcast it yourself to the chain's public RPC."
93
95
  };
94
96
  // END v3-client-text
@@ -191,7 +193,7 @@ function retryAfterOf(res, data) {
191
193
  * here: a single call either answers or throws; only the poll loop waits and asks
192
194
  * again.
193
195
  */
194
- async function fetchJson(method, path, { json, form, deadlineMs = REQUEST_CLIENT_ABORT_MS } = {}) {
196
+ async function fetchJson(method, path, { json, form, deadlineMs = REQUEST_CLIENT_ABORT_MS, headers = {} } = {}) {
195
197
  const controller = new AbortController();
196
198
  const timer = setTimeout(() => controller.abort(), deadlineMs);
197
199
  let res;
@@ -199,7 +201,7 @@ async function fetchJson(method, path, { json, form, deadlineMs = REQUEST_CLIENT
199
201
  try {
200
202
  res = await fetch(`${BASE_URL}${path}`, {
201
203
  method,
202
- headers: { ...authHeader, ...(json !== undefined ? { "Content-Type": "application/json" } : {}) },
204
+ headers: { ...authHeader, ...headers, ...(json !== undefined ? { "Content-Type": "application/json" } : {}) },
203
205
  ...(json !== undefined ? { body: JSON.stringify(json) } : form !== undefined ? { body: form } : {}),
204
206
  signal: controller.signal,
205
207
  });
@@ -261,8 +263,8 @@ async function apiGet(path) {
261
263
  return fetchJson("GET", path);
262
264
  }
263
265
 
264
- async function apiPatch(path, body) {
265
- return fetchJson("PATCH", path, { json: body });
266
+ async function apiPatch(path, body, headers) {
267
+ return fetchJson("PATCH", path, { json: body, headers });
266
268
  }
267
269
 
268
270
  async function uploadProof(milestoneId, filename, content) {
@@ -485,13 +487,27 @@ const TOOLS = [
485
487
  type: "string",
486
488
  enum: ["AUTO", "MANUAL", "MANUAL_AUTO"],
487
489
  description:
488
- "What happens when the AI panel is unsure (confidence 60–85). AUTO (default) rejects; MANUAL waits for your decision " +
489
- "via cascrow_review_contract, and completes the milestone automatically after 14 days without one; MANUAL_AUTO waits 48 h for your decision, then completes it. " +
490
- "AUTO also completes a milestone after 48 h without your decision when it reaches your review anyway — a rejection overturned on appeal, or a panel outage after an earlier real verdict. " +
491
- "On either 48-hour mode a milestone the panel never judged (every run timed out) is not completed; it waits for your decision. " +
490
+ "What happens when the AI panel is unsure (confidence 60–85). AUTO (default) rejects: the panel decides alone (the same as panelDecidesAlone: true). " +
491
+ "MANUAL and MANUAL_AUTO put the milestone to your decision via cascrow_review_contract; since 2026-10-20 the two behave the same, and your window is reviewWindowDays. " +
492
+ "Without your decision a milestone completes automatically only when the panel's own verdict opened the review with a YES; any other review " +
493
+ "(a NO majority, a panel outage, a rejection overturned on appeal, an escalation after the builder used up their resubmissions) is handed to cascrow's human review instead — it never completes on silence. " +
492
494
  "The window is counted from the email notice to you, never before the milestone was put to you (a panel verdict, an escalation after the builder used up their resubmissions, or a rejection overturned on appeal); without a sent notice it does not start. " +
493
495
  "Contracts without an amount only, and nothing moves: a milestone completed this way reports decidedBy \"sla\".",
494
496
  },
497
+ reviewWindowDays: {
498
+ type: "integer",
499
+ minimum: 2,
500
+ maximum: 30,
501
+ description:
502
+ "Your review window in days (default 14). Below 7 days only when both you and the builder registered as businesses — " +
503
+ "otherwise the contract is refused (REVIEW_WINDOW_NEEDS_BUSINESS). The builder sees it before joining.",
504
+ },
505
+ panelDecidesAlone: {
506
+ type: "boolean",
507
+ description:
508
+ "true: the panel decides alone — a YES above 85 % completes, anything else is a rejection the builder can resubmit against; you do not review. " +
509
+ "Defaults to agentReviewMode === \"AUTO\"; contradicting agentReviewMode is refused (REVIEW_MODE_CONFLICT). The builder sees it before joining.",
510
+ },
495
511
  milestones: {
496
512
  type: "array",
497
513
  minItems: 1,
@@ -533,7 +549,7 @@ const TOOLS = [
533
549
  },
534
550
  amountUSD: {
535
551
  type: "number",
536
- description: "Optional. Omit or set to 0 for a verification-only contract: it is active by itself once the builder has joined, and no money moves. An amount above 0 makes the milestone a MilestoneEscrowV3 escrow in native XRP — the payer offers terms with cascrow_v3_offer, the builder accepts, and the payer funds it from their own machine (npx cascrow-signer@0.1.0 fund).",
552
+ description: "Optional. Omit or set to 0 for a verification-only contract: it is active by itself once the builder has joined, and no money moves. An amount above 0 makes the milestone a MilestoneEscrowV3 escrow in native XRP — the payer offers terms with cascrow_v3_offer, the builder accepts, and the payer funds it from their own machine (npx cascrow-signer@0.2.0 fund).",
537
553
  },
538
554
  },
539
555
  },
@@ -571,7 +587,7 @@ const TOOLS = [
571
587
  name: "cascrow_verify",
572
588
  description:
573
589
  "Start (or re-run) AI verification for a proof and return the result. " +
574
- "5 AI models vote in parallel (Claude, GPT-4o, Gemini, Mistral, Cerebras). " +
590
+ "5 AI models vote in parallel (Claude, GPT-4o-mini, Gemini, Mistral, Cerebras). " +
575
591
  "Returns decision, confidence score, action (VERIFIED/PENDING_REVIEW/REJECTED), and per-model votes. " +
576
592
  "Upload already starts verification by itself: for a proof you just submitted use cascrow_watch_verification { proofId } instead. " +
577
593
  "If a verification of this proof is already running, this call waits for it rather than failing. " +
@@ -602,7 +618,7 @@ const TOOLS = [
602
618
  "Use this when another agent or user has created a contract and shared an invite code with you. " +
603
619
  "The answer says whether the contract is active now (activated: true — a $0 verification contract; submit proof right away) " +
604
620
  "or waits for the payer's MilestoneEscrowV3 offer (activated: false — accept it with your own wallet key: " +
605
- "npx cascrow-signer@0.1.0 accept --milestone <id>, then submit proof once the payer has funded it).",
621
+ "npx cascrow-signer@0.2.0 accept --milestone <id>, then submit proof once the payer has funded it).",
606
622
  inputSchema: {
607
623
  type: "object",
608
624
  required: ["inviteCode"],
@@ -704,15 +720,26 @@ const TOOLS = [
704
720
  name: "cascrow_set_discoverable",
705
721
  description:
706
722
  "Configure this agent's discovery settings: opt in/out of the agent marketplace, " +
723
+ "whether its name is shown on the public check page of its certificates, " +
707
724
  "set skill tags so Requester agents can find you, and register a callback URL to receive " +
708
725
  "real-time events instead of polling (handoff received, milestone funded, verified, rejected). " +
709
- "Call this once after registering to make yourself visible and reactive.",
726
+ "Turning discoverable or showNameOnCertificates ON publishes the account holder's name and needs " +
727
+ "their consent: call without consentVersion first — nothing changes, and the answer carries the exact " +
728
+ "text to show them and the consentVersion to send if they agree. Turning either off needs nothing.",
710
729
  inputSchema: {
711
730
  type: "object",
712
731
  properties: {
713
732
  discoverable: {
714
733
  type: "boolean",
715
- description: "Set to true to appear in cascrow_discover_agents searches",
734
+ description: "Set to true to appear in cascrow_discover_agents searches (needs consentVersion, see above)",
735
+ },
736
+ showNameOnCertificates: {
737
+ type: "boolean",
738
+ description: "Set to true to show the company name (or name) as \u201cDelivered by\u201d on the public check page of this account's certificates (needs consentVersion, see above)",
739
+ },
740
+ consentVersion: {
741
+ type: "string",
742
+ description: "The consent version the server named, sent only after the account holder agreed to the text it returned",
716
743
  },
717
744
  skills: {
718
745
  type: "array",
@@ -721,11 +748,25 @@ const TOOLS = [
721
748
  },
722
749
  callbackUrl: {
723
750
  type: "string",
724
- description: "HTTPS URL to receive real-time event notifications (POST). Set to null to disable.",
751
+ description: "HTTPS URL to receive real-time event notifications (POST, signed — see cascrow_rotate_callback_secret). Set to null to disable.",
725
752
  },
726
753
  },
727
754
  },
728
755
  },
756
+ {
757
+ name: "cascrow_rotate_callback_secret",
758
+ description:
759
+ "Replace this agent's callback signing secret and return the new one. Callbacks are signed " +
760
+ "X-Cascrow-Signature: sha256=HMAC-SHA256(secret, \"t=\" + <unix ms of X-Cascrow-Timestamp> + \".\" + <raw body>). " +
761
+ "The previous secret stops verifying immediately, and the new secret enters this conversation. " +
762
+ "Call without confirm first: nothing changes and the answer is the warning; call again with confirm true to rotate.",
763
+ inputSchema: {
764
+ type: "object",
765
+ properties: {
766
+ confirm: { type: "boolean", description: "true only after reading the warning a call without confirm returns." },
767
+ },
768
+ },
769
+ },
729
770
  {
730
771
  name: "cascrow_list_my_contracts",
731
772
  description:
@@ -771,7 +812,7 @@ const TOOLS = [
771
812
  description:
772
813
  "As the PAYER, offer MilestoneEscrowV3 terms for a milestone (native XRP, one escrow per milestone). cascrow prices it from two XRP/USD sources, freezes the terms " +
773
814
  "and notifies the builder, who accepts with their own key. reviewPeriodSec is your objection window after a claim: 24 h only with watcherDeclared=true " +
774
- "(you declare that you run a watcher, e.g. npx cascrow-signer@0.1.0 watch), otherwise at least 72 h; a first job with a new builder gets 7 days. " +
815
+ "(you declare that you run a watcher, e.g. npx cascrow-signer@0.2.0 watch), otherwise at least 72 h; a first job with a new builder gets 7 days. " +
775
816
  "An arbiter is optional — without one an objection costs nothing and the money returns to you after the deadline. Answers OFFER_EXISTS with the open offer's expiry.",
776
817
  inputSchema: {
777
818
  type: "object",
@@ -790,7 +831,8 @@ const TOOLS = [
790
831
  description:
791
832
  "As the BUILDER (payee), read the payer's frozen MilestoneEscrowV3 offer: the EIP-712 typed data to sign, escrowId, payer, amount in wei, the rate snapshot and acceptBy. " +
792
833
  "Check every term yourself before signing — your signature binds you until acceptBy and cascrow cannot revoke it. " +
793
- "npx cascrow-signer@0.1.0 accept checks it against its own pinned chain and contract and signs locally. Answers V3_NOT_OFFERED while the payer has not offered.",
834
+ "npx cascrow-signer@0.2.0 accept checks it against its own pinned chain and contract and signs locally. Answers V3_NOT_OFFERED while the payer has not offered. " +
835
+ `If the offer names no arbiter (arbiter null; the zero address in the typed data), tell the builder before signing: ${V3_CLIENT_TEXT.noArbiterVeto}`,
794
836
  inputSchema: { type: "object", required: ["milestoneId"], properties: { milestoneId: { type: "string" } } },
795
837
  },
796
838
  {
@@ -809,7 +851,7 @@ const TOOLS = [
809
851
  description:
810
852
  "Get the UNSIGNED MilestoneEscrowV3 transaction for an action: { chainId, to, value, data, call: { fn, args }, escrowId, action, role, expect }. " +
811
853
  "cascrow never signs, sends or relays it. Before signing, rebuild data from the contract ABI yourself and check to/chainId against your OWN configuration — " +
812
- "npx cascrow-signer@0.1.0 does all of that and sends to the public RPC. After funding, object/release/refund/claim never depend on this tool: the escrow can be moved from the chain alone.",
854
+ "npx cascrow-signer@0.2.0 does all of that and sends to the public RPC. After funding, object/release/refund/claim never depend on this tool: the escrow can be moved from the chain alone.",
813
855
  inputSchema: {
814
856
  type: "object",
815
857
  required: ["milestoneId", "action"],
@@ -825,7 +867,7 @@ const TOOLS = [
825
867
  {
826
868
  name: "cascrow_v3_report_tx",
827
869
  description:
828
- "After YOU broadcast a MilestoneEscrowV3 transaction, tell cascrow its hash so the index re-reads the chain now instead of within five minutes. " +
870
+ "After YOU broadcast a MilestoneEscrowV3 transaction, tell cascrow its hash so the index re-reads the chain now instead of at the next scheduled read (up to 30 minutes). " +
829
871
  "Takes a transaction HASH only — never a signed transaction; cascrow relays nothing. Reporting is optional: the index reads the chain anyway.",
830
872
  inputSchema: {
831
873
  type: "object",
@@ -851,7 +893,7 @@ const TOOLS = [
851
893
  "(confidence 60–85 lands in PENDING_REVIEW). APPROVE completes the milestone; " +
852
894
  "REJECT hands the milestone back so they can resubmit stronger evidence — it is not terminal. " +
853
895
  "This decides verification-only ($0) contracts and moves no money: a MilestoneEscrowV3 escrow is released or objected to " +
854
- "on chain by the payer's own key (cascrow_v3_prepare, or npx cascrow-signer@0.1.0). " +
896
+ "on chain by the payer's own key (cascrow_v3_prepare, or npx cascrow-signer@0.2.0). " +
855
897
  "Call cascrow_review_queue first: it names the contractId and the milestoneId. " +
856
898
  "milestoneId is required once more than one milestone of that contract is pending.",
857
899
  inputSchema: {
@@ -864,6 +906,35 @@ const TOOLS = [
864
906
  },
865
907
  },
866
908
  },
909
+ {
910
+ name: "cascrow_request_human_review",
911
+ description:
912
+ "As the funder OR the builder of a contract, ask for a person at cascrow to review a verification outcome (Art. 22(3) GDPR) — at any time, on any milestone the panel has decided. " +
913
+ "A person decides within one month (dueAt) and both parties receive the decision with its reasons (webhook human_review.decided; read it with cascrow_human_reviews). " +
914
+ "On a verification-only ($0) contract the decision can complete the milestone, reject it, or revoke a completion and withdraw its certificate; on a MilestoneEscrowV3 milestone it is a statement and changes nothing on chain. " +
915
+ "One open request per milestone: asking again while one is open answers ALREADY_OPEN with its dueAt. milestoneId is required once the contract has more than one milestone. " +
916
+ "reason is your own words to the reviewer (at most 2000 characters).",
917
+ inputSchema: {
918
+ type: "object",
919
+ required: ["contractId"],
920
+ properties: {
921
+ contractId: { type: "string" },
922
+ milestoneId: { type: "string", description: "Required once the contract has more than one milestone." },
923
+ reason: { type: "string", maxLength: 2000, description: "Why you ask for a review — shown to the reviewer only, never to the other party." },
924
+ },
925
+ },
926
+ },
927
+ {
928
+ name: "cascrow_human_reviews",
929
+ description:
930
+ "The human-review requests on a contract you are a party to, newest first: status (OPEN / DECIDED / CLOSED), dueAt, outcome and the reviewer's statement (decisionNote, which the reviewer wrote for both parties). " +
931
+ "Your own reason is returned to you only. The pull side of the human_review.requested / human_review.decided webhooks.",
932
+ inputSchema: {
933
+ type: "object",
934
+ required: ["contractId"],
935
+ properties: { contractId: { type: "string" } },
936
+ },
937
+ },
867
938
  {
868
939
  name: "cascrow_get_proof_status",
869
940
  description:
@@ -1010,7 +1081,7 @@ const TOOLS = [
1010
1081
 
1011
1082
  // ─── Tool handlers ────────────────────────────────────────────────────────────
1012
1083
 
1013
- async function handleCreateContract({ milestones, agentReviewMode }) {
1084
+ async function handleCreateContract({ milestones, agentReviewMode, reviewWindowDays, panelDecidesAlone }) {
1014
1085
  const deadline = (days) => {
1015
1086
  const d = new Date();
1016
1087
  d.setDate(d.getDate() + (days ?? 7));
@@ -1038,6 +1109,9 @@ async function handleCreateContract({ milestones, agentReviewMode }) {
1038
1109
  };
1039
1110
  }),
1040
1111
  ...(agentReviewMode !== undefined ? { agentReviewMode } : {}),
1112
+ // The review terms — validated by the server (review-terms.ts).
1113
+ ...(reviewWindowDays !== undefined ? { reviewWindowDays } : {}),
1114
+ ...(panelDecidesAlone !== undefined ? { panelDecidesAlone } : {}),
1041
1115
  };
1042
1116
 
1043
1117
  const data = await apiPost("/api/contracts", payload);
@@ -1052,6 +1126,8 @@ async function handleCreateContract({ milestones, agentReviewMode }) {
1052
1126
  inviteLink: inviteCode,
1053
1127
  contractUrl,
1054
1128
  milestoneCount: milestones.length,
1129
+ // What the builder will see before joining (window, who decides) — the server's own words.
1130
+ reviewTerms: data.reviewTerms ?? null,
1055
1131
  message: `Contract created with ${milestones.length} milestone(s). View it here: ${contractUrl}`,
1056
1132
  };
1057
1133
  }
@@ -1426,9 +1502,6 @@ async function handleGetReputation({ walletAddress }) {
1426
1502
  : data.milestones,
1427
1503
  };
1428
1504
  const { stats, milestones = [], skills = [] } = marked;
1429
- const successPct = stats.successRate !== null
1430
- ? `${Math.round(stats.successRate * 100)}%`
1431
- : "no data yet";
1432
1505
 
1433
1506
  const milestoneLines = milestones.slice(0, 5).map((m) => {
1434
1507
  const proofLinks = Object.entries(m.proofs)
@@ -1445,6 +1518,25 @@ async function handleGetReputation({ walletAddress }) {
1445
1518
  // "Completed: 12" next to no evidence and no reason — a refusal with no reader
1446
1519
  // (F-72). Kept byte-identical to the in-repo server's copy; the two are a pair
1447
1520
  // this repo already knows about (SEC-009).
1521
+ // `stats` is null when the builder has not consented to publishing their
1522
+ // figures (the server answers 404 when nothing at all is public). Said in words,
1523
+ // because "no figures" must not read as "no track record". Byte-identical to the
1524
+ // in-repo server's copy.
1525
+ if (!stats) {
1526
+ return {
1527
+ ...marked,
1528
+ message: [
1529
+ `Agent reputation for ${walletAddress}:`,
1530
+ " Figures: not published — this builder has not agreed to publish their track-record figures. Only the milestones they published one by one are shown.",
1531
+ skills.length > 0 ? ` Skills: ${skills.join(", ")}` : null,
1532
+ milestones.length > 0 ? `\nPublished milestones:\n${milestoneLines}` : null,
1533
+ ].filter(Boolean).join("\n"),
1534
+ };
1535
+ }
1536
+ const successPct = stats.successRate !== null
1537
+ ? `${Math.round(stats.successRate * 100)}%`
1538
+ : "no data yet";
1539
+
1448
1540
  const noPublishedNote = stats.milestonesCompleted > 0
1449
1541
  ? "\nPublished milestones: none — this builder has not made any milestone record public. The counts above cover every eligible milestone."
1450
1542
  : null;
@@ -1505,32 +1597,88 @@ async function handleDiscoverAgents({ skill, minRate, limit } = {}) {
1505
1597
  };
1506
1598
  }
1507
1599
 
1508
- async function handleSetDiscoverable({ discoverable, skills, callbackUrl } = {}) {
1600
+ async function handleSetDiscoverable({ discoverable, showNameOnCertificates, consentVersion, skills, callbackUrl } = {}) {
1509
1601
  const body = {};
1510
- if (discoverable !== undefined) body.discoverable = discoverable;
1511
- if (skills !== undefined) body.skills = skills;
1512
- if (callbackUrl !== undefined) body.callbackUrl = callbackUrl;
1602
+ if (discoverable !== undefined) body.discoverable = discoverable;
1603
+ if (showNameOnCertificates !== undefined) body.showNameOnCertificates = showNameOnCertificates;
1604
+ if (consentVersion !== undefined) body.consentVersion = consentVersion;
1605
+ if (skills !== undefined) body.skills = skills;
1606
+ if (callbackUrl !== undefined) body.callbackUrl = callbackUrl;
1513
1607
 
1514
- const data = await apiPatch("/api/agent/settings", body);
1608
+ let data;
1609
+ try {
1610
+ data = await apiPatch("/api/agent/settings", body, { "x-cascrow-channel": "mcp" });
1611
+ } catch (err) {
1612
+ // Turning a name consent on without the account holder's agreement is refused
1613
+ // by the server, and its answer carries the words to show. They are passed on
1614
+ // as they come, never kept here, so this package cannot show an outdated text.
1615
+ if (!(err instanceof CascrowApiError) || err.code !== "CONSENT_REQUIRED") throw err;
1616
+ const consents = Array.isArray(err.body?.consents) ? err.body.consents : [];
1617
+ const texts = consents.map((c) => `${c.purpose}: ${c.text?.en ?? ""}`).join("\n\n");
1618
+ return {
1619
+ code: "CONSENT_REQUIRED",
1620
+ consentVersion: err.body?.consentVersion ?? null,
1621
+ consents,
1622
+ message:
1623
+ "Nothing was changed — neither the switch nor any other setting sent with it. Turning this on publishes the account holder's name. Show them the text below; only if they agree, " +
1624
+ `call cascrow_set_discoverable again with consentVersion "${err.body?.consentVersion ?? ""}".\n\n${texts}`,
1625
+ };
1626
+ }
1515
1627
 
1516
1628
  const parts = [];
1517
1629
  if (data.discoverable !== undefined)
1518
1630
  parts.push(`Discoverable: ${data.discoverable ? "✅ yes" : "❌ no"}`);
1631
+ if (data.showNameOnCertificates !== undefined)
1632
+ parts.push(`Name on certificate check pages: ${data.showNameOnCertificates ? "✅ yes" : "❌ no"}`);
1519
1633
  if (data.skills?.length > 0)
1520
1634
  parts.push(`Skills: ${data.skills.join(", ")}`);
1521
- if (data.callbackUrl)
1522
- parts.push(`Callback URL: ${data.callbackUrl}`);
1523
- if (data.profileUrl)
1524
- parts.push(`Public profile: ${BASE_URL}${data.profileUrl}`);
1635
+ // A settings change never relays the callback secret into the model's context
1636
+ // (owner decision, 2026-10-19) — only cascrow_rotate_callback_secret does, after
1637
+ // its warning. The secret the server created is dropped here, unread.
1638
+ const { callbackSecret, callbackSecretNotice, ...shown } = data;
1639
+ void callbackSecretNotice;
1640
+ if (shown.callbackUrl)
1641
+ parts.push(`Callback URL: ${shown.callbackUrl}`);
1642
+ if (shown.callbackUrl && callbackSecret)
1643
+ parts.push("Callbacks are signed with a new secret that is not shown here — call cascrow_rotate_callback_secret to obtain it.");
1644
+ if (shown.profileUrl)
1645
+ parts.push(`Public profile: ${BASE_URL}${shown.profileUrl}`);
1525
1646
 
1526
1647
  return {
1527
- ...data,
1648
+ ...shown,
1528
1649
  message: parts.length > 0
1529
1650
  ? `Agent settings updated:\n${parts.map((p) => ` ${p}`).join("\n")}`
1530
1651
  : "Settings saved.",
1531
1652
  };
1532
1653
  }
1533
1654
 
1655
+ // The same words as src/lib/callback-secret-text.ts ROTATE_WARNING (this package
1656
+ // cannot import it; callback-secret-doors.test.ts holds the two equal).
1657
+ const ROTATE_WARNING =
1658
+ "WARNING: this replaces the callback signing secret. The previous secret stops verifying immediately — " +
1659
+ "callbacks signed from now on use the new one, so update your receiver at once. The new secret is returned " +
1660
+ "in this tool result, which means it enters this conversation's context and transcript: store it only in the " +
1661
+ "receiver's configuration, do not repeat it, and rotate again if this conversation may be read by anyone else.";
1662
+
1663
+ /**
1664
+ * The ONE MCP door to the callback signing secret. Two steps: without `confirm`
1665
+ * nothing changes and the answer is the warning; with `confirm: true` it rotates
1666
+ * (POST /api/agent/settings/callback-secret) and returns the secret.
1667
+ */
1668
+ async function handleRotateCallbackSecret({ confirm } = {}) {
1669
+ if (confirm !== true) {
1670
+ return { rotated: false, warning: ROTATE_WARNING, message: `${ROTATE_WARNING}\n\nNothing was changed. Call cascrow_rotate_callback_secret again with confirm: true to rotate.` };
1671
+ }
1672
+ let data;
1673
+ try {
1674
+ data = await apiPost("/api/agent/settings/callback-secret", {});
1675
+ } catch (err) {
1676
+ if (!(err instanceof CascrowApiError) || err.code !== "NO_CALLBACK_URL") throw err;
1677
+ return { rotated: false, code: "NO_CALLBACK_URL", message: "No callback URL is set, so there is no secret to rotate. Set one with cascrow_set_discoverable { callbackUrl }, then call this tool again." };
1678
+ }
1679
+ return { rotated: true, callbackSecret: data.callbackSecret, warning: ROTATE_WARNING, message: `${ROTATE_WARNING}\n\n${data.message ?? ""}` };
1680
+ }
1681
+
1534
1682
  /**
1535
1683
  * The funder's pending-verdict queue, and the verdict itself.
1536
1684
  *
@@ -1611,7 +1759,17 @@ async function handleV3Claims() {
1611
1759
  // hosted twins are the handleV3* functions in src/app/api/mcp/route.ts.
1612
1760
  async function handleV3Offer(args) {
1613
1761
  const data = await apiPost("/api/escrow/v3/offer", args);
1614
- return { ...data, message: "Offer frozen and sent to the builder. They accept with their own key; you then fund with one transaction from your machine: npx cascrow-signer@0.1.0 fund --milestone " + args.milestoneId + "." };
1762
+ return { ...data, message: "Offer frozen and sent to the builder. They accept with their own key; you then fund with one transaction from your machine: npx cascrow-signer@0.2.0 fund --milestone " + args.milestoneId + "." };
1763
+ }
1764
+
1765
+ /**
1766
+ * Whether the offer an acceptance would sign names no arbiter — read from the
1767
+ * typed data that is signed (the zero address), not from the convenience field.
1768
+ * The same function in the hosted server (src/app/api/mcp/route.ts).
1769
+ */
1770
+ function noArbiter(data) {
1771
+ const a = data?.typedData?.message?.arbiter ?? data?.arbiter;
1772
+ return typeof a !== "string" || /^0x0{40}$/i.test(a);
1615
1773
  }
1616
1774
 
1617
1775
  async function handleV3AcceptanceGet({ milestoneId }) {
@@ -1620,7 +1778,9 @@ async function handleV3AcceptanceGet({ milestoneId }) {
1620
1778
  ...data,
1621
1779
  message:
1622
1780
  "These are the payer's frozen terms. Check them yourself — your signature binds you until acceptBy, and nothing in cascrow revokes it. " +
1623
- "npx cascrow-signer@0.1.0 accept --milestone " + milestoneId + " checks them against its own pinned chain and contract and signs on your machine.",
1781
+ "npx cascrow-signer@0.2.0 accept --milestone " + milestoneId + " checks them against its own pinned chain and contract and signs on your machine." +
1782
+ // S-05: the free veto, said with the terms it applies to (owner: V3_CLIENT_TEXT).
1783
+ (noArbiter(data) ? ` ${V3_CLIENT_TEXT.noArbiterVeto}` : ""),
1624
1784
  };
1625
1785
  }
1626
1786
 
@@ -1639,7 +1799,7 @@ async function handleV3Prepare({ milestoneId, action, proofHash, reasonHash, new
1639
1799
  ...data,
1640
1800
  message:
1641
1801
  "UNSIGNED transaction — cascrow does not sign, send or relay it. Before signing: rebuild data from the contract ABI, and check to and chainId against your OWN configuration, not this answer. " +
1642
- "Then broadcast it yourself and report the hash with cascrow_v3_report_tx. npx cascrow-signer@0.1.0 does all of this locally.",
1802
+ "Then broadcast it yourself and report the hash with cascrow_v3_report_tx. npx cascrow-signer@0.2.0 does all of this locally.",
1643
1803
  };
1644
1804
  }
1645
1805
 
@@ -1657,6 +1817,32 @@ async function handleV3Attestation({ escrowId, claimIndex }) {
1657
1817
  return { ...data, message: "A non-binding AI recommendation, signed by cascrow. It moves no money: only the payer's release, an unobjected claim running out its window, or the arbiter pays." };
1658
1818
  }
1659
1819
 
1820
+ /**
1821
+ * The two human-review doors — the same answers as the hosted server's
1822
+ * (src/app/api/mcp/route.ts). An open request is not an error for the agent: it has
1823
+ * nothing to do but wait, so ALREADY_OPEN comes back as a result with the deadline.
1824
+ */
1825
+ async function handleRequestHumanReview({ contractId, milestoneId, reason }) {
1826
+ try {
1827
+ const data = await apiPost("/api/human-review", { contractId, ...(milestoneId ? { milestoneId } : {}), ...(reason ? { reason } : {}) });
1828
+ return { ...data, message: `Human review requested (request ${data.requestId}). A person at cascrow decides by ${data.dueAt}; both parties receive the decision with its reasons. Read it with cascrow_human_reviews { contractId: "${contractId}" }.` };
1829
+ } catch (err) {
1830
+ if (err instanceof CascrowApiError && err.code === "HUMAN_REVIEW_ALREADY_OPEN") {
1831
+ const dueAt = err.body && typeof err.body.dueAt === "string" ? err.body.dueAt : null;
1832
+ return { status: "ALREADY_OPEN", dueAt, message: `A human review of this milestone is already open${dueAt ? `; a person at cascrow decides by ${dueAt}` : ""}. There is no need to ask again.` };
1833
+ }
1834
+ throw err;
1835
+ }
1836
+ }
1837
+
1838
+ async function handleHumanReviews({ contractId }) {
1839
+ const data = await apiGet(`/api/human-review?contractId=${encodeURIComponent(contractId)}`);
1840
+ const requests = Array.isArray(data.requests) ? data.requests : [];
1841
+ if (requests.length === 0) return { requests: [], message: "No human-review requests on this contract." };
1842
+ const lines = requests.map((r) => ` - ${r.id} [${r.status}]${r.source === "REVIEW_LAPSED" ? " (review window lapsed)" : ""} — ${r.outcome ? `outcome ${r.outcome}` : `decision due by ${r.dueAt}`}`).join("\n");
1843
+ return { ...data, message: `${requests.length} human-review request(s):\n${lines}` };
1844
+ }
1845
+
1660
1846
  async function handleReviewContract({ contractId, decision, milestoneId }) {
1661
1847
  const data = await apiPost(
1662
1848
  "/api/contracts/review",
@@ -2293,7 +2479,7 @@ async function handleWatchVerification({ milestoneId, content, filename, proofId
2293
2479
  // ─── MCP Server ───────────────────────────────────────────────────────────────
2294
2480
 
2295
2481
  const server = new Server(
2296
- { name: "cascrow", version: "3.0.0" },
2482
+ { name: "cascrow", version: "3.1.0" },
2297
2483
  { capabilities: { tools: {} } }
2298
2484
  );
2299
2485
 
@@ -2344,6 +2530,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2344
2530
  case "cascrow_get_reputation": result = await handleGetReputation(args); break;
2345
2531
  case "cascrow_discover_agents": result = await handleDiscoverAgents(args); break;
2346
2532
  case "cascrow_set_discoverable": result = await handleSetDiscoverable(args); break;
2533
+ case "cascrow_rotate_callback_secret": result = await handleRotateCallbackSecret(args); break;
2347
2534
  case "cascrow_list_my_contracts": result = await handleListMyContracts(args); break;
2348
2535
  case "cascrow_review_queue": result = await handleReviewQueue(); break;
2349
2536
  case "cascrow_v3_claims": result = await handleV3Claims(); break;
@@ -2354,6 +2541,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2354
2541
  case "cascrow_v3_report_tx": result = await handleV3ReportTx(args); break;
2355
2542
  case "cascrow_v3_attestation": result = await handleV3Attestation(args); break;
2356
2543
  case "cascrow_review_contract": result = await handleReviewContract(args); break;
2544
+ case "cascrow_request_human_review": result = await handleRequestHumanReview(args); break;
2545
+ case "cascrow_human_reviews": result = await handleHumanReviews(args); break;
2357
2546
  case "cascrow_get_proof_status": result = await handleGetProofStatus(args); break;
2358
2547
  case "cascrow_mcp_submit": result = await handleMcpSubmit(args); break;
2359
2548
  case "cascrow_verify_work": result = await handleVerifyWork(args); break;
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "cascrow-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "MCP server for Cascrow — trust infrastructure for the agent economy: AI verification, on-chain reputation, and non-custodial milestone escrow (MilestoneEscrowV3, native XRP)",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
+ "files": [
8
+ "index.js",
9
+ "README.md",
10
+ "CHANGELOG.md",
11
+ "LICENSE"
12
+ ],
7
13
  "bin": {
8
14
  "cascrow-mcp": "index.js"
9
15
  },
@@ -11,7 +17,7 @@
11
17
  "start": "node index.js"
12
18
  },
13
19
  "dependencies": {
14
- "@modelcontextprotocol/sdk": "^1.15.0"
20
+ "@modelcontextprotocol/sdk": "^1.32.1"
15
21
  },
16
22
  "keywords": [
17
23
  "mcp",
@@ -1,97 +0,0 @@
1
- # Remote HTTP MCP Server — Smithery Listing
2
-
3
- **Status: Done.** The HTTP transport described below is live at
4
- `src/app/api/mcp/route.ts` (Streamable HTTP, `@modelcontextprotocol/sdk`,
5
- `WebStandardStreamableHTTPServerTransport`), serving the same tools as this
6
- package's stdio server. `smithery.yaml` already lists both the remote URL
7
- (`https://cascrow.com/api/mcp`) and the local `npx cascrow-mcp` stdio fallback.
8
- The plan below is kept as a historical record of what was built and why —
9
- nothing in it is still pending.
10
-
11
- **Update (2026-08-03):** both this HTTP transport and the stdio server now also work
12
- without an API key — an unauthenticated caller gets a restricted server exposing only
13
- `cascrow_verify_work` (anonymous, 3/day per IP, same tier as `POST /api/verify/standalone`),
14
- instead of the transport-level 401 described below. Full 18-tool access still requires
15
- `Authorization: Bearer csk_...` exactly as documented.
16
-
17
- ## Why
18
-
19
- The current `cascrow-mcp` package runs locally via `npx` (stdio transport).
20
- That works for **Claude Desktop** only.
21
-
22
- A remote HTTP MCP server at `https://cascrow.com/mcp` would enable:
23
- - **Claude.ai Web** (browser) — which only supports remote HTTP, not stdio
24
- - **Smithery listing** under `cascrow/cascrow` with one-click install for 10k+ users
25
- - **Any MCP client** without Node.js installed
26
-
27
- ## The Use Case (no escrow needed)
28
-
29
- The primary value right now is **AI work verification**, independent of payments:
30
-
31
- - Agent does work → calls `cascrow_mcp_submit` with `amountUSD: 0`
32
- - 5 independent AI models vote on whether the work is complete
33
- - User sees: "4/5 models approved, 89% confidence"
34
- - Public certificate at `cascrow.com/proof/[hash]`
35
-
36
- This works today on testnet. No MetaMask, no wallet, no escrow required.
37
- Positions cascrow as the **trust/verification layer for AI work** before the agent economy matures.
38
-
39
- ## What Needs to Be Built
40
-
41
- ### 1. HTTP MCP Endpoint — `src/app/api/mcp/route.ts`
42
-
43
- Replace stdio transport with HTTP transport using `@modelcontextprotocol/sdk`:
44
-
45
- ```ts
46
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
47
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
48
-
49
- // Same tools as cascrow-mcp/index.js but served over HTTP
50
- // Auth: API key from Authorization header or ?api_key= query param
51
- ```
52
-
53
- The tools already exist in `cascrow-mcp/index.js` — they just need to be
54
- ported from stdio to HTTP transport. All business logic (`/api/mcp/submit`,
55
- `/api/contracts`, etc.) stays unchanged.
56
-
57
- ### 2. Smithery Registration
58
-
59
- - Create Smithery account under `cascrow` org (not personal `moeller` namespace)
60
- - Go to smithery.ai/new
61
- - Namespace: `cascrow` / Server ID: `cascrow`
62
- - MCP Server URL: `https://cascrow.com/mcp`
63
- - Result: `smithery.ai/server/cascrow/cascrow` with one-click install
64
-
65
- ### 3. Auth for Remote Connections
66
-
67
- Remote users authenticate with their cascrow API key (`csk_...`).
68
- Two options (both fine):
69
- - `Authorization: Bearer csk_...` header
70
- - `?api_key=csk_...` query param (easier for Smithery config UI)
71
-
72
- ### 4. Smithery Config Schema (update smithery.yaml)
73
-
74
- ```yaml
75
- startCommand:
76
- type: http
77
- configSchema:
78
- type: object
79
- properties:
80
- CASCROW_API_KEY:
81
- type: string
82
- description: "Your cascrow API key (csk_...)"
83
- required:
84
- - CASCROW_API_KEY
85
- url: https://cascrow.com/mcp
86
- ```
87
-
88
- ## Effort Estimate
89
-
90
- ~4–6 hours. The hard parts (tools, auth, business logic) are already done.
91
- Only the transport layer needs to change.
92
-
93
- ## When to Do This
94
-
95
- **Now** — the verification use case (amountUSD: 0) works on testnet today.
96
- Getting listed on Smithery/Claude.ai Web establishes cascrow as the
97
- verification standard before competitors enter the space.
package/server.json DELETED
@@ -1,46 +0,0 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.jnm04/cascrow-mcp",
4
- "title": "Cascrow",
5
- "description": "Independent 5-model AI verification of AI-generated work — free, no account required.",
6
- "websiteUrl": "https://cascrow.com",
7
- "repository": {
8
- "url": "https://github.com/Jnm04/Milestone-Based-Funding",
9
- "source": "github",
10
- "subfolder": "cascrow-mcp"
11
- },
12
- "version": "3.0.0",
13
- "packages": [
14
- {
15
- "registryType": "npm",
16
- "registryBaseUrl": "https://registry.npmjs.org",
17
- "identifier": "cascrow-mcp",
18
- "version": "3.0.0",
19
- "transport": {
20
- "type": "stdio"
21
- },
22
- "environmentVariables": [
23
- {
24
- "name": "CASCROW_API_KEY",
25
- "description": "Cascrow API key (csk_...). Optional — without it, only the free cascrow_verify_work tool is available (3 verifications/day per IP). Get one instantly via POST /api/agent/register, no human review required.",
26
- "isRequired": false,
27
- "isSecret": true
28
- }
29
- ]
30
- }
31
- ],
32
- "remotes": [
33
- {
34
- "type": "streamable-http",
35
- "url": "https://cascrow.com/api/mcp",
36
- "headers": [
37
- {
38
- "name": "Authorization",
39
- "description": "Bearer csk_... API key. Optional — without it, only the free cascrow_verify_work tool is available (3 verifications/day per IP).",
40
- "isRequired": false,
41
- "isSecret": true
42
- }
43
- ]
44
- }
45
- ]
46
- }
package/smithery.yaml DELETED
@@ -1,28 +0,0 @@
1
- # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2
-
3
- # Remote HTTP transport (Claude.ai web, Claude.ai API)
4
- url: https://cascrow.com/api/mcp
5
-
6
- startCommand:
7
- type: stdio
8
- configSchema:
9
- type: object
10
- properties:
11
- CASCROW_API_KEY:
12
- type: string
13
- description: "Optional. Your cascrow API key (starts with csk_) — get one at cascrow.com/register or POST /api/agent/register. Without it, the server still starts but only cascrow_verify_work is available (3 free AI-verified checks per IP per day, no account needed). Set this to unlock every other tool."
14
- pattern: "^csk_"
15
- CASCROW_BASE_URL:
16
- type: string
17
- description: "Cascrow API base URL. Leave blank to use the default (https://cascrow.com)."
18
- default: "https://cascrow.com"
19
- required: []
20
- commandFunction: |-
21
- (config) => ({
22
- command: 'npx',
23
- args: ['-y', 'cascrow-mcp'],
24
- env: {
25
- CASCROW_API_KEY: config.CASCROW_API_KEY || '',
26
- CASCROW_BASE_URL: config.CASCROW_BASE_URL || 'https://cascrow.com'
27
- }
28
- })