cascrow-mcp 1.1.3 → 1.1.5
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 +116 -104
- package/package.json +2 -2
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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,20 +155,20 @@ const TOOLS = [
|
|
|
141
155
|
description: "List of milestones to verify",
|
|
142
156
|
items: {
|
|
143
157
|
type: "object",
|
|
144
|
-
required: ["title", "
|
|
158
|
+
required: ["title", "deadlineDays"],
|
|
145
159
|
properties: {
|
|
146
160
|
title: {
|
|
147
161
|
type: "string",
|
|
148
|
-
description: "
|
|
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",
|
|
156
166
|
description: "Days from now until deadline (e.g. 7)",
|
|
157
167
|
},
|
|
168
|
+
amountUSD: {
|
|
169
|
+
type: "number",
|
|
170
|
+
description: "Optional RLUSD amount to lock in escrow (e.g. 1.0). Omit or set to 0 for verification-only.",
|
|
171
|
+
},
|
|
158
172
|
},
|
|
159
173
|
},
|
|
160
174
|
},
|
|
@@ -199,7 +213,9 @@ const TOOLS = [
|
|
|
199
213
|
"Submit proof of completion for a milestone. " +
|
|
200
214
|
"Write a detailed, specific proof report describing exactly what was built, " +
|
|
201
215
|
"with verifiable evidence (URLs, metrics, specific implementation details). " +
|
|
202
|
-
"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.",
|
|
203
219
|
inputSchema: {
|
|
204
220
|
type: "object",
|
|
205
221
|
required: ["milestoneId", "proof"],
|
|
@@ -219,10 +235,11 @@ const TOOLS = [
|
|
|
219
235
|
{
|
|
220
236
|
name: "cascrow_verify",
|
|
221
237
|
description:
|
|
222
|
-
"
|
|
238
|
+
"Stream live AI verification results for a submitted proof. " +
|
|
223
239
|
"5 AI models vote in parallel (Claude, GPT-4o, Gemini, Mistral, Cerebras). " +
|
|
224
|
-
"Returns decision
|
|
225
|
-
"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.",
|
|
226
243
|
inputSchema: {
|
|
227
244
|
type: "object",
|
|
228
245
|
required: ["proofId"],
|
|
@@ -231,6 +248,17 @@ const TOOLS = [
|
|
|
231
248
|
},
|
|
232
249
|
},
|
|
233
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
|
+
},
|
|
234
262
|
{
|
|
235
263
|
name: "cascrow_join_contract",
|
|
236
264
|
description:
|
|
@@ -248,17 +276,6 @@ const TOOLS = [
|
|
|
248
276
|
},
|
|
249
277
|
},
|
|
250
278
|
},
|
|
251
|
-
{
|
|
252
|
-
name: "cascrow_get_contract",
|
|
253
|
-
description: "Get the current status and details of a contract, including all milestones.",
|
|
254
|
-
inputSchema: {
|
|
255
|
-
type: "object",
|
|
256
|
-
required: ["contractId"],
|
|
257
|
-
properties: {
|
|
258
|
-
contractId: { type: "string" },
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
279
|
{
|
|
263
280
|
name: "cascrow_handoff",
|
|
264
281
|
description:
|
|
@@ -302,24 +319,28 @@ const TOOLS = [
|
|
|
302
319
|
async function handleCreateContract({ milestones }) {
|
|
303
320
|
const deadline = (days) => {
|
|
304
321
|
const d = new Date();
|
|
305
|
-
d.setDate(d.getDate() + days);
|
|
322
|
+
d.setDate(d.getDate() + (days ?? 7));
|
|
306
323
|
return d.toISOString();
|
|
307
324
|
};
|
|
308
325
|
|
|
309
326
|
const payload = {
|
|
310
327
|
milestones: milestones.map((m) => ({
|
|
311
|
-
title:
|
|
312
|
-
amountUSD:
|
|
313
|
-
cancelAfter: deadline(m.deadlineDays
|
|
328
|
+
title: m.title,
|
|
329
|
+
amountUSD: m.amountUSD ?? 0,
|
|
330
|
+
cancelAfter: deadline(m.deadlineDays),
|
|
314
331
|
})),
|
|
315
332
|
};
|
|
316
333
|
|
|
317
334
|
const data = await apiPost("/api/contracts", payload);
|
|
318
335
|
const contractUrl = `${BASE_URL}/contract/${data.contractId}`;
|
|
319
336
|
|
|
337
|
+
// API returns inviteLink — expose as both keys so callers can use either
|
|
338
|
+
const inviteCode = data.inviteLink ?? null;
|
|
339
|
+
|
|
320
340
|
return {
|
|
321
341
|
contractId: data.contractId,
|
|
322
|
-
inviteCode
|
|
342
|
+
inviteCode,
|
|
343
|
+
inviteLink: inviteCode,
|
|
323
344
|
contractUrl,
|
|
324
345
|
milestoneCount: milestones.length,
|
|
325
346
|
message: `Contract created with ${milestones.length} milestone(s). View it here: ${contractUrl}`,
|
|
@@ -357,7 +378,7 @@ async function handleSubmitProof({ milestoneId, proof, filename }) {
|
|
|
357
378
|
);
|
|
358
379
|
return {
|
|
359
380
|
proofId: data.proofId,
|
|
360
|
-
message: `Proof submitted
|
|
381
|
+
message: `Proof submitted (proofId: ${data.proofId}). AI verification has been triggered automatically. Call cascrow_verify with this proofId to stream the live results.`,
|
|
361
382
|
};
|
|
362
383
|
}
|
|
363
384
|
|
|
@@ -369,7 +390,10 @@ async function handleVerify({ proofId }) {
|
|
|
369
390
|
const reasoning = result.reasoning ?? "";
|
|
370
391
|
const votes = result.votes ?? [];
|
|
371
392
|
|
|
372
|
-
|
|
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";
|
|
373
397
|
|
|
374
398
|
const TOTAL_MODELS = 5;
|
|
375
399
|
const yesCount = votes.filter((v) => v.decision === "YES").length;
|
|
@@ -380,26 +404,39 @@ async function handleVerify({ proofId }) {
|
|
|
380
404
|
.join("\n");
|
|
381
405
|
const failedNote = failedCount > 0 ? `\n ⚠️ ${failedCount} model(s) failed to respond` : "";
|
|
382
406
|
const voteHeader = votes.length > 0
|
|
383
|
-
? `${yesCount}/${TOTAL_MODELS} models approved
|
|
407
|
+
? `${yesCount}/${TOTAL_MODELS} models approved:\n${voteSummary}${failedNote}\n\n`
|
|
384
408
|
: "";
|
|
385
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
|
+
|
|
386
419
|
return {
|
|
387
420
|
decision,
|
|
388
421
|
confidence,
|
|
389
422
|
action,
|
|
390
|
-
reasoning,
|
|
391
423
|
passed,
|
|
424
|
+
pendingReview,
|
|
425
|
+
reasoning,
|
|
392
426
|
totalModels: TOTAL_MODELS,
|
|
393
427
|
modelsResponded: votes.length,
|
|
394
428
|
modelsFailed: failedCount,
|
|
395
429
|
txHash: result.txHash ?? null,
|
|
396
430
|
modelVotes: votes.map((v) => ({ model: v.model, decision: v.decision, confidence: v.confidence })),
|
|
397
|
-
message:
|
|
398
|
-
? `✅ VERIFIED — Cascrow consensus confidence: ${confidence}%\n\n${voteHeader}${reasoning.slice(0, 300)}`
|
|
399
|
-
: `❌ NOT VERIFIED — Cascrow consensus confidence: ${confidence}%\n\n${voteHeader}${reasoning.slice(0, 300)}`,
|
|
431
|
+
message: `${statusLine}\n\n${voteHeader}${reasoning.slice(0, 400)}`,
|
|
400
432
|
};
|
|
401
433
|
}
|
|
402
434
|
|
|
435
|
+
async function handleGetContract({ contractId }) {
|
|
436
|
+
const data = await apiGet(`/api/contracts/${contractId}`);
|
|
437
|
+
return data;
|
|
438
|
+
}
|
|
439
|
+
|
|
403
440
|
async function handleJoinContract({ inviteCode }) {
|
|
404
441
|
const data = await apiPost("/api/contracts/join", { inviteCode });
|
|
405
442
|
return {
|
|
@@ -408,11 +445,6 @@ async function handleJoinContract({ inviteCode }) {
|
|
|
408
445
|
};
|
|
409
446
|
}
|
|
410
447
|
|
|
411
|
-
async function handleGetContract({ contractId }) {
|
|
412
|
-
const data = await apiGet(`/api/contracts/${contractId}`);
|
|
413
|
-
return data;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
448
|
async function handleHandoff({ inviteCode, builderAgentId, contractId, message }) {
|
|
417
449
|
const data = await apiPost("/api/agent/handoff", { inviteCode, builderAgentId, contractId, message });
|
|
418
450
|
return {
|
|
@@ -444,7 +476,7 @@ async function handleCheckInvites() {
|
|
|
444
476
|
// ─── MCP Server ───────────────────────────────────────────────────────────────
|
|
445
477
|
|
|
446
478
|
const server = new Server(
|
|
447
|
-
{ name: "cascrow", version: "1.
|
|
479
|
+
{ name: "cascrow", version: "1.1.5" },
|
|
448
480
|
{ capabilities: { tools: {} } }
|
|
449
481
|
);
|
|
450
482
|
|
|
@@ -458,36 +490,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
458
490
|
try {
|
|
459
491
|
let result;
|
|
460
492
|
switch (name) {
|
|
461
|
-
case "cascrow_create_contract":
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
case "
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
case "
|
|
468
|
-
|
|
469
|
-
break;
|
|
470
|
-
case "
|
|
471
|
-
result = await handleSubmitProof(args);
|
|
472
|
-
break;
|
|
473
|
-
case "cascrow_verify":
|
|
474
|
-
result = await handleVerify(args);
|
|
475
|
-
break;
|
|
476
|
-
case "cascrow_join_contract":
|
|
477
|
-
result = await handleJoinContract(args);
|
|
478
|
-
break;
|
|
479
|
-
case "cascrow_get_contract":
|
|
480
|
-
result = await handleGetContract(args);
|
|
481
|
-
break;
|
|
482
|
-
case "cascrow_handoff":
|
|
483
|
-
result = await handleHandoff(args);
|
|
484
|
-
break;
|
|
485
|
-
case "cascrow_check_invites":
|
|
486
|
-
result = await handleCheckInvites();
|
|
487
|
-
break;
|
|
488
|
-
case "cascrow_get_agent_id":
|
|
489
|
-
result = await handleGetAgentId();
|
|
490
|
-
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;
|
|
491
503
|
default:
|
|
492
504
|
throw new Error(`Unknown tool: ${name}`);
|
|
493
505
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cascrow-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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.
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.15.0"
|
|
15
15
|
}
|
|
16
16
|
}
|