mrmainspring 0.1.0 → 0.1.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,47 +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
-
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
9
  ```bash
10
10
  npm install -g mrmainspring
11
+ mainspring setup cursor
11
12
  ```
12
13
 
13
14
  Run the stdio MCP server:
14
-
15
- ```bash
16
- mainspring
17
- ```
18
-
19
- For local development from this repository:
20
-
21
- ```bash
22
- npm run build
23
- npm run mcp:stdio
24
- ```
25
-
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
+
26
27
  ## Environment
27
28
 
28
- The server loads `.env` from the current directory, backend directory, or repo
29
- root. Use `SIGIL_ENV_FILE` to point at a specific env file.
30
-
31
- Important package boundaries:
32
-
33
- - Keep `.env`, `.sigil/`, local keys, and generated demo data outside the npm
34
- package.
35
- - Real Casper submission remains gated by `CASPER_ENABLE_REAL_SUBMISSION=true`.
36
- - Real x402 settlement remains gated by `X402_ENABLE_REAL_SETTLEMENT=true` and
37
- a configured `X402_SIGNER_URL`.
38
- - The signer private key must live outside the repository workspace.
39
-
40
- ## Library Entry
41
-
42
- ```ts
43
- import { createSigilServer } from "mrmainspring";
44
- ```
45
-
46
- The CLI entry is also exported as `mrmainspring/mcp`, but importing it starts
47
- the stdio server; use the package bin for normal MCP client configuration.
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 ADDED
@@ -0,0 +1,9 @@
1
+ type InitResult = {
2
+ envFile: string;
3
+ dataDir: string;
4
+ logsDir: string;
5
+ };
6
+ export declare function runCliCommand(args: string[]): boolean;
7
+ export declare function initializeLocalSetup(env?: NodeJS.ProcessEnv): InitResult;
8
+ export declare function formatMcpConfig(): string;
9
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { loadConfig } from "./config.js";
4
+ import { ensureGrimoireMasterKey, loadLocalEnvFile, resolveEnvPath } from "./env-file.js";
5
+ import { getDefaultMainspringPaths } from "./paths.js";
6
+ const VERSION = "0.1.1";
7
+ const HELP = `Mr Mainspring MCP server
8
+
9
+ Usage:
10
+ mainspring Start the MCP stdio server
11
+ mainspring --help Show this help
12
+ mainspring --version Show the installed version
13
+ mainspring init Create local config, data, and logs directories
14
+ mainspring config Print MCP client config JSON
15
+ mainspring setup [client] Initialize local files and print MCP config
16
+ mainspring doctor Check the local setup
17
+
18
+ MCP client config:
19
+ {
20
+ "mcpServers": {
21
+ "mainspring": {
22
+ "command": "mainspring"
23
+ }
24
+ }
25
+ }
26
+
27
+ Advanced users can still set SIGIL_ENV_FILE, SIGIL_DATA_DIR, Supabase, Casper,
28
+ and x402 env vars. No env vars are required for local memory, Grimoire, audit,
29
+ or payment preflight tools.
30
+ `;
31
+ export function runCliCommand(args) {
32
+ const [command, target] = args;
33
+ if (!command || command === "stdio" || command === "server" || command === "mcp") {
34
+ return false;
35
+ }
36
+ if (command === "--help" || command === "-h" || command === "help") {
37
+ process.stdout.write(HELP);
38
+ return true;
39
+ }
40
+ if (command === "--version" || command === "-v" || command === "version") {
41
+ process.stdout.write(`${VERSION}\n`);
42
+ return true;
43
+ }
44
+ if (command === "init") {
45
+ const result = initializeLocalSetup();
46
+ process.stdout.write(formatInitResult(result));
47
+ return true;
48
+ }
49
+ if (command === "config") {
50
+ process.stdout.write(`${formatMcpConfig()}\n`);
51
+ return true;
52
+ }
53
+ if (command === "setup") {
54
+ const result = initializeLocalSetup();
55
+ process.stdout.write(formatInitResult(result));
56
+ process.stdout.write(`\nPaste this into ${formatClientName(target)} MCP config:\n\n`);
57
+ process.stdout.write(`${formatMcpConfig()}\n`);
58
+ return true;
59
+ }
60
+ if (command === "doctor") {
61
+ process.stdout.write(formatDoctorReport());
62
+ return true;
63
+ }
64
+ process.stderr.write(`Unknown command: ${command}\n\n${HELP}`);
65
+ process.exitCode = 1;
66
+ return true;
67
+ }
68
+ export function initializeLocalSetup(env = process.env) {
69
+ const paths = getDefaultMainspringPaths(env);
70
+ const envFile = env.SIGIL_ENV_FILE?.trim() ? resolveEnvPath(env) : paths.envFile;
71
+ mkdirSync(paths.appDir, { recursive: true });
72
+ mkdirSync(paths.dataDir, { recursive: true });
73
+ mkdirSync(paths.logsDir, { recursive: true });
74
+ mkdirSync(dirname(envFile), { recursive: true });
75
+ if (!existsSync(envFile)) {
76
+ writeFileSync(envFile, [
77
+ "# Mr Mainspring local config",
78
+ "SIGIL_STORAGE_BACKEND=file",
79
+ `SIGIL_DATA_DIR=${quoteEnvValue(paths.dataDir)}`,
80
+ ""
81
+ ].join("\n"), "utf8");
82
+ }
83
+ const setupEnv = { ...env, SIGIL_ENV_FILE: envFile };
84
+ loadLocalEnvFile(setupEnv);
85
+ ensureGrimoireMasterKey(setupEnv, { announce: false });
86
+ return {
87
+ envFile,
88
+ dataDir: paths.dataDir,
89
+ logsDir: paths.logsDir
90
+ };
91
+ }
92
+ export function formatMcpConfig() {
93
+ return JSON.stringify({
94
+ mcpServers: {
95
+ mainspring: {
96
+ command: "mainspring"
97
+ }
98
+ }
99
+ }, null, 2);
100
+ }
101
+ function formatInitResult(result) {
102
+ return [
103
+ "Mr Mainspring local setup is ready.",
104
+ `Config: ${result.envFile}`,
105
+ `Data: ${result.dataDir}`,
106
+ `Logs: ${result.logsDir}`,
107
+ ""
108
+ ].join("\n");
109
+ }
110
+ function formatDoctorReport(env = process.env) {
111
+ const paths = getDefaultMainspringPaths(env);
112
+ const envFile = resolveEnvPath(env);
113
+ const envCopy = { ...env };
114
+ loadLocalEnvFile(envCopy);
115
+ const lines = ["Mr Mainspring doctor"];
116
+ const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
117
+ lines.push(formatCheck(nodeMajor >= 20, `Node.js ${process.versions.node}`, "Node.js 20 or newer is required"));
118
+ lines.push(formatCheck(existsSync(envFile), `Config file: ${envFile}`, "Config file missing; run mainspring init"));
119
+ lines.push(formatCheck(existsSync(paths.dataDir), `Data dir: ${paths.dataDir}`, "Data dir missing; run mainspring init"));
120
+ lines.push(formatCheck(existsSync(paths.logsDir), `Logs dir: ${paths.logsDir}`, "Logs dir missing; run mainspring init"));
121
+ try {
122
+ const config = loadConfig(envCopy);
123
+ lines.push(`[ok] Storage backend: ${config.storage.backend}`);
124
+ lines.push(`[ok] Casper real submission: ${config.casper.submissionEnabled ? "enabled" : "disabled"}`);
125
+ lines.push(`[ok] x402 real settlement: ${config.x402.settlementEnabled ? "enabled" : "disabled"}`);
126
+ }
127
+ catch (error) {
128
+ lines.push(`[error] ${error instanceof Error ? error.message : String(error)}`);
129
+ process.exitCode = 1;
130
+ }
131
+ return `${lines.join("\n")}\n`;
132
+ }
133
+ function formatCheck(ok, success, failure) {
134
+ return ok ? `[ok] ${success}` : `[warn] ${failure}`;
135
+ }
136
+ function formatClientName(target) {
137
+ if (!target)
138
+ return "your";
139
+ if (target.toLowerCase() === "cursor")
140
+ return "Cursor";
141
+ if (target.toLowerCase() === "claude")
142
+ return "Claude Desktop";
143
+ return target;
144
+ }
145
+ function quoteEnvValue(value) {
146
+ return `"${value.replace(/"/g, "")}"`;
147
+ }
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { getDefaultMainspringPaths } from "./paths.js";
4
5
  const CASPER_HASH_PATTERN = /^(hash-)?[a-f0-9]{64}$/i;
