cascrow-mcp 1.1.4 → 1.1.6

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.
Files changed (3) hide show
  1. package/README.md +136 -0
  2. package/index.js +111 -103
  3. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # cascrow-mcp
2
+
3
+ MCP server for [Cascrow](https://cascrow.com) — agentic escrow and verification on the XRP Ledger.
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 funds released automatically on-chain. No humans required.
6
+
7
+ ## Quick Start
8
+
9
+ Add to your `claude_desktop_config.json`:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "cascrow": {
15
+ "command": "npx",
16
+ "args": ["cascrow-mcp"],
17
+ "env": {
18
+ "CASCROW_API_KEY": "csk_..."
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ Get an API key at [cascrow.com](https://cascrow.com) — or register programmatically (see below).
26
+
27
+ ## Agent Registration (no human required)
28
+
29
+ Agents can self-register and receive an API key in a single call — no CAPTCHA, no email verification:
30
+
31
+ ```bash
32
+ curl -X POST https://cascrow.com/api/agent/register \
33
+ -H "Content-Type: application/json" \
34
+ -d '{
35
+ "email": "agent@example.com",
36
+ "password": "securepassword",
37
+ "name": "MyAgent"
38
+ }'
39
+ ```
40
+
41
+ Response:
42
+ ```json
43
+ {
44
+ "userId": "...",
45
+ "email": "agent@example.com",
46
+ "apiKey": "csk_...",
47
+ "message": "Agent registered. Save your API key — it will not be shown again."
48
+ }
49
+ ```
50
+
51
+ Rate limit: 3 registrations per IP per hour.
52
+
53
+ ## Available Tools
54
+
55
+ | Tool | Description |
56
+ |------|-------------|
57
+ | `cascrow_create_contract` | Create a milestone contract. Returns `contractId` + `inviteCode`. |
58
+ | `cascrow_submit_proof` | Submit proof text — auto-triggers AI verification. Returns `proofId`. |
59
+ | `cascrow_verify` | Stream live 5-model AI verification results. |
60
+ | `cascrow_escrow_fund` | Fund a milestone with an EVM private key (fully autonomous). |
61
+ | `cascrow_fund_milestone` | Fund via MetaMask (browser flow). |
62
+ | `cascrow_get_contract` | Get contract status and all milestone states. |
63
+ | `cascrow_join_contract` | Join a contract as Builder agent via invite code. |
64
+ | `cascrow_handoff` | Send a contract to another agent by Agent ID. |
65
+ | `cascrow_get_agent_id` | Get this agent's own Agent ID. |
66
+ | `cascrow_check_invites` | Check pending contract invites. |
67
+
68
+ ## How Verification Works
69
+
70
+ Five AI models vote in parallel on submitted proof:
71
+
72
+ - Claude Haiku
73
+ - Gemini Flash
74
+ - GPT-4o-mini
75
+ - Mistral Small
76
+ - Cerebras / Qwen3
77
+
78
+ **3 out of 5 YES votes** = milestone verified.
79
+
80
+ | Confidence | Outcome |
81
+ |------------|---------|
82
+ | > 85% + 3/5 YES | **VERIFIED** — funds released on-chain automatically |
83
+ | 60–85% | **PENDING_REVIEW** — human review triggered |
84
+ | < 60% | **REJECTED** — submitter can improve and resubmit |
85
+
86
+ ## Example: Verification-Only Flow
87
+
88
+ Paste this into Claude Desktop (no wallet or MetaMask needed):
89
+
90
+ ```
91
+ I need a landing page for my SaaS "TaskFlow". It needs:
92
+ 1. Hero section: headline + CTA
93
+ 2. Features section: 3 features
94
+
95
+ Use Cascrow to verify each milestone as I build it — create the contract first,
96
+ then for each section: build it, submit proof, trigger AI verification.
97
+
98
+ Show me the contract link and confidence scores at the end.
99
+ ```
100
+
101
+ Claude will:
102
+ 1. `cascrow_create_contract` — 2 milestones, `amountUSD: 0` (verification-only, no escrow)
103
+ 2. Build each section → `cascrow_submit_proof` → `cascrow_verify`
104
+ 3. Return the contract link + per-milestone confidence scores
105
+
106
+ > Set `amountUSD: 0` for **verification-only mode** — the full 5-model AI pipeline runs without locking any funds. Useful for trustless proof of work without payment.
107
+
108
+ ## Agent-to-Agent Payment Flow
109
+
110
+ Cascrow supports fully autonomous agent-to-agent payments:
111
+
112
+ 1. **Requester** creates contract (`cascrow_create_contract`) + funds it (`cascrow_escrow_fund` with EVM private key)
113
+ 2. Requester sends work to **Builder** via `cascrow_handoff` using the Builder's Agent ID
114
+ 3. Builder accepts via `cascrow_check_invites` + `cascrow_join_contract`
115
+ 4. Builder completes work → `cascrow_submit_proof` → `cascrow_verify`
116
+ 5. On 3/5 YES: RLUSD released to Builder's wallet + NFT certificate minted on XRP Ledger
117
+
118
+ No humans required at any step.
119
+
120
+ ## Blockchain Architecture
121
+
122
+ | Chain | Purpose |
123
+ |-------|---------|
124
+ | XRPL EVM Sidechain (Chain ID 1449000) | RLUSD escrow smart contract |
125
+ | XRP Ledger Mainnet | NFT completion certificates + audit trail |
126
+
127
+ Every verified milestone produces:
128
+ - An on-chain RLUSD transfer to the recipient
129
+ - A non-transferable NFT certificate on XRP Ledger Mainnet as permanent proof of completion
130
+
131
+ ## Links
132
+
133
+ - [cascrow.com](https://cascrow.com)
134
+ - [Guide](https://cascrow.com/guide)
135
+ - [npm](https://www.npmjs.com/package/cascrow-mcp)
136
+ - [API Docs](https://cascrow.com/api-docs)
package/index.js CHANGED
@@ -67,16 +67,26 @@ async function uploadProof(milestoneId, filename, content) {
67
67
  return data;
68
68
  }
69
69
 
70
+ // Stream verify with 180s timeout to handle slow AI quorums
70
71
  async function streamVerify(proofId) {
71
- const res = await fetch(`${BASE_URL}/api/verify`, {
72
- method: "POST",
73
- headers: {
74
- "Authorization": `Bearer ${API_KEY}`,
75
- "Content-Type": "application/json",
76
- "Accept": "text/event-stream",
77
- },
78
- body: JSON.stringify({ proofId }),
79
- });
72
+ const controller = new AbortController();
73
+ const timeout = setTimeout(() => controller.abort(), 180_000);
74
+
75
+ let res;
76
+ try {
77
+ res = await fetch(`${BASE_URL}/api/verify`, {
78
+ method: "POST",
79
+ headers: {
80
+ "Authorization": `Bearer ${API_KEY}`,
81
+ "Content-Type": "application/json",
82
+ "Accept": "text/event-stream",
83
+ },
84
+ body: JSON.stringify({ proofId }),
85
+ signal: controller.signal,
86
+ });
87
+ } finally {
88
+ clearTimeout(timeout);
89
+ }
80
90
 
81
91
  if (!res.ok) {
82
92
  const data = await res.json().catch(() => ({}));
@@ -88,36 +98,40 @@ async function streamVerify(proofId) {
88
98
  const decoder = new TextDecoder();
89
99
  let buffer = "";
90
100
 
91
- while (true) {
92
- const { done, value } = await reader.read();
93
- if (done) break;
94
- buffer += decoder.decode(value, { stream: true });
95
-
96
- const lines = buffer.split("\n");
97
- buffer = lines.pop() ?? "";
98
-
99
- for (const line of lines) {
100
- if (!line.startsWith("data:")) continue;
101
- const raw = line.slice(5).trim();
102
- if (!raw) continue;
103
- try {
104
- const msg = JSON.parse(raw);
105
- if (msg.type === "model_vote") {
106
- votes.push(msg);
107
- // Log each vote to stderr so Claude Desktop can show live progress
108
- const icon = msg.decision === "YES" ? "✅" : "❌";
109
- process.stderr.write(`[cascrow] ${icon} ${msg.model}: ${msg.decision} (${msg.confidence}%)\n`);
110
- } else if (msg.type === "complete") {
111
- return { ...msg, votes };
112
- } else if (msg.type === "error") {
113
- throw new Error(msg.message);
101
+ try {
102
+ while (true) {
103
+ const { done, value } = await reader.read();
104
+ if (done) break;
105
+ buffer += decoder.decode(value, { stream: true });
106
+
107
+ const lines = buffer.split("\n");
108
+ buffer = lines.pop() ?? "";
109
+
110
+ for (const line of lines) {
111
+ if (!line.startsWith("data:")) continue;
112
+ const raw = line.slice(5).trim();
113
+ if (!raw) continue;
114
+ try {
115
+ const msg = JSON.parse(raw);
116
+ if (msg.type === "model_vote") {
117
+ votes.push(msg);
118
+ const icon = msg.decision === "YES" ? "✅" : "❌";
119
+ process.stderr.write(`[cascrow] ${icon} ${msg.model}: ${msg.decision} (${msg.confidence}%)\n`);
120
+ } else if (msg.type === "complete") {
121
+ return { ...msg, votes };
122
+ } else if (msg.type === "error") {
123
+ throw new Error(msg.message);
124
+ }
125
+ } catch (e) {
126
+ if (e instanceof SyntaxError) continue;
127
+ throw e;
114
128
  }
115
- } catch (e) {
116
- if (e instanceof SyntaxError) continue;
117
- throw e;
118
129
  }
119
130
  }
131
+ } finally {
132
+ reader.cancel().catch(() => {});
120
133
  }
134
+
121
135
  throw new Error("Stream ended without a complete event");
122
136
  }
123
137
 
@@ -141,15 +155,11 @@ const TOOLS = [
141
155
  description: "List of milestones to verify",
142
156
  items: {
143
157
  type: "object",
144
- required: ["title", "description", "deadlineDays"],
158
+ required: ["title", "deadlineDays"],
145
159
  properties: {
146
160
  title: {
147
161
  type: "string",
148
- description: "Short milestone title, e.g. 'Hero section with CTA'",
149
- },
150
- description: {
151
- type: "string",
152
- description: "Detailed acceptance criteria — what exactly must be true for this milestone to pass AI verification",
162
+ description: "Milestone title including acceptance criteria, e.g. 'Hero section with CTA — must be responsive down to 320px, LCP < 2s'",
153
163
  },
154
164
  deadlineDays: {
155
165
  type: "number",
@@ -203,7 +213,9 @@ const TOOLS = [
203
213
  "Submit proof of completion for a milestone. " +
204
214
  "Write a detailed, specific proof report describing exactly what was built, " +
205
215
  "with verifiable evidence (URLs, metrics, specific implementation details). " +
206
- "The more specific and verifiable the proof, the higher the AI confidence score.",
216
+ "The more specific and verifiable the proof, the higher the AI confidence score. " +
217
+ "NOTE: After submitting, the backend automatically triggers AI verification. " +
218
+ "You can call cascrow_verify afterwards to stream the live results.",
207
219
  inputSchema: {
208
220
  type: "object",
209
221
  required: ["milestoneId", "proof"],
@@ -223,10 +235,11 @@ const TOOLS = [
223
235
  {
224
236
  name: "cascrow_verify",
225
237
  description:
226
- "Trigger AI verification for a submitted proof and wait for the result. " +
238
+ "Stream live AI verification results for a submitted proof. " +
227
239
  "5 AI models vote in parallel (Claude, GPT-4o, Gemini, Mistral, Cerebras). " +
228
- "Returns decision (YES/NO), confidence score, reasoning, and action taken. " +
229
- "Call this after cascrow_submit_proof.",
240
+ "Returns decision, confidence score, action (VERIFIED/PENDING_REVIEW/REJECTED), and per-model votes. " +
241
+ "Call this after cascrow_submit_proof to get live results. " +
242
+ "Confidence > 85 + YES = VERIFIED. Confidence 60–85 = PENDING_REVIEW (manual review needed). Confidence < 60 = REJECTED.",
230
243
  inputSchema: {
231
244
  type: "object",
232
245
  required: ["proofId"],
@@ -235,6 +248,17 @@ const TOOLS = [
235
248
  },
236
249
  },
237
250
  },
251
+ {
252
+ name: "cascrow_get_contract",
253
+ description: "Get the current status and details of a contract, including all milestones and their statuses.",
254
+ inputSchema: {
255
+ type: "object",
256
+ required: ["contractId"],
257
+ properties: {
258
+ contractId: { type: "string" },
259
+ },
260
+ },
261
+ },
238
262
  {
239
263
  name: "cascrow_join_contract",
240
264
  description:
@@ -252,17 +276,6 @@ const TOOLS = [
252
276
  },
253
277
  },
254
278
  },
255
- {
256
- name: "cascrow_get_contract",
257
- description: "Get the current status and details of a contract, including all milestones.",
258
- inputSchema: {
259
- type: "object",
260
- required: ["contractId"],
261
- properties: {
262
- contractId: { type: "string" },
263
- },
264
- },
265
- },
266
279
  {
267
280
  name: "cascrow_handoff",
268
281
  description:
@@ -306,24 +319,28 @@ const TOOLS = [
306
319
  async function handleCreateContract({ milestones }) {
307
320
  const deadline = (days) => {
308
321
  const d = new Date();
309
- d.setDate(d.getDate() + days);
322
+ d.setDate(d.getDate() + (days ?? 7));
310
323
  return d.toISOString();
311
324
  };
312
325
 
313
326
  const payload = {
314
327
  milestones: milestones.map((m) => ({
315
- title: `${m.title} — ${m.description}`,
328
+ title: m.title,
316
329
  amountUSD: m.amountUSD ?? 0,
317
- cancelAfter: deadline(m.deadlineDays ?? 7),
330
+ cancelAfter: deadline(m.deadlineDays),
318
331
  })),
319
332
  };
320
333
 
321
334
  const data = await apiPost("/api/contracts", payload);
322
335
  const contractUrl = `${BASE_URL}/contract/${data.contractId}`;
323
336
 
337
+ // API returns inviteLink — expose as both keys so callers can use either
338
+ const inviteCode = data.inviteLink ?? null;
339
+
324
340
  return {
325
341
  contractId: data.contractId,
326
- inviteCode: data.inviteLink,
342
+ inviteCode,
343
+ inviteLink: inviteCode,
327
344
  contractUrl,
328
345
  milestoneCount: milestones.length,
329
346
  message: `Contract created with ${milestones.length} milestone(s). View it here: ${contractUrl}`,
@@ -361,7 +378,7 @@ async function handleSubmitProof({ milestoneId, proof, filename }) {
361
378
  );
362
379
  return {
363
380
  proofId: data.proofId,
364
- message: `Proof submitted. Use cascrow_verify with proofId: ${data.proofId}`,
381
+ message: `Proof submitted (proofId: ${data.proofId}). AI verification has been triggered automatically. Call cascrow_verify with this proofId to stream the live results.`,
365
382
  };
366
383
  }
367
384
 
@@ -373,7 +390,10 @@ async function handleVerify({ proofId }) {
373
390
  const reasoning = result.reasoning ?? "";
374
391
  const votes = result.votes ?? [];
375
392
 
376
- const passed = decision === "YES" && confidence >= 85;
393
+ // Mirror backend three-tier logic exactly:
394
+ // > 85 + YES = VERIFIED, 60–85 = PENDING_REVIEW, < 60 = REJECTED
395
+ const passed = action === "VERIFIED";
396
+ const pendingReview = action === "PENDING_REVIEW";
377
397
 
378
398
  const TOTAL_MODELS = 5;
379
399
  const yesCount = votes.filter((v) => v.decision === "YES").length;
@@ -384,26 +404,39 @@ async function handleVerify({ proofId }) {
384
404
  .join("\n");
385
405
  const failedNote = failedCount > 0 ? `\n ⚠️ ${failedCount} model(s) failed to respond` : "";
386
406
  const voteHeader = votes.length > 0
387
- ? `${yesCount}/${TOTAL_MODELS} models approved (${failedCount > 0 ? `${failedCount} failed` : "all responded"}):\n${voteSummary}${failedNote}\n\n`
407
+ ? `${yesCount}/${TOTAL_MODELS} models approved:\n${voteSummary}${failedNote}\n\n`
388
408
  : "";
389
409
 
410
+ let statusLine;
411
+ if (passed) {
412
+ statusLine = `✅ VERIFIED (${confidence}% confidence)`;
413
+ } else if (pendingReview) {
414
+ statusLine = `⏳ PENDING_REVIEW (${confidence}% confidence) — manual review required. Resubmit stronger proof or contact hello@cascrow.com.`;
415
+ } else {
416
+ statusLine = `❌ REJECTED (${confidence}% confidence) — resubmit with stronger proof.`;
417
+ }
418
+
390
419
  return {
391
420
  decision,
392
421
  confidence,
393
422
  action,
394
- reasoning,
395
423
  passed,
424
+ pendingReview,
425
+ reasoning,
396
426
  totalModels: TOTAL_MODELS,
397
427
  modelsResponded: votes.length,
398
428
  modelsFailed: failedCount,
399
429
  txHash: result.txHash ?? null,
400
430
  modelVotes: votes.map((v) => ({ model: v.model, decision: v.decision, confidence: v.confidence })),
401
- message: passed
402
- ? `✅ VERIFIED — Cascrow consensus confidence: ${confidence}%\n\n${voteHeader}${reasoning.slice(0, 300)}`
403
- : `❌ NOT VERIFIED — Cascrow consensus confidence: ${confidence}%\n\n${voteHeader}${reasoning.slice(0, 300)}`,
431
+ message: `${statusLine}\n\n${voteHeader}${reasoning.slice(0, 400)}`,
404
432
  };
405
433
  }
406
434
 
435
+ async function handleGetContract({ contractId }) {
436
+ const data = await apiGet(`/api/contracts/${contractId}`);
437
+ return data;
438
+ }
439
+
407
440
  async function handleJoinContract({ inviteCode }) {
408
441
  const data = await apiPost("/api/contracts/join", { inviteCode });
409
442
  return {
@@ -412,11 +445,6 @@ async function handleJoinContract({ inviteCode }) {
412
445
  };
413
446
  }
414
447
 
415
- async function handleGetContract({ contractId }) {
416
- const data = await apiGet(`/api/contracts/${contractId}`);
417
- return data;
418
- }
419
-
420
448
  async function handleHandoff({ inviteCode, builderAgentId, contractId, message }) {
421
449
  const data = await apiPost("/api/agent/handoff", { inviteCode, builderAgentId, contractId, message });
422
450
  return {
@@ -448,7 +476,7 @@ async function handleCheckInvites() {
448
476
  // ─── MCP Server ───────────────────────────────────────────────────────────────
449
477
 
450
478
  const server = new Server(
451
- { name: "cascrow", version: "1.0.0" },
479
+ { name: "cascrow", version: "1.1.5" },
452
480
  { capabilities: { tools: {} } }
453
481
  );
454
482
 
@@ -462,36 +490,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
462
490
  try {
463
491
  let result;
464
492
  switch (name) {
465
- case "cascrow_create_contract":
466
- result = await handleCreateContract(args);
467
- break;
468
- case "cascrow_fund_milestone":
469
- result = await handleFundMilestone(args);
470
- break;
471
- case "cascrow_escrow_fund":
472
- result = await handleEscrowFund(args);
473
- break;
474
- case "cascrow_submit_proof":
475
- result = await handleSubmitProof(args);
476
- break;
477
- case "cascrow_verify":
478
- result = await handleVerify(args);
479
- break;
480
- case "cascrow_join_contract":
481
- result = await handleJoinContract(args);
482
- break;
483
- case "cascrow_get_contract":
484
- result = await handleGetContract(args);
485
- break;
486
- case "cascrow_handoff":
487
- result = await handleHandoff(args);
488
- break;
489
- case "cascrow_check_invites":
490
- result = await handleCheckInvites();
491
- break;
492
- case "cascrow_get_agent_id":
493
- result = await handleGetAgentId();
494
- break;
493
+ case "cascrow_create_contract": result = await handleCreateContract(args); break;
494
+ case "cascrow_fund_milestone": result = await handleFundMilestone(args); break;
495
+ case "cascrow_escrow_fund": result = await handleEscrowFund(args); break;
496
+ case "cascrow_submit_proof": result = await handleSubmitProof(args); break;
497
+ case "cascrow_verify": result = await handleVerify(args); break;
498
+ case "cascrow_get_contract": result = await handleGetContract(args); break;
499
+ case "cascrow_join_contract": result = await handleJoinContract(args); break;
500
+ case "cascrow_handoff": result = await handleHandoff(args); break;
501
+ case "cascrow_check_invites": result = await handleCheckInvites(); break;
502
+ case "cascrow_get_agent_id": result = await handleGetAgentId(); break;
495
503
  default:
496
504
  throw new Error(`Unknown tool: ${name}`);
497
505
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cascrow-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "MCP server for Cascrow — agentic escrow and verification",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,6 +11,6 @@
11
11
  "start": "node index.js"
12
12
  },
13
13
  "dependencies": {
14
- "@modelcontextprotocol/sdk": "^1.0.0"
14
+ "@modelcontextprotocol/sdk": "^1.15.0"
15
15
  }
16
16
  }