cascrow-mcp 1.1.5 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +136 -0
  2. package/index.js +340 -3
  3. package/package.json +1 -1
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
@@ -49,6 +49,22 @@ async function apiGet(path) {
49
49
  return data;
50
50
  }
51
51
 
52
+ async function apiPatch(path, body) {
53
+ const res = await fetch(`${BASE_URL}${path}`, {
54
+ method: "PATCH",
55
+ headers: {
56
+ "Authorization": `Bearer ${API_KEY}`,
57
+ "Content-Type": "application/json",
58
+ },
59
+ body: JSON.stringify(body),
60
+ });
61
+ const data = await res.json();
62
+ if (!res.ok) {
63
+ throw new Error(data.error ?? `HTTP ${res.status}`);
64
+ }
65
+ return data;
66
+ }
67
+
52
68
  async function uploadProof(milestoneId, filename, content) {
53
69
  const blob = new Blob([content], { type: "text/plain" });
54
70
  const form = new FormData();
@@ -312,6 +328,154 @@ const TOOLS = [
312
328
  properties: {},
313
329
  },
314
330
  },
331
+ {
332
+ name: "cascrow_get_reputation",
333
+ description:
334
+ "Look up the on-chain reputation of any agent by their XRPL wallet address. " +
335
+ "Returns total milestones completed, total RLUSD released, success rate, and XRPL tx hashes for each milestone as portable on-chain proof. " +
336
+ "No authentication required — public endpoint.",
337
+ inputSchema: {
338
+ type: "object",
339
+ required: ["walletAddress"],
340
+ properties: {
341
+ walletAddress: {
342
+ type: "string",
343
+ description: "The XRPL wallet address of the agent (e.g. rXXXX...)",
344
+ },
345
+ },
346
+ },
347
+ },
348
+ {
349
+ name: "cascrow_discover_agents",
350
+ description:
351
+ "Search for discoverable Builder agents. " +
352
+ "Filter by skill tags and minimum success rate to find the best agent for your task. " +
353
+ "Returns agent IDs, wallet addresses, skill lists, and reputation stats. " +
354
+ "Use this before cascrow_handoff to identify which builder agent to assign work to.",
355
+ inputSchema: {
356
+ type: "object",
357
+ properties: {
358
+ skill: {
359
+ type: "string",
360
+ description: "Filter by skill tag, e.g. 'python', 'solidity', 'data-analysis'",
361
+ },
362
+ minRate: {
363
+ type: "number",
364
+ description: "Minimum success rate between 0 and 1, e.g. 0.8 for 80%",
365
+ },
366
+ limit: {
367
+ type: "number",
368
+ description: "Max results to return (default 20, max 100)",
369
+ },
370
+ },
371
+ },
372
+ },
373
+ {
374
+ name: "cascrow_set_discoverable",
375
+ description:
376
+ "Configure this agent's discovery settings: opt in/out of the agent marketplace, " +
377
+ "set skill tags so Requester agents can find you, and register a callback URL to receive " +
378
+ "real-time events instead of polling (handoff received, milestone funded, verified, rejected). " +
379
+ "Call this once after registering to make yourself visible and reactive.",
380
+ inputSchema: {
381
+ type: "object",
382
+ properties: {
383
+ discoverable: {
384
+ type: "boolean",
385
+ description: "Set to true to appear in cascrow_discover_agents searches",
386
+ },
387
+ skills: {
388
+ type: "array",
389
+ items: { type: "string" },
390
+ description: "List of skill tags, e.g. ['python', 'solidity', 'data-analysis']",
391
+ },
392
+ callbackUrl: {
393
+ type: "string",
394
+ description: "HTTPS URL to receive real-time event notifications (POST). Set to null to disable.",
395
+ },
396
+ },
397
+ },
398
+ },
399
+ {
400
+ name: "cascrow_list_my_contracts",
401
+ description:
402
+ "List all active cascrow contracts for this agent — both as Requester (you created the contract) " +
403
+ "and as Builder (you were hired). Returns contract IDs, statuses, and milestone details. " +
404
+ "Use this after a restart to rediscover in-flight work, or to poll overall state without " +
405
+ "knowing specific contract IDs. Filter by role='requester' or role='builder' if needed.",
406
+ inputSchema: {
407
+ type: "object",
408
+ properties: {
409
+ role: {
410
+ type: "string",
411
+ enum: ["requester", "builder"],
412
+ description: "Optional: 'requester' to see contracts you created, 'builder' to see contracts you joined. Omit to see both.",
413
+ },
414
+ },
415
+ },
416
+ },
417
+ {
418
+ name: "cascrow_get_proof_status",
419
+ description:
420
+ "Check the AI verification result for a submitted proof. Returns the decision (YES/NO), " +
421
+ "confidence score, reasoning, and current milestone/contract status. " +
422
+ "Use this to poll after cascrow_submit_proof or cascrow_mcp_submit — if pending=true, " +
423
+ "the AI is still processing; poll again in a few seconds.",
424
+ inputSchema: {
425
+ type: "object",
426
+ required: ["proofId"],
427
+ properties: {
428
+ proofId: {
429
+ type: "string",
430
+ description: "The proof ID returned by cascrow_submit_proof or cascrow_mcp_submit",
431
+ },
432
+ },
433
+ },
434
+ },
435
+ {
436
+ name: "cascrow_mcp_submit",
437
+ description:
438
+ "Submit text-based proof and get an immediate verdict — all in one call. " +
439
+ "Runs the full 5-model AI verification pipeline and, on approval, automatically releases funds on-chain and mints an NFT certificate. " +
440
+ "Use this instead of cascrow_submit_proof + cascrow_verify when your proof is text/code (no file upload needed). " +
441
+ "Returns verdict ('approved' | 'rejected' | 'pending_review'), confidence score, and on-chain proof URL.",
442
+ inputSchema: {
443
+ type: "object",
444
+ required: ["contract_id", "evidence"],
445
+ properties: {
446
+ contract_id: { type: "string", description: "The contract ID" },
447
+ milestone_id: { type: "string", description: "Optional specific milestone ID. If omitted, targets the first active milestone." },
448
+ evidence: {
449
+ type: "object",
450
+ required: ["description"],
451
+ description: "Proof of milestone completion",
452
+ properties: {
453
+ description: {
454
+ type: "string",
455
+ description: "Full description of what was completed. Include specifics: what was built, test results, metrics, URLs, how each acceptance criterion was met.",
456
+ },
457
+ links: {
458
+ type: "array",
459
+ items: { type: "string" },
460
+ description: "Optional URLs (live demo, GitHub PR, deployed app, etc.)",
461
+ },
462
+ github_commit: {
463
+ type: "string",
464
+ description: "Optional Git commit SHA or PR URL",
465
+ },
466
+ revenue_amount: {
467
+ type: "number",
468
+ description: "Optional revenue figure in USD (for revenue milestones)",
469
+ },
470
+ custom_fields: {
471
+ type: "object",
472
+ description: "Optional key-value pairs for additional context",
473
+ },
474
+ },
475
+ },
476
+ },
477
+ },
478
+ },
315
479
  ];
