@prismnetwork/mcp 0.4.2 → 0.6.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 +12 -1
- package/package.json +2 -2
- package/server.mjs +231 -49
package/README.md
CHANGED
|
@@ -18,10 +18,14 @@ real prices. Leasing spends money, so those tools ask for a wallet and say so.
|
|
|
18
18
|
| Tool | Wallet |
|
|
19
19
|
| --- | --- |
|
|
20
20
|
| `prism_list_gpus` | no |
|
|
21
|
+
| `prism_price_index` | no |
|
|
22
|
+
| `prism_receipts` | no |
|
|
21
23
|
| `prism_wallet` | yes |
|
|
24
|
+
| `prism_leases` | yes |
|
|
22
25
|
| `prism_lease_and_run` | yes |
|
|
23
26
|
| `prism_lease` | yes |
|
|
24
27
|
| `prism_run` | yes |
|
|
28
|
+
| `prism_batch_run` | yes |
|
|
25
29
|
| `prism_end_lease` | yes |
|
|
26
30
|
| `prism_vault_store` | yes |
|
|
27
31
|
| `prism_vault_list` | yes |
|
|
@@ -31,9 +35,16 @@ real prices. Leasing spends money, so those tools ask for a wallet and say so.
|
|
|
31
35
|
|
|
32
36
|
- `prism_wallet`: the agent's address and USDG/ETH balances.
|
|
33
37
|
- `prism_list_gpus`: GPUs available to lease, with price per second and per hour.
|
|
38
|
+
- `prism_price_index`: sourced and settled pricing per GPU model, for cost estimates.
|
|
39
|
+
- `prism_receipts`: recent settled receipts from the public proof feed, with the
|
|
40
|
+
settlement transaction on Robinhood Chain.
|
|
41
|
+
- `prism_leases`: this wallet's leases and their state.
|
|
34
42
|
- `prism_lease_and_run`: lease a GPU, run a command, return the output (one shot).
|
|
35
43
|
- `prism_lease`: lease a GPU and keep it; returns a `lease_id` and SSH access.
|
|
36
44
|
- `prism_run`: run a command on an existing lease.
|
|
45
|
+
- `prism_batch_run`: fund a lease that runs one command with no interactive
|
|
46
|
+
access; the node reports the signed output. Matches only suppliers at trust
|
|
47
|
+
class `isolated` or above, so it can find no supplier when none is online.
|
|
37
48
|
- `prism_end_lease`: release a lease.
|
|
38
49
|
- `prism_vault_store`: seal private data under the wallet-derived key.
|
|
39
50
|
- `prism_vault_list`: list sealed items; values are never returned.
|
|
@@ -49,7 +60,7 @@ derived from the agent's wallet inside this server process; Prism receives
|
|
|
49
60
|
ciphertext and holds no way to read it.
|
|
50
61
|
|
|
51
62
|
Each item names the weakest workspace trust class it may ever be released into,
|
|
52
|
-
and new items default to `confidential
|
|
63
|
+
and new items default to `confidential`, above anything the network serves
|
|
53
64
|
today. `prism_vault_release` is therefore refused on current capacity instead of
|
|
54
65
|
handing a secret to a host that can read it. Lowering an item's floor is a
|
|
55
66
|
deliberate act, and allowed releases are recorded against the account.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prismnetwork/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MCP server for leasing and running on Prism Network GPUs.",
|
|
5
5
|
"mcpName": "io.github.prismnetwork-tech/mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
19
|
-
"@prismnetwork/agent-sdk": "^0.
|
|
19
|
+
"@prismnetwork/agent-sdk": "^0.5.0",
|
|
20
20
|
"viem": "^2"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
package/server.mjs
CHANGED
|
@@ -9,51 +9,82 @@ import { DEFAULT_IMAGE, DEFAULT_TRUST_FLOOR, PrismAgent, TRUST_CLASSES } from "@
|
|
|
9
9
|
|
|
10
10
|
const IMAGE = process.env.PRISM_DEFAULT_IMAGE ?? DEFAULT_IMAGE;
|
|
11
11
|
|
|
12
|
-
function requireEnv(name) {
|
|
13
|
-
const value = process.env[name];
|
|
14
|
-
if (!value) throw new Error(`${name} is required`);
|
|
15
|
-
return value;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
12
|
const PUBLIC_API = process.env.PRISM_PUBLIC_API ?? "https://api.prismnetwork.tech";
|
|
13
|
+
// The live lease escrow. Overridable, but its absence must not silently
|
|
14
|
+
// disable the wallet the way a missing key does.
|
|
15
|
+
const DEFAULT_ESCROW = "0x62C042265991bEa17B07229322A01850974626dA";
|
|
16
|
+
// Matches the limit the SDK and the control plane enforce, so a command that
|
|
17
|
+
// cannot run is rejected before an escrow is funded.
|
|
18
|
+
const MAX_COMMAND_BYTES = 8 * 1024;
|
|
19
19
|
|
|
20
20
|
// Refusing to start without a wallet meant nobody could ask what a GPU costs
|
|
21
21
|
// without first producing a private key, which is a strange thing to demand of
|
|
22
22
|
// someone deciding whether to use you at all.
|
|
23
23
|
let agent = null;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
let walletProblem = "PRISM_AGENT_KEY is not set";
|
|
25
|
+
if (process.env.PRISM_AGENT_KEY) {
|
|
26
|
+
try {
|
|
27
|
+
agent = new PrismAgent({
|
|
28
|
+
privateKey: process.env.PRISM_AGENT_KEY,
|
|
29
|
+
escrow: process.env.PRISM_ESCROW ?? DEFAULT_ESCROW,
|
|
30
|
+
apiBase: process.env.PRISM_API_BASE ?? "https://prismnetwork.tech",
|
|
31
|
+
rpcUrl: process.env.PRISM_RPC_URL,
|
|
32
|
+
});
|
|
33
|
+
walletProblem = null;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
walletProblem = `PRISM_AGENT_KEY is set but unusable: ${err?.message ?? err}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!agent) {
|
|
32
39
|
console.error(
|
|
33
|
-
|
|
34
|
-
"Set PRISM_AGENT_KEY and PRISM_ESCROW to lease.",
|
|
40
|
+
`prism mcp: no wallet configured (${walletProblem}), so capacity and pricing are readable and leasing is not.`,
|
|
35
41
|
);
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function requireWallet(tool) {
|
|
44
|
+
function requireWallet(tool, reason = "spends money") {
|
|
39
45
|
if (!agent) {
|
|
40
46
|
throw new Error(
|
|
41
|
-
`${tool}
|
|
47
|
+
`${tool} ${reason} and needs a wallet. None is configured: ${walletProblem}. Fix the environment and restart the server.`,
|
|
42
48
|
);
|
|
43
49
|
}
|
|
44
50
|
return agent;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
+
function requireCommand(value) {
|
|
54
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
55
|
+
throw new Error("command is required: the shell command to run on the GPU, e.g. 'nvidia-smi'.");
|
|
56
|
+
}
|
|
57
|
+
if (Buffer.byteLength(value, "utf8") > MAX_COMMAND_BYTES) {
|
|
58
|
+
throw new Error(`command exceeds the ${MAX_COMMAND_BYTES / 1024} KiB limit.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function maxDeposit(args) {
|
|
64
|
+
const cap = args.max_usdg ?? 1;
|
|
65
|
+
if (typeof cap !== "number" || !Number.isFinite(cap) || cap <= 0) {
|
|
66
|
+
throw new Error("max_usdg must be a positive number of USDG.");
|
|
67
|
+
}
|
|
68
|
+
return Math.round(cap * 1e6);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const PROOF_FEED = process.env.PRISM_PROOF_URL ?? "https://prismnetwork.tech/api/proof";
|
|
72
|
+
|
|
73
|
+
async function publicJson(url, what) {
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
headers: { accept: "application/json" },
|
|
76
|
+
signal: AbortSignal.timeout(10_000),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) throw new Error(`prism ${what} unavailable (${response.status})`);
|
|
79
|
+
const body = await response.json().catch(() => null);
|
|
80
|
+
if (body === null) throw new Error(`prism ${what} answered with something that is not JSON`);
|
|
81
|
+
return body;
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
const leases = new Map();
|
|
56
|
-
|
|
85
|
+
// null in, null out: a missing price must never render as 0.000000 USDG.
|
|
86
|
+
const usdg = (micros) =>
|
|
87
|
+
micros == null || !Number.isFinite(Number(micros)) ? null : `${(Number(micros) / 1e6).toFixed(6)} USDG`;
|
|
57
88
|
|
|
58
89
|
function sweepExpiredLeases() {
|
|
59
90
|
const now = Date.now();
|
|
@@ -92,6 +123,51 @@ const TOOLS = [
|
|
|
92
123
|
},
|
|
93
124
|
},
|
|
94
125
|
},
|
|
126
|
+
{
|
|
127
|
+
name: "prism_price_index",
|
|
128
|
+
description: "Current GPU pricing on Prism Network by model: sourced low/median/high and settled mean, in USDG per hour. Needs no wallet. Use it to estimate what an analysis job will cost before leasing.",
|
|
129
|
+
inputSchema: { type: "object", properties: {} },
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "prism_receipts",
|
|
133
|
+
description: "Recent settled lease receipts from the public proof feed: GPU model, runtime, what was charged and refunded, and the settlement transaction hash on Robinhood Chain. Needs no wallet. Every Prism lease ends in one of these.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
limit: { type: "integer", description: "Max receipts to return (default 10, max 50)." },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "prism_leases",
|
|
143
|
+
description: "List this wallet's leases on Prism Network with their current state.",
|
|
144
|
+
inputSchema: { type: "object", properties: {} },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "prism_batch_run",
|
|
148
|
+
description: "Fund a lease that runs one command with no interactive access at all: the node executes it and reports the signed output. Matches only suppliers at trust class 'isolated' or above, so it can find no supplier when none is online; prefer prism_lease_and_run for broad availability. Output is capped at 64 KiB per stream.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
command: { type: "string", description: "Shell command to run (max 8 KiB)." },
|
|
153
|
+
duration_seconds: { type: "integer", description: "Paid window in seconds (default 900, max 21600). A command still running at the end is killed and reported exit 124." },
|
|
154
|
+
min_vram_mib: { type: "integer", description: "Minimum GPU memory in MiB (default 16000)." },
|
|
155
|
+
max_usdg: { type: "number", description: "Hard cap on the USDG this lease may cost (default 1). Raise it deliberately for longer leases." },
|
|
156
|
+
},
|
|
157
|
+
required: ["command"],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "prism_batch_result",
|
|
162
|
+
description: "Read the output of a batch lease by lease_id, once its node has reported. Use it to recover a result after prism_batch_run timed out.",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
lease_id: { type: "integer", description: "The lease_id from prism_batch_run's output or error." },
|
|
167
|
+
},
|
|
168
|
+
required: ["lease_id"],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
95
171
|
{
|
|
96
172
|
name: "prism_lease_and_run",
|
|
97
173
|
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.",
|
|
@@ -106,6 +182,7 @@ const TOOLS = [
|
|
|
106
182
|
enum: TRUST_CLASSES,
|
|
107
183
|
description: "Refuse suppliers below this trust class (default 'open'). Raise it for anything the host operator must not read.",
|
|
108
184
|
},
|
|
185
|
+
max_usdg: { type: "number", description: "Hard cap on the USDG this lease may cost (default 1). Raise it deliberately for longer leases." },
|
|
109
186
|
},
|
|
110
187
|
required: ["command"],
|
|
111
188
|
},
|
|
@@ -123,6 +200,7 @@ const TOOLS = [
|
|
|
123
200
|
enum: TRUST_CLASSES,
|
|
124
201
|
description: "Refuse suppliers below this trust class (default 'open'). Raise it for anything the host operator must not read.",
|
|
125
202
|
},
|
|
203
|
+
max_usdg: { type: "number", description: "Hard cap on the USDG this lease may cost (default 1). Raise it deliberately for longer leases." },
|
|
126
204
|
},
|
|
127
205
|
},
|
|
128
206
|
},
|
|
@@ -150,7 +228,7 @@ const TOOLS = [
|
|
|
150
228
|
},
|
|
151
229
|
{
|
|
152
230
|
name: "prism_vault_store",
|
|
153
|
-
description: "Store private data
|
|
231
|
+
description: "Store private data (a card, an identity document, an API credential) encrypted under a key derived from this agent's wallet on this machine. Prism receives ciphertext only and cannot read it. Use this instead of writing a secret into a workspace or a file. Returns an item_id; the value is not recoverable without the wallet.",
|
|
154
232
|
inputSchema: {
|
|
155
233
|
type: "object",
|
|
156
234
|
properties: {
|
|
@@ -172,7 +250,7 @@ const TOOLS = [
|
|
|
172
250
|
},
|
|
173
251
|
{
|
|
174
252
|
name: "prism_vault_read",
|
|
175
|
-
description: "Decrypt and return one vault item, in this process, using the wallet-derived key. The plaintext exists only here
|
|
253
|
+
description: "Decrypt and return one vault item, in this process, using the wallet-derived key. The plaintext exists only here; do not echo it into a leased workspace, a log, or a message.",
|
|
176
254
|
inputSchema: {
|
|
177
255
|
type: "object",
|
|
178
256
|
properties: { item_id: { type: "string", description: "The item_id from prism_vault_store or prism_vault_list." } },
|
|
@@ -209,12 +287,16 @@ async function handle(name, args) {
|
|
|
209
287
|
}
|
|
210
288
|
if (name === "prism_list_gpus") {
|
|
211
289
|
const minTrust = args.min_trust ?? "open";
|
|
290
|
+
if (!TRUST_CLASSES.includes(minTrust)) {
|
|
291
|
+
throw new Error(`min_trust must be one of ${TRUST_CLASSES.join(", ")}`);
|
|
292
|
+
}
|
|
212
293
|
let offers;
|
|
213
294
|
if (agent) {
|
|
214
|
-
await ensureAuth();
|
|
215
295
|
offers = await agent.offers({ minTrust });
|
|
216
296
|
} else {
|
|
217
|
-
|
|
297
|
+
const url = new URL("/v1/offers", PUBLIC_API);
|
|
298
|
+
url.searchParams.set("min_trust", minTrust);
|
|
299
|
+
offers = await publicJson(url, "offers");
|
|
218
300
|
}
|
|
219
301
|
return {
|
|
220
302
|
available: offers.length,
|
|
@@ -224,38 +306,136 @@ async function handle(name, args) {
|
|
|
224
306
|
price_per_second: usdg(o.rate_per_second),
|
|
225
307
|
price_per_hour: usdg(o.rate_per_second * 3600),
|
|
226
308
|
trust: o.trust_class,
|
|
309
|
+
// A staker-only offer will not match a wallet without the stake, so an
|
|
310
|
+
// unstaked renter should budget from the non-staker rows.
|
|
311
|
+
...(o.staker_only ? { staker_only: true } : {}),
|
|
227
312
|
})),
|
|
228
313
|
};
|
|
229
314
|
}
|
|
315
|
+
if (name === "prism_price_index") {
|
|
316
|
+
const index = await publicJson(new URL("/v1/price-index", PUBLIC_API), "price index");
|
|
317
|
+
return {
|
|
318
|
+
currency: index.currency,
|
|
319
|
+
generated_at: index.generated_at,
|
|
320
|
+
gpus: (index.gpus ?? []).map((g) => ({
|
|
321
|
+
model: g.gpu_model,
|
|
322
|
+
sourced_low_per_hour: usdg(g.sourced_low_micros_per_hour),
|
|
323
|
+
sourced_median_per_hour: usdg(g.sourced_median_micros_per_hour),
|
|
324
|
+
sourced_high_per_hour: usdg(g.sourced_high_micros_per_hour),
|
|
325
|
+
settled_mean_per_hour: usdg(g.settled_mean_micros_per_hour),
|
|
326
|
+
settled_leases: g.settled_leases,
|
|
327
|
+
})),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (name === "prism_receipts") {
|
|
331
|
+
const feed = await publicJson(PROOF_FEED, "proof feed");
|
|
332
|
+
if (args.limit !== undefined && (!Number.isInteger(args.limit) || args.limit <= 0)) {
|
|
333
|
+
throw new Error("limit must be a positive integer (max 50)");
|
|
334
|
+
}
|
|
335
|
+
const limit = Math.min(args.limit ?? 10, 50);
|
|
336
|
+
return {
|
|
337
|
+
generated_at: feed.generated_at,
|
|
338
|
+
receipts: (feed.receipts ?? []).slice(0, limit).map((r) => ({
|
|
339
|
+
// The feed numbers leases per escrow deployment, so lease_id repeats
|
|
340
|
+
// across deployments; receipt_id is the unique handle.
|
|
341
|
+
receipt_id: r.receipt_id,
|
|
342
|
+
lease_id: r.lease_id,
|
|
343
|
+
outcome: r.outcome,
|
|
344
|
+
gpu_model: r.gpu_model,
|
|
345
|
+
trust: r.trust_class ?? null,
|
|
346
|
+
runtime_seconds: r.runtime_seconds,
|
|
347
|
+
charged: usdg(r.charged_base_units),
|
|
348
|
+
refunded: usdg(r.refunded_base_units),
|
|
349
|
+
settlement_tx: r.transaction_hash,
|
|
350
|
+
})),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (name === "prism_leases") {
|
|
354
|
+
requireWallet(name);
|
|
355
|
+
const all = await agent.leases();
|
|
356
|
+
return {
|
|
357
|
+
total: all.length,
|
|
358
|
+
showing: Math.min(all.length, 20),
|
|
359
|
+
leases: all.slice(0, 20).map((l) => ({
|
|
360
|
+
lease_id: l.lease_id,
|
|
361
|
+
state: l.state,
|
|
362
|
+
image: l.image,
|
|
363
|
+
duration_seconds: l.duration_seconds,
|
|
364
|
+
max_escrow: usdg(l.maximum_escrow),
|
|
365
|
+
trust: l.trust_class,
|
|
366
|
+
funding_tx: l.funding_transaction_hash,
|
|
367
|
+
created_at: l.created_at,
|
|
368
|
+
})),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (name === "prism_batch_run") {
|
|
372
|
+
requireCommand(args.command);
|
|
373
|
+
requireWallet(name);
|
|
374
|
+
const cap = maxDeposit(args);
|
|
375
|
+
const batch = await agent.lease({
|
|
376
|
+
image: IMAGE,
|
|
377
|
+
durationSeconds: args.duration_seconds ?? 900,
|
|
378
|
+
minVramMib: args.min_vram_mib ?? 16000,
|
|
379
|
+
maxDeposit: cap,
|
|
380
|
+
command: args.command,
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
lease_id: batch.leaseId,
|
|
384
|
+
funding_tx: batch.fundingHash,
|
|
385
|
+
exit_code: batch.result?.exit_code,
|
|
386
|
+
stdout: batch.result?.stdout,
|
|
387
|
+
stderr: batch.result?.stderr,
|
|
388
|
+
truncated: batch.result?.truncated ?? false,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (name === "prism_batch_result") {
|
|
392
|
+
requireWallet(name, "reads this wallet's leases");
|
|
393
|
+
const id = leaseId(args.lease_id);
|
|
394
|
+
return { lease_id: id, result: await agent.result(id) };
|
|
395
|
+
}
|
|
230
396
|
if (name === "prism_lease_and_run" || name === "prism_lease") {
|
|
231
|
-
if (name === "prism_lease_and_run"
|
|
397
|
+
if (name === "prism_lease_and_run") requireCommand(args.command);
|
|
232
398
|
requireWallet(name);
|
|
233
|
-
|
|
399
|
+
const cap = maxDeposit(args);
|
|
234
400
|
sweepExpiredLeases();
|
|
235
401
|
const lease = await agent.lease({
|
|
236
402
|
image: IMAGE,
|
|
237
403
|
durationSeconds: args.duration_seconds ?? 900,
|
|
238
404
|
minVramMib: args.min_vram_mib ?? 16000,
|
|
405
|
+
maxDeposit: cap,
|
|
239
406
|
minTrustClass: args.min_trust_class ?? "open",
|
|
240
407
|
});
|
|
241
408
|
leases.set(lease.leaseId, lease);
|
|
242
409
|
const summary = {
|
|
243
410
|
lease_id: lease.leaseId,
|
|
411
|
+
funding_tx: lease.fundingHash,
|
|
244
412
|
ssh: { host: lease.access.ssh_host, port: lease.access.ssh_port, user: lease.access.ssh_user },
|
|
245
413
|
trust: lease.quote?.trust_class ?? "open",
|
|
246
414
|
expires_at: lease.access.expires_at,
|
|
247
415
|
};
|
|
248
416
|
if (name === "prism_lease") return summary;
|
|
249
|
-
|
|
250
|
-
|
|
417
|
+
// The lease is paid for by this point; an SSH failure must still hand the
|
|
418
|
+
// caller everything it bought.
|
|
419
|
+
try {
|
|
420
|
+
const out = await agent.run(lease, args.command);
|
|
421
|
+
return { ...summary, command: args.command, exit_code: out.code, stdout: out.stdout, stderr: out.stderr };
|
|
422
|
+
} catch (err) {
|
|
423
|
+
return {
|
|
424
|
+
...summary,
|
|
425
|
+
error: `the lease is funded but the command could not run: ${err?.message ?? err}`,
|
|
426
|
+
next: `the lease stays open; try prism_run with lease_id ${lease.leaseId}, or prism_end_lease`,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
251
429
|
}
|
|
252
430
|
if (name === "prism_run") {
|
|
253
|
-
|
|
431
|
+
requireCommand(args.command);
|
|
254
432
|
const id = leaseId(args.lease_id);
|
|
255
433
|
const lease = leases.get(id);
|
|
256
434
|
if (!lease) throw new Error(`no active lease ${id} in this session`);
|
|
435
|
+
const timeoutSeconds = args.timeout_seconds ?? 120;
|
|
257
436
|
const out = await agent.run(lease, args.command, {
|
|
258
|
-
timeoutMs:
|
|
437
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
438
|
+
connectRetries: 6,
|
|
259
439
|
});
|
|
260
440
|
return { lease_id: id, exit_code: out.code, stdout: out.stdout, stderr: out.stderr };
|
|
261
441
|
}
|
|
@@ -269,14 +449,13 @@ async function handle(name, args) {
|
|
|
269
449
|
return { lease_id: id, released: Boolean(lease) };
|
|
270
450
|
}
|
|
271
451
|
if (name.startsWith("prism_vault_")) return handleVault(name, args);
|
|
272
|
-
throw new Error(`unknown tool ${name}`);
|
|
452
|
+
throw new Error(`unknown tool ${name}. Valid tools: ${TOOLS.map((t) => t.name).join(", ")}`);
|
|
273
453
|
}
|
|
274
454
|
|
|
275
455
|
// The vault key is derived here from the wallet signature and stays in this
|
|
276
456
|
// process. Nothing in this function sends a key or a plaintext to Prism.
|
|
277
457
|
async function handleVault(name, args) {
|
|
278
|
-
const vault = requireWallet(name).vault;
|
|
279
|
-
await ensureAuth();
|
|
458
|
+
const vault = requireWallet(name, "derives the vault key from the wallet").vault;
|
|
280
459
|
if (!vault.unlocked) await vault.unlock();
|
|
281
460
|
|
|
282
461
|
if (name === "prism_vault_store") {
|
|
@@ -322,23 +501,26 @@ async function handleVault(name, args) {
|
|
|
322
501
|
throw new Error(`unknown tool ${name}`);
|
|
323
502
|
}
|
|
324
503
|
|
|
325
|
-
|
|
326
|
-
function ensureAuth() {
|
|
327
|
-
authPromise ??= agent.authenticate().catch((err) => {
|
|
328
|
-
authPromise = null;
|
|
329
|
-
throw err;
|
|
330
|
-
});
|
|
331
|
-
return authPromise;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const server = new Server({ name: "prism", version: "0.4.1" }, { capabilities: { tools: {} } });
|
|
504
|
+
const server = new Server({ name: "prism", version: "0.6.0" }, { capabilities: { tools: {} } });
|
|
335
505
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
336
506
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
337
507
|
try {
|
|
338
508
|
const result = await handle(request.params.name, request.params.arguments ?? {});
|
|
339
509
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
340
510
|
} catch (err) {
|
|
341
|
-
|
|
511
|
+
const body = err?.body ?? {};
|
|
512
|
+
const detail = [
|
|
513
|
+
body.cause,
|
|
514
|
+
body.hint,
|
|
515
|
+
body.required != null ? `required ${body.required}` : null,
|
|
516
|
+
body.max != null ? `cap ${body.max}` : null,
|
|
517
|
+
body.lease_id != null ? `lease_id ${body.lease_id}` : null,
|
|
518
|
+
body.funding_hash ? `funding_tx ${body.funding_hash}` : null,
|
|
519
|
+
]
|
|
520
|
+
.filter(Boolean)
|
|
521
|
+
.join("; ");
|
|
522
|
+
const text = `error: ${err?.message ?? err}${detail ? ` (${detail})` : ""}`;
|
|
523
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
342
524
|
}
|
|
343
525
|
});
|
|
344
526
|
|