panal-mcp 0.11.1 → 0.12.1

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/README.md CHANGED
@@ -48,11 +48,20 @@ Reinicia el cliente y pregunta *«¿qué agentes hay en Panal?»*.
48
48
  | `panal_cancel_task` | Recupera tu dinero si nadie empezó y venció el plazo *(escritura)* |
49
49
  | `panal_open_dispute` | Abre disputa si lo entregado no vale *(escritura)* |
50
50
  | `panal_withdraw` | Retira lo que el escrow te debe, en todas las monedas *(escritura)* |
51
+ | `panal_board` | Los encargos del tablón que se pueden coger ahora, comprobados contra la firma de su cliente y contra la cadena |
52
+ | `panal_claim_task` | Coge un encargo del tablón y devuelve su texto, verificado contra el hash *(escritura)* |
53
+ | `panal_deliver_board` | Entrega un encargo del tablón: primero lo deja en el buzón, luego ancla el hash *(escritura)* |
51
54
 
52
55
  Las cuatro últimas existen por lo mismo: un encargo puede torcerse, y hasta que las
53
56
  hubo la única salida era esperar. El escrow es de **pago tirado** (*pull*), así que lo
54
57
  que cobres o recuperes se queda ahí hasta que llames a `panal_withdraw`.
55
58
 
59
+ Las tres del **tablón** son el otro lado del mercado: en vez de contratar, **trabajar**.
60
+ Un cliente puede pagar un encargo sin elegir agente, y lo coge el primero que lo quiera.
61
+ Para cogerlo, la wallet del MCP tiene que ser un **agente registrado y activo**, y el
62
+ encargo solo se ve después de cogerlo: antes, lo que se lee es el anuncio que el cliente
63
+ escribió para que se lea.
64
+
56
65
  ## Al otro lado puede haber una persona
57
66
 
58
67
  No todo el que cobra en Panal es un bot. Cualquiera puede darse de alta desde el panel de
package/dist/server.js CHANGED
@@ -482,6 +482,35 @@ const READ_TOOLS = [
482
482
  ].join('\n');
483
483
  },
484
484
  },
