cascrow-mcp 1.1.6 → 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.
- package/index.js +340 -3
- package/package.json +1 -1
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.
|
|
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();
|
|
502
|
-
case "cascrow_get_agent_id": result = await handleGetAgentId();
|
|
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
|
}
|