@prismnetwork/mcp 0.1.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 +43 -0
- package/package.json +24 -0
- package/server.mjs +189 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @prismnetwork/mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that lets Claude (or any MCP client) lease and run on real GPUs through [Prism Network](https://prismnetwork.tech). Give it a wallet; it handles auth, on-chain payment, provisioning, and SSH.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
- `prism_wallet`: the agent's address and USDG/ETH balances.
|
|
8
|
+
- `prism_list_gpus`: GPUs available to lease, with price per second and per hour.
|
|
9
|
+
- `prism_lease_and_run`: lease a GPU, run a command, return the output (one shot).
|
|
10
|
+
- `prism_lease`: lease a GPU and keep it; returns a `lease_id` and SSH access.
|
|
11
|
+
- `prism_run`: run a command on an existing lease.
|
|
12
|
+
- `prism_end_lease`: release a lease.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Not yet published to npm. Until it is, install from the repo:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
cd prism-public/mcp && npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then point your MCP client at the local entrypoint (Claude Desktop / Code):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"prism": {
|
|
28
|
+
"command": "node",
|
|
29
|
+
"args": ["/path/to/prism-public/mcp/server.mjs"],
|
|
30
|
+
"env": {
|
|
31
|
+
"PRISM_AGENT_KEY": "0x<agent wallet private key>",
|
|
32
|
+
"PRISM_ESCROW": "0x71Df0eF3bc81022cB3bec0b1a05f52f12bAfcDeD"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The wallet needs USDG (`0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168`) and Robinhood-Chain ETH for gas. See the SDK's Funding section for how to fund a fresh wallet.
|
|
40
|
+
|
|
41
|
+
## Timing
|
|
42
|
+
|
|
43
|
+
`prism_lease` and `prism_lease_and_run` block while a GPU provisions (usually one to four minutes, occasionally longer on a slow host). Configure your MCP client to allow long tool calls.
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prismnetwork/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for leasing and running on Prism Network GPUs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "prism-mcp": "server.mjs" },
|
|
7
|
+
"files": ["server.mjs", "README.md"],
|
|
8
|
+
"engines": { "node": ">=20" },
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
11
|
+
"@prismnetwork/agent-sdk": "^0.1.0",
|
|
12
|
+
"viem": "^2"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["prism", "mcp", "gpu", "agent", "claude", "compute"],
|
|
15
|
+
"homepage": "https://prismnetwork.tech",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/prismnetwork-tech/prism.git",
|
|
19
|
+
"directory": "mcp"
|
|
20
|
+
},
|
|
21
|
+
"bugs": { "url": "https://github.com/prismnetwork-tech/prism/issues" },
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"publishConfig": { "access": "public" }
|
|
24
|
+
}
|
package/server.mjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Prism Network MCP server: lets an MCP client (Claude, agents) lease and run on
|
|
3
|
+
// real GPUs. Configure with a wallet: PRISM_AGENT_KEY, PRISM_ESCROW.
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { DEFAULT_IMAGE, PrismAgent } from "@prismnetwork/agent-sdk";
|
|
8
|
+
|
|
9
|
+
const IMAGE = process.env.PRISM_DEFAULT_IMAGE ?? DEFAULT_IMAGE;
|
|
10
|
+
|
|
11
|
+
function requireEnv(name) {
|
|
12
|
+
const value = process.env[name];
|
|
13
|
+
if (!value) throw new Error(`${name} is required`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let agent;
|
|
18
|
+
try {
|
|
19
|
+
agent = new PrismAgent({
|
|
20
|
+
privateKey: requireEnv("PRISM_AGENT_KEY"),
|
|
21
|
+
escrow: requireEnv("PRISM_ESCROW"),
|
|
22
|
+
apiBase: process.env.PRISM_API_BASE ?? "https://prismnetwork.tech",
|
|
23
|
+
rpcUrl: process.env.PRISM_RPC_URL,
|
|
24
|
+
});
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(`prism mcp config error: ${err.message}. Set PRISM_AGENT_KEY and PRISM_ESCROW in the server env.`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const leases = new Map();
|
|
31
|
+
const usdg = (micros) => `${(Number(micros) / 1e6).toFixed(6)} USDG`;
|
|
32
|
+
|
|
33
|
+
function sweepExpiredLeases() {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
for (const [id, lease] of leases) {
|
|
36
|
+
const expiry = Date.parse(lease.access?.expires_at ?? "");
|
|
37
|
+
if (Number.isFinite(expiry) && expiry < now) {
|
|
38
|
+
agent.endLease(lease);
|
|
39
|
+
leases.delete(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function leaseId(value) {
|
|
45
|
+
const id = Number(value);
|
|
46
|
+
if (!Number.isInteger(id) || id <= 0) throw new Error("lease_id must be a positive integer");
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const TOOLS = [
|
|
51
|
+
{
|
|
52
|
+
name: "prism_wallet",
|
|
53
|
+
description: "Show the agent's wallet address and on-chain balances (USDG and ETH for gas) on Robinhood Chain. Check this before leasing to confirm the wallet can pay.",
|
|
54
|
+
inputSchema: { type: "object", properties: {} },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "prism_list_gpus",
|
|
58
|
+
description: "List GPUs currently available to lease on Prism Network, with model, VRAM, and price per second in USDG.",
|
|
59
|
+
inputSchema: { type: "object", properties: {} },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "prism_lease_and_run",
|
|
63
|
+
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.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
command: { type: "string", description: "Shell command to run on the GPU (e.g. 'nvidia-smi')." },
|
|
68
|
+
duration_seconds: { type: "integer", description: "Lease length in seconds (default 900, max 21600)." },
|
|
69
|
+
min_vram_mib: { type: "integer", description: "Minimum GPU memory in MiB (default 16000)." },
|
|
70
|
+
},
|
|
71
|
+
required: ["command"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "prism_lease",
|
|
76
|
+
description: "Lease a GPU and keep it running. Returns a lease_id and SSH access. Use prism_run to execute commands and prism_end_lease when done.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
duration_seconds: { type: "integer", description: "Lease length in seconds (default 900, max 21600)." },
|
|
81
|
+
min_vram_mib: { type: "integer", description: "Minimum GPU memory in MiB (default 16000)." },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "prism_run",
|
|
87
|
+
description: "Run a shell command on a GPU you already leased with prism_lease.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
lease_id: { type: "integer", description: "The lease_id returned by prism_lease." },
|
|
92
|
+
command: { type: "string", description: "Shell command to run." },
|
|
93
|
+
timeout_seconds: { type: "integer", description: "Max seconds to wait (default 120)." },
|
|
94
|
+
},
|
|
95
|
+
required: ["lease_id", "command"],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "prism_end_lease",
|
|
100
|
+
description: "Release a lease's local access. The on-chain lease settles at the end of its paid duration.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: { lease_id: { type: "integer" } },
|
|
104
|
+
required: ["lease_id"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
async function handle(name, args) {
|
|
110
|
+
if (name === "prism_wallet") {
|
|
111
|
+
const b = await agent.balances();
|
|
112
|
+
return { address: b.address, usdg: usdg(b.usdg), eth_wei: b.eth };
|
|
113
|
+
}
|
|
114
|
+
if (name === "prism_list_gpus") {
|
|
115
|
+
await ensureAuth();
|
|
116
|
+
const offers = await agent.offers();
|
|
117
|
+
return {
|
|
118
|
+
available: offers.length,
|
|
119
|
+
gpus: offers.map((o) => ({
|
|
120
|
+
model: o.gpu.model,
|
|
121
|
+
vram_mib: o.gpu.vram_mib,
|
|
122
|
+
price_per_second: usdg(o.rate_per_second),
|
|
123
|
+
price_per_hour: usdg(o.rate_per_second * 3600),
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (name === "prism_lease_and_run" || name === "prism_lease") {
|
|
128
|
+
if (name === "prism_lease_and_run" && !args.command) throw new Error("command is required");
|
|
129
|
+
await ensureAuth();
|
|
130
|
+
sweepExpiredLeases();
|
|
131
|
+
const lease = await agent.lease({
|
|
132
|
+
image: IMAGE,
|
|
133
|
+
durationSeconds: args.duration_seconds ?? 900,
|
|
134
|
+
minVramMib: args.min_vram_mib ?? 16000,
|
|
135
|
+
});
|
|
136
|
+
leases.set(lease.leaseId, lease);
|
|
137
|
+
const summary = {
|
|
138
|
+
lease_id: lease.leaseId,
|
|
139
|
+
ssh: { host: lease.access.ssh_host, port: lease.access.ssh_port, user: lease.access.ssh_user },
|
|
140
|
+
expires_at: lease.access.expires_at,
|
|
141
|
+
};
|
|
142
|
+
if (name === "prism_lease") return summary;
|
|
143
|
+
const out = await agent.run(lease, args.command);
|
|
144
|
+
return { ...summary, command: args.command, exit_code: out.code, stdout: out.stdout, stderr: out.stderr };
|
|
145
|
+
}
|
|
146
|
+
if (name === "prism_run") {
|
|
147
|
+
if (!args.command) throw new Error("command is required");
|
|
148
|
+
const id = leaseId(args.lease_id);
|
|
149
|
+
const lease = leases.get(id);
|
|
150
|
+
if (!lease) throw new Error(`no active lease ${id} in this session`);
|
|
151
|
+
const out = await agent.run(lease, args.command, {
|
|
152
|
+
timeoutMs: (args.timeout_seconds ?? 120) * 1000,
|
|
153
|
+
});
|
|
154
|
+
return { lease_id: id, exit_code: out.code, stdout: out.stdout, stderr: out.stderr };
|
|
155
|
+
}
|
|
156
|
+
if (name === "prism_end_lease") {
|
|
157
|
+
const id = leaseId(args.lease_id);
|
|
158
|
+
const lease = leases.get(id);
|
|
159
|
+
if (lease) {
|
|
160
|
+
agent.endLease(lease);
|
|
161
|
+
leases.delete(id);
|
|
162
|
+
}
|
|
163
|
+
return { lease_id: id, released: Boolean(lease) };
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`unknown tool ${name}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let authPromise = null;
|
|
169
|
+
function ensureAuth() {
|
|
170
|
+
authPromise ??= agent.authenticate().catch((err) => {
|
|
171
|
+
authPromise = null;
|
|
172
|
+
throw err;
|
|
173
|
+
});
|
|
174
|
+
return authPromise;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const server = new Server({ name: "prism", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
178
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
179
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
180
|
+
try {
|
|
181
|
+
const result = await handle(request.params.name, request.params.arguments ?? {});
|
|
182
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
183
|
+
} catch (err) {
|
|
184
|
+
return { isError: true, content: [{ type: "text", text: `error: ${err.message ?? err}` }] };
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await server.connect(new StdioServerTransport());
|
|
189
|
+
console.error("prism mcp server ready");
|