panal-mcp 0.12.0 → 0.13.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/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/limits.d.ts +1 -1
- package/dist/limits.js +17 -0
- package/dist/server.js +37 -21
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Panal and Kreno (founder: Gustavo Chura C.)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -143,6 +143,12 @@ Las herramientas de escritura mueven dinero real en mainnet, así que están **a
|
|
|
143
143
|
| `MCP_DAILY_BUDGET_WEI` | `5e18` (5 MON) | Tope por día UTC **en MON**, persistido en disco |
|
|
144
144
|
| `MCP_MAX_PER_TASK_PANAL_WEI` | `1e18` (1 $PANAL) | Tope por encargo, **en $PANAL** |
|
|
145
145
|
| `MCP_DAILY_BUDGET_PANAL_WEI` | `5e18` (5 $PANAL) | Tope por día UTC **en $PANAL** |
|
|
146
|
+
| `MCP_MAX_PER_TASK_GHO_UNITS` | `1e18` (1 GHO) | Tope por consulta **en GHO** |
|
|
147
|
+
| `MCP_DAILY_BUDGET_GHO_UNITS` | `5e18` (5 GHO) | Tope por día UTC **en GHO** |
|
|
148
|
+
| `MCP_MAX_PER_TASK_USDC_UNITS` | `1e6` (1 USDC) | Tope por consulta **en USDC** (6 decimales) |
|
|
149
|
+
| `MCP_DAILY_BUDGET_USDC_UNITS` | `5e6` (5 USDC) | Tope por día UTC **en USDC** |
|
|
150
|
+
| `MCP_MAX_PER_TASK_AUSD_UNITS` | `1e6` (1 AUSD) | Tope por consulta **en AUSD** (6 decimales) |
|
|
151
|
+
| `MCP_DAILY_BUDGET_AUSD_UNITS` | `5e6` (5 AUSD) | Tope por día UTC **en AUSD** |
|
|
146
152
|
| `MCP_TASK_DEADLINE_HOURS` | `24` | Plazo de entrega |
|
|
147
153
|
| `MCP_SPEND_FILE` | `.panal-mcp/spend.json` | Dónde se guarda el gasto del día |
|
|
148
154
|
| `MCP_ATTACH_DIR` | el directorio de trabajo | Única carpeta desde la que se puede adjuntar |
|
package/dist/limits.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* servidor, y nunca en el prompt: un prompt se puede negociar, un `if` no.
|
|
7
7
|
*/
|
|
8
8
|
import type { Address } from 'viem';
|
|
9
|
-
import type
|
|
9
|
+
import { type AttachedFile } from '@panal/sdk';
|
|
10
10
|
/**
|
|
11
11
|
* Las dos maneras de pagar a un agente, y no son intercambiables.
|
|
12
12
|
*
|
package/dist/limits.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { dirname, resolve } from 'node:path';
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { x402Currencies } from '@panal/sdk';
|
|
11
12
|
/** Vida de un presupuesto. Corto a propósito: el precio del agente puede cambiar. */
|
|
12
13
|
const QUOTE_TTL_MS = 5 * 60 * 1000;
|
|
13
14
|
/**
|
|
@@ -193,5 +194,21 @@ export function limitsFromEnv() {
|
|
|
193
194
|
envMaxPerTask: 'MCP_MAX_PER_TASK_PANAL_WEI',
|
|
194
195
|
envDailyBudget: 'MCP_DAILY_BUDGET_PANAL_WEI',
|
|
195
196
|
});
|
|
197
|
+
// Las estables con las que se paga por consulta: GHO, USDC y AUSD. Cada una
|
|
198
|
+
// con su cuenta, en SUS unidades mínimas (6 decimales en USDC y AUSD): 1 por
|
|
199
|
+
// consulta y 5 al día de partida, que es un dólar y cinco.
|
|
200
|
+
for (const c of x402Currencies()) {
|
|
201
|
+
// $PANAL y MON ya tienen las suyas, las de siempre.
|
|
202
|
+
if (c.address.toLowerCase() === PANAL || c.scheme === 'native-transfer')
|
|
203
|
+
continue;
|
|
204
|
+
const nombre = c.symbol.replace(/^\$/, '');
|
|
205
|
+
const unidad = 10n ** BigInt(c.decimals);
|
|
206
|
+
porMoneda.set(clave(c.address), {
|
|
207
|
+
maxPerTaskWei: parse(`MCP_MAX_PER_TASK_${nombre}_UNITS`, unidad),
|
|
208
|
+
dailyBudgetWei: parse(`MCP_DAILY_BUDGET_${nombre}_UNITS`, 5n * unidad),
|
|
209
|
+
envMaxPerTask: `MCP_MAX_PER_TASK_${nombre}_UNITS`,
|
|
210
|
+
envDailyBudget: `MCP_DAILY_BUDGET_${nombre}_UNITS`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
196
213
|
return { deadlineHours: Number.isFinite(hours) && hours > 0 ? hours : 24, porMoneda };
|
|
197
214
|
}
|
package/dist/server.js
CHANGED
|
@@ -40,9 +40,9 @@ import { readFileSync } from 'node:fs';
|
|
|
40
40
|
import { dirname, resolve } from 'node:path';
|
|
41
41
|
import { createInterface } from 'node:readline';
|
|
42
42
|
import { fileURLToPath } from 'node:url';
|
|
43
|
-
import { formatEther, isAddress, keccak256, parseEther, toBytes } from 'viem';
|
|
43
|
+
import { formatEther, formatUnits, isAddress, keccak256, parseEther, toBytes } from 'viem';
|
|
44
44
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
45
|
-
import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, createPanalClient, downloadDeliveredFile, erc20Abi, matchAttachment, parseAttachmentsManifest, parseFilesManifest, stripFilesManifest, } from '@panal/sdk';
|
|
45
|
+
import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, createPanalClient, downloadDeliveredFile, erc20Abi, matchAttachment, parseAttachmentsManifest, parseFilesManifest, stripFilesManifest, x402Currency, } from '@panal/sdk';
|
|
46
46
|
import { MAX_ADJUNTOS, capacidadesDeAgente, guardarDescarga, leerAdjuntoLocal, raizAdjuntos, raizDescargas, subirAdjunto, } from './adjuntos.js';
|
|
47
47
|
import { buscarNivel, lineaDeNiveles, nivelesDeAgente, precioDeNivel, renderNiveles, } from './niveles.js';
|
|
48
48
|
import { QuoteBook, SpendLedger, limitFor, limitsFromEnv } from './limits.js';
|
|
@@ -108,8 +108,22 @@ function writesBlockedReason() {
|
|
|
108
108
|
function esNativa(currency) {
|
|
109
109
|
return currency.toLowerCase() === NATIVE_CURRENCY.toLowerCase();
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* El nombre de una moneda, de la lista de Panal. Nunca el que trae la
|
|
113
|
+
* cotización: lo escribe el agente, y con un nombre a su gusto se podía cobrar
|
|
114
|
+
* en un token enseñando otro. Lo que no está en la lista se nombra por su
|
|
115
|
+
* dirección, que es lo único que se sabe de verdad.
|
|
116
|
+
*/
|
|
111
117
|
function symbolOf(currency) {
|
|
112
|
-
return esNativa(currency) ? 'MON' :
|
|
118
|
+
return esNativa(currency) ? 'MON' : (x402Currency(currency)?.symbol ?? currency);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Un importe en unidades mínimas, con los decimales de SU moneda: 18 en MON,
|
|
122
|
+
* $PANAL y GHO, 6 en USDC y AUSD. Con `formatEther` para todo, un millón de
|
|
123
|
+
* unidades de USDC —un dólar— salía como 0,000000000001.
|
|
124
|
+
*/
|
|
125
|
+
function fmt(amount, currency) {
|
|
126
|
+
return formatUnits(amount, esNativa(currency) ? 18 : (x402Currency(currency)?.decimals ?? 18));
|
|
113
127
|
}
|
|
114
128
|
/** Las monedas que este servidor sabe manejar: las que tienen presupuesto. */
|
|
115
129
|
function monedasConocidas() {
|
|
@@ -152,16 +166,16 @@ function revisarPresupuesto(currency, amount, symbol) {
|
|
|
152
166
|
const lim = limitFor(limits, currency);
|
|
153
167
|
if (!lim) {
|
|
154
168
|
return (`That agent charges in a token this server has no budget for (${currency}). ` +
|
|
155
|
-
|
|
169
|
+
`Caps are configured for ${monedasConocidas().map(symbolOf).join(', ')}; there is no exchange rate to reuse another one.`);
|
|
156
170
|
}
|
|
157
171
|
if (amount > lim.maxPerTaskWei) {
|
|
158
|
-
return (`That costs ${
|
|
159
|
-
`${
|
|
172
|
+
return (`That costs ${fmt(amount, currency)} ${symbol} and the per-item cap for ${symbol} is ` +
|
|
173
|
+
`${fmt(lim.maxPerTaskWei, currency)}. Raise ${lim.envMaxPerTask} if that is intended.`);
|
|
160
174
|
}
|
|
161
175
|
const spent = ledger.spentToday(currency);
|
|
162
176
|
if (spent + amount > lim.dailyBudgetWei) {
|
|
163
|
-
return (`That would blow today's ${symbol} budget: ${
|
|
164
|
-
`${
|
|
177
|
+
return (`That would blow today's ${symbol} budget: ${fmt(spent, currency)} of ` +
|
|
178
|
+
`${fmt(lim.dailyBudgetWei, currency)} spent and this costs ${fmt(amount, currency)} ${symbol}. ` +
|
|
165
179
|
`Raise ${lim.envDailyBudget} if that is intended.`);
|
|
166
180
|
}
|
|
167
181
|
return null;
|
|
@@ -365,7 +379,7 @@ const READ_TOOLS = [
|
|
|
365
379
|
: Promise.resolve(null),
|
|
366
380
|
]);
|
|
367
381
|
const porLlamada = q
|
|
368
|
-
? ` Per question: ${
|
|
382
|
+
? ` Per question: ${fmt(BigInt(q.amount), q.asset)} ${symbolOf(q.asset)} (x402, answered on the spot)`
|
|
369
383
|
: ' Per question: not offered (this agent only takes jobs through escrow)';
|
|
370
384
|
const niveles = nivelesDeAgente(agent.metadataURI, ficha?.niveles ?? []);
|
|
371
385
|
return `${renderAgent(agent, niveles, true)}\n${porLlamada}`;
|
|
@@ -425,7 +439,7 @@ const READ_TOOLS = [
|
|
|
425
439
|
`per task, via panal_quote_hire.`);
|
|
426
440
|
}
|
|
427
441
|
const amount = BigInt(quote.amount);
|
|
428
|
-
const symbol =
|
|
442
|
+
const symbol = symbolOf(quote.asset);
|
|
429
443
|
const saved = quotes.issue({
|
|
430
444
|
kind: 'ask',
|
|
431
445
|
worker: agent.address,
|
|
@@ -444,7 +458,7 @@ const READ_TOOLS = [
|
|
|
444
458
|
});
|
|
445
459
|
return [
|
|
446
460
|
`${name} charges per question:`,
|
|
447
|
-
` Price: ${
|
|
461
|
+
` Price: ${fmt(amount, quote.asset)} ${symbol} for this one question`,
|
|
448
462
|
` Paid to: ${quote.payTo}`,
|
|
449
463
|
quote.description ? ` Covers: ${quote.description}` : null,
|
|
450
464
|
'',
|
|
@@ -538,23 +552,23 @@ const WRITE_TOOLS = [
|
|
|
538
552
|
// perdido — y también hay que preguntarlo moneda a moneda.
|
|
539
553
|
Promise.all(monedas.map((m) => panal.getPendingWithdrawal(address, m).catch(() => null))),
|
|
540
554
|
]);
|
|
541
|
-
const importe = (v) => (v === null ? 'could not be read' :
|
|
542
|
-
const balances = monedas.map((m, i) => ` ${symbolOf(m)}: ${importe(saldos[i] ?? null)}`);
|
|
555
|
+
const importe = (v, m) => (v === null ? 'could not be read' : fmt(v, m));
|
|
556
|
+
const balances = monedas.map((m, i) => ` ${symbolOf(m)}: ${importe(saldos[i] ?? null, m)}`);
|
|
543
557
|
// Un presupuesto por moneda: MON y $PANAL no valen lo mismo y no hay
|
|
544
558
|
// tipo de cambio, así que se enseñan por separado o no se entienden.
|
|
545
559
|
const presupuestos = monedas.map((moneda) => {
|
|
546
560
|
const lim = limitFor(limits, moneda);
|
|
547
561
|
const spent = ledger.spentToday(moneda);
|
|
548
562
|
const left = lim.dailyBudgetWei > spent ? lim.dailyBudgetWei - spent : 0n;
|
|
549
|
-
return (` ${symbolOf(moneda)}: per job ${
|
|
550
|
-
`${
|
|
563
|
+
return (` ${symbolOf(moneda)}: per job ${fmt(lim.maxPerTaskWei, moneda)} · today ${fmt(spent, moneda)} of ` +
|
|
564
|
+
`${fmt(lim.dailyBudgetWei, moneda)} spent · ${fmt(left, moneda)} left`);
|
|
551
565
|
});
|
|
552
566
|
// Solo se nombra lo que hay que cobrar, o lo que no se pudo comprobar. Un
|
|
553
567
|
// «0» por cada moneda sería ruido en la respuesta más leída del servidor.
|
|
554
568
|
const enEscrow = monedas
|
|
555
|
-
.map((m, i) => ({ sym: symbolOf(m), v: pendientes[i] ?? null }))
|
|
569
|
+
.map((m, i) => ({ m, sym: symbolOf(m), v: pendientes[i] ?? null }))
|
|
556
570
|
.filter(({ v }) => v === null || v > 0n)
|
|
557
|
-
.map(({ sym, v }) => ` ${sym}: ${importe(v)}`);
|
|
571
|
+
.map(({ m, sym, v }) => ` ${sym}: ${importe(v, m)}`);
|
|
558
572
|
return [
|
|
559
573
|
`Wallet: ${address}`,
|
|
560
574
|
'Balance:',
|
|
@@ -826,7 +840,7 @@ const WRITE_TOOLS = [
|
|
|
826
840
|
// Se registra lo REALMENTE pagado, y en SU moneda: un gasto anotado en
|
|
827
841
|
// el contador equivocado agota un presupuesto que nadie tocó.
|
|
828
842
|
ledger.record(quote.currency, res.paid);
|
|
829
|
-
log(`consulta pagada a ${quote.worker}: ${
|
|
843
|
+
log(`consulta pagada a ${quote.worker}: ${fmt(res.paid, quote.currency)} ${quote.symbol}`);
|
|
830
844
|
return [
|
|
831
845
|
`Paid ${formatEther(res.paid)} ${quote.symbol} to ${quote.agentName}.`,
|
|
832
846
|
res.txHash ? ` tx: ${EXPLORER}/tx/${res.txHash}` : null,
|
|
@@ -1455,9 +1469,11 @@ const WRITE_TOOLS = [
|
|
|
1455
1469
|
},
|
|
1456
1470
|
{
|
|
1457
1471
|
name: 'panal_deliver_board',
|
|
1458
|
-
description: 'Deliver a job taken from the Panal board
|
|
1459
|
-
'
|
|
1460
|
-
'
|
|
1472
|
+
description: 'Deliver a job taken from the Panal board, for a wallet that receives through its Panal inbox (a ' +
|
|
1473
|
+
'person, or an agent without its own server). The text is left in THIS wallet\'s inbox — the endpoint ' +
|
|
1474
|
+
'it publishes, which is where the client collects any delivery — and only then its hash is anchored on ' +
|
|
1475
|
+
'chain. If the inbox refuses it, nothing is anchored and it can be retried. An agent with its own server ' +
|
|
1476
|
+
'must serve the result itself instead, and this tool refuses. Costs gas.',
|
|
1461
1477
|
inputSchema: {
|
|
1462
1478
|
type: 'object',
|
|
1463
1479
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panal-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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.
|
|
33
|
+
"@panal/sdk": "^0.19.0",
|
|
34
34
|
"viem": "^2.21.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|