5
6
  const CASPER_PACKAGE_HASH_PATTERN = /^(hash-|package-)?[a-f0-9]{64}$/i;
6
7
  export function loadConfig(env = process.env) {
@@ -37,10 +38,10 @@ export function loadConfig(env = process.env) {
37
38
  };
38
39
  validateX402Config(x402);
39
40
  return {
40
- dataDir: resolve(optionalEnv(env.SIGIL_DATA_DIR) ?? ".sigil"),
41
+ dataDir: resolve(optionalEnv(env.SIGIL_DATA_DIR) ?? getDefaultMainspringPaths(env).dataDir),
41
42
  grimoireMasterKey: loadMasterKey(env.GRIMOIRE_MASTER_KEY),
42
43
  serverName: optionalEnv(env.SIGIL_MCP_NAME) ?? "mr-mainspring",
43
- serverVersion: optionalEnv(env.SIGIL_MCP_VERSION) ?? "0.1.0",
44
+ serverVersion: optionalEnv(env.SIGIL_MCP_VERSION) ?? "0.1.1",
44
45
  storage: loadStorageConfig(env),
45
46
  casper,
46
47
  x402
@@ -1 +1,5 @@
1
1
  export declare function loadLocalEnvFile(env?: NodeJS.ProcessEnv): string | null;
2
+ export declare function resolveEnvPath(env?: NodeJS.ProcessEnv): string;
3
+ export declare function ensureGrimoireMasterKey(env?: NodeJS.ProcessEnv, options?: {
4
+ announce?: boolean;
5
+ }): void;
package/dist/env-file.js CHANGED
@@ -1,9 +1,11 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { dirname, join, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
5
+ import { getDefaultMainspringPaths } from "./paths.js";
4
6
  export function loadLocalEnvFile(env = process.env) {
5
7
  const envPath = resolveEnvPath(env);
6
- if (!envPath || !existsSync(envPath)) {
8
+ if (!existsSync(envPath)) {
7
9
  return null;
8
10
  }
9
11
  const raw = readFileSync(envPath, "utf8");
@@ -14,14 +16,20 @@ export function loadLocalEnvFile(env = process.env) {
14
16
  }
15
17
  return envPath;
16
18
  }
17
- function resolveEnvPath(env) {
19
+ export function resolveEnvPath(env = process.env) {
18
20
  if (env.SIGIL_ENV_FILE?.trim()) {
19
- return env.SIGIL_ENV_FILE.trim();
21
+ return resolve(env.SIGIL_ENV_FILE.trim());
20
22
  }
21
23
  const backendRoot = dirname(dirname(fileURLToPath(import.meta.url)));
22
24
  const repoRoot = dirname(backendRoot);
23
- const candidates = [join(process.cwd(), ".env"), join(backendRoot, ".env"), join(repoRoot, ".env")];
24
- return candidates.find((candidate) => existsSync(candidate)) ?? candidates.at(-1) ?? null;
25
+ const defaultEnvFile = getDefaultMainspringPaths(env).envFile;
26
+ const candidates = [
27
+ defaultEnvFile,
28
+ join(process.cwd(), ".env"),
29
+ join(backendRoot, ".env"),
30
+ join(repoRoot, ".env")
31
+ ];
32
+ return resolve(candidates.find((candidate) => existsSync(candidate)) ?? defaultEnvFile);
25
33
  }
26
34
  function parseEnv(raw) {
27
35
  const entries = [];
@@ -42,6 +50,19 @@ function parseEnv(raw) {
42
50
  }
43
51
  return entries;
44
52
  }
53
+ export function ensureGrimoireMasterKey(env = process.env, options = {}) {
54
+ if (env.GRIMOIRE_MASTER_KEY)
55
+ return;
56
+ const key = randomBytes(32).toString("base64");
57
+ const targetPath = resolveEnvPath(env);
58
+ mkdirSync(dirname(targetPath), { recursive: true });
59
+ const prefix = existsSync(targetPath) && readFileSync(targetPath, "utf8").trim() ? "\n" : "";
60
+ appendFileSync(targetPath, `${prefix}GRIMOIRE_MASTER_KEY=${key}\n`, "utf8");
61
+ env.GRIMOIRE_MASTER_KEY = key;
62
+ if (options.announce !== false) {
63
+ process.stderr.write(`[mr-mainspring] Generated GRIMOIRE_MASTER_KEY at ${targetPath}\n`);
64
+ }
65
+ }
45
66
  function unquote(value) {
46
67
  if ((value.startsWith('"') && value.endsWith('"')) ||
47
68
  (value.startsWith("'") && value.endsWith("'"))) {
package/dist/index.js CHANGED
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { runCliCommand } from "./cli.js";
3
4
  import { loadConfig } from "./config.js";
4
- import { loadLocalEnvFile } from "./env-file.js";
5
+ import { ensureGrimoireMasterKey, loadLocalEnvFile } from "./env-file.js";
5
6
  import { createSigilServer } from "./server.js";
6
7
  async function main() {
8
+ if (runCliCommand(process.argv.slice(2))) {
9
+ return;
10
+ }
7
11
  loadLocalEnvFile();
12
+ ensureGrimoireMasterKey();
8
13
  const config = loadConfig();
9
14
  const server = createSigilServer(config);
10
15
  const transport = new StdioServerTransport();
@@ -0,0 +1,7 @@
1
+ export type MainspringPaths = {
2
+ appDir: string;
3
+ envFile: string;
4
+ dataDir: string;
5
+ logsDir: string;
6
+ };
7
+ export declare function getDefaultMainspringPaths(env?: NodeJS.ProcessEnv, platformName?: NodeJS.Platform, homeDir?: string): MainspringPaths;
package/dist/paths.js ADDED
@@ -0,0 +1,20 @@
1
+ import { homedir, platform } from "node:os";
2
+ import { join, resolve } from "node:path";
3
+ export function getDefaultMainspringPaths(env = process.env, platformName = platform(), homeDir = homedir()) {
4
+ const appDir = resolve(getDefaultAppDir(env, platformName, homeDir));
5
+ return {
6
+ appDir,
7
+ envFile: join(appDir, ".env"),
8
+ dataDir: join(appDir, "data"),
9
+ logsDir: join(appDir, "logs")
10
+ };
11
+ }
12
+ function getDefaultAppDir(env, platformName, homeDir) {
13
+ if (platformName === "win32") {
14
+ return join(env.APPDATA?.trim() || join(homeDir, "AppData", "Roaming"), "MrMainspring");
15
+ }
16
+ if (platformName === "darwin") {
17
+ return join(homeDir, "Library", "Application Support", "MrMainspring");
18
+ }
19
+ return join(env.XDG_CONFIG_HOME?.trim() || join(homeDir, ".config"), "mrmainspring");
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "mrmainspring",
3
- "version": "0.1.0",
2
+ "name": "mrmainspring",
3
+ "version": "0.1.1",
4
4
  "description": "Mr Mainspring MCP backend with memory, Grimoire policies, audit, Casper anchoring, and x402 settlement boundaries.",
5
5
  "license": "MIT",
6
6
  "type": "module",