cascrow-mcp 1.1.4 → 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 +111 -103
- 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,15 +155,11 @@ 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",
|
|
@@ -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
|
-
"
|
|
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
|
|
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:
|
|
328
|
+
title: m.title,
|
|
316
329
|
amountUSD: m.amountUSD ?? 0,
|
|
317
|
-
cancelAfter: deadline(m.deadlineDays
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
case "
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
case "
|
|
472
|
-
|
|
473
|
-
break;
|
|
474
|
-
case "
|
|
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.
|
|
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
|
}
|