@jaw.id/cli 0.0.2
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 +239 -0
- package/bin/run.js +5 -0
- package/dist/base-command.js +124 -0
- package/dist/base-command.js.map +1 -0
- package/dist/commands/config/set.js +244 -0
- package/dist/commands/config/set.js.map +1 -0
- package/dist/commands/config/show.js +147 -0
- package/dist/commands/config/show.js.map +1 -0
- package/dist/commands/disconnect.js +196 -0
- package/dist/commands/disconnect.js.map +1 -0
- package/dist/commands/mcp/index.js +670 -0
- package/dist/commands/mcp/index.js.map +1 -0
- package/dist/commands/rpc/call.js +531 -0
- package/dist/commands/rpc/call.js.map +1 -0
- package/dist/index.js +460 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/bridge-singleton.js +362 -0
- package/dist/lib/bridge-singleton.js.map +1 -0
- package/dist/lib/config.js +75 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/output.js +56 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/paths.js +16 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/session-store.js +81 -0
- package/dist/lib/session-store.js.map +1 -0
- package/dist/lib/types.js +3 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/validation.js +45 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/ws-bridge.js +141 -0
- package/dist/lib/ws-bridge.js.map +1 -0
- package/dist/lib/ws-daemon.js +344 -0
- package/dist/lib/ws-daemon.js.map +1 -0
- package/dist/mcp/handlers/config.js +149 -0
- package/dist/mcp/handlers/config.js.map +1 -0
- package/dist/mcp/handlers/daemon.js +185 -0
- package/dist/mcp/handlers/daemon.js.map +1 -0
- package/dist/mcp/handlers/resources.js +60 -0
- package/dist/mcp/handlers/resources.js.map +1 -0
- package/dist/mcp/handlers/rpc.js +444 -0
- package/dist/mcp/handlers/rpc.js.map +1 -0
- package/dist/mcp/helpers.js +26 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/server.js +658 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.js +22 -0
- package/dist/mcp/tools.js.map +1 -0
- package/oclif.manifest.json +335 -0
- package/package.json +100 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as fs2 from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { spawn, execSync } from 'child_process';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import WebSocket from 'ws';
|
|
9
|
+
|
|
10
|
+
// src/mcp/tools.ts
|
|
11
|
+
var rpcMethodSchema = {
|
|
12
|
+
method: z.string().describe(
|
|
13
|
+
"EIP-1193 RPC method name (e.g. wallet_connect, wallet_sendCalls, personal_sign). Read the jaw://api-reference resource for the full list and jaw://api-reference/{method} for parameter details."
|
|
14
|
+
),
|
|
15
|
+
params: z.any().optional().describe(
|
|
16
|
+
"Method parameters \u2014 structure varies by method. Read the jaw://api-reference/{method} resource for the expected format."
|
|
17
|
+
),
|
|
18
|
+
chainId: z.number().optional().describe(
|
|
19
|
+
"Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia"
|
|
20
|
+
)
|
|
21
|
+
};
|
|
22
|
+
({
|
|
23
|
+
key: z.enum(["apiKey", "defaultChain", "keysUrl", "paymasterUrl", "ens"]).describe("Config key"),
|
|
24
|
+
value: z.string().describe("Config value")
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/mcp/helpers.ts
|
|
28
|
+
function mcpError(err) {
|
|
29
|
+
return {
|
|
30
|
+
isError: true,
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "text",
|
|
34
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function mcpResult(data) {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: JSON.stringify(data)
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
var JAW_DIR = path.join(os.homedir(), ".jaw");
|
|
50
|
+
var PATHS = {
|
|
51
|
+
root: JAW_DIR,
|
|
52
|
+
config: path.join(JAW_DIR, "config.json"),
|
|
53
|
+
session: path.join(JAW_DIR, "session.json"),
|
|
54
|
+
bridge: path.join(JAW_DIR, "bridge.json"),
|
|
55
|
+
daemonLog: path.join(JAW_DIR, "daemon.log")
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/lib/validation.ts
|
|
59
|
+
function isValidKeysUrl(url) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(url);
|
|
62
|
+
const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
63
|
+
const isSecure = parsed.protocol === "https:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
64
|
+
return isTrustedHost && isSecure;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/lib/config.ts
|
|
71
|
+
function ensureDir(dir) {
|
|
72
|
+
fs2.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
73
|
+
fs2.chmodSync(dir, 448);
|
|
74
|
+
}
|
|
75
|
+
function loadConfig() {
|
|
76
|
+
if (!fs2.existsSync(PATHS.config)) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
const raw = fs2.readFileSync(PATHS.config, "utf-8");
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(raw);
|
|
82
|
+
} catch {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
89
|
+
var WSBridge = class {
|
|
90
|
+
port;
|
|
91
|
+
token;
|
|
92
|
+
timeout;
|
|
93
|
+
ws = null;
|
|
94
|
+
constructor(options) {
|
|
95
|
+
this.port = options.port;
|
|
96
|
+
this.token = options.token;
|
|
97
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Connect to the daemon's WebSocket server.
|
|
101
|
+
*/
|
|
102
|
+
async connect() {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const url = `ws://127.0.0.1:${this.port}?token=${encodeURIComponent(this.token)}&role=cli`;
|
|
105
|
+
const ws = new WebSocket(url);
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
ws.close();
|
|
108
|
+
reject(
|
|
109
|
+
new Error(
|
|
110
|
+
"Browser SDK did not connect in time.\nIf the browser tab failed to open, run `jaw disconnect` then try again."
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}, 3e4);
|
|
114
|
+
ws.on("open", () => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
this.ws = ws;
|
|
117
|
+
});
|
|
118
|
+
ws.on("message", (data) => {
|
|
119
|
+
let msg;
|
|
120
|
+
try {
|
|
121
|
+
msg = JSON.parse(data.toString());
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (msg.type === "status" && msg.browserConnected) {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve();
|
|
128
|
+
} else if (msg.type === "browser_connected") {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
resolve();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
ws.on("error", (err) => {
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
reject(err);
|
|
136
|
+
});
|
|
137
|
+
ws.on("close", () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Send an RPC request through the daemon to the browser SDK.
|
|
144
|
+
*/
|
|
145
|
+
async request(method, params) {
|
|
146
|
+
const ws = this.ws;
|
|
147
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
148
|
+
throw new Error("Not connected to bridge daemon");
|
|
149
|
+
}
|
|
150
|
+
const id = crypto.randomUUID();
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
reject(
|
|
154
|
+
new Error(
|
|
155
|
+
`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
this.close();
|
|
159
|
+
}, this.timeout);
|
|
160
|
+
const onMessage = (data) => {
|
|
161
|
+
let msg;
|
|
162
|
+
try {
|
|
163
|
+
msg = JSON.parse(data.toString());
|
|
164
|
+
} catch {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (msg.type === "rpc_response" && msg.id === id) {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
ws.off("message", onMessage);
|
|
170
|
+
if (msg.success) {
|
|
171
|
+
resolve(msg.data);
|
|
172
|
+
} else {
|
|
173
|
+
const err = msg.error;
|
|
174
|
+
reject(
|
|
175
|
+
new Error(
|
|
176
|
+
err ? `[${err.code}] ${err.message}` : "Request failed"
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
ws.on("message", onMessage);
|
|
183
|
+
ws.send(
|
|
184
|
+
JSON.stringify({
|
|
185
|
+
id,
|
|
186
|
+
type: "rpc_request",
|
|
187
|
+
method,
|
|
188
|
+
params
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Check if the WebSocket connection is open.
|
|
195
|
+
*/
|
|
196
|
+
isOpen() {
|
|
197
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Send a shutdown signal to the daemon.
|
|
201
|
+
*/
|
|
202
|
+
shutdown() {
|
|
203
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
204
|
+
this.ws.send(JSON.stringify({ type: "shutdown" }));
|
|
205
|
+
}
|
|
206
|
+
this.close();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Close the client connection (daemon stays alive).
|
|
210
|
+
*/
|
|
211
|
+
close() {
|
|
212
|
+
if (this.ws) {
|
|
213
|
+
try {
|
|
214
|
+
this.ws.close();
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
this.ws = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/lib/bridge-singleton.ts
|
|
223
|
+
function findDistDir() {
|
|
224
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
225
|
+
for (let i = 0; i < 10; i++) {
|
|
226
|
+
const candidate = path.join(dir, "lib", "ws-daemon.js");
|
|
227
|
+
if (fs2.existsSync(candidate)) return dir;
|
|
228
|
+
dir = path.dirname(dir);
|
|
229
|
+
}
|
|
230
|
+
throw new Error("Cannot find ws-daemon.js in dist tree");
|
|
231
|
+
}
|
|
232
|
+
var JAW_KEYS_URL = "https://keys.jaw.id";
|
|
233
|
+
var LOCK_PATH = path.join(PATHS.root, "daemon.lock");
|
|
234
|
+
function isDaemonProcess(pid) {
|
|
235
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid > 4194304) return false;
|
|
236
|
+
try {
|
|
237
|
+
process.kill(pid, 0);
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const cmd = execSync(`ps -p ${String(pid)} -o command=`, {
|
|
243
|
+
encoding: "utf-8",
|
|
244
|
+
timeout: 3e3
|
|
245
|
+
}).trim();
|
|
246
|
+
return cmd.includes("ws-daemon");
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function loadBridgeInfo() {
|
|
252
|
+
try {
|
|
253
|
+
if (!fs2.existsSync(PATHS.bridge)) return null;
|
|
254
|
+
const raw = fs2.readFileSync(PATHS.bridge, "utf-8");
|
|
255
|
+
const info = JSON.parse(raw);
|
|
256
|
+
if (!isDaemonProcess(info.pid)) {
|
|
257
|
+
try {
|
|
258
|
+
fs2.unlinkSync(PATHS.bridge);
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return info;
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function getBridge(options) {
|
|
269
|
+
let info = loadBridgeInfo();
|
|
270
|
+
if (!info) {
|
|
271
|
+
info = await spawnDaemon(options);
|
|
272
|
+
}
|
|
273
|
+
const bridge = new WSBridge({
|
|
274
|
+
port: info.port,
|
|
275
|
+
token: info.token,
|
|
276
|
+
timeout: options.timeout
|
|
277
|
+
});
|
|
278
|
+
await bridge.connect();
|
|
279
|
+
return bridge;
|
|
280
|
+
}
|
|
281
|
+
function acquireLock() {
|
|
282
|
+
ensureDir(PATHS.root);
|
|
283
|
+
try {
|
|
284
|
+
const fd = fs2.openSync(LOCK_PATH, "wx");
|
|
285
|
+
fs2.writeFileSync(LOCK_PATH, String(process.pid), { mode: 384 });
|
|
286
|
+
return fd;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (err.code === "EEXIST") {
|
|
289
|
+
try {
|
|
290
|
+
const lockPid = parseInt(fs2.readFileSync(LOCK_PATH, "utf-8").trim(), 10);
|
|
291
|
+
if (Number.isInteger(lockPid) && lockPid > 0) {
|
|
292
|
+
try {
|
|
293
|
+
process.kill(lockPid, 0);
|
|
294
|
+
return null;
|
|
295
|
+
} catch {
|
|
296
|
+
try {
|
|
297
|
+
fs2.unlinkSync(LOCK_PATH);
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
return acquireLock();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
try {
|
|
305
|
+
fs2.unlinkSync(LOCK_PATH);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function releaseLock(fd) {
|
|
315
|
+
try {
|
|
316
|
+
fs2.closeSync(fd);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
fs2.unlinkSync(LOCK_PATH);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function spawnDaemon(options) {
|
|
325
|
+
ensureDir(PATHS.root);
|
|
326
|
+
const lockFd = acquireLock();
|
|
327
|
+
if (lockFd === null) {
|
|
328
|
+
const deadline = Date.now() + 15e3;
|
|
329
|
+
while (Date.now() < deadline) {
|
|
330
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
331
|
+
const info = loadBridgeInfo();
|
|
332
|
+
if (info) return info;
|
|
333
|
+
}
|
|
334
|
+
throw new Error(
|
|
335
|
+
"Another process is starting the daemon. Timed out waiting for it."
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const existing = loadBridgeInfo();
|
|
340
|
+
if (existing) return existing;
|
|
341
|
+
const config = loadConfig();
|
|
342
|
+
const keysUrl = options.keysUrl ?? config.keysUrl ?? JAW_KEYS_URL;
|
|
343
|
+
if (!isValidKeysUrl(keysUrl)) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Untrusted keysUrl: ${keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const daemonArgs = {
|
|
349
|
+
keysUrl,
|
|
350
|
+
chainId: options.chainId ?? config.defaultChain ?? 1,
|
|
351
|
+
ens: options.ens ?? config.ens,
|
|
352
|
+
paymasterUrl: options.paymasterUrl ?? config.paymasterUrl,
|
|
353
|
+
timeout: options.timeout ?? 12e4
|
|
354
|
+
};
|
|
355
|
+
const daemonScript = path.join(findDistDir(), "lib", "ws-daemon.js");
|
|
356
|
+
try {
|
|
357
|
+
if (fs2.existsSync(PATHS.bridge)) fs2.unlinkSync(PATHS.bridge);
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
const logFd = fs2.openSync(PATHS.daemonLog, "w", 384);
|
|
361
|
+
const child = spawn(
|
|
362
|
+
process.execPath,
|
|
363
|
+
[daemonScript, JSON.stringify(daemonArgs)],
|
|
364
|
+
{
|
|
365
|
+
detached: true,
|
|
366
|
+
stdio: ["ignore", logFd, logFd],
|
|
367
|
+
// Pass API key via env var instead of process args to avoid ps aux exposure
|
|
368
|
+
env: { ...process.env, JAW_DAEMON_API_KEY: options.apiKey }
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
child.unref();
|
|
372
|
+
fs2.closeSync(logFd);
|
|
373
|
+
const deadline = Date.now() + 15e3;
|
|
374
|
+
while (Date.now() < deadline) {
|
|
375
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
376
|
+
const info = loadBridgeInfo();
|
|
377
|
+
if (info) return info;
|
|
378
|
+
}
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Daemon failed to start within 15s. Check ${PATHS.daemonLog} for details.`
|
|
381
|
+
);
|
|
382
|
+
} finally {
|
|
383
|
+
releaseLock(lockFd);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/mcp/handlers/rpc.ts
|
|
388
|
+
function resolveApiKey() {
|
|
389
|
+
const apiKey = process.env["JAW_API_KEY"] ?? loadConfig().apiKey;
|
|
390
|
+
if (!apiKey) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
"API key required. Set JAW_API_KEY env var or run: jaw config set apiKey <key>"
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return apiKey;
|
|
396
|
+
}
|
|
397
|
+
var cachedBridge = null;
|
|
398
|
+
async function getOrCreateBridge(chainId) {
|
|
399
|
+
if (cachedBridge && cachedBridge.isOpen()) {
|
|
400
|
+
return cachedBridge;
|
|
401
|
+
}
|
|
402
|
+
const config = loadConfig();
|
|
403
|
+
const apiKey = resolveApiKey();
|
|
404
|
+
cachedBridge = await getBridge({
|
|
405
|
+
keysUrl: config.keysUrl,
|
|
406
|
+
apiKey,
|
|
407
|
+
chainId: chainId ?? config.defaultChain,
|
|
408
|
+
ens: config.ens,
|
|
409
|
+
paymasterUrl: config.paymasterUrl
|
|
410
|
+
});
|
|
411
|
+
return cachedBridge;
|
|
412
|
+
}
|
|
413
|
+
function closeCachedBridge() {
|
|
414
|
+
if (cachedBridge) {
|
|
415
|
+
cachedBridge.close();
|
|
416
|
+
cachedBridge = null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function isBridgeCached() {
|
|
420
|
+
return cachedBridge !== null && cachedBridge.isOpen();
|
|
421
|
+
}
|
|
422
|
+
function registerRpcTool(server) {
|
|
423
|
+
server.tool(
|
|
424
|
+
"jaw_rpc",
|
|
425
|
+
"Execute any JAW.id wallet RPC method via the browser bridge. Supports transactions, signing, permissions, and queries. Methods that require signing will open the browser for passkey authentication. IMPORTANT: Read the jaw://api-reference resource for the full list of methods, and jaw://api-reference/{method} for detailed parameter formats and examples.",
|
|
426
|
+
rpcMethodSchema,
|
|
427
|
+
async (params) => {
|
|
428
|
+
try {
|
|
429
|
+
const bridge = await getOrCreateBridge(params.chainId);
|
|
430
|
+
const result = await bridge.request(params.method, params.params);
|
|
431
|
+
return mcpResult(result);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
if (cachedBridge && !cachedBridge.isOpen()) {
|
|
434
|
+
cachedBridge = null;
|
|
435
|
+
}
|
|
436
|
+
return mcpError(err);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export { closeCachedBridge, isBridgeCached, registerRpcTool };
|
|
443
|
+
//# sourceMappingURL=rpc.js.map
|
|
444
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/mcp/tools.ts","../../../src/mcp/helpers.ts","../../../src/lib/paths.ts","../../../src/lib/validation.ts","../../../src/lib/config.ts","../../../src/lib/ws-bridge.ts","../../../src/lib/bridge-singleton.ts","../../../src/mcp/handlers/rpc.ts"],"names":["fs","path2"],"mappings":"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","file":"rpc.js","sourcesContent":["import { z } from \"zod\";\n\n/**\n * Single generic RPC method schema.\n * Accepts any EIP-1193 RPC method and forwards to JAWProvider.\n */\nexport const rpcMethodSchema = {\n method: z\n .string()\n .describe(\n \"EIP-1193 RPC method name (e.g. wallet_connect, wallet_sendCalls, personal_sign). \" +\n \"Read the jaw://api-reference resource for the full list and jaw://api-reference/{method} for parameter details.\",\n ),\n params: z\n .any()\n .optional()\n .describe(\n \"Method parameters — structure varies by method. \" +\n \"Read the jaw://api-reference/{method} resource for the expected format.\",\n ),\n chainId: z\n .number()\n .optional()\n .describe(\n \"Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia\",\n ),\n};\n\nexport const configSetSchema = {\n key: z\n .enum([\"apiKey\", \"defaultChain\", \"keysUrl\", \"paymasterUrl\", \"ens\"])\n .describe(\"Config key\"),\n value: z.string().describe(\"Config value\"),\n};\n","export function mcpError(err: unknown) {\n return {\n isError: true as const,\n content: [\n {\n type: \"text\" as const,\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n}\n\nexport function mcpResult(data: unknown) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(data),\n },\n ],\n };\n}","import * as path from \"node:path\";\nimport * as os from \"node:os\";\n\nconst JAW_DIR = path.join(os.homedir(), \".jaw\");\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, \"config.json\"),\n session: path.join(JAW_DIR, \"session.json\"),\n bridge: path.join(JAW_DIR, \"bridge.json\"),\n daemonLog: path.join(JAW_DIR, \"daemon.log\"),\n} as const;\n","export function isValidAddress(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nexport function isValidHex(value: string): boolean {\n return /^0x[0-9a-fA-F]*$/.test(value);\n}\n\nexport function isValidChainId(value: string | number): boolean {\n const num = typeof value === \"string\" ? parseInt(value, 10) : value;\n return Number.isInteger(num) && num > 0;\n}\n\nexport function parseChainId(value: string): number {\n const num = parseInt(value, 10);\n if (!isValidChainId(num)) {\n throw new Error(`Invalid chain ID: ${value}`);\n }\n return num;\n}\n\nexport function assertAddress(value: string, label = \"address\"): `0x${string}` {\n if (!isValidAddress(value)) {\n throw new Error(`Invalid ${label}: ${value}`);\n }\n return value as `0x${string}`;\n}\n\nexport function parseWei(raw: string, label = \"value\"): bigint {\n try {\n return BigInt(raw);\n } catch {\n throw new Error(`Invalid ${label}: \"${raw}\" is not a valid wei amount`);\n }\n}\n\nexport function isValidKeysUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith(\".jaw.id\") ||\n parsed.hostname === \"jaw.id\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n const isSecure =\n parsed.protocol === \"https:\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n return isTrustedHost && isSecure;\n } catch {\n return false;\n }\n}\n","import * as fs from \"node:fs\";\nimport { PATHS } from \"./paths.js\";\nimport type { JawConfig } from \"./types.js\";\nimport { isValidKeysUrl } from \"./validation.js\";\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, \"utf-8\");\n try {\n return JSON.parse(raw) as JawConfig;\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`,\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function getConfigValue(\n key: keyof JawConfig,\n): string | number | undefined {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue(\n key: keyof JawConfig,\n value: string | number,\n): void {\n if (\n key === \"keysUrl\" &&\n typeof value === \"string\" &&\n !isValidKeysUrl(value)\n ) {\n throw new Error(\n `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`,\n );\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","/**\n * WSBridge — CLI-side client\n *\n * Lightweight WebSocket client that connects to the background daemon.\n * Each CLI command creates a WSBridge, sends one request, and exits.\n * The daemon (and browser SDK) stay alive across commands.\n */\n\nimport * as crypto from \"node:crypto\";\nimport WebSocket from \"ws\";\n\nexport interface WSBridgeOptions {\n port: number;\n token: string;\n timeout?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\nexport class WSBridge {\n private readonly port: number;\n private readonly token: string;\n private readonly timeout: number;\n private ws: WebSocket | null = null;\n\n constructor(options: WSBridgeOptions) {\n this.port = options.port;\n this.token = options.token;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Connect to the daemon's WebSocket server.\n */\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const url = `ws://127.0.0.1:${this.port}?token=${encodeURIComponent(this.token)}&role=cli`;\n const ws = new WebSocket(url);\n\n const timer = setTimeout(() => {\n ws.close();\n reject(\n new Error(\n \"Browser SDK did not connect in time.\\n\" +\n \"If the browser tab failed to open, run `jaw disconnect` then try again.\",\n ),\n );\n }, 30_000);\n\n ws.on(\"open\", () => {\n clearTimeout(timer);\n this.ws = ws;\n });\n\n ws.on(\"message\", (data) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(data.toString()) as Record<string, unknown>;\n } catch {\n return;\n }\n // Wait for the status message confirming daemon is ready.\n // If browser isn't connected yet, stay connected and wait for\n // a \"browser_connected\" event (the daemon notifies CLI clients\n // when the browser joins).\n if (msg.type === \"status\" && msg.browserConnected) {\n clearTimeout(timer);\n resolve();\n } else if (msg.type === \"browser_connected\") {\n clearTimeout(timer);\n resolve();\n }\n });\n\n ws.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n ws.on(\"close\", () => {\n clearTimeout(timer);\n });\n });\n }\n\n /**\n * Send an RPC request through the daemon to the browser SDK.\n */\n async request(method: string, params?: unknown): Promise<unknown> {\n const ws = this.ws;\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to bridge daemon\");\n }\n\n const id = crypto.randomUUID();\n\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `Request timed out after ${this.timeout / 1000}s. ` +\n \"Did you complete the action in the browser?\",\n ),\n );\n this.close();\n }, this.timeout);\n\n const onMessage = (data: WebSocket.Data) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(data.toString()) as Record<string, unknown>;\n } catch {\n return;\n }\n\n if (msg.type === \"rpc_response\" && msg.id === id) {\n clearTimeout(timer);\n ws.off(\"message\", onMessage);\n\n if (msg.success) {\n resolve(msg.data);\n } else {\n const err = msg.error as\n | { code: number; message: string }\n | undefined;\n reject(\n new Error(\n err ? `[${err.code}] ${err.message}` : \"Request failed\",\n ),\n );\n }\n }\n };\n\n ws.on(\"message\", onMessage);\n\n ws.send(\n JSON.stringify({\n id,\n type: \"rpc_request\",\n method,\n params,\n }),\n );\n });\n }\n\n /**\n * Check if the WebSocket connection is open.\n */\n isOpen(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Send a shutdown signal to the daemon.\n */\n shutdown(): void {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({ type: \"shutdown\" }));\n }\n this.close();\n }\n\n /**\n * Close the client connection (daemon stays alive).\n */\n close(): void {\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n // ignore\n }\n this.ws = null;\n }\n }\n}\n","/**\n * Bridge lifecycle management.\n *\n * - Spawns a background daemon if one isn't running\n * - Creates a WSBridge client connected to the daemon\n * - Provides shutdown to kill the daemon\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawn, execSync } from \"node:child_process\";\nimport { PATHS } from \"./paths.js\";\nimport { ensureDir } from \"./config.js\";\nimport { WSBridge } from \"./ws-bridge.js\";\nimport { loadConfig } from \"./config.js\";\nimport { isValidKeysUrl } from \"./validation.js\";\n\n/**\n * Find the dist directory root by walking up from the current file.\n * Needed because tsup inlines this code into consuming entry points,\n * so import.meta.url may point to e.g. dist/commands/rpc/call.js\n * rather than dist/lib/bridge-singleton.js.\n */\nfunction findDistDir(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n // Walk up until we find a directory that contains lib/ws-daemon.js\n for (let i = 0; i < 10; i++) {\n const candidate = path.join(dir, \"lib\", \"ws-daemon.js\");\n if (fs.existsSync(candidate)) return dir;\n dir = path.dirname(dir);\n }\n throw new Error(\"Cannot find ws-daemon.js in dist tree\");\n}\n\nconst JAW_KEYS_URL = \"https://keys.jaw.id\";\nconst LOCK_PATH = path.join(PATHS.root, \"daemon.lock\");\n\ninterface BridgeInfo {\n port: number;\n token: string;\n pid: number;\n startedAt: string;\n}\n\nexport interface BridgeOptions {\n keysUrl?: string;\n apiKey: string;\n chainId?: number;\n ens?: string;\n paymasterUrl?: string;\n timeout?: number;\n}\n\n/**\n * Verify that the process at `pid` is actually our ws-daemon, not an\n * impersonator that killed the real daemon and took over the port.\n */\nfunction isDaemonProcess(pid: number): boolean {\n // Validate PID is a positive integer to prevent command injection via bridge.json\n if (!Number.isInteger(pid) || pid <= 0 || pid > 4_194_304) return false;\n\n try {\n // Signal 0 checks if process exists\n process.kill(pid, 0);\n } catch {\n return false;\n }\n\n try {\n // Verify the process command line contains ws-daemon\n const cmd = execSync(`ps -p ${String(pid)} -o command=`, {\n encoding: \"utf-8\",\n timeout: 3000,\n }).trim();\n return cmd.includes(\"ws-daemon\");\n } catch {\n // If ps fails (process just died), treat as invalid\n return false;\n }\n}\n\nfunction loadBridgeInfo(): BridgeInfo | null {\n try {\n if (!fs.existsSync(PATHS.bridge)) return null;\n const raw = fs.readFileSync(PATHS.bridge, \"utf-8\");\n const info = JSON.parse(raw) as BridgeInfo;\n if (!isDaemonProcess(info.pid)) {\n // Process is dead or not our daemon — clean up stale file\n try {\n fs.unlinkSync(PATHS.bridge);\n } catch {\n /* ignore */\n }\n return null;\n }\n return info;\n } catch {\n return null;\n }\n}\n\n/**\n * Ensure a daemon is running and return a connected WSBridge client.\n */\nexport async function getBridge(options: BridgeOptions): Promise<WSBridge> {\n let info = loadBridgeInfo();\n\n if (!info) {\n info = await spawnDaemon(options);\n }\n\n const bridge = new WSBridge({\n port: info.port,\n token: info.token,\n timeout: options.timeout,\n });\n\n await bridge.connect();\n return bridge;\n}\n\n/**\n * Shutdown the running daemon.\n */\nexport async function shutdownDaemon(): Promise<void> {\n const info = loadBridgeInfo();\n if (!info) return;\n\n // Kill the daemon process directly\n try {\n process.kill(info.pid, \"SIGTERM\");\n } catch {\n // Process already dead\n }\n\n // Clean up bridge file\n try {\n if (fs.existsSync(PATHS.bridge)) fs.unlinkSync(PATHS.bridge);\n } catch {\n // ignore\n }\n}\n\n/**\n * Acquire an exclusive lockfile to prevent concurrent daemon spawning.\n * Returns the file descriptor on success, or null if another process holds the lock.\n */\nfunction acquireLock(): number | null {\n ensureDir(PATHS.root);\n try {\n // O_CREAT | O_EXCL — fails atomically if file already exists\n const fd = fs.openSync(LOCK_PATH, \"wx\");\n fs.writeFileSync(LOCK_PATH, String(process.pid), { mode: 0o600 });\n return fd;\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n // Check if the lock holder is still alive (stale lock recovery)\n try {\n const lockPid = parseInt(fs.readFileSync(LOCK_PATH, \"utf-8\").trim(), 10);\n if (Number.isInteger(lockPid) && lockPid > 0) {\n try {\n process.kill(lockPid, 0);\n return null; // Process alive — lock is held\n } catch {\n // Process dead — stale lock, remove and retry once\n try { fs.unlinkSync(LOCK_PATH); } catch { /* ignore */ }\n return acquireLock();\n }\n }\n } catch {\n // Can't read lock — try removing stale file\n try { fs.unlinkSync(LOCK_PATH); } catch { /* ignore */ }\n }\n return null;\n }\n throw err;\n }\n}\n\nfunction releaseLock(fd: number): void {\n try { fs.closeSync(fd); } catch { /* ignore */ }\n try { fs.unlinkSync(LOCK_PATH); } catch { /* ignore */ }\n}\n\nasync function spawnDaemon(options: BridgeOptions): Promise<BridgeInfo> {\n ensureDir(PATHS.root);\n\n // Acquire lock to prevent concurrent daemon spawning (TOCTOU guard)\n const lockFd = acquireLock();\n if (lockFd === null) {\n // Another process is spawning — wait for bridge.json to appear\n const deadline = Date.now() + 15_000;\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, 300));\n const info = loadBridgeInfo();\n if (info) return info;\n }\n throw new Error(\n \"Another process is starting the daemon. Timed out waiting for it.\",\n );\n }\n\n try {\n // Re-check after acquiring lock — another process may have just finished\n const existing = loadBridgeInfo();\n if (existing) return existing;\n\n const config = loadConfig();\n\n const keysUrl = options.keysUrl ?? config.keysUrl ?? JAW_KEYS_URL;\n if (!isValidKeysUrl(keysUrl)) {\n throw new Error(\n `Untrusted keysUrl: ${keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.`,\n );\n }\n\n const daemonArgs = {\n keysUrl,\n chainId: options.chainId ?? config.defaultChain ?? 1,\n ens: options.ens ?? config.ens,\n paymasterUrl: options.paymasterUrl ?? config.paymasterUrl,\n timeout: options.timeout ?? 120_000,\n };\n\n const daemonScript = path.join(findDistDir(), \"lib\", \"ws-daemon.js\");\n\n // Remove stale bridge file before spawning\n try {\n if (fs.existsSync(PATHS.bridge)) fs.unlinkSync(PATHS.bridge);\n } catch {\n // ignore\n }\n\n // Truncate log to prevent unbounded growth from previous sessions\n const logFd = fs.openSync(PATHS.daemonLog, \"w\", 0o600);\n const child = spawn(\n process.execPath,\n [daemonScript, JSON.stringify(daemonArgs)],\n {\n detached: true,\n stdio: [\"ignore\", logFd, logFd],\n // Pass API key via env var instead of process args to avoid ps aux exposure\n env: { ...process.env, JAW_DAEMON_API_KEY: options.apiKey },\n },\n );\n child.unref();\n fs.closeSync(logFd);\n\n // Poll for bridge.json to appear (daemon writes it once WS server is ready)\n const deadline = Date.now() + 15_000;\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, 200));\n const info = loadBridgeInfo();\n if (info) return info;\n }\n\n throw new Error(\n `Daemon failed to start within 15s. Check ${PATHS.daemonLog} for details.`,\n );\n } finally {\n releaseLock(lockFd);\n }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { rpcMethodSchema } from \"../tools.js\";\nimport { mcpError, mcpResult } from \"../helpers.js\";\nimport { getBridge } from \"../../lib/bridge-singleton.js\";\nimport { loadConfig } from \"../../lib/config.js\";\nimport type { WSBridge } from \"../../lib/ws-bridge.js\";\n\nfunction resolveApiKey(): string {\n const apiKey = process.env[\"JAW_API_KEY\"] ?? loadConfig().apiKey;\n if (!apiKey) {\n throw new Error(\n \"API key required. Set JAW_API_KEY env var or run: jaw config set apiKey <key>\",\n );\n }\n return apiKey;\n}\n\n/** Cached bridge connection — reused across sequential MCP tool calls. */\nlet cachedBridge: WSBridge | null = null;\n\nasync function getOrCreateBridge(chainId?: number): Promise<WSBridge> {\n // Reuse if the connection is still open\n if (cachedBridge && cachedBridge.isOpen()) {\n return cachedBridge;\n }\n\n const config = loadConfig();\n const apiKey = resolveApiKey();\n\n cachedBridge = await getBridge({\n keysUrl: config.keysUrl,\n apiKey,\n chainId: chainId ?? config.defaultChain,\n ens: config.ens,\n paymasterUrl: config.paymasterUrl,\n });\n\n return cachedBridge;\n}\n\nexport function closeCachedBridge(): void {\n if (cachedBridge) {\n cachedBridge.close();\n cachedBridge = null;\n }\n}\n\nexport function isBridgeCached(): boolean {\n return cachedBridge !== null && cachedBridge.isOpen();\n}\n\nexport function registerRpcTool(server: McpServer): void {\n // @ts-expect-error — MCP SDK deep type inference with z.any() in schema\n server.tool(\n \"jaw_rpc\",\n \"Execute any JAW.id wallet RPC method via the browser bridge. \" +\n \"Supports transactions, signing, permissions, and queries. \" +\n \"Methods that require signing will open the browser for passkey authentication. \" +\n \"IMPORTANT: Read the jaw://api-reference resource for the full list of methods, \" +\n \"and jaw://api-reference/{method} for detailed parameter formats and examples.\",\n rpcMethodSchema,\n async (params) => {\n try {\n const bridge = await getOrCreateBridge(params.chainId);\n const result = await bridge.request(params.method, params.params);\n return mcpResult(result);\n } catch (err) {\n // If the connection broke, clear the cache so the next call reconnects\n if (cachedBridge && !cachedBridge.isOpen()) {\n cachedBridge = null;\n }\n return mcpError(err);\n }\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/mcp/helpers.ts
|
|
2
|
+
function mcpError(err) {
|
|
3
|
+
return {
|
|
4
|
+
isError: true,
|
|
5
|
+
content: [
|
|
6
|
+
{
|
|
7
|
+
type: "text",
|
|
8
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function mcpResult(data) {
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: JSON.stringify(data)
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { mcpError, mcpResult };
|
|
25
|
+
//# sourceMappingURL=helpers.js.map
|
|
26
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/helpers.ts"],"names":[],"mappings":";AAAO,SAAS,SAAS,GAAA,EAAc;AACrC,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA;AAAA,IACT,OAAA,EAAS;AAAA,MACP;AAAA,QACE,IAAA,EAAM,MAAA;AAAA,QACN,IAAA,EAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA;AAClE;AACF,GACF;AACF;AAEO,SAAS,UAAU,IAAA,EAAe;AACvC,EAAA,OAAO;AAAA,IACL,OAAA,EAAS;AAAA,MACP;AAAA,QACE,IAAA,EAAM,MAAA;AAAA,QACN,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI;AAAA;AAC3B;AACF,GACF;AACF","file":"helpers.js","sourcesContent":["export function mcpError(err: unknown) {\n return {\n isError: true as const,\n content: [\n {\n type: \"text\" as const,\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n}\n\nexport function mcpResult(data: unknown) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(data),\n },\n ],\n };\n}"]}
|