@llmnesia/mcp 0.1.5 → 0.1.6

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
@@ -52,6 +52,9 @@ The installer:
52
52
  Cursor, and Codex), and
53
53
  4. registers that copy with each detected client — using the client's own CLI
54
54
  where available, or with an automatic backup before editing its config file.
55
+ It also prepares the standard Claude Desktop, Cursor, and Codex/ChatGPT
56
+ config paths, so installing one of those desktop apps later does not require
57
+ running setup again.
55
58
 
56
59
  The `install` command is the approval to perform those setup steps. It does not
57
60
  interrupt setup with a separate prompt for every detected app, and it never
@@ -119,9 +122,9 @@ received data — check it before concluding a chat does not exist.
119
122
  | Client | How `install` wires it |
120
123
  | --- | --- |
121
124
  | Claude Code | Runs `claude mcp add --scope user` with the absolute Node and installed runtime paths, making it available in every project (detects `claude` on PATH). |
122
- | Claude Desktop | Edits `claude_desktop_config.json` at the standard OS-specific path. |
123
- | Cursor | Edits `~/.cursor/mcp.json`. |
124
- | Codex | Runs `codex mcp add` with the absolute runtime paths when available. If Windows does not expose the app's bundled CLI, the installer safely edits `~/.codex/config.toml` itself and backs up existing settings first. Read-only LLMnesia tools are configured to run without repeated approval prompts; writes still ask. |
125
+ | Claude Desktop | Edits `claude_desktop_config.json` at the standard OS-specific path, preparing it in advance if Claude Desktop is installed later. |
126
+ | Cursor | Edits `~/.cursor/mcp.json`, preparing it in advance if Cursor is installed later. |
127
+ | Codex / ChatGPT desktop | Runs `codex mcp add` with the absolute runtime paths when available. If the bundled CLI is unavailable, the installer safely edits `~/.codex/config.toml` itself and backs up existing settings first, including preparing it for a later installation. Read-only LLMnesia tools are configured to run without repeated approval prompts; writes still ask. |
125
128
  | Others (Windsurf, Zed, LM Studio, …) | Prints the generic stdio block for you to paste. |
126
129
 
127
130
  **Cloud-only clients cannot reach a local stdio server** (web ChatGPT, Lovable,
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  ));
39
39
 
40
40
  // src/version.ts
41
- var VERSION = true ? "0.1.5" : "0.0.0-dev";
41
+ var VERSION = true ? "0.1.6" : "0.0.0-dev";
42
42
 
43
43
  export {
44
44
  __require,
@@ -15,7 +15,7 @@ import {
15
15
  __commonJS,
16
16
  __require,
17
17
  __toESM
18
- } from "./chunk-YVBAQ5VA.js";
18
+ } from "./chunk-JBCYML3I.js";
19
19
 
20
20
  // ../node_modules/isexe/windows.js
