@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 @@
1
+ {"version":3,"sources":["../../src/lib/ws-daemon.ts"],"names":[],"mappings":"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","file":"ws-daemon.js","sourcesContent":["#!/usr/bin/env node\n/**\n * WS Bridge Daemon\n *\n * Long-lived background process that:\n * 1. Starts a WebSocket server on 127.0.0.1:{random_port}\n * 2. Opens browser to keys.jaw.id/cli-bridge\n * 3. Accepts WebSocket connections from the browser (SDK) and CLI clients\n * 4. Routes RPC requests from CLI clients to the browser SDK\n * 5. Writes connection info to ~/.jaw/bridge.json\n *\n * The daemon stays alive across CLI commands so the browser SDK instance\n * (and its in-memory state) persists.\n */\n\nimport * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { WebSocketServer, WebSocket } from \"ws\";\n\nconst JAW_DIR = path.join(os.homedir(), \".jaw\");\nconst BRIDGE_PATH = path.join(JAW_DIR, \"bridge.json\");\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 min idle → auto-shutdown\n\n// ── Limits ────────────────────────────────────────────────────────\nconst MAX_PENDING_REQUESTS = 100;\nconst MAX_CLI_CLIENTS = 50;\nconst RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute\nconst RATE_LIMIT_MAX_REQUESTS = 60; // 60 requests per minute per client\n\n// ── Types ──────────────────────────────────────────────────────────\n\ninterface BridgeInfo {\n port: number;\n token: string;\n pid: number;\n startedAt: string;\n}\n\ninterface PendingRequest {\n clientWs: WebSocket;\n clientId: string;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface RateLimitEntry {\n timestamps: number[];\n}\n\n// ── keysUrl validation (duplicated from validation.ts for standalone daemon) ─\n\nfunction 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\n// ── Parse args from parent process ─────────────────────────────────\n\nconst args = JSON.parse(process.argv[2] ?? \"{}\") as {\n keysUrl: string;\n chainId: number;\n ens?: string;\n paymasterUrl?: string;\n timeout?: number;\n};\n\n// Validate keysUrl before proceeding\nif (!isValidKeysUrl(args.keysUrl)) {\n process.stderr.write(\n `Untrusted keysUrl: ${args.keysUrl}. Must be a *.jaw.id domain (HTTPS) or localhost.\\n`,\n );\n process.exit(1);\n}\n\n// API key is passed via env var to avoid exposure in `ps aux` output\nconst apiKey = process.env.JAW_DAEMON_API_KEY ?? \"\";\n// Clear from environment immediately after reading\ndelete process.env.JAW_DAEMON_API_KEY;\n\nconst token = crypto.randomUUID();\nconst timeout = args.timeout ?? 120_000;\n\n// Track pending requests: daemonRequestId -> { clientWs, clientId }\nconst pendingRequests = new Map<string, PendingRequest>();\n\n// Browser SDK connection\nlet browserWs: WebSocket | null = null;\nlet browserReady = false;\nlet idleTimer: ReturnType<typeof setTimeout> | null = null;\nlet heartbeat: ReturnType<typeof setInterval> | null = null;\n\n// Browser reopen state\nlet lastBrowserOpenTime = 0;\nconst BROWSER_REOPEN_COOLDOWN_MS = 5_000; // Min 5s between reopen attempts\nlet serverPort: number | null = null; // Stored after server starts listening\n\n// Track connected CLI clients (to notify when browser connects)\nconst cliClients = new Set<WebSocket>();\n\n// Per-client rate limiting\nconst rateLimits = new WeakMap<WebSocket, RateLimitEntry>();\n\n// ── Rate limiting ─────────────────────────────────────────────────\n\nfunction isRateLimited(ws: WebSocket): boolean {\n const now = Date.now();\n let entry = rateLimits.get(ws);\n if (!entry) {\n entry = { timestamps: [] };\n rateLimits.set(ws, entry);\n }\n\n // Remove timestamps outside the window\n entry.timestamps = entry.timestamps.filter(\n (t) => now - t < RATE_LIMIT_WINDOW_MS,\n );\n\n if (entry.timestamps.length >= RATE_LIMIT_MAX_REQUESTS) {\n return true;\n }\n\n entry.timestamps.push(now);\n return false;\n}\n\n// ── Browser reopen ────────────────────────────────────────────────\n\nasync function reopenBrowser(): Promise<void> {\n if (!serverPort) return;\n if (browserReady || browserWs?.readyState === WebSocket.OPEN) return;\n\n const now = Date.now();\n if (now - lastBrowserOpenTime < BROWSER_REOPEN_COOLDOWN_MS) return;\n\n lastBrowserOpenTime = now;\n const bridgeUrl = buildBridgeUrl(serverPort);\n try {\n const { default: open } = await import(\"open\");\n await open(bridgeUrl);\n } catch {\n // Best-effort; CLI will time out if browser never connects\n }\n}\n\n// ── Idle management ────────────────────────────────────────────────\n\nfunction resetIdleTimer(): void {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(() => {\n cleanup();\n process.exit(0);\n }, IDLE_TIMEOUT_MS);\n}\n\n// ── Cleanup ────────────────────────────────────────────────────────\n\nfunction cleanup(): void {\n if (heartbeat) clearInterval(heartbeat);\n // Notify browser to close the tab\n if (browserWs?.readyState === WebSocket.OPEN) {\n browserWs.send(JSON.stringify({ type: \"shutdown\" }));\n }\n try {\n if (fs.existsSync(BRIDGE_PATH)) fs.unlinkSync(BRIDGE_PATH);\n } catch {\n // ignore\n }\n}\n\nprocess.on(\"SIGTERM\", () => {\n cleanup();\n process.exit(0);\n});\nprocess.on(\"SIGINT\", () => {\n cleanup();\n process.exit(0);\n});\nprocess.on(\"exit\", cleanup);\n\n// ── Start WebSocket server ─────────────────────────────────────────\n\nconst MAX_PAYLOAD_BYTES = 1_048_576; // 1 MB – plenty for any RPC payload\nconst wss = new WebSocketServer({\n host: \"127.0.0.1\",\n port: 0,\n maxPayload: MAX_PAYLOAD_BYTES,\n});\n\nwss.on(\"listening\", async () => {\n const addr = wss.address();\n if (typeof addr === \"string\" || !addr) {\n process.exit(1);\n }\n\n const port = addr.port;\n serverPort = port;\n\n // Write bridge info\n fs.mkdirSync(JAW_DIR, { recursive: true, mode: 0o700 });\n const info: BridgeInfo = {\n port,\n token,\n pid: process.pid,\n startedAt: new Date().toISOString(),\n };\n fs.writeFileSync(BRIDGE_PATH, JSON.stringify(info, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n\n // Open browser\n lastBrowserOpenTime = Date.now();\n const bridgeUrl = buildBridgeUrl(port);\n const { default: open } = await import(\"open\");\n await open(bridgeUrl);\n\n resetIdleTimer();\n});\n\nwss.on(\"connection\", (ws, req) => {\n const url = new URL(req.url ?? \"\", \"http://127.0.0.1\");\n const urlToken = url.searchParams.get(\"token\");\n const role = url.searchParams.get(\"role\"); // \"browser\" or \"cli\"\n\n if (urlToken !== token) {\n ws.close(4001, \"Invalid token\");\n return;\n }\n\n if (role === \"browser\") {\n handleBrowserConnection(ws);\n } else {\n handleCliConnection(ws);\n }\n});\n\n// ── Browser connection ─────────────────────────────────────────────\n\nfunction handleBrowserConnection(ws: WebSocket): void {\n if (browserWs) {\n browserWs.close(4000, \"Replaced by new browser connection\");\n }\n browserWs = ws;\n browserReady = false;\n\n // Send config over the authenticated WebSocket rather than via URL\n ws.send(\n JSON.stringify({\n type: \"init\",\n apiKey,\n chainId: args.chainId,\n ens: args.ens,\n paymasterUrl: args.paymasterUrl,\n }),\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\n switch (msg.type) {\n case \"ready\":\n browserReady = true;\n // Start heartbeat\n if (heartbeat) clearInterval(heartbeat);\n heartbeat = setInterval(() => {\n if (browserWs?.readyState === WebSocket.OPEN) {\n browserWs.send(JSON.stringify({ type: \"ping\" }));\n }\n }, HEARTBEAT_INTERVAL_MS);\n // Notify any waiting CLI clients that browser is now connected\n for (const cli of cliClients) {\n if (cli.readyState === WebSocket.OPEN) {\n cli.send(JSON.stringify({ type: \"browser_connected\" }));\n }\n }\n break;\n\n case \"rpc_response\": {\n const id = msg.id as string;\n const pending = pendingRequests.get(id);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n pendingRequests.delete(id);\n\n // Forward response to the CLI client using its original request ID\n if (pending.clientWs.readyState === WebSocket.OPEN) {\n pending.clientWs.send(\n JSON.stringify({\n id: pending.clientId,\n type: \"rpc_response\",\n success: msg.success,\n data: msg.data,\n error: msg.error,\n address: msg.address,\n }),\n );\n }\n break;\n }\n\n case \"pong\":\n break;\n }\n });\n\n ws.on(\"close\", () => {\n if (browserWs === ws) {\n browserReady = false;\n browserWs = null;\n\n // Fail in-flight requests immediately — they can't complete without a browser.\n // The next CLI request will trigger reopenBrowser() automatically.\n for (const [, pending] of pendingRequests) {\n clearTimeout(pending.timer);\n if (pending.clientWs.readyState === WebSocket.OPEN) {\n pending.clientWs.send(\n JSON.stringify({\n id: pending.clientId,\n type: \"rpc_response\",\n success: false,\n error: {\n code: -32001,\n message:\n \"Browser tab was closed. Reopening — please retry in a few seconds.\",\n },\n }),\n );\n }\n }\n pendingRequests.clear();\n }\n });\n}\n\n// ── CLI client connection ──────────────────────────────────────────\n\nfunction handleCliConnection(ws: WebSocket): void {\n // Enforce max CLI client connections\n if (cliClients.size >= MAX_CLI_CLIENTS) {\n ws.close(4002, \"Too many CLI connections\");\n return;\n }\n\n resetIdleTimer();\n cliClients.add(ws);\n\n // Send current status\n ws.send(\n JSON.stringify({\n type: \"status\",\n browserConnected: browserReady,\n }),\n );\n\n // Auto-reopen browser if it's disconnected\n if (!browserReady) {\n reopenBrowser();\n }\n\n ws.on(\"close\", () => {\n cliClients.delete(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\n if (msg.type === \"rpc_request\") {\n resetIdleTimer();\n handleCliRpcRequest(ws, msg);\n } else if (msg.type === \"shutdown\") {\n cleanup();\n process.exit(0);\n }\n });\n}\n\nfunction handleCliRpcRequest(\n clientWs: WebSocket,\n msg: Record<string, unknown>,\n): void {\n const clientId = msg.id as string;\n\n // Rate limit per client\n if (isRateLimited(clientWs)) {\n clientWs.send(\n JSON.stringify({\n id: clientId,\n type: \"rpc_response\",\n success: false,\n error: {\n code: -32005,\n message: \"Rate limited. Too many requests — try again shortly.\",\n },\n }),\n );\n return;\n }\n\n // Cap pending requests to prevent memory exhaustion\n if (pendingRequests.size >= MAX_PENDING_REQUESTS) {\n clientWs.send(\n JSON.stringify({\n id: clientId,\n type: \"rpc_response\",\n success: false,\n error: {\n code: -32006,\n message:\n \"Too many pending requests. Wait for current requests to complete.\",\n },\n }),\n );\n return;\n }\n\n if (!browserReady || !browserWs || browserWs.readyState !== WebSocket.OPEN) {\n // Attempt to reopen browser and tell CLI to wait\n reopenBrowser();\n clientWs.send(\n JSON.stringify({\n id: clientId,\n type: \"rpc_response\",\n success: false,\n error: {\n code: -32001,\n message:\n \"Browser tab was closed. Reopening — please retry in a few seconds.\",\n },\n }),\n );\n return;\n }\n\n // Generate a daemon-internal ID and forward to browser\n const daemonId = crypto.randomUUID();\n\n const timer = setTimeout(() => {\n pendingRequests.delete(daemonId);\n if (clientWs.readyState === WebSocket.OPEN) {\n clientWs.send(\n JSON.stringify({\n id: clientId,\n type: \"rpc_response\",\n success: false,\n error: {\n code: -32000,\n message: `Request timed out after ${timeout / 1000}s`,\n },\n }),\n );\n }\n }, timeout);\n\n pendingRequests.set(daemonId, { clientWs, clientId, timer });\n\n browserWs.send(\n JSON.stringify({\n id: daemonId,\n type: \"rpc_request\",\n method: msg.method,\n params: msg.params,\n }),\n );\n}\n\n// ── Build bridge URL ───────────────────────────────────────────────\n\nfunction buildBridgeUrl(port: number): string {\n const url = new URL(\"/cli-bridge\", args.keysUrl);\n url.searchParams.set(\"wsPort\", String(port));\n // Only wsPort in query params — all config (apiKey, chainId, ens, paymasterUrl)\n // is sent over the authenticated WebSocket after connection\n url.hash = `token=${encodeURIComponent(token)}`;\n return url.toString();\n}\n"]}
@@ -0,0 +1,149 @@
1
+ import { z } from 'zod';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+
6
+ // src/mcp/tools.ts
7
+ ({
8
+ method: z.string().describe(
9
+ "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."
10
+ ),
11
+ params: z.any().optional().describe(
12
+ "Method parameters \u2014 structure varies by method. Read the jaw://api-reference/{method} resource for the expected format."
13
+ ),
14
+ chainId: z.number().optional().describe(
15
+ "Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia"
16
+ )
17
+ });
18
+ var configSetSchema = {
19
+ key: z.enum(["apiKey", "defaultChain", "keysUrl", "paymasterUrl", "ens"]).describe("Config key"),
20
+ value: z.string().describe("Config value")
21
+ };
22
+
23
+ // src/mcp/helpers.ts
24
+ function mcpError(err) {
25
+ return {
26
+ isError: true,
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
31
+ }
32
+ ]
33
+ };
34
+ }
35
+ var JAW_DIR = path.join(os.homedir(), ".jaw");
36
+ var PATHS = {
37
+ root: JAW_DIR,
38
+ config: path.join(JAW_DIR, "config.json"),
39
+ session: path.join(JAW_DIR, "session.json"),
40
+ bridge: path.join(JAW_DIR, "bridge.json"),
41
+ daemonLog: path.join(JAW_DIR, "daemon.log")
42
+ };
43
+
44
+ // src/lib/validation.ts
45
+ function isValidKeysUrl(url) {
46
+ try {
47
+ const parsed = new URL(url);
48
+ const isTrustedHost = parsed.hostname.endsWith(".jaw.id") || parsed.hostname === "jaw.id" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
49
+ const isSecure = parsed.protocol === "https:" || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
50
+ return isTrustedHost && isSecure;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ // src/lib/config.ts
57
+ function ensureDir(dir) {
58
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
59
+ fs.chmodSync(dir, 448);
60
+ }
61
+ function loadConfig() {
62
+ if (!fs.existsSync(PATHS.config)) {
63
+ return {};
64
+ }
65
+ const raw = fs.readFileSync(PATHS.config, "utf-8");
66
+ try {
67
+ return JSON.parse(raw);
68
+ } catch {
69
+ throw new Error(
70
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
71
+ );
72
+ }
73
+ }
74
+ function saveConfig(config) {
75
+ ensureDir(PATHS.root);
76
+ fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
77
+ encoding: "utf-8",
78
+ mode: 384
79
+ });
80
+ }
81
+ function redactConfig(config) {
82
+ return {
83
+ ...config,
84
+ apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : void 0
85
+ };
86
+ }
87
+ function setConfigValue(key, value) {
88
+ if (key === "keysUrl" && typeof value === "string" && !isValidKeysUrl(value)) {
89
+ throw new Error(
90
+ `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`
91
+ );
92
+ }
93
+ const config = loadConfig();
94
+ const updated = { ...config, [key]: value };
95
+ saveConfig(updated);
96
+ }
97
+
98
+ // src/mcp/handlers/config.ts
99
+ function registerConfigTools(server) {
100
+ server.tool(
101
+ "jaw_config_show",
102
+ "Show current CLI configuration (API key redacted).",
103
+ {},
104
+ async () => {
105
+ try {
106
+ const config = redactConfig(loadConfig());
107
+ return {
108
+ content: [
109
+ { type: "text", text: JSON.stringify(config) }
110
+ ]
111
+ };
112
+ } catch (err) {
113
+ return mcpError(err);
114
+ }
115
+ }
116
+ );
117
+ server.tool(
118
+ "jaw_config_set",
119
+ "Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens).",
120
+ configSetSchema,
121
+ async (params) => {
122
+ try {
123
+ if (params.key === "defaultChain") {
124
+ const num = parseInt(params.value, 10);
125
+ if (isNaN(num) || num <= 0) {
126
+ throw new Error(`Invalid chain ID: ${params.value}`);
127
+ }
128
+ setConfigValue(params.key, num);
129
+ } else {
130
+ setConfigValue(params.key, params.value);
131
+ }
132
+ return {
133
+ content: [
134
+ {
135
+ type: "text",
136
+ text: `Set ${params.key} successfully`
137
+ }
138
+ ]
139
+ };
140
+ } catch (err) {
141
+ return mcpError(err);
142
+ }
143
+ }
144
+ );
145
+ }
146
+
147
+ export { registerConfigTools };
148
+ //# sourceMappingURL=config.js.map
149
+ //# sourceMappingURL=config.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/mcp/handlers/config.ts"],"names":[],"mappings":"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","file":"config.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","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { configSetSchema } from \"../tools.js\";\nimport { mcpError } from \"../helpers.js\";\nimport { loadConfig, setConfigValue, redactConfig } from \"../../lib/config.js\";\n\nexport function registerConfigTools(server: McpServer): void {\n server.tool(\n \"jaw_config_show\",\n \"Show current CLI configuration (API key redacted).\",\n {},\n async () => {\n try {\n const config = redactConfig(loadConfig());\n return {\n content: [\n { type: \"text\" as const, text: JSON.stringify(config) },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n\n server.tool(\n \"jaw_config_set\",\n \"Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens).\",\n configSetSchema,\n async (params) => {\n try {\n if (params.key === \"defaultChain\") {\n const num = parseInt(params.value, 10);\n if (isNaN(num) || num <= 0) {\n throw new Error(`Invalid chain ID: ${params.value}`);\n }\n setConfigValue(params.key, num);\n } else {\n setConfigValue(params.key, params.value);\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Set ${params.key} successfully`,\n },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n}\n"]}
@@ -0,0 +1,185 @@
1
+ import * as fs2 from 'fs';
2
+ import * as path from 'path';
3
+ import 'url';
4
+ import { execSync } from 'child_process';
5
+ import * as os from 'os';
6
+ import 'crypto';
7
+ import 'ws';
8
+ import { z } from 'zod';
9
+
10
+ // src/mcp/handlers/daemon.ts
11
+
12
+ // src/mcp/helpers.ts
13
+ function mcpError(err) {
14
+ return {
15
+ isError: true,
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`
20
+ }
21
+ ]
22
+ };
23
+ }
24
+ var JAW_DIR = path.join(os.homedir(), ".jaw");
25
+ var PATHS = {
26
+ root: JAW_DIR,
27
+ config: path.join(JAW_DIR, "config.json"),
28
+ session: path.join(JAW_DIR, "session.json"),
29
+ bridge: path.join(JAW_DIR, "bridge.json"),
30
+ daemonLog: path.join(JAW_DIR, "daemon.log")
31
+ };
32
+ function loadConfig() {
33
+ if (!fs2.existsSync(PATHS.config)) {
34
+ return {};
35
+ }
36
+ const raw = fs2.readFileSync(PATHS.config, "utf-8");
37
+ try {
38
+ return JSON.parse(raw);
39
+ } catch {
40
+ throw new Error(
41
+ `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
42
+ );
43
+ }
44
+ }
45
+ function redactConfig(config) {
46
+ return {
47
+ ...config,
48
+ apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : void 0
49
+ };
50
+ }
51
+
52
+ // src/lib/bridge-singleton.ts
53
+ path.join(PATHS.root, "daemon.lock");
54
+ function isDaemonProcess(pid) {
55
+ if (!Number.isInteger(pid) || pid <= 0 || pid > 4194304) return false;
56
+ try {
57
+ process.kill(pid, 0);
58
+ } catch {
59
+ return false;
60
+ }
61
+ try {
62
+ const cmd = execSync(`ps -p ${String(pid)} -o command=`, {
63
+ encoding: "utf-8",
64
+ timeout: 3e3
65
+ }).trim();
66
+ return cmd.includes("ws-daemon");
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ function loadBridgeInfo() {
72
+ try {
73
+ if (!fs2.existsSync(PATHS.bridge)) return null;
74
+ const raw = fs2.readFileSync(PATHS.bridge, "utf-8");
75
+ const info = JSON.parse(raw);
76
+ if (!isDaemonProcess(info.pid)) {
77
+ try {
78
+ fs2.unlinkSync(PATHS.bridge);
79
+ } catch {
80
+ }
81
+ return null;
82
+ }
83
+ return info;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ async function shutdownDaemon() {
89
+ const info = loadBridgeInfo();
90
+ if (!info) return;
91
+ try {
92
+ process.kill(info.pid, "SIGTERM");
93
+ } catch {
94
+ }
95
+ try {
96
+ if (fs2.existsSync(PATHS.bridge)) fs2.unlinkSync(PATHS.bridge);
97
+ } catch {
98
+ }
99
+ }
100
+ ({
101
+ method: z.string().describe(
102
+ "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."
103
+ ),
104
+ params: z.any().optional().describe(
105
+ "Method parameters \u2014 structure varies by method. Read the jaw://api-reference/{method} resource for the expected format."
106
+ ),
107
+ chainId: z.number().optional().describe(
108
+ "Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia"
109
+ )
110
+ });
111
+ ({
112
+ key: z.enum(["apiKey", "defaultChain", "keysUrl", "paymasterUrl", "ens"]).describe("Config key"),
113
+ value: z.string().describe("Config value")
114
+ });
115
+
116
+ // src/mcp/handlers/rpc.ts
117
+ var cachedBridge = null;
118
+ function closeCachedBridge() {
119
+ }
120
+ function isBridgeCached() {
121
+ return cachedBridge !== null;
122
+ }
123
+
124
+ // src/mcp/handlers/daemon.ts
125
+ function registerDaemonTools(server) {
126
+ server.tool(
127
+ "jaw_status",
128
+ "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.",
129
+ {},
130
+ async () => {
131
+ try {
132
+ let daemonRunning = false;
133
+ let daemonPid = null;
134
+ try {
135
+ if (fs2.existsSync(PATHS.bridge)) {
136
+ const info = JSON.parse(fs2.readFileSync(PATHS.bridge, "utf-8"));
137
+ daemonPid = info.pid;
138
+ process.kill(info.pid, 0);
139
+ daemonRunning = true;
140
+ }
141
+ } catch {
142
+ daemonRunning = false;
143
+ }
144
+ const config = redactConfig(loadConfig());
145
+ const status = {
146
+ daemon: daemonRunning ? { running: true, pid: daemonPid } : { running: false },
147
+ bridgeConnection: isBridgeCached() ? "connected" : "disconnected",
148
+ config
149
+ };
150
+ return {
151
+ content: [
152
+ { type: "text", text: JSON.stringify(status, null, 2) }
153
+ ]
154
+ };
155
+ } catch (err) {
156
+ return mcpError(err);
157
+ }
158
+ }
159
+ );
160
+ server.tool(
161
+ "jaw_disconnect",
162
+ "Stop the background bridge daemon and close the browser session. Call this when you are done making wallet requests to clean up resources.",
163
+ {},
164
+ async () => {
165
+ try {
166
+ closeCachedBridge();
167
+ await shutdownDaemon();
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: "Bridge daemon stopped and browser session closed."
173
+ }
174
+ ]
175
+ };
176
+ } catch (err) {
177
+ return mcpError(err);
178
+ }
179
+ }
180
+ );
181
+ }
182
+
183
+ export { registerDaemonTools };
184
+ //# sourceMappingURL=daemon.js.map
185
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mcp/helpers.ts","../../../src/lib/paths.ts","../../../src/lib/config.ts","../../../src/lib/bridge-singleton.ts","../../../src/mcp/tools.ts","../../../src/mcp/handlers/rpc.ts","../../../src/mcp/handlers/daemon.ts"],"names":["fs","path2","fs3"],"mappings":"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","file":"daemon.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}","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","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 * 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 { 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","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","import * as fs from \"node:fs\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { mcpError } from \"../helpers.js\";\nimport { shutdownDaemon } from \"../../lib/bridge-singleton.js\";\nimport { closeCachedBridge, isBridgeCached } from \"./rpc.js\";\nimport { PATHS } from \"../../lib/paths.js\";\nimport { loadConfig, redactConfig } from \"../../lib/config.js\";\n\nexport function registerDaemonTools(server: McpServer): void {\n server.tool(\n \"jaw_status\",\n \"Check the current status of the JAW.id bridge — whether the daemon is running, \" +\n \"the bridge connection is active, and what configuration is in use.\",\n {},\n async () => {\n try {\n let daemonRunning = false;\n let daemonPid: number | null = null;\n\n try {\n if (fs.existsSync(PATHS.bridge)) {\n const info = JSON.parse(fs.readFileSync(PATHS.bridge, \"utf-8\"));\n daemonPid = info.pid;\n // Check if process is alive\n process.kill(info.pid, 0);\n daemonRunning = true;\n }\n } catch {\n daemonRunning = false;\n }\n\n const config = redactConfig(loadConfig());\n\n const status = {\n daemon: daemonRunning\n ? { running: true, pid: daemonPid }\n : { running: false },\n bridgeConnection: isBridgeCached() ? \"connected\" : \"disconnected\",\n config,\n };\n\n return {\n content: [\n { type: \"text\" as const, text: JSON.stringify(status, null, 2) },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n\n server.tool(\n \"jaw_disconnect\",\n \"Stop the background bridge daemon and close the browser session. \" +\n \"Call this when you are done making wallet requests to clean up resources.\",\n {},\n async () => {\n try {\n closeCachedBridge();\n await shutdownDaemon();\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"Bridge daemon stopped and browser session closed.\",\n },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n}"]}
@@ -0,0 +1,60 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ // src/mcp/handlers/resources.ts
4
+ var DOCS_BASE = "https://docs.jaw.id/api-reference";
5
+ async function fetchDocs(url) {
6
+ const res = await fetch(url);
7
+ if (!res.ok) {
8
+ throw new Error(`Failed to fetch docs: ${res.status} ${res.statusText}`);
9
+ }
10
+ const html = await res.text();
11
+ 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();
12
+ }
13
+ function registerResources(server) {
14
+ server.resource(
15
+ "api-reference",
16
+ "jaw://api-reference",
17
+ {
18
+ 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.",
19
+ mimeType: "text/plain"
20
+ },
21
+ async () => ({
22
+ contents: [
23
+ {
24
+ uri: "jaw://api-reference",
25
+ mimeType: "text/plain",
26
+ text: await fetchDocs(DOCS_BASE)
27
+ }
28
+ ]
29
+ })
30
+ );
31
+ server.resource(
32
+ "api-reference-method",
33
+ new ResourceTemplate("jaw://api-reference/{method}", { list: void 0 }),
34
+ {
35
+ 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).",
36
+ mimeType: "text/plain"
37
+ },
38
+ async (uri, variables) => {
39
+ const method = String(variables.method);
40
+ if (!/^[\w_]+$/.test(method)) {
41
+ throw new Error(
42
+ `Invalid method name: "${method}". Expected an RPC method like wallet_sendCalls.`
43
+ );
44
+ }
45
+ return {
46
+ contents: [
47
+ {
48
+ uri: uri.href,
49
+ mimeType: "text/plain",
50
+ text: await fetchDocs(`${DOCS_BASE}/${method}`)
51
+ }
52
+ ]
53
+ };
54
+ }
55
+ );
56
+ }
57
+
58
+ export { registerResources };
59
+ //# sourceMappingURL=resources.js.map
60
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mcp/handlers/resources.ts"],"names":[],"mappings":";;;AAEA,IAAM,SAAA,GAAY,mCAAA;AAElB,eAAe,UAAU,GAAA,EAA8B;AACrD,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAG,CAAA;AAC3B,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,IAAI,MAAM,CAAA,sBAAA,EAAyB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,UAAU,CAAA,CAAE,CAAA;AAAA,EACzE;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAG5B,EAAA,OAAO,KACJ,OAAA,CAAQ,mCAAA,EAAqC,EAAE,CAAA,CAC/C,OAAA,CAAQ,mCAAmC,EAAE,CAAA,CAC7C,OAAA,CAAQ,UAAA,EAAY,GAAG,CAAA,CACvB,OAAA,CAAQ,WAAW,GAAG,CAAA,CACtB,QAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,OAAA,CAAQ,SAAS,GAAG,CAAA,CACpB,QAAQ,OAAA,EAAS,GAAG,EACpB,OAAA,CAAQ,SAAA,EAAW,GAAG,CAAA,CACtB,OAAA,CAAQ,UAAU,GAAG,CAAA,CACrB,QAAQ,SAAA,EAAW,GAAG,EACtB,IAAA,EAAK;AACV;AAEO,SAAS,kBAAkB,MAAA,EAAyB;AAEzD,EAAA,MAAA,CAAO,QAAA;AAAA,IACL,eAAA;AAAA,IACA,qBAAA;AAAA,IACA;AAAA,MACE,WAAA,EACE,yJAAA;AAAA,MAEF,QAAA,EAAU;AAAA,KACZ;AAAA,IACA,aAAa;AAAA,MACX,QAAA,EAAU;AAAA,QACR;AAAA,UACE,GAAA,EAAK,qBAAA;AAAA,UACL,QAAA,EAAU,YAAA;AAAA,UACV,IAAA,EAAM,MAAM,SAAA,CAAU,SAAS;AAAA;AACjC;AACF,KACF;AAAA,GACF;AAGA,EAAA,MAAA,CAAO,QAAA;AAAA,IACL,sBAAA;AAAA,IACA,IAAI,gBAAA,CAAiB,8BAAA,EAAgC,EAAE,IAAA,EAAM,QAAW,CAAA;AAAA,IACxE;AAAA,MACE,WAAA,EACE,2MAAA;AAAA,MAGF,QAAA,EAAU;AAAA,KACZ;AAAA,IACA,OAAO,KAAK,SAAA,KAAc;AACxB,MAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA;AAGtC,MAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA,EAAG;AAC5B,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,yBAAyB,MAAM,CAAA,gDAAA;AAAA,SACjC;AAAA,MACF;AAEA,MAAA,OAAO;AAAA,QACL,QAAA,EAAU;AAAA,UACR;AAAA,YACE,KAAK,GAAA,CAAI,IAAA;AAAA,YACT,QAAA,EAAU,YAAA;AAAA,YACV,MAAM,MAAM,SAAA,CAAU,GAAG,SAAS,CAAA,CAAA,EAAI,MAAM,CAAA,CAAE;AAAA;AAChD;AACF,OACF;AAAA,IACF;AAAA,GACF;AACF","file":"resources.js","sourcesContent":["import { type McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nconst DOCS_BASE = \"https://docs.jaw.id/api-reference\";\n\nasync function fetchDocs(url: string): Promise<string> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Failed to fetch docs: ${res.status} ${res.statusText}`);\n }\n const html = await res.text();\n // Strip HTML tags for a readable text representation.\n // MCP resources are consumed by LLMs, so plain text is ideal.\n return html\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/&nbsp;/g, \" \")\n .replace(/&amp;/g, \"&\")\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\")\n .replace(/\\s{2,}/g, \" \")\n .trim();\n}\n\nexport function registerResources(server: McpServer): void {\n // Overview of all RPC methods\n server.resource(\n \"api-reference\",\n \"jaw://api-reference\",\n {\n description:\n \"JAW.id API reference — lists all supported RPC methods with descriptions. \" +\n \"Read this before using the jaw_rpc tool to understand available methods.\",\n mimeType: \"text/plain\",\n },\n async () => ({\n contents: [\n {\n uri: \"jaw://api-reference\",\n mimeType: \"text/plain\",\n text: await fetchDocs(DOCS_BASE),\n },\n ],\n }),\n );\n\n // Per-method documentation with parameter details\n server.resource(\n \"api-reference-method\",\n new ResourceTemplate(\"jaw://api-reference/{method}\", { list: undefined }),\n {\n description:\n \"Detailed documentation for a specific RPC method including parameters, \" +\n \"request/response format, and examples. Use the method name from the \" +\n \"api-reference overview (e.g. wallet_sendCalls, personal_sign).\",\n mimeType: \"text/plain\",\n },\n async (uri, variables) => {\n const method = String(variables.method);\n\n // Validate method name to prevent path traversal (e.g. ../../admin)\n if (!/^[\\w_]+$/.test(method)) {\n throw new Error(\n `Invalid method name: \"${method}\". Expected an RPC method like wallet_sendCalls.`,\n );\n }\n\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: \"text/plain\",\n text: await fetchDocs(`${DOCS_BASE}/${method}`),\n },\n ],\n };\n },\n );\n}"]}