@prismnetwork/mcp 0.2.0 → 0.4.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 +44 -1
- package/package.json +2 -2
- package/server.mjs +146 -11
package/README.md
CHANGED
|
@@ -1,15 +1,58 @@
|
|
|
1
1
|
# @prismnetwork/mcp
|
|
2
2
|
|
|
3
|
-
An MCP server that lets Claude (or any MCP client)
|
|
3
|
+
An MCP server that lets Claude (or any MCP client) see and lease real GPUs through [Prism Network](https://prismnetwork.tech). Give it a wallet and it handles auth, onchain payment, provisioning, and SSH.
|
|
4
|
+
|
|
5
|
+
## Try it without a wallet
|
|
6
|
+
|
|
7
|
+
Looking costs nothing and needs no configuration:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
claude mcp add prism -- npx -y @prismnetwork/mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then ask what you can rent. `prism_list_gpus` answers from live capacity with
|
|
14
|
+
real prices. Leasing spends money, so those tools ask for a wallet and say so.
|
|
4
15
|
|
|
5
16
|
## Tools
|
|
6
17
|
|
|
18
|
+
| Tool | Wallet |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `prism_list_gpus` | no |
|
|
21
|
+
| `prism_wallet` | yes |
|
|
22
|
+
| `prism_lease_and_run` | yes |
|
|
23
|
+
| `prism_lease` | yes |
|
|
24
|
+
| `prism_run` | yes |
|
|
25
|
+
| `prism_end_lease` | yes |
|
|
26
|
+
| `prism_vault_store` | yes |
|
|
27
|
+
| `prism_vault_list` | yes |
|
|
28
|
+
| `prism_vault_read` | yes |
|
|
29
|
+
| `prism_vault_delete` | yes |
|
|
30
|
+
| `prism_vault_release` | yes |
|
|
31
|
+
|
|
7
32
|
- `prism_wallet`: the agent's address and USDG/ETH balances.
|
|
8
33
|
- `prism_list_gpus`: GPUs available to lease, with price per second and per hour.
|
|
9
34
|
- `prism_lease_and_run`: lease a GPU, run a command, return the output (one shot).
|
|
10
35
|
- `prism_lease`: lease a GPU and keep it; returns a `lease_id` and SSH access.
|
|
11
36
|
- `prism_run`: run a command on an existing lease.
|
|
12
37
|
- `prism_end_lease`: release a lease.
|
|
38
|
+
- `prism_vault_store`: seal private data under the wallet-derived key.
|
|
39
|
+
- `prism_vault_list`: list sealed items; values are never returned.
|
|
40
|
+
- `prism_vault_read`: decrypt one item in this process.
|
|
41
|
+
- `prism_vault_delete`: permanently delete an item.
|
|
42
|
+
- `prism_vault_release`: authorize an item into a lease that clears its trust floor.
|
|
43
|
+
|
|
44
|
+
## Vault
|
|
45
|
+
|
|
46
|
+
An agent that handles a card, an identity document or a credential should not
|
|
47
|
+
write it into a leased workspace. `prism_vault_store` seals it under a key
|
|
48
|
+
derived from the agent's wallet inside this server process; Prism receives
|
|
49
|
+
ciphertext and holds no way to read it.
|
|
50
|
+
|
|
51
|
+
Each item names the weakest workspace trust class it may ever be released into,
|
|
52
|
+
and new items default to `confidential` — above anything the network serves
|
|
53
|
+
today. `prism_vault_release` is therefore refused on current capacity instead of
|
|
54
|
+
handing a secret to a host that can read it. Lowering an item's floor is a
|
|
55
|
+
deliberate act, and allowed releases are recorded against the account.
|
|
13
56
|
|
|
14
57
|
## Configure
|
|
15
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prismnetwork/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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.3.0",
|
|
20
20
|
"viem": "^2"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
package/server.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Prism Network MCP server: lets an MCP client (Claude, agents)
|
|
3
|
-
// real GPUs.
|
|
2
|
+
// Prism Network MCP server: lets an MCP client (Claude, agents) see and lease
|
|
3
|
+
// real GPUs. Looking is free and needs no configuration. Leasing spends money,
|
|
4
|
+
// so it needs a wallet: PRISM_AGENT_KEY, PRISM_ESCROW.
|
|
4
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
import { DEFAULT_IMAGE, PrismAgent, TRUST_CLASSES } from "@prismnetwork/agent-sdk";
|
|
8
|
+
import { DEFAULT_IMAGE, DEFAULT_TRUST_FLOOR, PrismAgent, TRUST_CLASSES } from "@prismnetwork/agent-sdk";
|
|
8
9
|
|
|
9
10
|
const IMAGE = process.env.PRISM_DEFAULT_IMAGE ?? DEFAULT_IMAGE;
|
|
10
11
|
|
|
@@ -14,7 +15,12 @@ function requireEnv(name) {
|
|
|
14
15
|
return value;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const PUBLIC_API = process.env.PRISM_PUBLIC_API ?? "https://api.prismnetwork.tech";
|
|
19
|
+
|
|
20
|
+
// Refusing to start without a wallet meant nobody could ask what a GPU costs
|
|
21
|
+
// without first producing a private key, which is a strange thing to demand of
|
|
22
|
+
// someone deciding whether to use you at all.
|
|
23
|
+
let agent = null;
|
|
18
24
|
try {
|
|
19
25
|
agent = new PrismAgent({
|
|
20
26
|
privateKey: requireEnv("PRISM_AGENT_KEY"),
|
|
@@ -22,9 +28,28 @@ try {
|
|
|
22
28
|
apiBase: process.env.PRISM_API_BASE ?? "https://prismnetwork.tech",
|
|
23
29
|
rpcUrl: process.env.PRISM_RPC_URL,
|
|
24
30
|
});
|
|
25
|
-
} catch
|
|
26
|
-
console.error(
|
|
27
|
-
|
|
31
|
+
} catch {
|
|
32
|
+
console.error(
|
|
33
|
+
"prism mcp: no wallet configured, so capacity and pricing are readable and leasing is not. " +
|
|
34
|
+
"Set PRISM_AGENT_KEY and PRISM_ESCROW to lease.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function requireWallet(tool) {
|
|
39
|
+
if (!agent) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`${tool} spends money, so it needs a wallet. Set PRISM_AGENT_KEY and PRISM_ESCROW in this server's environment and restart it.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return agent;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function publicOffers(minTrust) {
|
|
48
|
+
const url = new URL("/v1/offers", PUBLIC_API);
|
|
49
|
+
if (minTrust) url.searchParams.set("min_trust", minTrust);
|
|
50
|
+
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
51
|
+
if (!response.ok) throw new Error(`prism offers unavailable (${response.status})`);
|
|
52
|
+
return response.json();
|
|
28
53
|
}
|
|
29
54
|
|
|
30
55
|
const leases = new Map();
|
|
@@ -55,7 +80,7 @@ const TOOLS = [
|
|
|
55
80
|
},
|
|
56
81
|
{
|
|
57
82
|
name: "prism_list_gpus",
|
|
58
|
-
description: "List GPUs currently available to lease on Prism Network, with model, VRAM, price per second in USDG, and trust class. Trust class runs open < isolated < attested < confidential; on an 'open' supplier the host operator can read anything the workload touches
|
|
83
|
+
description: "List GPUs currently available to lease on Prism Network, with model, VRAM, price per second in USDG, and trust class. Trust class runs open < isolated < attested < confidential; on an 'open' supplier the host operator can read anything the workload touches. Keep secrets and credentials in prism_vault_store rather than on the box, and raise min_trust when the workload itself must not be readable.",
|
|
59
84
|
inputSchema: {
|
|
60
85
|
type: "object",
|
|
61
86
|
properties: {
|
|
@@ -123,16 +148,74 @@ const TOOLS = [
|
|
|
123
148
|
required: ["lease_id"],
|
|
124
149
|
},
|
|
125
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: "prism_vault_store",
|
|
153
|
+
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
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
value: { type: "string", description: "The data to seal. Encrypted before it leaves this process." },
|
|
158
|
+
label: { type: "string", description: "Optional plain-text name so the item is findable. Stored unencrypted, so keep it non-revealing (e.g. 'billing card', not the number)." },
|
|
159
|
+
trust_floor: {
|
|
160
|
+
type: "string",
|
|
161
|
+
enum: TRUST_CLASSES,
|
|
162
|
+
description: "The weakest workspace this item may ever be released into. Defaults to 'confidential', which is above anything the network serves today, so the item cannot reach a rented GPU at all. Only lower it deliberately.",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ["value"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "prism_vault_list",
|
|
170
|
+
description: "List the agent's sealed vault items: item_id, label, version and trust floor. Values are not returned and are not readable by Prism.",
|
|
171
|
+
inputSchema: { type: "object", properties: {} },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
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 — do not echo it into a leased workspace, a log, or a message.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: { item_id: { type: "string", description: "The item_id from prism_vault_store or prism_vault_list." } },
|
|
179
|
+
required: ["item_id"],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "prism_vault_delete",
|
|
184
|
+
description: "Permanently delete a vault item. The ciphertext is removed and the value cannot be recovered.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: { item_id: { type: "string" } },
|
|
188
|
+
required: ["item_id"],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "prism_vault_release",
|
|
193
|
+
description: "Authorize a vault item into a lease you hold and return its plaintext for use there. Refused when the lease's trust class is below the item's trust floor, which is what stops a secret reaching a host that can read it. Every allowed release is recorded against the account.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {
|
|
197
|
+
item_id: { type: "string" },
|
|
198
|
+
lease_id: { type: "integer", description: "A lease from prism_lease." },
|
|
199
|
+
},
|
|
200
|
+
required: ["item_id", "lease_id"],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
126
203
|
];
|
|
127
204
|
|
|
128
205
|
async function handle(name, args) {
|
|
129
206
|
if (name === "prism_wallet") {
|
|
130
|
-
const b = await
|
|
207
|
+
const b = await requireWallet("prism_wallet").balances();
|
|
131
208
|
return { address: b.address, usdg: usdg(b.usdg), eth_wei: b.eth };
|
|
132
209
|
}
|
|
133
210
|
if (name === "prism_list_gpus") {
|
|
134
|
-
|
|
135
|
-
|
|
211
|
+
const minTrust = args.min_trust ?? "open";
|
|
212
|
+
let offers;
|
|
213
|
+
if (agent) {
|
|
214
|
+
await ensureAuth();
|
|
215
|
+
offers = await agent.offers({ minTrust });
|
|
216
|
+
} else {
|
|
217
|
+
offers = await publicOffers(minTrust);
|
|
218
|
+
}
|
|
136
219
|
return {
|
|
137
220
|
available: offers.length,
|
|
138
221
|
gpus: offers.map((o) => ({
|
|
@@ -146,6 +229,7 @@ async function handle(name, args) {
|
|
|
146
229
|
}
|
|
147
230
|
if (name === "prism_lease_and_run" || name === "prism_lease") {
|
|
148
231
|
if (name === "prism_lease_and_run" && !args.command) throw new Error("command is required");
|
|
232
|
+
requireWallet(name);
|
|
149
233
|
await ensureAuth();
|
|
150
234
|
sweepExpiredLeases();
|
|
151
235
|
const lease = await agent.lease({
|
|
@@ -184,6 +268,57 @@ async function handle(name, args) {
|
|
|
184
268
|
}
|
|
185
269
|
return { lease_id: id, released: Boolean(lease) };
|
|
186
270
|
}
|
|
271
|
+
if (name.startsWith("prism_vault_")) return handleVault(name, args);
|
|
272
|
+
throw new Error(`unknown tool ${name}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// The vault key is derived here from the wallet signature and stays in this
|
|
276
|
+
// process. Nothing in this function sends a key or a plaintext to Prism.
|
|
277
|
+
async function handleVault(name, args) {
|
|
278
|
+
const vault = requireWallet(name).vault;
|
|
279
|
+
await ensureAuth();
|
|
280
|
+
if (!vault.unlocked) await vault.unlock();
|
|
281
|
+
|
|
282
|
+
if (name === "prism_vault_store") {
|
|
283
|
+
if (typeof args.value !== "string" || args.value.length === 0) {
|
|
284
|
+
throw new Error("value is required");
|
|
285
|
+
}
|
|
286
|
+
const item = await vault.put(args.value, {
|
|
287
|
+
label: args.label ?? "",
|
|
288
|
+
trustFloor: args.trust_floor ?? DEFAULT_TRUST_FLOOR,
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
item_id: item.item_id,
|
|
292
|
+
version: item.version,
|
|
293
|
+
label: item.label,
|
|
294
|
+
trust_floor: item.min_trust_class,
|
|
295
|
+
stored: "sealed on this machine; Prism holds ciphertext only",
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (name === "prism_vault_list") {
|
|
299
|
+
const items = await vault.list();
|
|
300
|
+
return {
|
|
301
|
+
count: items.length,
|
|
302
|
+
items: items.map((item) => ({
|
|
303
|
+
item_id: item.item_id,
|
|
304
|
+
label: item.label,
|
|
305
|
+
version: item.version,
|
|
306
|
+
trust_floor: item.min_trust_class,
|
|
307
|
+
updated_at: item.updated_at,
|
|
308
|
+
})),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (name === "prism_vault_read") {
|
|
312
|
+
return { item_id: args.item_id, value: await vault.get(args.item_id) };
|
|
313
|
+
}
|
|
314
|
+
if (name === "prism_vault_delete") {
|
|
315
|
+
await vault.remove(args.item_id);
|
|
316
|
+
return { item_id: args.item_id, deleted: true };
|
|
317
|
+
}
|
|
318
|
+
if (name === "prism_vault_release") {
|
|
319
|
+
const id = leaseId(args.lease_id);
|
|
320
|
+
return { item_id: args.item_id, lease_id: id, value: await vault.releaseInto(id, args.item_id) };
|
|
321
|
+
}
|
|
187
322
|
throw new Error(`unknown tool ${name}`);
|
|
188
323
|
}
|
|
189
324
|
|