@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.
Files changed (50) hide show
  1. package/README.md +239 -0
  2. package/bin/run.js +5 -0
  3. package/dist/base-command.js +124 -0
  4. package/dist/base-command.js.map +1 -0
  5. package/dist/commands/config/set.js +244 -0
  6. package/dist/commands/config/set.js.map +1 -0
  7. package/dist/commands/config/show.js +147 -0
  8. package/dist/commands/config/show.js.map +1 -0
  9. package/dist/commands/disconnect.js +196 -0
  10. package/dist/commands/disconnect.js.map +1 -0
  11. package/dist/commands/mcp/index.js +670 -0
  12. package/dist/commands/mcp/index.js.map +1 -0
  13. package/dist/commands/rpc/call.js +531 -0
  14. package/dist/commands/rpc/call.js.map +1 -0
  15. package/dist/index.js +460 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/lib/bridge-singleton.js +362 -0
  18. package/dist/lib/bridge-singleton.js.map +1 -0
  19. package/dist/lib/config.js +75 -0
  20. package/dist/lib/config.js.map +1 -0
  21. package/dist/lib/output.js +56 -0
  22. package/dist/lib/output.js.map +1 -0
  23. package/dist/lib/paths.js +16 -0
  24. package/dist/lib/paths.js.map +1 -0
  25. package/dist/lib/session-store.js +81 -0
  26. package/dist/lib/session-store.js.map +1 -0
  27. package/dist/lib/types.js +3 -0
  28. package/dist/lib/types.js.map +1 -0
  29. package/dist/lib/validation.js +45 -0
  30. package/dist/lib/validation.js.map +1 -0
  31. package/dist/lib/ws-bridge.js +141 -0
  32. package/dist/lib/ws-bridge.js.map +1 -0
  33. package/dist/lib/ws-daemon.js +344 -0
  34. package/dist/lib/ws-daemon.js.map +1 -0
  35. package/dist/mcp/handlers/config.js +149 -0
  36. package/dist/mcp/handlers/config.js.map +1 -0
  37. package/dist/mcp/handlers/daemon.js +185 -0
  38. package/dist/mcp/handlers/daemon.js.map +1 -0
  39. package/dist/mcp/handlers/resources.js +60 -0
  40. package/dist/mcp/handlers/resources.js.map +1 -0
  41. package/dist/mcp/handlers/rpc.js +444 -0
  42. package/dist/mcp/handlers/rpc.js.map +1 -0
  43. package/dist/mcp/helpers.js +26 -0
  44. package/dist/mcp/helpers.js.map +1 -0
  45. package/dist/mcp/server.js +658 -0
  46. package/dist/mcp/server.js.map +1 -0
  47. package/dist/mcp/tools.js +22 -0
  48. package/dist/mcp/tools.js.map +1 -0
  49. package/oclif.manifest.json +335 -0
  50. package/package.json +100 -0