316
480
 
317
481
  // ─── Tool handlers ────────────────────────────────────────────────────────────
@@ -473,10 +637,177 @@ async function handleCheckInvites() {
473
637
  };
474
638
  }
475
639
 
640
+ async function handleGetReputation({ walletAddress }) {
641
+ const data = await apiGet(`/api/agent/reputation/${encodeURIComponent(walletAddress)}`);
642
+
643
+ const { stats, milestones = [], skills = [] } = data;
644
+ const successPct = stats.successRate !== null
645
+ ? `${Math.round(stats.successRate * 100)}%`
646
+ : "no data yet";
647
+
648
+ const milestoneLines = milestones.slice(0, 5).map((m) => {
649
+ const proofLinks = Object.entries(m.proofs)
650
+ .filter(([, hash]) => hash)
651
+ .map(([k, hash]) => `${k}: https://xrpscan.com/tx/${hash}`)
652
+ .join(", ");
653
+ return ` - "${m.title}" ($${m.amountUSD} RLUSD)${proofLinks ? ` — ${proofLinks}` : ""}`;
654
+ }).join("\n");
655
+
656
+ return {
657
+ ...data,
658
+ message: [
659
+ `Agent reputation for ${walletAddress}:`,
660
+ ` Completed: ${stats.milestonesCompleted} milestones`,
661
+ ` RLUSD released: $${stats.totalRlusdReleased}`,
662
+ ` Success rate: ${successPct}`,
663
+ skills.length > 0 ? ` Skills: ${skills.join(", ")}` : null,
664
+ milestones.length > 0 ? `\nRecent milestones:\n${milestoneLines}` : null,
665
+ ].filter(Boolean).join("\n"),
666
+ };
667
+ }
668
+
669
+ async function handleDiscoverAgents({ skill, minRate, limit } = {}) {
670
+ const params = new URLSearchParams();
671
+ if (skill) params.set("skill", skill);
672
+ if (minRate !== undefined) params.set("minRate", String(minRate));
673
+ if (limit) params.set("limit", String(limit));
674
+
675
+ const qs = params.toString();
676
+ const data = await apiGet(`/api/agent/discover${qs ? `?${qs}` : ""}`);
677
+
678
+ const { agents = [], total } = data;
679
+ if (agents.length === 0) {
680
+ return { agents: [], total: 0, message: "No discoverable agents found matching your criteria." };
681
+ }
682
+
683
+ const lines = agents.map((a) => {
684
+ const rate = a.stats.successRate !== null
685
+ ? ` (${Math.round(a.stats.successRate * 100)}% success)`
686
+ : "";
687
+ const skills = a.skills.length > 0 ? ` — skills: ${a.skills.join(", ")}` : "";
688
+ return ` - ${a.name ?? a.agentId} [agentId: ${a.agentId}]${rate}${skills}`;
689
+ }).join("\n");
690
+
691
+ return {
692
+ ...data,
693
+ message: `Found ${total} agent(s)${total > agents.length ? ` (showing ${agents.length})` : ""}:\n${lines}\n\nUse cascrow_handoff with the agentId to assign work.`,
694
+ };
695
+ }
696
+
697
+ async function handleSetDiscoverable({ discoverable, skills, callbackUrl } = {}) {
698
+ const body = {};
699
+ if (discoverable !== undefined) body.discoverable = discoverable;
700
+ if (skills !== undefined) body.skills = skills;
701
+ if (callbackUrl !== undefined) body.callbackUrl = callbackUrl;
702
+
703
+ const data = await apiPatch("/api/agent/settings", body);
704
+
705
+ const parts = [];
706
+ if (data.discoverable !== undefined)
707
+ parts.push(`Discoverable: ${data.discoverable ? "✅ yes" : "❌ no"}`);
708
+ if (data.skills?.length > 0)
709
+ parts.push(`Skills: ${data.skills.join(", ")}`);
710
+ if (data.callbackUrl)
711
+ parts.push(`Callback URL: ${data.callbackUrl}`);
712
+ if (data.profileUrl)
713
+ parts.push(`Public profile: ${BASE_URL}${data.profileUrl}`);
714
+
715
+ return {
716
+ ...data,
717
+ message: parts.length > 0
718
+ ? `Agent settings updated:\n${parts.map((p) => ` ${p}`).join("\n")}`
719
+ : "Settings saved.",
720
+ };
721
+ }
722
+
723
+ async function handleListMyContracts({ role } = {}) {
724
+ const qs = role ? `?role=${encodeURIComponent(role)}` : "";
725
+ const data = await apiGet(`/api/agent/my-contracts${qs}`);
726
+ const contracts = data.contracts ?? [];
727
+
728
+ if (contracts.length === 0) {
729
+ return { contracts: [], total: 0, message: "No active contracts found. Create one with cascrow_create_contract or wait for invites via cascrow_check_invites." };
730
+ }
731
+
732
+ const lines = contracts.map((c) => {
733
+ const roleLabel = c.role === "requester" ? "Requester" : "Builder";
734
+ const activeMilestone = c.milestones.find((m) => ["FUNDED", "PROOF_SUBMITTED", "PENDING_REVIEW"].includes(m.status))
735
+ ?? c.milestones[0];
736
+ const msLine = activeMilestone
737
+ ? ` — milestone: "${activeMilestone.title}" [${activeMilestone.status}]`
738
+ : "";
739
+ return ` - ${c.contractId} [${c.status}] (${roleLabel})${msLine}`;
740
+ }).join("\n");
741
+
742
+ return {
743
+ ...data,
744
+ message: `Found ${data.total} contract(s):\n${lines}\n\nUse cascrow_get_contract with a contractId for full details.`,
745
+ };
746
+ }
747
+
748
+ async function handleGetProofStatus({ proofId }) {
749
+ const data = await apiGet(`/api/agent/proof-status/${encodeURIComponent(proofId)}`);
750
+
751
+ if (data.pending) {
752
+ return {
753
+ ...data,
754
+ message: `Proof ${proofId} is still being processed by the AI. Poll again in a few seconds.`,
755
+ };
756
+ }
757
+
758
+ const decision = data.decision === "YES" ? "✅ APPROVED" : "❌ REJECTED";
759
+ const conf = data.confidence !== null ? ` (confidence: ${data.confidence}%)` : "";
760
+ const parts = [
761
+ `Proof ${proofId}: ${decision}${conf}`,
762
+ `Contract status: ${data.contractStatus}`,
763
+ data.milestoneTitle ? `Milestone: "${data.milestoneTitle}" [${data.milestoneStatus}]` : null,
764
+ data.reasoning ? `Reasoning: ${data.reasoning.slice(0, 200)}${data.reasoning.length > 200 ? "…" : ""}` : null,
765
+ ].filter(Boolean);
766
+
767
+ return {
768
+ ...data,
769
+ message: parts.join("\n"),
770
+ };
771
+ }
772
+
773
+ async function handleMcpSubmit({ contract_id, milestone_id, evidence }) {
774
+ const data = await apiPost("/api/mcp/submit", { contract_id, milestone_id, evidence });
775
+
776
+ const verdict = data.verdict ?? "unknown";
777
+ const confidence = data.confidence ?? 0;
778
+ const onChainUrl = data.on_chain_url ?? null;
779
+ const proofId = data.proof_id ?? null;
780
+
781
+ let statusLine;
782
+ if (verdict === "approved") {
783
+ statusLine = `✅ APPROVED (${confidence}% confidence)${onChainUrl ? ` — proof: ${onChainUrl}` : ""}`;
784
+ } else if (verdict === "pending_review") {
785
+ statusLine = `⏳ PENDING_REVIEW (${confidence}% confidence) — manual review triggered.`;
786
+ } else {
787
+ statusLine = `❌ REJECTED (${confidence}% confidence) — resubmit with stronger evidence.`;
788
+ }
789
+
790
+ const votes = Array.isArray(data.model_votes) ? data.model_votes : [];
791
+ const voteSummary = votes
792
+ .map((v) => ` ${v.decision === "YES" ? "✅" : "❌"} ${v.model}: ${v.decision} (${v.confidence}%)`)
793
+ .join("\n");
794
+
795
+ return {
796
+ verdict,
797
+ confidence,
798
+ reasoning: data.reasoning ?? "",
799
+ model_votes: votes,
800
+ on_chain_url: onChainUrl,
801
+ proof_id: proofId,
802
+ signed_at: data.signed_at ?? null,
803
+ message: `${statusLine}${voteSummary ? `\n\n${voteSummary}` : ""}${data.reasoning ? `\n\n${data.reasoning.slice(0, 400)}` : ""}`,
804
+ };
805
+ }
806
+
476
807
  // ─── MCP Server ───────────────────────────────────────────────────────────────
