mrmainspring 0.2.9 → 0.3.1

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 CHANGED
@@ -1,49 +1,49 @@
1
- # Mr Mainspring
2
-
3
- Installable MCP backend for local agent demos. It exposes memory, Grimoire
4
- policy/secret storage, payment intent tools, audit trail tools, Casper anchoring
5
- boundaries, and x402 settlement-provider wiring.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install -g mrmainspring
11
- mainspring setup cursor
12
- ```
13
-
14
- Run the stdio MCP server:
15
-
16
- ```bash
17
- mainspring
18
- ```
19
-
20
- For local development from this repository:
21
-
22
- ```bash
23
- npm run build
24
- npm run mcp:stdio
25
- ```
26
-
27
- ## Environment
28
-
29
- No environment variables are required for local memory, Grimoire, audit, or
30
- payment preflight tools. `mainspring setup` creates the local config, data, and
31
- logs directories under the user's standard app config folder. Use
32
- `SIGIL_ENV_FILE` to point at a specific env file for advanced setups.
33
-
34
- Important package boundaries:
35
-
36
- - Keep `.env`, local keys, and generated demo data outside the npm package.
37
- - Real Casper submission remains gated by `CASPER_ENABLE_REAL_SUBMISSION=true`.
38
- - Real x402 settlement remains gated by `X402_ENABLE_REAL_SETTLEMENT=true` and
39
- a configured `X402_SIGNER_URL`.
40
- - The signer private key must live outside the repository workspace.
41
-
42
- ## Library Entry
43
-
44
- ```ts
45
- import { createSigilServer } from "mrmainspring";
46
- ```
47
-
48
- The CLI entry is also exported as `mrmainspring/mcp`, but importing it starts
49
- the stdio server; use the package bin for normal MCP client configuration.
1
+ # Mr Mainspring
2
+
3
+ Installable MCP backend for local agent demos. It exposes memory, Grimoire
4
+ policy/secret storage, payment intent tools, audit trail tools, Casper anchoring
5
+ boundaries, and x402 settlement-provider wiring.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g mrmainspring
11
+ mainspring setup cursor
12
+ ```
13
+
14
+ Run the stdio MCP server:
15
+
16
+ ```bash
17
+ mainspring
18
+ ```
19
+
20
+ For local development from this repository:
21
+
22
+ ```bash
23
+ npm run build
24
+ npm run mcp:stdio
25
+ ```
26
+
27
+ ## Environment
28
+
29
+ No environment variables are required for local memory, Grimoire, audit, or
30
+ payment preflight tools. `mainspring setup` creates the local config, data, and
31
+ logs directories under the user's standard app config folder. Use
32
+ `SIGIL_ENV_FILE` to point at a specific env file for advanced setups.
33
+
34
+ Important package boundaries:
35
+
36
+ - Keep `.env`, local keys, and generated demo data outside the npm package.
37
+ - Real Casper submission remains gated by `CASPER_ENABLE_REAL_SUBMISSION=true`.
38
+ - Real x402 settlement remains gated by `X402_ENABLE_REAL_SETTLEMENT=true` and
39
+ a configured `X402_SIGNER_URL`.
40
+ - The signer private key must live outside the repository workspace.
41
+
42
+ ## Library Entry
43
+
44
+ ```ts
45
+ import { createSigilServer } from "mrmainspring";
46
+ ```
47
+
48
+ The CLI entry is also exported as `mrmainspring/mcp`, but importing it starts
49
+ the stdio server; use the package bin for normal MCP client configuration.
package/dist/cli.d.ts CHANGED
@@ -5,5 +5,5 @@ type InitResult = {
5
5
  };
6
6
  export declare function runCliCommand(args: string[]): Promise<boolean>;
7
7
  export declare function initializeLocalSetup(env?: NodeJS.ProcessEnv): InitResult;
8
- export declare function formatMcpConfig(): string;
8
+ export declare function formatMcpConfig(target?: string): string;
9
9
  export {};
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
@@ -9,42 +9,32 @@ import { ensureGrimoireMasterKey, loadLocalEnvFile, resolveEnvPath } from "./env
9
9
  import { getDefaultMainspringPaths } from "./paths.js";
10
10
  const _pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
11
11
  const VERSION = JSON.parse(readFileSync(join(_pkgRoot, "package.json"), "utf8")).version;
