@prismnetwork/mcp 0.6.0 → 0.7.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/README.md +5 -0
- package/package.json +1 -1
- package/server.mjs +77 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ real prices. Leasing spends money, so those tools ask for a wallet and say so.
|
|
|
22
22
|
| `prism_receipts` | no |
|
|
23
23
|
| `prism_wallet` | yes |
|
|
24
24
|
| `prism_leases` | yes |
|
|
25
|
+
| `prism_infer` | yes |
|
|
25
26
|
| `prism_lease_and_run` | yes |
|
|
26
27
|
| `prism_lease` | yes |
|
|
27
28
|
| `prism_run` | yes |
|
|
@@ -39,6 +40,10 @@ real prices. Leasing spends money, so those tools ask for a wallet and say so.
|
|
|
39
40
|
- `prism_receipts`: recent settled receipts from the public proof feed, with the
|
|
40
41
|
settlement transaction on Robinhood Chain.
|
|
41
42
|
- `prism_leases`: this wallet's leases and their state.
|
|
43
|
+
- `prism_infer`: buy one LLM generation from the managed inference endpoint,
|
|
44
|
+
paying the quoted USDG price from this wallet (about 0.01 USDG). Waits
|
|
45
|
+
through a cold start; an unconsumed payment is kept and reused on the next
|
|
46
|
+
call instead of paying twice.
|
|
42
47
|
- `prism_lease_and_run`: lease a GPU, run a command, return the output (one shot).
|
|
43
48
|
- `prism_lease`: lease a GPU and keep it; returns a `lease_id` and SSH access.
|
|
44
49
|
- `prism_run`: run a command on an existing lease.
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -82,6 +82,9 @@ async function publicJson(url, what) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const leases = new Map();
|
|
85
|
+
// Unconsumed inference payments, keyed by endpoint and price, so a failed
|
|
86
|
+
// generation is retried with the same paid header instead of paying again.
|
|
87
|
+
const pendingInference = new Map();
|
|
85
88
|
// null in, null out: a missing price must never render as 0.000000 USDG.
|
|
86
89
|
const usdg = (micros) =>
|
|
87
90
|
micros == null || !Number.isFinite(Number(micros)) ? null : `${(Number(micros) / 1e6).toFixed(6)} USDG`;
|
|
@@ -168,6 +171,19 @@ const TOOLS = [
|
|
|
168
171
|
required: ["lease_id"],
|
|
169
172
|
},
|
|
170
173
|
},
|
|
174
|
+
{
|
|
175
|
+
name: "prism_infer",
|
|
176
|
+
description: "Buy one LLM generation from Prism's managed inference endpoint. Pays the quoted USDG price from this wallet (about 0.01 USDG), waits through a cold start when no box is warm (up to a few minutes), and returns the generation with token usage. Cheaper and simpler than leasing when all you need is a completion.",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
prompt: { type: "string", description: "The prompt to generate from (max 32 KiB)." },
|
|
181
|
+
model: { type: "string", description: "Model to use; defaults to the endpoint's first offered model." },
|
|
182
|
+
max_usdg: { type: "number", description: "Refuse if the quoted price exceeds this (default 0.05)." },
|
|
183
|
+
},
|
|
184
|
+
required: ["prompt"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
171
187
|
{
|
|
172
188
|
name: "prism_lease_and_run",
|
|
173
189
|
description: "Lease a GPU, run one shell command on it, and return the output. The lease stays alive (use prism_run for more commands, prism_end_lease to release). Prefer this for a single command; use prism_lease when you'll run several.",
|
|
@@ -393,6 +409,66 @@ async function handle(name, args) {
|
|
|
393
409
|
const id = leaseId(args.lease_id);
|
|
394
410
|
return { lease_id: id, result: await agent.result(id) };
|
|
395
411
|
}
|
|
412
|
+
if (name === "prism_infer") {
|
|
413
|
+
if (typeof args.prompt !== "string" || args.prompt.trim() === "") {
|
|
414
|
+
throw new Error("prompt is required.");
|
|
415
|
+
}
|
|
416
|
+
requireWallet(name);
|
|
417
|
+
const base = (process.env.PRISM_INFERENCE_URL ?? "https://api.prismnetwork.tech/inference").replace(/\/$/, "");
|
|
418
|
+
const offer = await publicJson(`${base}/v1/models`, "inference endpoint");
|
|
419
|
+
const model = args.model ?? offer.models?.[0];
|
|
420
|
+
if (!model || (Array.isArray(offer.models) && !offer.models.includes(model))) {
|
|
421
|
+
throw new Error(`model must be one of ${offer.models?.join(", ") ?? "(endpoint offered none)"}`);
|
|
422
|
+
}
|
|
423
|
+
const price = BigInt(offer.price_micros ?? 0);
|
|
424
|
+
const cap = args.max_usdg ?? 0.05;
|
|
425
|
+
if (typeof cap !== "number" || !Number.isFinite(cap) || cap <= 0) {
|
|
426
|
+
throw new Error("max_usdg must be a positive number of USDG.");
|
|
427
|
+
}
|
|
428
|
+
if (price <= 0n || price > BigInt(Math.round(cap * 1e6))) {
|
|
429
|
+
throw new Error(`the endpoint quotes ${usdg(price)} per generation, past the ${cap} USDG cap.`);
|
|
430
|
+
}
|
|
431
|
+
const pendingKey = `${base}:${price}`;
|
|
432
|
+
let pending = pendingInference.get(pendingKey);
|
|
433
|
+
if (!pending) {
|
|
434
|
+
const paymentTx = await agent.transferUsdg(offer.pay_to, price);
|
|
435
|
+
const signature = await agent.account.signMessage({ message: paymentTx });
|
|
436
|
+
pending = {
|
|
437
|
+
tx: paymentTx,
|
|
438
|
+
header: Buffer.from(JSON.stringify({ txHash: paymentTx, signature })).toString("base64"),
|
|
439
|
+
};
|
|
440
|
+
pendingInference.set(pendingKey, pending);
|
|
441
|
+
}
|
|
442
|
+
// A cold endpoint holds the request through provisioning; when it answers
|
|
443
|
+
// 503 instead, the payment is not consumed and the same header retries.
|
|
444
|
+
const deadline = Date.now() + 600_000;
|
|
445
|
+
for (;;) {
|
|
446
|
+
const res = await fetch(`${base}/v1/inference`, {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers: { "content-type": "application/json", "x-payment": pending.header },
|
|
449
|
+
body: JSON.stringify({ model, prompt: args.prompt }),
|
|
450
|
+
signal: AbortSignal.timeout(620_000),
|
|
451
|
+
});
|
|
452
|
+
const body = await res.json().catch(() => null);
|
|
453
|
+
if (res.status === 200 && body) {
|
|
454
|
+
pendingInference.delete(pendingKey);
|
|
455
|
+
return { ...body, paid: usdg(price), payment_tx: pending.tx };
|
|
456
|
+
}
|
|
457
|
+
const last = body?.detail ?? body?.error ?? `status ${res.status}`;
|
|
458
|
+
// 503 means the box is warming; a 402 for a payment that is merely too
|
|
459
|
+
// young (confirmations still landing, receipt not yet visible) heals by
|
|
460
|
+
// itself. Everything else is final.
|
|
461
|
+
const retryable =
|
|
462
|
+
res.status === 503 ||
|
|
463
|
+
(res.status === 402 && ["insufficient_confirmations", "tx_not_found"].includes(body?.error));
|
|
464
|
+
if (!retryable || Date.now() > deadline) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`inference failed: ${last}. The payment (tx ${pending.tx}) was not consumed and is kept; the next prism_infer call retries with it instead of paying again.`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
await new Promise((r) => setTimeout(r, 15_000));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
396
472
|
if (name === "prism_lease_and_run" || name === "prism_lease") {
|
|
397
473
|
if (name === "prism_lease_and_run") requireCommand(args.command);
|
|
398
474
|
requireWallet(name);
|
|
@@ -501,7 +577,7 @@ async function handleVault(name, args) {
|
|
|
501
577
|
throw new Error(`unknown tool ${name}`);
|
|
502
578
|
}
|
|
503
579
|
|
|
504
|
-
const server = new Server({ name: "prism", version: "0.
|
|
580
|
+
const server = new Server({ name: "prism", version: "0.7.0" }, { capabilities: { tools: {} } });
|
|
505
581
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
506
582
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
507
583
|
try {
|