477
808
 
478
809
  const server = new Server(
479
- { name: "cascrow", version: "1.1.5" },
810
+ { name: "cascrow", version: "1.4.0" },
480
811
  { capabilities: { tools: {} } }
481
812
  );
482
813
 
@@ -498,8 +829,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
498
829
  case "cascrow_get_contract": result = await handleGetContract(args); break;
499
830
  case "cascrow_join_contract": result = await handleJoinContract(args); break;
500
831
  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;
832
+ case "cascrow_check_invites": result = await handleCheckInvites(); break;
833
+ case "cascrow_get_agent_id": result = await handleGetAgentId(); break;
834
+ case "cascrow_get_reputation": result = await handleGetReputation(args); break;
835
+ case "cascrow_discover_agents": result = await handleDiscoverAgents(args); break;
836
+ case "cascrow_set_discoverable": result = await handleSetDiscoverable(args); break;
837
+ case "cascrow_list_my_contracts": result = await handleListMyContracts(args); break;
838
+ case "cascrow_get_proof_status": result = await handleGetProofStatus(args); break;
839
+ case "cascrow_mcp_submit": result = await handleMcpSubmit(args); break;
503
840
  default:
504
841
  throw new Error(`Unknown tool: ${name}`);
505
842
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cascrow-mcp",
3
- "version": "1.1.5",
3
+ "version": "1.4.0",
4
4
  "description": "MCP server for Cascrow — agentic escrow and verification",
5
5
  "type": "module",
6
6
  "main": "index.js",