panal-mcp 0.13.1 → 0.15.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/dist/fetch-result.d.ts +5 -0
- package/dist/fetch-result.js +7 -2
- package/dist/niveles.d.ts +1 -1
- package/dist/niveles.js +2 -2
- package/dist/server.js +54 -15
- package/package.json +2 -2
package/dist/fetch-result.d.ts
CHANGED
|
@@ -84,6 +84,11 @@ export interface LimitesDelAgente {
|
|
|
84
84
|
* el agente caído y el único que nadie puede cambiar entre mirar y pagar.
|
|
85
85
|
*/
|
|
86
86
|
niveles: Nivel[];
|
|
87
|
+
/**
|
|
88
|
+
* Los encargos de ejemplo de la ficha, si trae. Salen de la misma lectura,
|
|
89
|
+
* por lo mismo. La fuente es la cadena: ver `conEjemplosDeLaFicha`.
|
|
90
|
+
*/
|
|
91
|
+
ejemplos?: string[];
|
|
87
92
|
}
|
|
88
93
|
export declare function fetchAgentLimits(botUrl: string): Promise<LimitesDelAgente>;
|
|
89
94
|
/** Descarga acotada: se corta en cuanto se pasa del tope. */
|
package/dist/fetch-result.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// implementaciones equivalentes de lo mismo —la del SDK y una aquí—, y dos
|
|
15
15
|
// copias de un control de seguridad son una que se queda atrás sin que nadie
|
|
16
16
|
// se entere el día que la otra mejora.
|
|
17
|
-
import { assertPublicUrl, leerMaxBriefChars, leerNiveles, rutaDeAgente } from '@panal/sdk';
|
|
17
|
+
import { assertPublicUrl, leerEjemplos, leerMaxBriefChars, leerNiveles, rutaDeAgente } from '@panal/sdk';
|
|
18
18
|
/** Tope de la respuesta. Sin esto, un endpoint hostil tumba el proceso. */
|
|
19
19
|
const MAX_BYTES = 512 * 1024;
|
|
20
20
|
const TIMEOUT_MS = 20_000;
|
|
@@ -129,7 +129,12 @@ export async function fetchAgentLimits(botUrl) {
|
|
|
129
129
|
return { alcanzable: true, maxBriefChars: null, niveles: [] };
|
|
130
130
|
try {
|
|
131
131
|
const card = await res.json();
|
|
132
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
alcanzable: true,
|
|
134
|
+
maxBriefChars: parseMaxBriefChars(card),
|
|
135
|
+
niveles: leerNiveles(card),
|
|
136
|
+
ejemplos: leerEjemplos(card),
|
|
137
|
+
};
|
|
133
138
|
}
|
|
134
139
|
catch {
|
|
135
140
|
return { alcanzable: true, maxBriefChars: null, niveles: [] };
|
package/dist/niveles.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare function precioDeNivel(nivel: Nivel, symbol: string): string;
|
|
|
41
41
|
* agente que vende tres tamaños se veía con un precio suelto —el más barato— y
|
|
42
42
|
* el encargo grande se contrataba al precio del pequeño.
|
|
43
43
|
*/
|
|
44
|
-
export declare function renderNiveles(niveles: Nivel[], symbol: string): string[];
|
|
44
|
+
export declare function renderNiveles(niveles: Nivel[], symbol: string, enUsd?: (wei: bigint) => string): string[];
|
|
45
45
|
/**
|
|
46
46
|
* Los niveles en UNA línea, para las listas.
|
|
47
47
|
*
|
package/dist/niveles.js
CHANGED
|
@@ -47,13 +47,13 @@ export function precioDeNivel(nivel, symbol) {
|
|
|
47
47
|
* agente que vende tres tamaños se veía con un precio suelto —el más barato— y
|
|
48
48
|
* el encargo grande se contrataba al precio del pequeño.
|
|
49
49
|
*/
|
|
50
|
-
export function renderNiveles(niveles, symbol) {
|
|
50
|
+
export function renderNiveles(niveles, symbol, enUsd = () => '') {
|
|
51
51
|
if (niveles.length === 0)
|
|
52
52
|
return [];
|
|
53
53
|
return [
|
|
54
54
|
` Sizes (${niveles.length}) — pass the one the person picks as \`tier\` to panal_quote_hire:`,
|
|
55
55
|
...niveles.map((n) => {
|
|
56
|
-
const partes = [` · ${n.name ?? '(unnamed)'} — ${precioDeNivel(n, symbol)}`];
|
|
56
|
+
const partes = [` · ${n.name ?? '(unnamed)'} — ${precioDeNivel(n, symbol)}${enUsd(n.wei)}`];
|
|
57
57
|
if (n.description)
|
|
58
58
|
partes.push(n.description);
|
|
59
59
|
if (n.maxBriefChars)
|
package/dist/server.js
CHANGED
|
@@ -42,7 +42,7 @@ import { createInterface } from 'node:readline';
|
|
|
42
42
|
import { fileURLToPath } from 'node:url';
|
|
43
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, x402Currency, } from '@panal/sdk';
|
|
45
|
+
import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, conEjemplosDeLaFicha, createPanalClient, downloadDeliveredFile, enDolares, formatearDolares, erc20Abi, leerEjemplosDeMetadata, 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';
|
|
@@ -125,6 +125,31 @@ function symbolOf(currency) {
|
|
|
125
125
|
function fmt(amount, currency) {
|
|
126
126
|
return formatUnits(amount, esNativa(currency) ? 18 : (x402Currency(currency)?.decimals ?? 18));
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Los precios en dólares, leídos como mucho cada 30 s.
|
|
130
|
+
*
|
|
131
|
+
* Solo para enseñar: lo que se firma y se bloquea sigue siendo la cantidad en
|
|
132
|
+
* su moneda. MON sale de Pyth y $PANAL de la curva de nad.fun, los dos en la
|
|
133
|
+
* cadena (ver precios.ts del SDK). Si no se pueden leer, las respuestas salen
|
|
134
|
+
* sin dólares, igual que antes.
|
|
135
|
+
*/
|
|
136
|
+
let preciosCache = { leidos: 0, valor: null };
|
|
137
|
+
async function refrescarPrecios() {
|
|
138
|
+
if (Date.now() - preciosCache.leidos < 30_000)
|
|
139
|
+
return;
|
|
140
|
+
preciosCache = { leidos: Date.now(), valor: await panal.precios().catch(() => null) };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* « (≈ $0.60)» si hay un precio de menos de cinco minutos para esa moneda;
|
|
144
|
+
* nada si no. Las estables ya son dólares y no llevan nada.
|
|
145
|
+
*/
|
|
146
|
+
function usd(amount, currency) {
|
|
147
|
+
const sym = symbolOf(currency);
|
|
148
|
+
if (sym !== 'MON' && sym !== '$PANAL')
|
|
149
|
+
return '';
|
|
150
|
+
const d = enDolares(amount, sym, preciosCache.valor);
|
|
151
|
+
return d ? ` (≈ ${formatearDolares(d.usd)})` : '';
|
|
152
|
+
}
|
|
128
153
|
/** Las monedas que este servidor sabe manejar: las que tienen presupuesto. */
|
|
129
154
|
function monedasConocidas() {
|
|
130
155
|
return [...limits.porMoneda.keys()];
|
|
@@ -234,7 +259,7 @@ function trustLine(agent) {
|
|
|
234
259
|
return ` Trust: ${partes.join('; ')}`;
|
|
235
260
|
}
|
|
236
261
|
function renderAgent(agent, niveles = [], detallado = false) {
|
|
237
|
-
const price = `${formatEther(agent.pricePerTask)} ${symbolOf(agent.currency)}`;
|
|
262
|
+
const price = `${formatEther(agent.pricePerTask)} ${symbolOf(agent.currency)}${usd(agent.pricePerTask, agent.currency)}`;
|
|
238
263
|
const skills = agent.metadata.skills.length ? agent.metadata.skills.join(', ') : '(no skills declared)';
|
|
239
264
|
return [
|
|
240
265
|
`${agent.metadata.name || '(no name)'} — ${agent.address}`,
|
|
@@ -247,7 +272,9 @@ function renderAgent(agent, niveles = [], detallado = false) {
|
|
|
247
272
|
// Los niveles van DEBAJO del precio de tarea y no en su lugar: ese precio
|
|
248
273
|
// sigue siendo el que se bloquea si nadie elige tamaño, y el que cobran
|
|
249
274
|
// los agentes —casi todos— que no venden ninguno.
|
|
250
|
-
...(detallado
|
|
275
|
+
...(detallado
|
|
276
|
+
? renderNiveles(niveles, symbolOf(agent.currency), (wei) => usd(wei, agent.currency))
|
|
277
|
+
: [lineaDeNiveles(niveles, symbolOf(agent.currency))]),
|
|
251
278
|
` Skills: ${skills}`,
|
|
252
279
|
agent.metadata.description ? ` Description: ${agent.metadata.description}` : null,
|
|
253
280
|
agent.metadata.botUrl ? ` Endpoint: ${agent.metadata.botUrl}` : null,
|
|
@@ -262,7 +289,7 @@ function renderTask(task) {
|
|
|
262
289
|
`Task #${task.id} — ${TaskStatus[task.status] ?? task.status}`,
|
|
263
290
|
` Client: ${task.client}`,
|
|
264
291
|
` Agent: ${task.worker}`,
|
|
265
|
-
` Amount: ${formatEther(task.amount)} ${symbolOf(task.currency)}`,
|
|
292
|
+
` Amount: ${formatEther(task.amount)} ${symbolOf(task.currency)}${usd(task.amount, task.currency)}`,
|
|
266
293
|
` Deadline: ${deadline}`,
|
|
267
294
|
` Job hash: ${task.taskHash}`,
|
|
268
295
|
task.resultHash && !/^0x0+$/.test(task.resultHash) ? ` Result hash: ${task.resultHash}` : null,
|
|
@@ -335,6 +362,7 @@ const READ_TOOLS = [
|
|
|
335
362
|
},
|
|
336
363
|
},
|
|
337
364
|
handler: async (args) => {
|
|
365
|
+
await refrescarPrecios();
|
|
338
366
|
const found = await panal.searchAgents(str(args.query), {
|
|
339
367
|
includeInactive: args.include_inactive === true,
|
|
340
368
|
});
|
|
@@ -352,7 +380,8 @@ const READ_TOOLS = [
|
|
|
352
380
|
},
|
|
353
381
|
{
|
|
354
382
|
name: 'panal_get_agent',
|
|
355
|
-
description: 'Full profile of a Panal agent, by address: price, the sizes it sells if it sells several, skills, status
|
|
383
|
+
description: 'Full profile of a Panal agent, by address: price, the sizes it sells if it sells several, skills, status, ' +
|
|
384
|
+
'its on-chain name and the example jobs its owner suggests.',
|
|
356
385
|
inputSchema: {
|
|
357
386
|
type: 'object',
|
|
358
387
|
properties: { address: { type: 'string', description: 'The agent 0x… address.' } },
|
|
@@ -362,7 +391,7 @@ const READ_TOOLS = [
|
|
|
362
391
|
const address = str(args.address);
|
|
363
392
|
if (!address || !isAddress(address))
|
|
364
393
|
return 'I need a valid 0x address.';
|
|
365
|
-
const agent = await panal.getAgent(address);
|
|
394
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
366
395
|
// El perfil es donde alguien mira el precio, así que aquí sí se paga la
|
|
367
396
|
// llamada al manifiesto del agente: enseñar solo el de tarea era la
|
|
368
397
|
// razón de que el precio por consulta fuese invisible desde aquí. En
|
|
@@ -382,7 +411,15 @@ const READ_TOOLS = [
|
|
|
382
411
|
? ` Per question: ${fmt(BigInt(q.amount), q.asset)} ${symbolOf(q.asset)} (x402, answered on the spot)`
|
|
383
412
|
: ' Per question: not offered (this agent only takes jobs through escrow)';
|
|
384
413
|
const niveles = nivelesDeAgente(agent.metadataURI, ficha?.niveles ?? []);
|
|
385
|
-
|
|
414
|
+
// Los encargos de ejemplo que propone su dueño: ayudan a redactar el
|
|
415
|
+
// encargo con lo que este agente hace de verdad. Son una idea, no un
|
|
416
|
+
// encargo listo — al que pide le falta añadir su material.
|
|
417
|
+
const ejemplos = conEjemplosDeLaFicha(leerEjemplosDeMetadata(agent.metadataURI), ficha?.ejemplos ?? []);
|
|
418
|
+
const lineaEjemplos = ejemplos.length > 0
|
|
419
|
+
? `\n Example jobs its owner suggests (a starting point; the brief still needs the actual material): ` +
|
|
420
|
+
ejemplos.map((e) => `"${e}"`).join(' · ')
|
|
421
|
+
: '';
|
|
422
|
+
return `${renderAgent(agent, niveles, true)}\n${porLlamada}${lineaEjemplos}`;
|
|
386
423
|
},
|
|
387
424
|
},
|
|
388
425
|
{
|
|
@@ -400,7 +437,8 @@ const READ_TOOLS = [
|
|
|
400
437
|
const count = await panal.getTaskCount();
|
|
401
438
|
if (BigInt(id) >= count)
|
|
402
439
|
return `Task #${id} does not exist: there are ${count} so far.`;
|
|
403
|
-
|
|
440
|
+
const [task] = await Promise.all([panal.getTask(BigInt(id)), refrescarPrecios()]);
|
|
441
|
+
return renderTask(task);
|
|
404
442
|
},
|
|
405
443
|
},
|
|
406
444
|
{
|
|
@@ -424,7 +462,7 @@ const READ_TOOLS = [
|
|
|
424
462
|
return 'I need a valid 0x address for the agent.';
|
|
425
463
|
if (!prompt)
|
|
426
464
|
return 'I need the question.';
|
|
427
|
-
const agent = await panal.getAgent(address);
|
|
465
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
428
466
|
const name = agent.metadata.name || address;
|
|
429
467
|
let quote;
|
|
430
468
|
try {
|
|
@@ -458,7 +496,7 @@ const READ_TOOLS = [
|
|
|
458
496
|
});
|
|
459
497
|
return [
|
|
460
498
|
`${name} charges per question:`,
|
|
461
|
-
` Price: ${fmt(amount, quote.asset)} ${symbol} for this one question`,
|
|
499
|
+
` Price: ${fmt(amount, quote.asset)} ${symbol}${usd(amount, quote.asset)} for this one question`,
|
|
462
500
|
` Paid to: ${quote.payTo}`,
|
|
463
501
|
quote.description ? ` Covers: ${quote.description}` : null,
|
|
464
502
|
'',
|
|
@@ -506,7 +544,7 @@ const READ_TOOLS = [
|
|
|
506
544
|
inputSchema: { type: 'object', properties: {} },
|
|
507
545
|
handler: async () => {
|
|
508
546
|
try {
|
|
509
|
-
const lista = await panal.listBoard();
|
|
547
|
+
const [lista] = await Promise.all([panal.listBoard(), refrescarPrecios()]);
|
|
510
548
|
if (!lista.length)
|
|
511
549
|
return 'The board is empty: no job is waiting to be taken right now.';
|
|
512
550
|
const ahora = Math.floor(Date.now() / 1000);
|
|
@@ -514,7 +552,7 @@ const READ_TOOLS = [
|
|
|
514
552
|
`${lista.length} job(s) on the board:`,
|
|
515
553
|
...lista.map((e) => {
|
|
516
554
|
const horas = Math.max(0, Math.floor((Number(e.deadline) - ahora) / 3600));
|
|
517
|
-
return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}, ${horas} h left\n` +
|
|
555
|
+
return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}${usd(e.amount, e.currency)}, ${horas} h left\n` +
|
|
518
556
|
` "${e.anuncio}"\n posted by ${e.cliente}`);
|
|
519
557
|
}),
|
|
520
558
|
'\nTo take one, use panal_claim_task with its number. Only an active registered agent can claim.',
|
|
@@ -552,7 +590,8 @@ const WRITE_TOOLS = [
|
|
|
552
590
|
// perdido — y también hay que preguntarlo moneda a moneda.
|
|
553
591
|
Promise.all(monedas.map((m) => panal.getPendingWithdrawal(address, m).catch(() => null))),
|
|
554
592
|
]);
|
|
555
|
-
|
|
593
|
+
await refrescarPrecios();
|
|
594
|
+
const importe = (v, m) => v === null ? 'could not be read' : `${fmt(v, m)}${v > 0n ? usd(v, m) : ''}`;
|
|
556
595
|
const balances = monedas.map((m, i) => ` ${symbolOf(m)}: ${importe(saldos[i] ?? null, m)}`);
|
|
557
596
|
// Un presupuesto por moneda: MON y $PANAL no valen lo mismo y no hay
|
|
558
597
|
// tipo de cambio, así que se enseñan por separado o no se entienden.
|
|
@@ -621,7 +660,7 @@ const WRITE_TOOLS = [
|
|
|
621
660
|
if (rutas.length > MAX_ADJUNTOS) {
|
|
622
661
|
return `That is ${rutas.length} attachments and the limit is ${MAX_ADJUNTOS} per job.`;
|
|
623
662
|
}
|
|
624
|
-
const agent = await panal.getAgent(address);
|
|
663
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
625
664
|
if (!agent.active)
|
|
626
665
|
return `${agent.metadata.name || address} is delisted and does not take jobs.`;
|
|
627
666
|
// ---- Adjuntos -------------------------------------------------------
|
|
@@ -766,7 +805,7 @@ const WRITE_TOOLS = [
|
|
|
766
805
|
});
|
|
767
806
|
return [
|
|
768
807
|
`Quote for ${quote.agentName}:`,
|
|
769
|
-
` Price: ${formatEther(amount)} ${symbol}${elegido ? ` — size "${elegido.name}"` : ''}`,
|
|
808
|
+
` Price: ${formatEther(amount)} ${symbol}${usd(amount, agent.currency)}${elegido ? ` — size "${elegido.name}"` : ''}`,
|
|
770
809
|
// Los otros tamaños se enseñan SIEMPRE que existan, también cuando ya
|
|
771
810
|
// se eligió uno: quien está leyendo esto es quien paga, y enterarse de
|
|
772
811
|
// que había uno más grande después de contratar no sirve de nada.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panal-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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.21.0",
|
|
34
34
|
"viem": "^2.21.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|