21
21
  var require_windows = __commonJS({
@@ -2606,7 +2606,7 @@ async function watchInbox(corpus, onDrain) {
2606
2606
  // src/clientRegistration.ts
2607
2607
  var import_cross_spawn = __toESM(require_cross_spawn(), 1);
2608
2608
  import { execFile as execFile2 } from "child_process";
2609
- import { access, readFile as readFile2, stat as stat5, writeFile as writeFile2 } from "fs/promises";
2609
+ import { access, mkdir as mkdir2, readFile as readFile2, stat as stat5, writeFile as writeFile2 } from "fs/promises";
2610
2610
  import { homedir as homedir2, platform as platform2 } from "os";
2611
2611
  import { dirname as dirname4, join as join4 } from "path";
2612
2612
  import { promisify as promisify2 } from "util";
@@ -2955,15 +2955,19 @@ claude mcp add --scope user ${SERVER_NAME} -- ${serve.command} ${serve.args.join
2955
2955
  function sameServeCommand(left, right) {
2956
2956
  return left.command === right.command && JSON.stringify(left.args) === JSON.stringify(right.args);
2957
2957
  }
2958
- async function registerCodex(serve, cli, configPath, confirm, now) {
2958
+ async function registerCodex(serve, cli, configPath, confirm, now, prepareMissing = false) {
2959
2959
  if (!await cli.detect()) {
2960
- if (!await clientDirExists(configPath)) {
2960
+ const configDirExists = await clientDirExists(configPath);
2961
+ if (!configDirExists && !prepareMissing) {
2961
2962
  return {
2962
2963
  client: "Codex",
2963
2964
  action: "skipped",
2964
2965
  detail: `Not installed (no Codex CLI and no ${dirname4(configPath)}).`
2965
2966
  };
2966
2967
  }
2968
+ if (!configDirExists) {
2969
+ await mkdir2(dirname4(configPath), { recursive: true });
2970
+ }
2967
2971
  try {
2968
2972
  const raw = await readFile2(configPath, "utf8");
2969
2973
  if (renderCodexConfig(raw, serve) === raw) {
@@ -2986,8 +2990,8 @@ ${codexBlock(serve)}`
2986
2990
  const update = await writeCodexConfig(configPath, serve, now);
2987
2991
  return {
2988
2992
  client: "Codex",
2989
- action: update.changed ? "registered" : "skipped",
2990
- detail: update.changed ? `Wrote ${configPath}${update.backupPath ? ` (backup: ${update.backupPath})` : ""}.` : `Already registered at ${configPath}.`
2993
+ action: update.changed ? configDirExists ? "registered" : "prepared" : "skipped",
2994
+ detail: update.changed ? configDirExists ? `Wrote ${configPath}${update.backupPath ? ` (backup: ${update.backupPath})` : ""}.` : `Prepared ${configPath} for a future Codex or ChatGPT installation.` : `Already registered at ${configPath}.`
2991
2995
  };
2992
2996
  } catch (err) {
2993
2997
  return { client: "Codex", action: "error", detail: `Could not update ${configPath}: ${errorMessage(err)}` };
@@ -3068,14 +3072,22 @@ codex mcp add ${SERVER_NAME} -- ${serve.command} ${serve.args.join(" ")}`
3068
3072
  detail: `Ran: codex mcp add ${SERVER_NAME} -- ${serve.command} ${serve.args.join(" ")}`
3069
3073
  };
3070
3074
  }
3071
- async function registerJsonClient(clientLabel, configPath, serve, confirm, now) {
3072
- if (!await clientDirExists(configPath)) {
3075
+ async function registerJsonClient(clientLabel, configPath, serve, confirm, now, prepareMissing = false) {
3076
+ const configDirExists = await clientDirExists(configPath);
3077
+ if (!configDirExists && !prepareMissing) {
3073
3078
  return {
3074
3079
  client: clientLabel,
3075
3080
  action: "skipped",
3076
3081
  detail: `Not installed (no ${dirname4(configPath)}). Re-run install after setting it up.`
3077
3082
  };
3078
3083
  }
3084
+ if (!configDirExists) {
3085
+ try {
3086
+ await mkdir2(dirname4(configPath), { recursive: true });
3087
+ } catch (err) {
3088
+ return { client: clientLabel, action: "error", detail: `Could not prepare ${configPath}: ${errorMessage(err)}` };
3089
+ }
3090
+ }
3079
3091
  let data;
3080
3092
  let existed;
3081
3093
  let raw;
@@ -3112,7 +3124,11 @@ ${preview}`;
3112
3124
  const nextData = { ...data, mcpServers: { ...servers, [SERVER_NAME]: nextEntry } };
3113
3125
  await writeFile2(configPath, `${JSON.stringify(nextData, null, 2)}
3114
3126
  `, "utf8");
3115
- return { client: clientLabel, action: "registered", detail: `Wrote ${configPath}.` };
3127
+ return {
3128
+ client: clientLabel,
3129
+ action: configDirExists ? "registered" : "prepared",
3130
+ detail: configDirExists ? `Wrote ${configPath}.` : `Prepared ${configPath} for a future ${clientLabel} installation.`
3131
+ };
3116
3132
  } catch (err) {
3117
3133
  return { client: clientLabel, action: "error", detail: `Could not write ${configPath}: ${errorMessage(err)}` };
3118
3134
  }
@@ -3205,6 +3221,7 @@ async function registerClients(options) {
3205
3221
  const home2 = options.home ?? homedir2();
3206
3222
  const osPlatform2 = options.osPlatform ?? platform2();
3207
3223
  const now = options.now ?? (() => Date.now());
3224
+ const prepareMissing = options.prepareMissingDesktopApps === true;
3208
3225
  const { claudeDesktop, cursor, codex } = paths(home2, osPlatform2);
3209
3226
  const results = [];
3210
3227
  const safe = async (client, fn) => {
@@ -3224,15 +3241,17 @@ async function registerClients(options) {
3224
3241
  results.push(
3225
3242
  await safe(
3226
3243
  "Claude Desktop",
3227
- () => registerJsonClient("Claude Desktop", claudeDesktop, options.serve, options.confirm, now)
3244
+ () => registerJsonClient("Claude Desktop", claudeDesktop, options.serve, options.confirm, now, prepareMissing)
3228
3245
  )
3229
3246
  );
3230
3247
  }
3231
- results.push(await safe("Cursor", () => registerJsonClient("Cursor", cursor, options.serve, options.confirm, now)));
3248
+ results.push(
3249
+ await safe("Cursor", () => registerJsonClient("Cursor", cursor, options.serve, options.confirm, now, prepareMissing))
3250
+ );
3232
3251
  results.push(
3233
3252
  await safe(
3234
3253
  "Codex",
3235
- () => registerCodex(options.serve, options.codexCli ?? realCodexCli, codex, options.confirm, now)
3254
+ () => registerCodex(options.serve, options.codexCli ?? realCodexCli, codex, options.confirm, now, prepareMissing)
3236
3255
  )
3237
3256
  );
3238
3257
  results.push({
@@ -3378,7 +3397,7 @@ import { delimiter, dirname as dirname6, isAbsolute as isAbsolute2, join as join
3378
3397
  import { homedir as homedir4, platform as platform3 } from "os";
3379
3398
 
3380
3399
  // src/runtimeInstall.ts
3381
- import { cp, mkdir as mkdir2, readdir as readdir3, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
3400
+ import { cp, mkdir as mkdir3, readdir as readdir3, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
3382
3401
  import { existsSync } from "fs";
3383
3402
  import { homedir as homedir3 } from "os";
3384
3403
  import { dirname as dirname5, join as join5 } from "path";
@@ -3404,10 +3423,10 @@ async function installRuntime(options = {}) {
3404
3423
  const nonce = `${process.pid}-${Date.now()}`;
3405
3424
  const stagingDir = join5(parentDir, `.bin-install-${nonce}`);
3406
3425
  const backupDir = join5(parentDir, `.bin-backup-${nonce}`);
3407
- await mkdir2(parentDir, { recursive: true });
3426
+ await mkdir3(parentDir, { recursive: true });
3408
3427
  await rm2(stagingDir, { recursive: true, force: true });
3409
3428
  await rm2(backupDir, { recursive: true, force: true });
3410
- await mkdir2(stagingDir, { recursive: true });
3429
+ await mkdir3(stagingDir, { recursive: true });
3411
3430
  try {
3412
3431
  for (const name of await readdir3(sourceDir)) {
3413
3432
  await cp(join5(sourceDir, name), join5(stagingDir, name), { recursive: true });
@@ -3793,66 +3812,90 @@ ${message}
3793
3812
  rl.close();
3794
3813
  }
3795
3814
  }
3796
- function printRegistrationResults(results) {
3797
- for (const r of results) {
3798
- if (r.client.startsWith("Other MCP clients")) {
3799
- continue;
3800
- }
3801
- if (r.action === "registered") {
3802
- process.stdout.write(`\u2713 ${r.client}
3803
- `);
3804
- continue;
3805
- }
3806
- if (r.action === "skipped" && r.detail.startsWith("Already registered")) {
3807
- process.stdout.write(`\u2713 ${r.client} (already configured)
3808
- `);
3809
- continue;
3810
- }
3811
- if (r.action === "error") {
3812
- process.stderr.write(`\u2717 ${r.client}: ${r.detail}
3813
- `);
3814
- continue;
3815
- }
3816
- if (r.action === "printed") {
3817
- process.stdout.write(`\u2022 ${r.client}: ${r.detail}
3818
- `);
3819
- }
3820
- }
3815
+ function summarizeClientRegistrations(results) {
3816
+ const connected = [];
3817
+ const prepared = [];
3818
+ const errors = [];
3819
+ for (const result of results) {
3820
+ if (result.client.startsWith("Other MCP clients")) continue;
3821
+ if (result.action === "registered" || result.action === "skipped" && result.detail.startsWith("Already registered")) {
3822
+ connected.push(result.client);
3823
+ } else if (result.action === "prepared") {
3824
+ prepared.push(result.client);
3825
+ } else if (result.action === "error") {
3826
+ errors.push({ client: result.client, detail: result.detail });
3827
+ }
3828
+ }
3829
+ return { connected, prepared, errors };
3830
+ }
3831
+ function naturalList(items) {
3832
+ if (items.length <= 1) return items[0] ?? "";
3833
+ if (items.length === 2) return `${items[0]} and ${items[1]}`;
3834
+ return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
3835
+ }
3836
+ function formatInstallSuccess(connectedClients, preparedClients = []) {
3837
+ const apps = naturalList(connectedClients);
3838
+ const restart = apps ? `Fully close and reopen your browser and ${apps}.
3839
+ ` : "Fully close and reopen your browser.\n";
3840
+ const future = preparedClients.length > 0 ? `${naturalList(preparedClients)} will also be ready if you install ${preparedClients.length === 1 ? "it" : "them"} later.
3841
+ ` : "";
3842
+ return `
3843
+ Setup complete.
3844
+ ${restart}${future}LLMnesia Settings will confirm when your history is ready.
3845
+ `;
3821
3846
  }
3822
3847
  async function runInstall(positionals, options, flags) {
3823
3848
  let hadProblems = false;
3824
- process.stdout.write("Installing LLMnesia MCP\u2026\n");
3849
+ process.stdout.write("Connecting your browser...\n");
3825
3850
  try {
3826
- const host = await installNativeHost({
3851
+ await installNativeHost({
3827
3852
  ids: positionals,
3828
3853
  corpus: options.corpus,
3829
3854
  force: flags.has("force"),
3830
3855
  quiet: true
3831
3856
  });
3832
- process.stdout.write(`\u2713 Browser helper (${host.installedCount} browser${host.installedCount === 1 ? "" : "s"})
3833
- `);
3857
+ process.stdout.write("Browser connected.\n");
3834
3858
  } catch (err) {
3835
3859
  hadProblems = true;
3836
- process.stderr.write(`\u2717 Browser helper: ${err instanceof Error ? err.message : String(err)}
3860
+ process.stderr.write(`Could not connect your browser: ${err instanceof Error ? err.message : String(err)}
3837
3861
  `);
3838
3862
  }
3839
- const runtime = await installRuntime();
3840
- process.stdout.write("\u2713 MCP runtime\n");
3841
- const results = await registerClients({ serve: runtime.serve, confirm: async () => true });
3842
- printRegistrationResults(results);
3843
- hadProblems ||= results.some((result) => result.action === "error");
3844
- process.stdout.write(
3845
- "\nDesktop helper installed. Fully quit and reopen your MCP apps and browser.\nBrowser sync is required or your MCP history will be empty. If you did not start in LLMnesia Settings, open it and click \u2018Enable automatic sync\u2019 once. LLMnesia will finish the first sync automatically.\n"
3846
- );
3847
- const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
3848
- process.stdout.write(`Then run \`${npxCommand} -y @llmnesia/mcp@latest doctor\` to check what's wired up.
3863
+ process.stdout.write("Connecting your desktop AI apps...\n");
3864
+ let runtime;
3865
+ try {
3866
+ runtime = await installRuntime();
3867
+ } catch (err) {
3868
+ process.stderr.write(`Could not install LLMnesia: ${err instanceof Error ? err.message : String(err)}
3849
3869
  `);
3850
- if (hadProblems) {
3870
+ process.stderr.write("Setup needs attention. Fix the message above, then run the same command again.\n");
3871
+ return 1;
3872
+ }
3873
+ const results = await registerClients({
3874
+ serve: runtime.serve,
3875
+ confirm: async () => true,
3876
+ prepareMissingDesktopApps: true
3877
+ });
3878
+ const registrations = summarizeClientRegistrations(results);
3879
+ for (const client of registrations.connected) {
3880
+ process.stdout.write(`${client} connected.
3881
+ `);
3882
+ }
3883
+ for (const error of registrations.errors) {
3884
+ process.stderr.write(`Could not connect ${error.client}: ${error.detail}
3885
+ `);
3886
+ }
3887
+ hadProblems ||= registrations.errors.length > 0;
3888
+ if (registrations.connected.length === 0 && registrations.prepared.length === 0) {
3889
+ hadProblems = true;
3851
3890
  process.stderr.write(
3852
- "\nInstall completed with problems. Review the error messages above; no failure was silently treated as success.\n"
3891
+ "No supported desktop AI app was found. Install or open Codex, Claude Desktop, or Cursor, then run the same command again.\n"
3853
3892
  );
3893
+ }
3894
+ if (hadProblems) {
3895
+ process.stderr.write("\nSetup needs attention. Fix the message above, then run the same command again.\n");
3854
3896
  return 1;
3855
3897
  }
3898
+ process.stdout.write(formatInstallSuccess(registrations.connected, registrations.prepared));
3856
3899
  return 0;
3857
3900
  }
3858
3901
  async function runUninstall(flags) {
@@ -3867,10 +3910,19 @@ async function runUninstall(flags) {
3867
3910
  }
3868
3911
  }
3869
3912
  const results = await unregisterClients();
3870
- printRegistrationResults(results);
3913
+ for (const result of results) {
3914
+ if (result.client.startsWith("Other MCP clients")) continue;
3915
+ if (result.action === "registered") {
3916
+ process.stdout.write(`${result.client} disconnected.
3917
+ `);
3918
+ } else if (result.action === "error") {
3919
+ process.stderr.write(`Could not disconnect ${result.client}: ${result.detail}
3920
+ `);
3921
+ }
3922
+ }
3871
3923
  if (results.some((result) => result.action === "error")) {
3872
3924
  process.stderr.write(
3873
- "Uninstall stopped before removing the runtime or browser host because one or more client registrations could not be removed. Fix the \u2717 messages above and retry.\n"
3925
+ "Uninstall stopped before removing the remaining files. Fix the message above and try again.\n"
3874
3926
  );
3875
3927
  return 1;
3876
3928
  }
@@ -4062,7 +4114,9 @@ function collapse(text) {
4062
4114
  return text.replace(/\s+/g, " ").trim();
4063
4115
  }
4064
4116
  export {
4065
- main
4117
+ formatInstallSuccess,
4118
+ main,
4119
+ summarizeClientRegistrations
4066
4120
  };
4067
4121
  /*! Bundled license information:
4068
4122
 
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __llmnesiaCreateRequire } from "node:module";
3
3
  const require = __llmnesiaCreateRequire(import.meta.url);
4
4
  import {
5
5
  VERSION
6
- } from "./chunk-YVBAQ5VA.js";
6
+ } from "./chunk-JBCYML3I.js";
7
7
 
8
8
  // src/nodeDownload.ts
9
9
  var NODE_DOWNLOAD_URLS = {
@@ -141,7 +141,7 @@ async function bootstrap() {
141
141
  await guideUnsupportedNodeVersion(process.versions.node);
142
142
  return 1;
143
143
  }
144
- const { main } = await import("./cli-TVE63AWC.js");
144
+ const { main } = await import("./cli-LNUL4QWX.js");
145
145
  return main();
146
146
  }
147
147
  bootstrap().then((code) => {
package/dist/mcp.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  __commonJS,
14
14
  __export,
15
15
  __toESM
16
- } from "./chunk-YVBAQ5VA.js";
16
+ } from "./chunk-JBCYML3I.js";
17
17
 
18
18
  // ../node_modules/ajv/dist/compile/codegen/code.js
19
19
  var require_code = __commonJS({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmnesia/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "comment:mcpName": "The MCP Registry's ownership marker: it must equal server.json's `name`, and the namespace must match how you authenticate with mcp-publisher. `com.llmnesia/...` requires DNS auth (a TXT record on llmnesia.com) — see docs/MCP_INSTALLER_BOARD.md.",