12
- const HELP = `Mr Mainspring MCP server
13
-
14
- Usage:
15
- mrmainspring Start the MCP stdio server
16
- mrmainspring --help Show this help
17
- mrmainspring --version Show the installed version
18
- mrmainspring init Create local config, data, and logs directories
19
- mrmainspring config Print MCP client config JSON
20
- mrmainspring setup Initialize local files and configure MCP clients
21
- mrmainspring setup -i Force interactive setup wizard (Windows)
22
- mrmainspring doctor Check the local setup
23
- mrmainspring update Show how to update to the latest version
24
-
25
- MCP client config:
26
- {
27
- "mcpServers": {
28
- "mainspring": {
29
- "command": "npx",
30
- "args": ["-y", "mrmainspring"]
31
- }
32
- }
33
- }
34
-
35
- Advanced users can set SIGIL_ENV_FILE, SIGIL_DATA_DIR, Supabase, Casper,
36
- and x402 env vars. No env vars are required for local memory, Grimoire, audit,
37
- or payment preflight tools.
12
+ const HELP = `Mr Mainspring MCP server
13
+
14
+ Usage:
15
+ mainspring Start the MCP stdio server
16
+ mainspring --help Show this help
17
+ mainspring --version Show the installed version
18
+ mainspring init Create local config, data, and logs directories
19
+ mainspring config [client] Print MCP client config for codex/cursor/claude
20
+ mainspring setup Initialize local files and print MCP config
21
+ mainspring doctor Check the local setup
22
+ mainspring update Show how to update to the latest version
23
+
24
+ MCP client config:
25
+ {
26
+ "mcpServers": {
27
+ "mainspring": {
28
+ "command": "npx",
29
+ "args": ["-y", "mrmainspring"]
30
+ }
31
+ }
32
+ }
33
+
34
+ Advanced users can still set SIGIL_ENV_FILE, SIGIL_DATA_DIR, Supabase, Casper,
35
+ and x402 env vars. No env vars are required for local memory, Grimoire, audit,
36
+ or payment preflight tools.
38
37
  `;
39
- function appendEnvVars(envFile, vars) {
40
- const existing = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
41
- const toWrite = Object.entries(vars).filter(([k]) => !existing.includes(`${k}=`));
42
- if (toWrite.length === 0)
43
- return [];
44
- const block = "\n# Mainspring setup\n" + toWrite.map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
45
- appendFileSync(envFile, block, "utf8");
46
- return toWrite.map(([k]) => k);
47
- }
48
38
  export async function runCliCommand(args) {
49
39
  const [command, target] = args;
50
40
  if (!command || command === "stdio" || command === "server" || command === "mcp") {
@@ -64,12 +54,11 @@ export async function runCliCommand(args) {
64
54
  return true;
65
55
  }
66
56
  if (command === "config") {
67
- process.stdout.write(`${formatMcpConfig()}\n`);
57
+ process.stdout.write(`${formatMcpConfig(target)}\n`);
68
58
  return true;
69
59
  }
70
60
  if (command === "setup") {
71
- const forceInteractive = args.includes("--interactive") || args.includes("-i");
72
- if (forceInteractive || (process.stdin.isTTY ?? process.stdout.isTTY)) {
61
+ if (process.stdin.isTTY) {
73
62
  await runInteractiveSetup();
74
63
  }
75
64
  else {
@@ -82,23 +71,14 @@ export async function runCliCommand(args) {
82
71
  }
83
72
  else {
84
73
  process.stdout.write("\nNo MCP clients detected automatically.\n" +
85
- "Add this to your MCP client config:\n\n");
74
+ "Add this to your MCP client config (Claude Desktop, Cursor, Windsurf, Zed, VS Code, Continue, or any MCP host):\n\n");
86
75
  process.stdout.write(`${formatMcpConfig()}\n`);
87
76
  process.stdout.write("\nConfig file locations:\n" +
88
- " Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json\n" +
89
- " Claude Code ~/.claude/settings.json\n" +
90
- " Cursor ~/.cursor/mcp.json\n" +
91
- " Windsurf ~/.codeium/windsurf/mcp_config.json\n" +
92
- " Zed ~/.config/zed/settings.json (context_servers format)\n" +
93
- " VS Code ~/.vscode/mcp.json\n" +
94
- " Cline ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\n" +
95
- " Roo Code ~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json\n" +
96
- " Kilo Code ~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json\n" +
97
- " OpenCode ~/.config/opencode/config.json\n" +
98
- " Amazon Q ~/.aws/amazonq/mcp.json\n" +
99
- " Gemini CLI ~/.gemini/settings.json\n" +
100
- " Codex CLI ~/.codex/config.json\n" +
101
- " Continue.dev ~/.continue/config.json (array format)\n\n");
77
+ " Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json\n" +
78
+ " Cursor ~/.cursor/mcp.json\n" +
79
+ " Windsurf ~/.codeium/windsurf/mcp_config.json\n" +
80
+ " Zed ~/.config/zed/settings.json (context_servers format)\n" +
81
+ " VS Code ~/.vscode/mcp.json\n\n");
102
82
  }
103
83
  }
104
84
  return true;
@@ -118,69 +98,42 @@ export async function runCliCommand(args) {
118
98
  process.exitCode = 1;
119
99
  return true;
120
100
  }
121
- const CLOCK_ART = [
122
- " .-.-. ",
123
- " (( (__I__) )) ",
124
- " .'_....._'. ",
125
- " / / .12 . \\ \\ ",
126
- " | | ' | ' | | ",
127
- " | | 9 / 3 | | ",
128
- " \\ \\ '.6.' / / ",
129
- " '.`-...-'.' ",
130
- " /'-- --'\\ ",
131
- " `\"\"\"\"\"\"\"\"\"` ",
132
- " ",
133
- " Mr Mainspring ",
134
- " Memory · Anchor · Pay ",
135
- ].join("\n");
136
101
  async function runInteractiveSetup() {
137
- process.stdout.write(`\n${CLOCK_ART}\n\n`);
138
- clack.intro("setup");
139
- // ── 1. Local files ──────────────────────────────────────────────────────────
140
- const hadKey = !!process.env.GRIMOIRE_MASTER_KEY;
102
+ clack.intro("Mr Mainspring — setup");
141
103
  const result = initializeLocalSetup();
142
- const keyNote = hadKey ? "encryption key loaded" : `encryption key generated → ${result.envFile}`;
143
- clack.log.success(`Local files ready\n Config: ${result.envFile}\n Data: ${result.dataDir}\n ${keyNote}`);
144
- // ── 2. Storage backend ──────────────────────────────────────────────────────
145
- const storageChoice = await clack.select({
146
- message: "Storage backend?",
147
- options: [
148
- { value: "file", label: "Local files", hint: result.dataDir },
149
- { value: "supabase", label: "Supabase (cloud)", hint: "requires PROJECT_URL + SECRET_KEY" }
150
- ]
151
- });
152
- if (clack.isCancel(storageChoice)) {
153
- clack.cancel("Setup cancelled.");
154
- process.exitCode = 1;
155
- return;
156
- }
157
- if (storageChoice === "supabase") {
158
- const projectUrl = await clack.text({ message: "Supabase PROJECT_URL:", placeholder: "https://xxx.supabase.co" });
159
- if (clack.isCancel(projectUrl)) {
160
- clack.cancel("Setup cancelled.");
161
- process.exitCode = 1;
162
- return;
163
- }
164
- const secretKey = await clack.text({ message: "Supabase SECRET_KEY (service role):", placeholder: "eyJ..." });
165
- if (clack.isCancel(secretKey)) {
166
- clack.cancel("Setup cancelled.");
167
- process.exitCode = 1;
168
- return;
169
- }
170
- appendFileSync(result.envFile, `\nSIGIL_STORAGE_BACKEND=supabase\nPROJECT_URL=${projectUrl}\nSECRET_KEY=${secretKey}\n`, "utf8");
171
- clack.log.success("Supabase config saved.");
172
- }
173
- // ── 3. MCP clients ──────────────────────────────────────────────────────────
104
+ clack.log.success(`Local files ready\n Config: ${result.envFile}\n Data: ${result.dataDir}`);
174
105
  const clients = detectClients();
175
106
  const installed = clients.filter(c => c.installed);
107
+ const notInstalled = clients.filter(c => !c.installed);
108
+ let selected;
109
+ if (installed.length === 0) {
110
+ clack.log.warn("No MCP clients detected on this machine.");
111
+ clack.log.info("Add this JSON to your client config manually:\n\n" +
112
+ formatMcpConfig() + "\n\n" +
113
+ " Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json\n" +
114
+ " Cursor ~/.cursor/mcp.json\n" +
115
+ " Windsurf ~/.codeium/windsurf/mcp_config.json\n" +
116
+ " Zed ~/.config/zed/settings.json\n" +
117
+ " VS Code ~/.vscode/mcp.json");
118
+ clack.outro("Restart your client after updating the config.");
119
+ return;
120
+ }
176
121
  const choices = await clack.multiselect({
177
122
  message: "Which MCP clients should we configure?",
178
- options: clients.map(c => ({
179
- value: c,
180
- label: c.name,
181
- hint: c.installed ? c.configPath : "not detected — config will be created"
182
- })),
183
- initialValues: installed,
123
+ options: [
124
+ ...installed.map(c => ({
125
+ value: c,
126
+ label: c.name,
127
+ hint: c.configPath,
128
+ selected: true
129
+ })),
130
+ ...notInstalled.map(c => ({
131
+ value: c,
132
+ label: c.name,
133
+ hint: "not found",
134
+ selected: false
135
+ }))
136
+ ],
184
137
  required: false
185
138
  });
186
139
  if (clack.isCancel(choices)) {
@@ -188,31 +141,13 @@ async function runInteractiveSetup() {
188
141
  process.exitCode = 1;
189
142
  return;
190
143
  }
191
- const selected = choices;
144
+ selected = choices;
192
145
  if (selected.length === 0) {
193
- clack.log.warn("No clients selected.");
194
- const customPath = await clack.text({
195
- message: "Enter a config file path to configure manually (or press Enter to skip):",
196
- placeholder: "~/.config/myapp/mcp.json",
197
- validate: () => undefined
198
- });
199
- if (!clack.isCancel(customPath) && customPath && customPath.trim()) {
200
- const expandedPath = customPath.replace(/^~/, process.env.HOME ?? "");
201
- const r = configureClients([{ name: "Custom", configPath: expandedPath, installed: true, format: "standard" }])[0];
202
- if (r?.status === "written")
203
- clack.log.success(`Configured: ${expandedPath}`);
204
- else if (r?.status === "already-set")
205
- clack.log.info(`Already configured: ${expandedPath}`);
206
- else if (r?.error)
207
- clack.log.error(`Failed: ${r.error}`);
208
- }
209
- else {
210
- clack.log.info("Add this JSON to your MCP client config:\n\n" + formatMcpConfig());
211
- }
212
- clack.outro("Restart your MCP client after updating the config.");
146
+ clack.log.warn("No clients selected — nothing configured.");
147
+ clack.outro("Run mainspring setup again when ready.");
213
148
  return;
214
149
  }
215
- const configResults = configureClients(selected);
150
+ const configResults = configureClients(selected.filter(c => c.installed));
216
151
  const written = configResults.filter(r => r.status === "written").map(r => r.name);
217
152
  const alreadySet = configResults.filter(r => r.status === "already-set").map(r => r.name);
218
153
  const errors = configResults.filter(r => r.status === "error");
@@ -222,154 +157,36 @@ async function runInteractiveSetup() {
222
157
  clack.log.info(`Already configured: ${alreadySet.join(", ")}`);
223
158
  for (const e of errors)
224
159
  clack.log.error(`${e.name}: ${e.error}`);
225
- // ── 4. Casper anchoring ─────────────────────────────────────────────────────
226
- const wantCasper = await clack.confirm({ message: "Enable Casper on-chain anchoring?", initialValue: false });
160
+ const wantCasper = await clack.confirm({
161
+ message: "Enable Casper on-chain anchoring?",
162
+ initialValue: false
163
+ });
227
164
  if (clack.isCancel(wantCasper)) {
228
165
  clack.cancel("Setup cancelled.");
229
166
  return;
230
167
  }
231
- let casperRpcUrl = "https://node.testnet.casper.network/rpc";
232
- let casperNetwork = "testnet";
233
168
  if (wantCasper) {
234
- const network = await clack.select({
235
- message: "Which Casper network?",
236
- options: [
237
- { value: "testnet", label: "Testnet", hint: "safe for testing — free faucet available" },
238
- { value: "mainnet", label: "Mainnet", hint: "real CSPR — costs money" }
239
- ]
240
- });
241
- if (clack.isCancel(network)) {
242
- clack.cancel("Setup cancelled.");
243
- return;
244
- }
245
- casperNetwork = network;
246
- casperRpcUrl = casperNetwork === "mainnet"
247
- ? "https://node.mainnet.casper.network/rpc"
248
- : "https://node.testnet.casper.network/rpc";
249
- const casperBin = process.env.CASPER_CLIENT_BIN ?? "casper-client";
250
- const casperAvailable = !spawnSync(casperBin, ["--version"], { stdio: "pipe" }).error;
251
- const keyOptions = [
252
- { value: "have", label: "I already have a key pair (.pem)" },
253
- { value: "skip", label: "Skip for now" }
254
- ];
255
- if (casperAvailable) {
256
- keyOptions.splice(1, 0, { value: "generate", label: "Generate a new key pair now", hint: "runs casper-client keygen ./keys" });
257
- }
258
- const keyAction = await clack.select({ message: "Casper key pair?", options: keyOptions });
259
- if (clack.isCancel(keyAction)) {
260
- clack.cancel("Setup cancelled.");
261
- return;
262
- }
263
- if (keyAction === "generate") {
264
- mkdirSync("./keys", { recursive: true });
265
- const keygen = spawnSync(casperBin, ["keygen", "./keys"], { stdio: "pipe" });
266
- if (keygen.status === 0) {
267
- clack.log.success("Key pair generated in ./keys/\n Secret: ./keys/secret_key.pem\n Public: ./keys/public_key.pem");
268
- }
269
- else {
270
- clack.log.error("keygen failed: " + (keygen.stderr?.toString() ?? "unknown error"));
271
- }
272
- }
273
- const keyPath = keyAction === "generate" ? "./keys/secret_key.pem" : "./keys/your-secret-key.pem";
274
- const casperVars = {
275
- CASPER_ENABLE_REAL_SUBMISSION: "true",
276
- CASPER_RPC_URL: casperRpcUrl,
277
- CASPER_ACCOUNT_KEY_PATH: keyPath
278
- };
279
- clack.log.info("Casper vars:\n\n" +
280
- Object.entries(casperVars).map(([k, v]) => ` ${k}=${v}`).join("\n") +
281
- `\n\n .env: ${result.envFile}`);
282
- const writeCasper = await clack.confirm({ message: "Write these to your .env now?", initialValue: true });
283
- if (!clack.isCancel(writeCasper) && writeCasper) {
284
- const written = appendEnvVars(result.envFile, casperVars);
285
- if (written.length > 0)
286
- clack.log.success(`Written: ${written.join(", ")}`);
287
- else
288
- clack.log.info("Already set in .env — skipped.");
289
- }
169
+ clack.log.info("Add to your .env file:\n\n" +
170
+ " CASPER_ENABLE_REAL_SUBMISSION=true\n" +
171
+ " CASPER_RPC_URL=https://node.testnet.casper.network/rpc\n" +
172
+ " CASPER_ACCOUNT_KEY_PATH=./keys/your-key.pem\n\n" +
173
+ "Then restart your MCP client.");
290
174
  }
291
- // ── 5. x402 payments ────────────────────────────────────────────────────────
292
- const wantX402 = await clack.confirm({ message: "Enable x402 micropayments?", initialValue: false });
175
+ const wantX402 = await clack.confirm({
176
+ message: "Enable x402 micropayments?",
177
+ initialValue: false
178
+ });
293
179
  if (clack.isCancel(wantX402)) {
294
180
  clack.cancel("Setup cancelled.");
295
181
  return;
296
182
  }
297
183
  if (wantX402) {
298
- let x402RpcUrl = casperRpcUrl;
299
- let x402Network = casperNetwork;
300
- if (!wantCasper) {
301
- const network = await clack.select({
302
- message: "Which Casper network for payments?",
303
- options: [
304
- { value: "testnet", label: "Testnet", hint: "free faucet available" },
305
- { value: "mainnet", label: "Mainnet", hint: "real CSPR" }
306
- ]
307
- });
308
- if (!clack.isCancel(network)) {
309
- x402Network = network;
310
- x402RpcUrl = x402Network === "mainnet"
311
- ? "https://node.mainnet.casper.network/rpc"
312
- : "https://node.testnet.casper.network/rpc";
313
- }
314
- }
315
- const explorerBase = x402Network === "mainnet" ? "https://cspr.live" : "https://testnet.cspr.live";
316
- clack.log.info("x402 setup — 3 steps:");
317
- clack.log.info("Step 1 — Get your Casper account hash\n\n" +
318
- " Your account hash identifies your wallet on-chain.\n" +
319
- " From a .pem file:\n\n" +
320
- " casper-client account-address --public-key ./keys/public_key.pem\n\n" +
321
- " From a hex public key:\n\n" +
322
- " casper-client account-address --public-key 02<your-hex-pubkey>\n\n" +
323
- " Output looks like:\n" +
324
- " account-hash-d0a57c6a95e74463de156cac761e17f0923eafc730ce3ce3a0c747c6598b0500\n\n" +
325
- " No casper-client? Install: cargo install casper-client");
326
- const x402Vars = {
327
- X402_ENABLE_REAL_SETTLEMENT: "true",
328
- X402_SETTLEMENT_MODE: "casper-cli",
329
- X402_BUYER_ACCOUNT_HASH: "account-hash-REPLACE_WITH_YOUR_HASH",
330
- CASPER_ENABLE_REAL_SUBMISSION: "true",
331
- CASPER_RPC_URL: x402RpcUrl,
332
- CASPER_ACCOUNT_KEY_PATH: "./keys/secret_key.pem"
333
- };
334
- clack.log.info("Step 2 — .env vars:\n\n" +
335
- Object.entries(x402Vars).map(([k, v]) => ` ${k}=${v}`).join("\n") +
336
- `\n\n .env: ${result.envFile}`);
337
- const writeX402 = await clack.confirm({
338
- message: "Write these to your .env? (you'll still need to replace X402_BUYER_ACCOUNT_HASH)",
339
- initialValue: true
340
- });
341
- if (!clack.isCancel(writeX402) && writeX402) {
342
- const written = appendEnvVars(result.envFile, x402Vars);
343
- if (written.length > 0) {
344
- clack.log.success(`Written: ${written.join(", ")}`);
345
- if (written.includes("X402_BUYER_ACCOUNT_HASH")) {
346
- clack.log.warn(`Edit X402_BUYER_ACCOUNT_HASH in ${result.envFile} with your actual account hash after running Step 1.`);
347
- }
348
- }
349
- else {
350
- clack.log.info("Already set in .env — skipped.");
351
- }
352
- }
353
- clack.log.info("Step 3 — Fund your account\n\n" +
354
- (x402Network === "testnet"
355
- ? ` Faucet: ${explorerBase}/tools/faucet\n`
356
- : " Transfer CSPR to your account.\n") +
357
- ` Verify balance: ${explorerBase} → search your account hash`);
358
- }
359
- // ── 6. Doctor check ─────────────────────────────────────────────────────────
360
- const doctorOutput = formatDoctorReport();
361
- const doctorLines = doctorOutput.split("\n").filter(Boolean);
362
- const hasIssues = doctorLines.some(l => l.startsWith("[warn]") || l.startsWith("[error]"));
363
- const doctorMsg = doctorLines
364
- .slice(1)
365
- .map(l => l.startsWith("[ok]") ? ` ✓ ${l.slice(5)}` :
366
- l.startsWith("[warn]") ? ` ⚠ ${l.slice(7)}` :
367
- l.startsWith("[error]") ? ` ✗ ${l.slice(8)}` : ` ${l}`).join("\n");
368
- if (hasIssues) {
369
- clack.log.warn(`System check:\n${doctorMsg}`);
370
- }
371
- else {
372
- clack.log.success(`System check:\n${doctorMsg}`);
184
+ clack.log.info("Add to your .env file:\n\n" +
185
+ " X402_ENABLE_REAL_SETTLEMENT=true\n" +
186
+ " X402_SETTLEMENT_MODE=casper-cli\n" +
187
+ " X402_BUYER_ACCOUNT_HASH=account-hash-<64 hex chars>\n" +
188
+ " CASPER_ENABLE_REAL_SUBMISSION=true\n\n" +
189
+ "Get your account hash: casper-client account-address --public-key <your-pubkey-hex>");
373
190
  }
374
191
  clack.outro("Restart your MCP clients to load the server.");
375
192
  }
@@ -397,7 +214,17 @@ export function initializeLocalSetup(env = process.env) {
397
214
  logsDir: paths.logsDir
398
215
  };
399
216
  }
400
- export function formatMcpConfig() {
217
+ export function formatMcpConfig(target) {
218
+ if (normalizeMcpClientTarget(target) === "codex") {
219
+ return [
220
+ "[mcp_servers.mainspring]",
221
+ 'command = "npx"',
222
+ 'args = ["-y", "mrmainspring"]',
223
+ "startup_timeout_sec = 20",
224
+ "tool_timeout_sec = 60",
225
+ "enabled = true"
226
+ ].join("\n");
227
+ }
401
228
  return JSON.stringify({
402
229
  mcpServers: {
403
230
  mainspring: {
@@ -445,14 +272,15 @@ function formatDoctorReport(env = process.env) {
445
272
  function formatCheck(ok, success, failure) {
446
273
  return ok ? `[ok] ${success}` : `[warn] ${failure}`;
447
274
  }
448
- function formatClientName(target) {
449
- if (!target)
450
- return "your";
451
- if (target.toLowerCase() === "cursor")
452
- return "Cursor";
453
- if (target.toLowerCase() === "claude")
454
- return "Claude Desktop";
455
- return target;
275
+ function normalizeMcpClientTarget(target) {
276
+ const normalized = target?.trim().toLowerCase();
277
+ if (normalized === "codex")
278
+ return "codex";
279
+ if (normalized === "cursor")
280
+ return "cursor";
281
+ if (normalized === "claude" || normalized === "claude-desktop" || normalized === "claude_desktop")
282
+ return "claude";
283
+ return "generic";
456
284
  }
457
285
  function quoteEnvValue(value) {
458
286
  return `"${value.replace(/"/g, "")}"`;
@@ -11,106 +11,20 @@ const CLIENTS = [
11
11
  { name: "Claude Desktop", path: "%APPDATA%/Claude/claude_desktop_config.json", platforms: ["win32"], format: "standard" },
12
12
  { name: "Claude Desktop", path: "~/.config/Claude/claude_desktop_config.json", platforms: ["linux"], format: "standard" },
13
13
  // Claude Code CLI
14
- {
15
- name: "Claude Code", path: "~/.claude/settings.json", platforms: ["darwin", "linux", "win32"], format: "standard",
16
- appPaths: ["/usr/local/bin/claude", "/usr/bin/claude"]
17
- },
14
+ { name: "Claude Code", path: "~/.claude/settings.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
18
15
  // Cursor
19
- {
20
- name: "Cursor", path: "~/.cursor/mcp.json", platforms: ["darwin", "linux"], format: "standard",
21
- appPaths: ["/Applications/Cursor.app", `${homedir()}/Applications/Cursor.app`]
22
- },
23
- {
24
- name: "Cursor", path: "%USERPROFILE%/.cursor/mcp.json", platforms: ["win32"], format: "standard",
25
- appPaths: ["C:/Users/Default/AppData/Local/Programs/cursor/Cursor.exe"]
26
- },
16
+ { name: "Cursor", path: "~/.cursor/mcp.json", platforms: ["darwin", "linux"], format: "standard" },
17
+ { name: "Cursor", path: "%USERPROFILE%/.cursor/mcp.json", platforms: ["win32"], format: "standard" },
27
18
  // Windsurf
28
- {
29
- name: "Windsurf", path: "~/.codeium/windsurf/mcp_config.json", platforms: ["darwin", "linux", "win32"], format: "standard",
30
- appPaths: ["/Applications/Windsurf.app", `${homedir()}/Applications/Windsurf.app`]
31
- },
19
+ { name: "Windsurf", path: "~/.codeium/windsurf/mcp_config.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
20
+ // Windsurf (also on Windows via AppData)
32
21
  { name: "Windsurf", path: "%APPDATA%/Windsurf/User/globalStorage/codeium.windsurf/mcp_config.json", platforms: ["win32"], format: "standard" },
33
22
  // Zed
34
- {
35
- name: "Zed", path: "~/.config/zed/settings.json", platforms: ["darwin", "linux"], format: "zed",
36
- appPaths: ["/Applications/Zed.app", `${homedir()}/Applications/Zed.app`]
37
- },
23
+ { name: "Zed", path: "~/.config/zed/settings.json", platforms: ["darwin", "linux"], format: "zed" },
38
24
  // Continue.dev
39
25
  { name: "Continue", path: "~/.continue/config.json", platforms: ["darwin", "linux", "win32"], format: "continue" },
40
- // VS Code (user-level MCP config — requires GitHub Copilot or MCP extension)
41
- {
42
- name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["darwin", "linux"], format: "standard",
43
- appPaths: ["/Applications/Visual Studio Code.app", `${homedir()}/Applications/Visual Studio Code.app`, "/usr/bin/code", "/usr/local/bin/code"]
44
- },
45
- { name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["win32"], format: "standard" },
46
- // Cline (VS Code extension by saoudrizwan)
47
- {
48
- name: "Cline",
49
- path: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
50
- detectDir: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev",
51
- platforms: ["darwin"], format: "standard"
52
- },
53
- {
54
- name: "Cline",
55
- path: "~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
56
- detectDir: "~/.config/Code/User/globalStorage/saoudrizwan.claude-dev",
57
- platforms: ["linux"], format: "standard"
58
- },
59
- {
60
- name: "Cline",
61
- path: "%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
62
- detectDir: "%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev",
63
- platforms: ["win32"], format: "standard"
64
- },
65
- // Roo Code (VS Code extension, fork of Cline)
66
- {
67
- name: "Roo Code",
68
- path: "~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
69
- detectDir: "~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline",
70
- platforms: ["darwin"], format: "standard"
71
- },
72
- {
73
- name: "Roo Code",
74
- path: "~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
75
- detectDir: "~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline",
76
- platforms: ["linux"], format: "standard"
77
- },
78
- {
79
- name: "Roo Code",
80
- path: "%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json",
81
- detectDir: "%APPDATA%/Code/User/globalStorage/rooveterinaryinc.roo-cline",
82
- platforms: ["win32"], format: "standard"
83
- },
84
- // Kilo Code (VS Code extension)
85
- {
86
- name: "Kilo Code",
87
- path: "~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
88
- detectDir: "~/Library/Application Support/Code/User/globalStorage/kilocode.kilo-code",
89
- platforms: ["darwin"], format: "standard"
90
- },
91
- {
92
- name: "Kilo Code",
93
- path: "~/.config/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
94
- detectDir: "~/.config/Code/User/globalStorage/kilocode.kilo-code",
95
- platforms: ["linux"], format: "standard"
96
- },
97
- {
98
- name: "Kilo Code",
99
- path: "%APPDATA%/Code/User/globalStorage/kilocode.kilo-code/settings/cline_mcp_settings.json",
100
- detectDir: "%APPDATA%/Code/User/globalStorage/kilocode.kilo-code",
101
- platforms: ["win32"], format: "standard"
102
- },
103
- // OpenCode (terminal AI coding assistant)
104
- { name: "OpenCode", path: "~/.config/opencode/config.json", platforms: ["darwin", "linux"], format: "standard" },
105
- { name: "OpenCode", path: "%APPDATA%/opencode/config.json", platforms: ["win32"], format: "standard" },
106
- // Amazon Q Developer
107
- { name: "Amazon Q", path: "~/.aws/amazonq/mcp.json", platforms: ["darwin", "linux"], format: "standard" },
108
- { name: "Amazon Q", path: "%USERPROFILE%/.aws/amazonq/mcp.json", platforms: ["win32"], format: "standard" },
109
- // Gemini CLI (Google)
110
- { name: "Gemini CLI", path: "~/.gemini/settings.json", platforms: ["darwin", "linux"], format: "standard" },
111
- { name: "Gemini CLI", path: "%USERPROFILE%/.gemini/settings.json", platforms: ["win32"], format: "standard" },
112
- // Codex CLI (OpenAI)
113
- { name: "Codex CLI", path: "~/.codex/config.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
26
+ // VS Code (workspace-agnostic user MCP config — requires MCP extension)
27
+ { name: "VS Code", path: "~/.vscode/mcp.json", platforms: ["darwin", "linux", "win32"], format: "standard" },
114
28
  ];
115
29
  function expandPath(p, env) {
116
30
  const home = env.HOME ?? env.USERPROFILE ?? homedir();
@@ -119,11 +33,8 @@ function expandPath(p, env) {
119
33
  .replace(/%APPDATA%/gi, env.APPDATA ?? "")
120
34
  .replace(/%USERPROFILE%/gi, env.USERPROFILE ?? home);
121
35
  }
122
- function isInstalled(configPath, detectDir, appPaths) {
123
- const dir = detectDir ?? dirname(configPath);
124
- if (existsSync(dir) || existsSync(configPath))
125
- return true;
126
- return appPaths?.some(p => existsSync(p)) ?? false;
36
+ function isInstalled(configPath) {
37
+ return existsSync(dirname(configPath)) || existsSync(configPath);
127
38
  }
128
39
  function readJson(path) {
129
40
  if (!existsSync(path))
@@ -183,14 +94,17 @@ export function detectClients(env = process.env) {
183
94
  if (seen.has(key))
184
95
  continue;
185
96
  seen.add(key);
186
- const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
187
- results.push({ name: client.name, configPath, installed: isInstalled(configPath, detectDir, client.appPaths), format: client.format });
97
+ results.push({ name: client.name, configPath, installed: isInstalled(configPath), format: client.format });
188
98
  }
189
99
  return results;
190
100
  }
191
101
  export function configureClients(selected, env = process.env) {
192
102
  const results = [];
193
103
  for (const client of selected) {
104
+ if (!client.installed) {
105
+ results.push({ name: client.name, configPath: client.configPath, status: "not-installed" });
106
+ continue;
107
+ }
194
108
  try {
195
109
  const json = readJson(client.configPath);
196
110
  if (alreadySet(json, client.format)) {
@@ -219,8 +133,7 @@ export function setupAllClients(env = process.env) {
219
133
  if (seen.has(key))
220
134
  continue;
221
135
  seen.add(key);
222
- const detectDir = client.detectDir ? expandPath(client.detectDir, env) : undefined;
223
- if (!isInstalled(configPath, detectDir, client.appPaths)) {
136
+ if (!isInstalled(configPath)) {
224
137
  results.push({ name: client.name, configPath, status: "not-installed" });
225
138
  continue;
226
139
  }
@@ -26,11 +26,6 @@ export function registerGrimoireTools(server, grimoireService) {
26
26
  }
27
27
  }, async (input) => {
28
28
  const secret = await grimoireService.putSecret(input);
29
- server.sendLoggingMessage({
30
- level: "info",
31
- logger: "mainspring",
32
- data: `Secret stored [${input.type}] ${input.name} (agent=${input.agent_id})`
33
- }).catch(() => { });
34
29
  return jsonResult({
35
30
  status: "stored",
36
31
  secret
@@ -67,11 +62,6 @@ export function registerGrimoireTools(server, grimoireService) {
67
62
  }
68
63
  }, async (input) => {
69
64
  const policy = await grimoireService.setPolicy(input);
70
- server.sendLoggingMessage({
71
- level: "info",
72
- logger: "mainspring",
73
- data: `Policy set [${input.policy_id}] max=${input.max_amount_per_call} per call (agent=${input.agent_id})`
74
- }).catch(() => { });
75
65
  return jsonResult({
76
66
  status: "stored",
77
67
  policy
@@ -18,14 +18,6 @@ export function registerMemoryTools(server, memoryService) {
18
18
  }
19
19
  }, async (input) => {
20
20
  const memory = await memoryService.write(input);
21
- const anchorNote = memory.anchor_status === "anchored" && memory.casper_transaction_hash
22
- ? ` · anchored tx=${memory.casper_transaction_hash.slice(0, 12)}…`
23
- : memory.anchor_status === "pending" ? " · anchor pending" : "";
24
- server.sendLoggingMessage({
25
- level: "info",
26
- logger: "mainspring",
27
- data: `Memory saved [${input.type}] ${memory.memory_id}${anchorNote}`
28
- }).catch(() => { });
29
21
  return jsonResult({
30
22
  memory_id: memory.memory_id,
31
23
  content_hash: memory.content_hash,
@@ -21,14 +21,6 @@ export function registerPaymentTools(server, paymentService) {
21
21
  }
22
22
  }, async (input) => {
23
23
  const result = await paymentService.fetch(input);
24
- const status = result.status ?? "unknown";
25
- const level = status === "settlement_unavailable" || status === "policy_blocked"
26
- ? "warning" : "info";
27
- server.sendLoggingMessage({
28
- level,
29
- logger: "mainspring",
30
- data: `Payment [${status}] ${input.method} ${input.url} (policy=${input.policy_id})`
31
- }).catch(() => { });
32
24
  return jsonResult(result);
33
25
  });
34
26
  server.registerTool("payment.receipt", {
package/package.json CHANGED
@@ -1,63 +1,63 @@
1
- {
2
- "name": "mrmainspring",
3
- "version": "0.2.9",
4
- "description": "Mr Mainspring MCP backend with memory, Grimoire policies, audit, Casper anchoring, and x402 settlement boundaries.",
5
- "license": "MIT",
6
- "type": "module",
7
- "bin": {
8
- "mainspring": "dist/index.js",
9
- "mrmainspring": "dist/index.js"
10
- },
11
- "main": "./dist/server.js",
12
- "types": "./dist/server.d.ts",
13
- "exports": {
14
- ".": {
15
- "types": "./dist/server.d.ts",
16
- "import": "./dist/server.js"
17
- },
18
- "./mcp": {
19
- "types": "./dist/index.d.ts",
20
- "import": "./dist/index.js"
21
- },
22
- "./package.json": "./package.json"
23
- },
24
- "publishConfig": {
25
- "access": "public"
26
- },
27
- "files": [
28
- "dist/**/*.js",
29
- "dist/**/*.d.ts",
30
- "LICENSE",
31
- "README.md",
32
- "package.json"
33
- ],
34
- "scripts": {
35
- "build": "tsc -p tsconfig.json",
36
- "demo:stdio": "npm run build && tsx scripts/demo-stdio.ts",
37
- "demo:x402-http": "tsx scripts/x402-demo-http-server.ts",
38
- "demo:x402-sidecars": "tsx scripts/x402-local-sidecars.ts",
39
- "demo:x402-sidecars:smoke": "tsx scripts/x402-local-sidecars.ts --smoke",
40
- "dev": "tsx src/index.ts",
41
- "mcp:stdio": "node dist/index.js",
42
- "prepack": "npm run build",
43
- "smoke:x402-payment-fetch": "tsx scripts/x402-payment-fetch-smoke.ts",
44
- "test": "vitest run",
45
- "x402:facilitator": "tsx scripts/x402-facilitator-sidecar.ts",
46
- "x402:resource": "tsx scripts/x402-local-sidecars.ts",
47
- "x402:signer": "tsx scripts/x402-signer-sidecar.ts"
48
- },
49
- "dependencies": {
50
- "@clack/prompts": "^1.5.1",
51
- "@modelcontextprotocol/sdk": "^1.13.0",
52
- "zod": "^3.25.76"
53
- },
54
- "devDependencies": {
55
- "@types/node": "^22.15.30",
56
- "tsx": "^4.19.4",
57
- "typescript": "^5.8.3",
58
- "vitest": "^4.1.8"
59
- },
60
- "engines": {
61
- "node": ">=20.0.0"
62
- }
63
- }
1
+ {
2
+ "name": "mrmainspring",
3
+ "version": "0.3.1",
4
+ "description": "Mr Mainspring MCP backend with memory, Grimoire policies, audit, Casper anchoring, and x402 settlement boundaries.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "mainspring": "dist/index.js",
9
+ "mrmainspring": "dist/index.js"
10
+ },
11
+ "main": "./dist/server.js",
12
+ "types": "./dist/server.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/server.d.ts",
16
+ "import": "./dist/server.js"
17
+ },
18
+ "./mcp": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "files": [
28
+ "dist/**/*.js",
29
+ "dist/**/*.d.ts",
30
+ "LICENSE",
31
+ "README.md",
32
+ "package.json"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "demo:stdio": "npm run build && tsx scripts/demo-stdio.ts",
37
+ "demo:x402-http": "tsx scripts/x402-demo-http-server.ts",
38
+ "demo:x402-sidecars": "tsx scripts/x402-local-sidecars.ts",
39
+ "demo:x402-sidecars:smoke": "tsx scripts/x402-local-sidecars.ts --smoke",
40
+ "dev": "tsx src/index.ts",
41
+ "mcp:stdio": "node dist/index.js",
42
+ "prepack": "npm run build",
43
+ "smoke:x402-payment-fetch": "tsx scripts/x402-payment-fetch-smoke.ts",
44
+ "test": "vitest run",
45
+ "x402:facilitator": "tsx scripts/x402-facilitator-sidecar.ts",
46
+ "x402:resource": "tsx scripts/x402-local-sidecars.ts",
47
+ "x402:signer": "tsx scripts/x402-signer-sidecar.ts"
48
+ },
49
+ "dependencies": {
50
+ "@clack/prompts": "^1.5.1",
51
+ "@modelcontextprotocol/sdk": "^1.13.0",
52
+ "zod": "^3.25.76"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.15.30",
56
+ "tsx": "^4.19.4",
57
+ "typescript": "^5.8.3",
58
+ "vitest": "^4.1.8"
59
+ },
60
+ "engines": {
61
+ "node": ">=20.0.0"
62
+ }
63
+ }