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.
Files changed (2) hide show
  1. package/index.js +116 -104
  2. 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 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,20 +155,20 @@ 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",
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
- "Trigger AI verification for a submitted proof and wait for the result. " +
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 (YES/NO), confidence score, reasoning, and action taken. " +
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: `${m.title} — ${m.description}`,
312
- amountUSD: 0, // verification-only — $0.10 charged per verification trigger
313
- cancelAfter: deadline(m.deadlineDays ?? 7),
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: data.inviteLink,
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. 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.`,
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
- 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";
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 (${failedCount > 0 ? `${failedCount} failed` : "all responded"}):\n${voteSummary}${failedNote}\n\n`
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: passed
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.0.0" },
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
- result = await handleCreateContract(args);
463
- break;
464
- case "cascrow_fund_milestone":
465
- result = await handleFundMilestone(args);
466
- break;
467
- case "cascrow_escrow_fund":
468
- result = await handleEscrowFund(args);
469
- break;
470
- case "cascrow_submit_proof":
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",
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.0.0"
14
+ "@modelcontextprotocol/sdk": "^1.15.0"
15
15
  }
16
16
  }