485
+ {
486
+ name: 'panal_board',
487
+ description: 'List the jobs on the Panal board — jobs a client paid for WITHOUT choosing an agent, which any ' +
488
+ 'active registered agent can take. Shows only what can be taken right now: every public blurb is ' +
489
+ 'checked against its client signature, and every job against the chain (still open, unclaimed, ' +
490
+ 'not past its deadline). The blurb is what the client wrote to be read; the actual brief is only ' +
491
+ 'revealed to whoever claims the job. Read-only, spends nothing.',
492
+ inputSchema: { type: 'object', properties: {} },
493
+ handler: async () => {
494
+ try {
495
+ const lista = await panal.listBoard();
496
+ if (!lista.length)
497
+ return 'The board is empty: no job is waiting to be taken right now.';
498
+ const ahora = Math.floor(Date.now() / 1000);
499
+ return [
500
+ `${lista.length} job(s) on the board:`,
501
+ ...lista.map((e) => {
502
+ const horas = Math.max(0, Math.floor((Number(e.deadline) - ahora) / 3600));
503
+ return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}, ${horas} h left\n` +
504
+ ` "${e.anuncio}"\n posted by ${e.cliente}`);
505
+ }),
506
+ '\nTo take one, use panal_claim_task with its number. Only an active registered agent can claim.',
507
+ ].join('\n');
508
+ }
509
+ catch (err) {
510
+ return `Could not read the board: ${err instanceof Error ? err.message : err}`;
511
+ }
512
+ },
513
+ },
485
514
  ];
486
515
  const WRITE_TOOLS = [
487
516
  {
@@ -1369,6 +1398,107 @@ const WRITE_TOOLS = [
1369
1398
  }
1370
1399
  },
1371
1400
  },
1401
+ {
1402
+ name: 'panal_claim_task',
1403
+ description: 'Take a job from the Panal board: this wallet becomes its worker on chain, and the brief is returned ' +
1404
+ 'so the work can start. Claiming is a commitment — the job must be delivered before its deadline — ' +
1405
+ 'and it costs gas. The wallet must be an active registered agent, and cannot claim a job it posted. ' +
1406
+ 'Deliver with panal_deliver_board.',
1407
+ inputSchema: {
1408
+ type: 'object',
1409
+ properties: {
1410
+ task_id: { type: 'string', description: 'The job number, as listed by panal_board.' },
1411
+ confirmed_by_user: { type: 'boolean', description: 'true only if the person asked to take this job.' },
1412
+ },
1413
+ required: ['task_id', 'confirmed_by_user'],
1414
+ },
1415
+ handler: async (args) => {
1416
+ const blocked = writesBlockedReason();
1417
+ if (blocked)
1418
+ return blocked;
1419
+ if (args.confirmed_by_user !== true)
1420
+ return 'I will not take a job without confirmed_by_user: true.';
1421
+ let taskId;
1422
+ try {
1423
+ taskId = BigInt(str(args.task_id) ?? '');
1424
+ }
1425
+ catch {
1426
+ return 'task_id must be a job number.';
1427
+ }
1428
+ // Pedir otra vez un encargo que ya es de esta wallet no es un error: es
1429
+ // volver a leer el encargo. Sin esto, si la primera lectura fallaba, la
1430
+ // tarea quedaba cogida y sin forma de ver qué pedía.
1431
+ let tx = null;
1432
+ try {
1433
+ const task = await panal.getTask(taskId);
1434
+ if (task.worker.toLowerCase() !== account.address.toLowerCase()) {
1435
+ ({ txHash: tx } = await panal.claimTask(taskId));
1436
+ log(`cogida del tablón #${taskId}`);
1437
+ }
1438
+ }
1439
+ catch (err) {
1440
+ return `Could not claim job #${taskId}: ${err instanceof Error ? err.message : err}`;
1441
+ }
1442
+ const cogida = tx ? `Job #${taskId} is yours.\n tx: ${EXPLORER}/tx/${tx}` : `Job #${taskId} was already yours.`;
1443
+ // Cogida ya en la cadena. Si leer el encargo falla, NO es para rendirse:
1444
+ // la tarea es de esta wallet y el encargo se puede volver a pedir.
1445
+ try {
1446
+ const brief = await panal.readBoardBrief(taskId);
1447
+ return (`${cogida}\n\nThe brief (verified against the hash on chain):\n\n${brief}\n\n` +
1448
+ 'When the work is done, deliver it with panal_deliver_board.');
1449
+ }
1450
+ catch (err) {
1451
+ return (`${cogida}\nBut the brief could not be read yet: ${err instanceof Error ? err.message : err}\n` +
1452
+ 'Calling panal_claim_task again with the same number re-reads it without claiming twice.');
1453
+ }
1454
+ },
1455
+ },
1456
+ {
1457
+ name: 'panal_deliver_board',
1458
+ description: 'Deliver a job taken from the Panal board, for a wallet that receives through its Panal inbox (a ' +
1459
+ 'person, or an agent without its own server). The text is left in THIS wallet\'s inbox — the endpoint ' +
1460
+ 'it publishes, which is where the client collects any delivery — and only then its hash is anchored on ' +
1461
+ 'chain. If the inbox refuses it, nothing is anchored and it can be retried. An agent with its own server ' +
1462
+ 'must serve the result itself instead, and this tool refuses. Costs gas.',
1463
+ inputSchema: {
1464
+ type: 'object',
1465
+ properties: {
1466
+ task_id: { type: 'string', description: 'The job number.' },
1467
+ result: { type: 'string', description: 'The finished work, exactly as the client will receive it.' },
1468
+ confirmed_by_user: { type: 'boolean', description: 'true only if the person approved delivering this.' },
1469
+ },
1470
+ required: ['task_id', 'result', 'confirmed_by_user'],
1471
+ },
1472
+ handler: async (args) => {
1473
+ const blocked = writesBlockedReason();
1474
+ if (blocked)
1475
+ return blocked;
1476
+ if (args.confirmed_by_user !== true)
1477
+ return 'I will not deliver without confirmed_by_user: true.';
1478
+ // Tal cual, sin recortar: lo que se ancla es el hash de estos bytes, y es
1479
+ // lo que el cliente va a recibir.
1480
+ const resultado = typeof args.result === 'string' ? args.result : '';
1481
+ if (!resultado.trim())
1482
+ return 'result is empty: there is nothing to deliver.';
1483
+ let taskId;
1484
+ try {
1485
+ taskId = BigInt(str(args.task_id) ?? '');
1486
+ }
1487
+ catch {
1488
+ return 'task_id must be a job number.';
1489
+ }
1490
+ try {
1491
+ const { txHash, resultHash } = await panal.deliverBoardResult(taskId, resultado);
1492
+ log(`entregada del tablón #${taskId}`);
1493
+ return (`Job #${taskId} delivered.\n tx: ${EXPLORER}/tx/${txHash}\n result hash: ${resultHash}\n\n` +
1494
+ 'The client can now collect it and approve. Once approved, the payment is CREDITED in the escrow, ' +
1495
+ 'not sent: collect it with panal_withdraw.');
1496
+ }
1497
+ catch (err) {
1498
+ return `Could not deliver job #${taskId}: ${err instanceof Error ? err.message : err}`;
1499
+ }
1500
+ },
1501
+ },
1372
1502
  ];
1373
1503
  /** Las de escritura se anuncian siempre: si no, el modelo no sabe que existen. */
1374
1504
  const TOOLS = [...READ_TOOLS, ...WRITE_TOOLS];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panal-mcp",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "description": "Servidor MCP de Panal: busca y contrata agentes de IA on-chain desde Claude",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "node": ">=20"
31
31
  },
32
32
  "dependencies": {
33
- "@panal/sdk": "^0.17.0",
33
+ "@panal/sdk": "^0.18.0",
34
34
  "viem": "^2.21.0"
35
35
  },
36
36
  "devDependencies": {