@@ -0,0 +1,658 @@
1
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import * as fs2 from 'fs';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawn, execSync } from 'child_process';
8
+ import * as os from 'os';
9
+ import * as crypto from 'crypto';
10
+ import WebSocket from 'ws';
11
+
12
+ // src/mcp/server.ts
13
+ var rpcMethodSchema = {
14
+ method: z.string().describe(
15
+ "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."
16
+ ),
17
+ params: z.any().optional().describe(
18
+ "Method parameters \u2014 structure varies by method. Read the jaw://api-reference/{method} resource for the expected format."
19
+ ),
20
+ chainId: z.number().optional().describe(
21
+ "Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia"
22
+ )
23
+ };
24
+ var configSetSchema = {
25
+ key: z.enum(["apiKey", "defaultChain", "keysUrl", "paymasterUrl", "ens"]).describe("Config key"),
26
+ value: z.string().describe("Config value")
27
+ };
28
+
29
+ // src/mcp/helpers.ts
30
+ function mcpError(err) {
31
+ return {
32
+ isError: true,
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
37
+ }
38
+ ]
39
+ };
40
+ }
41
+ function mcpResult(data) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: JSON.stringify(data)
47
+ }
48
+ ]
49
+ };
50
+ }
51
+ var JAW_DIR = path.join(os.homedir(), ".jaw");
52
+ var PATHS = {
53
+ root: JAW_DIR,
54
+ config: path.join(JAW_DIR, "config.json"),
55
+ session: path.join(JAW_DIR, "session.json"),
56
+ bridge: path.join(JAW_DIR, "bridge.json"),
57
+ daemonLog: path.join(JAW_DIR, "daemon.log")
58
+ };
59
+
60
+ // src/lib/validation.ts
61
+ function isValidKeysUrl(url) {
62
+ try {
63
+ const parsed = new URL(url);
64
+ const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
65
+ const isSecure = parsed.protocol === "https:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
66
+ return isTrustedHost && isSecure;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ // src/lib/config.ts
73
+ function ensureDir(dir) {
74
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
75
+ fs2.chmodSync(dir, 448);
76
+ }
77
+ function loadConfig() {
78
+ if (!fs2.existsSync(PATHS.config)) {
79
+ return {};
80
+ }
81
+ const raw = fs2.readFileSync(PATHS.config, "utf-8");
82
+ try {
83
+ return JSON.parse(raw);
84
+ } catch {
85
+ throw new Error(
86
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
87
+ );
88
+ }
89
+ }
90
+ function saveConfig(config) {
91
+ ensureDir(PATHS.root);
92
+ fs2.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
93
+ encoding: "utf-8",
94
+ mode: 384
95
+ });
96
+ }
97
+ function redactConfig(config) {
98
+ return {
99
+ ...config,
100
+ apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : void 0
101
+ };
102
+ }
103
+ function setConfigValue(key, value) {
104
+ if (key === "keysUrl" && typeof value === "string" && !isValidKeysUrl(value)) {
105
+ throw new Error(
106
+ `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`
107
+ );
108
+ }
109
+ const config = loadConfig();
110
+ const updated = { ...config, [key]: value };
111
+ saveConfig(updated);
112
+ }
113
+ var DEFAULT_TIMEOUT_MS = 12e4;
114
+ var WSBridge = class {
115
+ port;
116
+ token;
117
+ timeout;
118
+ ws = null;
119
+ constructor(options) {
120
+ this.port = options.port;
121
+ this.token = options.token;
122
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
123
+ }
124
+ /**
125
+ * Connect to the daemon's WebSocket server.
126
+ */
127
+ async connect() {
128
+ return new Promise((resolve, reject) => {
129
+ const url = `ws://127.0.0.1:${this.port}?token=${encodeURIComponent(this.token)}&role=cli`;
130
+ const ws = new WebSocket(url);
131
+ const timer = setTimeout(() => {
132
+ ws.close();
133
+ reject(
134
+ new Error(
135
+ "Browser SDK did not connect in time.\nIf the browser tab failed to open, run `jaw disconnect` then try again."
136
+ )
137
+ );
138
+ }, 3e4);
139
+ ws.on("open", () => {
140
+ clearTimeout(timer);
141
+ this.ws = ws;
142
+ });
143
+ ws.on("message", (data) => {
144
+ let msg;
145
+ try {
146
+ msg = JSON.parse(data.toString());
147
+ } catch {
148
+ return;
149
+ }
150
+ if (msg.type === "status" && msg.browserConnected) {
151
+ clearTimeout(timer);
152
+ resolve();
153
+ } else if (msg.type === "browser_connected") {
154
+ clearTimeout(timer);
155
+ resolve();
156
+ }
157
+ });
158
+ ws.on("error", (err) => {
159
+ clearTimeout(timer);
160
+ reject(err);
161
+ });
162
+ ws.on("close", () => {
163
+ clearTimeout(timer);
164
+ });
165
+ });
166
+ }
167
+ /**
168
+ * Send an RPC request through the daemon to the browser SDK.
169
+ */
170
+ async request(method, params) {
171
+ const ws = this.ws;
172
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
173
+ throw new Error("Not connected to bridge daemon");
174
+ }
175
+ const id = crypto.randomUUID();
176
+ return new Promise((resolve, reject) => {
177
+ const timer = setTimeout(() => {
178
+ reject(
179
+ new Error(
180
+ `Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
181
+ )
182
+ );
183
+ this.close();
184
+ }, this.timeout);
185
+ const onMessage = (data) => {
186
+ let msg;
187
+ try {
188
+ msg = JSON.parse(data.toString());
189
+ } catch {
190
+ return;
191
+ }
192
+ if (msg.type === "rpc_response" && msg.id === id) {
193
+ clearTimeout(timer);
194
+ ws.off("message", onMessage);
195
+ if (msg.success) {
196
+ resolve(msg.data);
197
+ } else {
198
+ const err = msg.error;
199
+ reject(
200
+ new Error(
201
+ err ? `[${err.code}] ${err.message}` : "Request failed"
202
+ )
203
+ );
204
+ }
205
+ }
206
+ };
207
+ ws.on("message", onMessage);
208
+ ws.send(
209
+ JSON.stringify({
210
+ id,
211
+ type: "rpc_request",
212
+ method,
213
+ params
214
+ })
215
+ );
216
+ });
217
+ }
218
+ /**
219
+ * Check if the WebSocket connection is open.
220
+ */
221
+ isOpen() {
222
+ return this.ws?.readyState === WebSocket.OPEN;
223
+ }
224
+ /**
225
+ * Send a shutdown signal to the daemon.
226
+ */
227
+ shutdown() {
228
+ if (this.ws?.readyState === WebSocket.OPEN) {
229
+ this.ws.send(JSON.stringify({ type: "shutdown" }));
230
+ }
231
+ this.close();
232
+ }
233
+ /**
234
+ * Close the client connection (daemon stays alive).
235
+ */
236
+ close() {
237
+ if (this.ws) {
238
+ try {
239
+ this.ws.close();
240
+ } catch {
241
+ }
242
+ this.ws = null;
243
+ }
244
+ }
245
+ };
246
+
247
+ // src/lib/bridge-singleton.ts
248
+ function findDistDir() {
249
+ let dir = path.dirname(fileURLToPath(import.meta.url));
250
+ for (let i = 0; i < 10; i++) {
251
+ const candidate = path.join(dir, "lib", "ws-daemon.js");
252
+ if (fs2.existsSync(candidate)) return dir;
253
+ dir = path.dirname(dir);
254
+ }
255
+ throw new Error("Cannot find ws-daemon.js in dist tree");
256
+ }
257
+ var JAW_KEYS_URL = "https://keys.jaw.id";
258
+ var LOCK_PATH = path.join(PATHS.root, "daemon.lock");
259
+ function isDaemonProcess(pid) {
260
+ if (!Number.isInteger(pid) || pid <= 0 || pid > 4194304) return false;
261
+ try {
262
+ process.kill(pid, 0);
263
+ } catch {
264
+ return false;
265
+ }
266
+ try {
267
+ const cmd = execSync(`ps -p ${String(pid)} -o command=`, {
268
+ encoding: "utf-8",
269
+ timeout: 3e3
270
+ }).trim();
271
+ return cmd.includes("ws-daemon");
272
+ } catch {
273
+ return false;
274
+ }
275
+ }
276
+ function loadBridgeInfo() {
277
+ try {
278
+ if (!fs2.existsSync(PATHS.bridge)) return null;
279
+ const raw = fs2.readFileSync(PATHS.bridge, "utf-8");
280
+ const info = JSON.parse(raw);
281
+ if (!isDaemonProcess(info.pid)) {
282
+ try {
283
+ fs2.unlinkSync(PATHS.bridge);
284
+ } catch {
285
+ }
286
+ return null;
287
+ }
288
+ return info;
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+ async function getBridge(options) {
294
+ let info = loadBridgeInfo();
295
+ if (!info) {
296
+ info = await spawnDaemon(options);
297
+ }
298
+ const bridge = new WSBridge({
299
+ port: info.port,
300
+ token: info.token,
301
+ timeout: options.timeout
302
+ });
303
+ await bridge.connect();
304
+ return bridge;
305
+ }
306
+ async function shutdownDaemon() {
307
+ const info = loadBridgeInfo();
308
+ if (!info) return;
309
+ try {
310
+ process.kill(info.pid, "SIGTERM");
311
+ } catch {
312
+ }
313
+ try {
314
+ if (fs2.existsSync(PATHS.bridge)) fs2.unlinkSync(PATHS.bridge);
315
+ } catch {
316
+ }
317
+ }
318
+ function acquireLock() {
319
+ ensureDir(PATHS.root);
320
+ try {
321
+ const fd = fs2.openSync(LOCK_PATH, "wx");
322
+ fs2.writeFileSync(LOCK_PATH, String(process.pid), { mode: 384 });
323
+ return fd;
324
+ } catch (err) {
325
+ if (err.code === "EEXIST") {
326
+ try {
327
+ const lockPid = parseInt(fs2.readFileSync(LOCK_PATH, "utf-8").trim(), 10);
328
+ if (Number.isInteger(lockPid) && lockPid > 0) {
329
+ try {
330
+ process.kill(lockPid, 0);
331
+ return null;
332
+ } catch {
333
+ try {
334
+ fs2.unlinkSync(LOCK_PATH);
335
+ } catch {
336
+ }
337
+ return acquireLock();
338
+ }
339
+ }
340
+ } catch {
341
+ try {
342
+ fs2.unlinkSync(LOCK_PATH);
343
+ } catch {
344
+ }
345
+ }
346
+ return null;
347
+ }
348
+ throw err;
349
+ }
350
+ }
351
+ function releaseLock(fd) {
352
+ try {
353
+ fs2.closeSync(fd);
354
+ } catch {
355
+ }
356
+ try {
357
+ fs2.unlinkSync(LOCK_PATH);
358
+ } catch {
359
+ }
360
+ }
361
+ async function spawnDaemon(options) {
362
+ ensureDir(PATHS.root);
363
+ const lockFd = acquireLock();
364
+ if (lockFd === null) {
365
+ const deadline = Date.now() + 15e3;
366
+ while (Date.now() < deadline) {
367
+ await new Promise((r) => setTimeout(r, 300));
368
+ const info = loadBridgeInfo();
369
+ if (info) return info;
370
+ }
371
+ throw new Error(
372
+ "Another process is starting the daemon. Timed out waiting for it."
373
+ );
374
+ }
375
+ try {
376
+ const existing = loadBridgeInfo();
377
+ if (existing) return existing;
378
+ const config = loadConfig();
379
+ const keysUrl = options.keysUrl ?? config.keysUrl ?? JAW_KEYS_URL;
380
+ if (!isValidKeysUrl(keysUrl)) {
381
+ throw new Error(
382
+ `Untrusted keysUrl: ${keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.`
383
+ );
384
+ }
385
+ const daemonArgs = {
386
+ keysUrl,
387
+ chainId: options.chainId ?? config.defaultChain ?? 1,
388
+ ens: options.ens ?? config.ens,
389
+ paymasterUrl: options.paymasterUrl ?? config.paymasterUrl,
390
+ timeout: options.timeout ?? 12e4
391
+ };
392
+ const daemonScript = path.join(findDistDir(), "lib", "ws-daemon.js");
393
+ try {
394
+ if (fs2.existsSync(PATHS.bridge)) fs2.unlinkSync(PATHS.bridge);
395
+ } catch {
396
+ }
397
+ const logFd = fs2.openSync(PATHS.daemonLog, "w", 384);
398
+ const child = spawn(
399
+ process.execPath,
400
+ [daemonScript, JSON.stringify(daemonArgs)],
401
+ {
402
+ detached: true,
403
+ stdio: ["ignore", logFd, logFd],
404
+ // Pass API key via env var instead of process args to avoid ps aux exposure
405
+ env: { ...process.env, JAW_DAEMON_API_KEY: options.apiKey }
406
+ }
407
+ );
408
+ child.unref();
409
+ fs2.closeSync(logFd);
410
+ const deadline = Date.now() + 15e3;
411
+ while (Date.now() < deadline) {
412
+ await new Promise((r) => setTimeout(r, 200));
413
+ const info = loadBridgeInfo();
414
+ if (info) return info;
415
+ }
416
+ throw new Error(
417
+ `Daemon failed to start within 15s. Check ${PATHS.daemonLog} for details.`
418
+ );
419
+ } finally {
420
+ releaseLock(lockFd);
421
+ }
422
+ }
423
+
424
+ // src/mcp/handlers/rpc.ts
425
+ function resolveApiKey() {
426
+ const apiKey = process.env["JAW_API_KEY"] ?? loadConfig().apiKey;
427
+ if (!apiKey) {
428
+ throw new Error(
429
+ "API key required. Set JAW_API_KEY env var or run: jaw config set apiKey <key>"
430
+ );
431
+ }
432
+ return apiKey;
433
+ }
434
+ var cachedBridge = null;
435
+ async function getOrCreateBridge(chainId) {
436
+ if (cachedBridge && cachedBridge.isOpen()) {
437
+ return cachedBridge;
438
+ }
439
+ const config = loadConfig();
440
+ const apiKey = resolveApiKey();
441
+ cachedBridge = await getBridge({
442
+ keysUrl: config.keysUrl,
443
+ apiKey,
444
+ chainId: chainId ?? config.defaultChain,
445
+ ens: config.ens,
446
+ paymasterUrl: config.paymasterUrl
447
+ });
448
+ return cachedBridge;
449
+ }
450
+ function closeCachedBridge() {
451
+ if (cachedBridge) {
452
+ cachedBridge.close();
453
+ cachedBridge = null;
454
+ }
455
+ }
456
+ function isBridgeCached() {
457
+ return cachedBridge !== null && cachedBridge.isOpen();
458
+ }
459
+ function registerRpcTool(server) {
460
+ server.tool(
461
+ "jaw_rpc",
462
+ "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.",
463
+ rpcMethodSchema,
464
+ async (params) => {
465
+ try {
466
+ const bridge = await getOrCreateBridge(params.chainId);
467
+ const result = await bridge.request(params.method, params.params);
468
+ return mcpResult(result);
469
+ } catch (err) {
470
+ if (cachedBridge && !cachedBridge.isOpen()) {
471
+ cachedBridge = null;
472
+ }
473
+ return mcpError(err);
474
+ }
475
+ }
476
+ );
477
+ }
478
+
479
+ // src/mcp/handlers/config.ts
480
+ function registerConfigTools(server) {
481
+ server.tool(
482
+ "jaw_config_show",
483
+ "Show current CLI configuration (API key redacted).",
484
+ {},
485
+ async () => {
486
+ try {
487
+ const config = redactConfig(loadConfig());
488
+ return {
489
+ content: [
490
+ { type: "text", text: JSON.stringify(config) }
491
+ ]
492
+ };
493
+ } catch (err) {
494
+ return mcpError(err);
495
+ }
496
+ }
497
+ );
498
+ server.tool(
499
+ "jaw_config_set",
500
+ "Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens).",
501
+ configSetSchema,
502
+ async (params) => {
503
+ try {
504
+ if (params.key === "defaultChain") {
505
+ const num = parseInt(params.value, 10);
506
+ if (isNaN(num) || num <= 0) {
507
+ throw new Error(`Invalid chain ID: ${params.value}`);
508
+ }
509
+ setConfigValue(params.key, num);
510
+ } else {
511
+ setConfigValue(params.key, params.value);
512
+ }
513
+ return {
514
+ content: [
515
+ {
516
+ type: "text",
517
+ text: `Set ${params.key} successfully`
518
+ }
519
+ ]
520
+ };
521
+ } catch (err) {
522
+ return mcpError(err);
523
+ }
524
+ }
525
+ );
526
+ }
527
+ function registerDaemonTools(server) {
528
+ server.tool(
529
+ "jaw_status",
530
+ "Check the current status of the JAW.id bridge \u2014 whether the daemon is running, the bridge connection is active, and what configuration is in use.",
531
+ {},
532
+ async () => {
533
+ try {
534
+ let daemonRunning = false;
535
+ let daemonPid = null;
536
+ try {
537
+ if (fs2.existsSync(PATHS.bridge)) {
538
+ const info = JSON.parse(fs2.readFileSync(PATHS.bridge, "utf-8"));
539
+ daemonPid = info.pid;
540
+ process.kill(info.pid, 0);
541
+ daemonRunning = true;
542
+ }
543
+ } catch {
544
+ daemonRunning = false;
545
+ }
546
+ const config = redactConfig(loadConfig());
547
+ const status = {
548
+ daemon: daemonRunning ? { running: true, pid: daemonPid } : { running: false },
549
+ bridgeConnection: isBridgeCached() ? "connected" : "disconnected",
550
+ config
551
+ };
552
+ return {
553
+ content: [
554
+ { type: "text", text: JSON.stringify(status, null, 2) }
555
+ ]
556
+ };
557
+ } catch (err) {
558
+ return mcpError(err);
559
+ }
560
+ }
561
+ );
562
+ server.tool(
563
+ "jaw_disconnect",
564
+ "Stop the background bridge daemon and close the browser session. Call this when you are done making wallet requests to clean up resources.",
565
+ {},
566
+ async () => {
567
+ try {
568
+ closeCachedBridge();
569
+ await shutdownDaemon();
570
+ return {
571
+ content: [
572
+ {
573
+ type: "text",
574
+ text: "Bridge daemon stopped and browser session closed."
575
+ }
576
+ ]
577
+ };
578
+ } catch (err) {
579
+ return mcpError(err);
580
+ }
581
+ }
582
+ );
583
+ }
584
+ var DOCS_BASE = "https://docs.jaw.id/api-reference";
585
+ async function fetchDocs(url) {
586
+ const res = await fetch(url);
587
+ if (!res.ok) {
588
+ throw new Error(`Failed to fetch docs: ${res.status} ${res.statusText}`);
589
+ }
590
+ const html = await res.text();
591
+ return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\s{2,}/g, " ").trim();
592
+ }
593
+ function registerResources(server) {
594
+ server.resource(
595
+ "api-reference",
596
+ "jaw://api-reference",
597
+ {
598
+ description: "JAW.id API reference \u2014 lists all supported RPC methods with descriptions. Read this before using the jaw_rpc tool to understand available methods.",
599
+ mimeType: "text/plain"
600
+ },
601
+ async () => ({
602
+ contents: [
603
+ {
604
+ uri: "jaw://api-reference",
605
+ mimeType: "text/plain",
606
+ text: await fetchDocs(DOCS_BASE)
607
+ }
608
+ ]
609
+ })
610
+ );
611
+ server.resource(
612
+ "api-reference-method",
613
+ new ResourceTemplate("jaw://api-reference/{method}", { list: void 0 }),
614
+ {
615
+ description: "Detailed documentation for a specific RPC method including parameters, request/response format, and examples. Use the method name from the api-reference overview (e.g. wallet_sendCalls, personal_sign).",
616
+ mimeType: "text/plain"
617
+ },
618
+ async (uri, variables) => {
619
+ const method = String(variables.method);
620
+ if (!/^[\w_]+$/.test(method)) {
621
+ throw new Error(
622
+ `Invalid method name: "${method}". Expected an RPC method like wallet_sendCalls.`
623
+ );
624
+ }
625
+ return {
626
+ contents: [
627
+ {
628
+ uri: uri.href,
629
+ mimeType: "text/plain",
630
+ text: await fetchDocs(`${DOCS_BASE}/${method}`)
631
+ }
632
+ ]
633
+ };
634
+ }
635
+ );
636
+ }
637
+
638
+ // src/mcp/server.ts
639
+ function createMcpServer() {
640
+ const server = new McpServer({
641
+ name: "jaw",
642
+ version: "0.0.1"
643
+ });
644
+ registerRpcTool(server);
645
+ registerConfigTools(server);
646
+ registerDaemonTools(server);
647
+ registerResources(server);
648
+ return server;
649
+ }
650
+ async function startMcpServer() {
651
+ const server = createMcpServer();
652
+ const transport = new StdioServerTransport();
653
+ await server.connect(transport);
654
+ }
655
+
656
+ export { createMcpServer, startMcpServer };
657
+ //# sourceMappingURL=server.js.map
658
+ //# sourceMappingURL=server.js.map