autorouter-mcp 0.2.5 → 0.2.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
@@ -364,12 +364,40 @@ autorouter adopt --target claude --servers-only # skip skills and plugins
364
364
  autorouter adopt --target claude --keep project-tools --keep-plugin ui-toolkit
365
365
  autorouter restore --target claude # undo the most recent adopt
366
366
 
367
+ autorouter update # upgrade in place
368
+ autorouter update --check # is there a newer version?
369
+
367
370
  autorouter login # which servers need a grant
368
371
  autorouter login remote-server # authorize one (opens a browser)
369
372
  autorouter login remote-server --device # headless: enter a code elsewhere
370
373
  autorouter logout remote-server # forget a stored grant
371
374
  ```
372
375
 
376
+ ## Updating
377
+
378
+ `autorouter update` upgrades in place, using the package manager that installed
379
+ the copy you are running:
380
+
381
+ ```sh
382
+ autorouter update # detect, then upgrade
383
+ autorouter update --check # report the available version, install nothing
384
+ autorouter update --dry-run # print the command, do not run it
385
+ ```
386
+
387
+ Which manager to use is decided by where the running file sits on disk, not by
388
+ what happens to be on `PATH` — running `npm i -g` against a pnpm-managed global
389
+ installs a second copy that shadows the first, and you would then be upgrading
390
+ one install while running the other.
391
+
392
+ Three cases do not run a package manager, and say so instead: a copy unpacked by
393
+ `npx`/`bunx`/`pnpm dlx` (nothing to upgrade — those refetch every run), a git
394
+ checkout (`git pull && bun install && bun run build`), and a layout it cannot
395
+ identify. All three still report whether a newer version exists.
396
+
397
+ The router is a long-lived stdio server, so a harness that already has it
398
+ running keeps the old build until it respawns it — restart the harness, or
399
+ reconnect the MCP server, after updating.
400
+
373
401
  ## OAuth servers
374
402
 
375
403
  Some remote MCP servers carry no credentials in their visible configuration.
package/dist/cli.js CHANGED
@@ -19474,6 +19474,18 @@ function run(command, args, opts = {}) {
19474
19474
  child.stdin.end();
19475
19475
  });
19476
19476
  }
19477
+ function runStreaming(command, args, opts = {}) {
19478
+ return new Promise((resolve, reject) => {
19479
+ const child = spawn2(command, args, {
19480
+ stdio: "inherit",
19481
+ cwd: opts.cwd,
19482
+ env: opts.env,
19483
+ shell: process.platform === "win32"
19484
+ });
19485
+ child.on("error", reject);
19486
+ child.on("close", (code) => resolve(code));
19487
+ });
19488
+ }
19477
19489
  function which(command) {
19478
19490
  const isWindows = process.platform === "win32";
19479
19491
  const exts = isWindows ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
@@ -22585,8 +22597,224 @@ function summarizeScopes(scope, limit = 6) {
22585
22597
  return `scope: ${all.length} granted, ${writes.length} of them write (${writes.slice(0, 3).join(", ") || "none"}${writes.length > 3 ? ", …" : ""})`;
22586
22598
  }
22587
22599
 
22600
+ // src/cli/update.ts
22601
+ import { existsSync, realpathSync } from "node:fs";
22602
+ import { fileURLToPath } from "node:url";
22603
+ import { dirname as dirname3, join as join18, resolve as resolve2, sep } from "node:path";
22604
+ var PACKAGE_NAME = "autorouter-mcp";
22605
+ function entryPath() {
22606
+ let raw;
22607
+ try {
22608
+ raw = fileURLToPath(import.meta.url);
22609
+ } catch {
22610
+ raw = process.argv[1] ?? "";
22611
+ }
22612
+ try {
22613
+ return realpathSync(raw);
22614
+ } catch {
22615
+ return raw;
22616
+ }
22617
+ }
22618
+ function packageRoot(from, exists) {
22619
+ let dir = dirname3(from);
22620
+ for (let i = 0;i < 12; i++) {
22621
+ if (exists(join18(dir, "package.json")))
22622
+ return dir;
22623
+ const parent = dirname3(dir);
22624
+ if (parent === dir)
22625
+ break;
22626
+ dir = parent;
22627
+ }
22628
+ return;
22629
+ }
22630
+ function managerFromLockfile(dir, exists) {
22631
+ if (exists(join18(dir, "bun.lock")) || exists(join18(dir, "bun.lockb")))
22632
+ return "bun";
22633
+ if (exists(join18(dir, "pnpm-lock.yaml")))
22634
+ return "pnpm";
22635
+ if (exists(join18(dir, "yarn.lock")))
22636
+ return "yarn";
22637
+ return "npm";
22638
+ }
22639
+ function detectInstall(entry, exists = existsSync) {
22640
+ const root = packageRoot(entry, exists);
22641
+ if (!root)
22642
+ return { kind: "unknown", dir: dirname3(entry) };
22643
+ const path = root.split(sep).join("/");
22644
+ if (exists(join18(root, ".git")))
22645
+ return { kind: "source", dir: root };
22646
+ const transient = path.includes("/_npx/") && "npx" || /\/dlx(-|\/)/.test(path) && "pnpm dlx" || path.includes("/.bun/install/cache/") && "bunx" || null;
22647
+ if (transient)
22648
+ return { kind: "transient", runner: transient, dir: root };
22649
+ const nodeModules = dirname3(root);
22650
+ const parent = dirname3(nodeModules);
22651
+ const globalManager = path.includes("/.bun/install/global/") ? "bun" : /\/pnpm\/global\/|\/pnpm\/[0-9]+\/node_modules\//.test(path) ? "pnpm" : /\/\.config\/yarn\/global\/|\/\.yarn\/global\//.test(path) ? "yarn" : null;
22652
+ if (globalManager)
22653
+ return { kind: "global", manager: globalManager, dir: root };
22654
+ if (nodeModules.split(sep).pop() === "node_modules") {
22655
+ if (exists(join18(parent, "package.json"))) {
22656
+ return {
22657
+ kind: "project",
22658
+ manager: managerFromLockfile(parent, exists),
22659
+ dir: root,
22660
+ projectDir: parent
22661
+ };
22662
+ }
22663
+ return { kind: "global", manager: "npm", dir: root };
22664
+ }
22665
+ return { kind: "unknown", dir: root };
22666
+ }
22667
+ function updateCommand(install, pkg, version = "latest") {
22668
+ const spec = `${pkg}@${version}`;
22669
+ if (install.kind === "global") {
22670
+ switch (install.manager) {
22671
+ case "bun":
22672
+ return ["bun", "add", "-g", spec];
22673
+ case "pnpm":
22674
+ return ["pnpm", "add", "-g", spec];
22675
+ case "yarn":
22676
+ return ["yarn", "global", "add", spec];
22677
+ default:
22678
+ return ["npm", "install", "-g", spec];
22679
+ }
22680
+ }
22681
+ if (install.kind === "project") {
22682
+ switch (install.manager) {
22683
+ case "bun":
22684
+ return ["bun", "add", spec];
22685
+ case "pnpm":
22686
+ return ["pnpm", "add", spec];
22687
+ case "yarn":
22688
+ return ["yarn", "add", spec];
22689
+ default:
22690
+ return ["npm", "install", spec];
22691
+ }
22692
+ }
22693
+ return null;
22694
+ }
22695
+ function compareVersions2(a, b) {
22696
+ const split = (v) => {
22697
+ const [core = "", pre] = v.replace(/^v/, "").split("-", 2);
22698
+ const parts = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
22699
+ return { parts, pre };
22700
+ };
22701
+ const x = split(a);
22702
+ const y = split(b);
22703
+ for (let i = 0;i < 3; i++) {
22704
+ const d = (x.parts[i] ?? 0) - (y.parts[i] ?? 0);
22705
+ if (d !== 0)
22706
+ return d < 0 ? -1 : 1;
22707
+ }
22708
+ if (x.pre && !y.pre)
22709
+ return -1;
22710
+ if (!x.pre && y.pre)
22711
+ return 1;
22712
+ if (x.pre && y.pre && x.pre !== y.pre)
22713
+ return x.pre < y.pre ? -1 : 1;
22714
+ return 0;
22715
+ }
22716
+ async function latestVersion(pkg, fetchFn = fetch) {
22717
+ const base = (process.env.npm_config_registry ?? process.env.NPM_CONFIG_REGISTRY ?? "https://registry.npmjs.org").replace(/\/+$/, "");
22718
+ try {
22719
+ const res = await fetchFn(`${base}/${encodeURIComponent(pkg)}/latest`, {
22720
+ headers: { accept: "application/json" },
22721
+ signal: AbortSignal.timeout(1e4)
22722
+ });
22723
+ if (!res.ok)
22724
+ return { error: `registry returned ${res.status}` };
22725
+ const body = await res.json();
22726
+ return body.version ? { version: body.version } : { error: "registry returned no version" };
22727
+ } catch (err) {
22728
+ return { error: err instanceof Error ? err.message : String(err) };
22729
+ }
22730
+ }
22731
+ function cannotUpdate(install, pkg) {
22732
+ if (install.kind === "transient") {
22733
+ return `This copy was unpacked by ${install.runner}, which fetches the package fresh on every run —
22734
+ ` + ` there is no install here to upgrade. You are already getting the latest each time.
22735
+ ` + ` To keep a copy that does not re-download: npm install -g ${pkg}`;
22736
+ }
22737
+ if (install.kind === "source") {
22738
+ return `This is a checkout running from source (${install.dir}), not a package install.
22739
+ ` + ` Update it with: git -C ${install.dir} pull && bun install && bun run build`;
22740
+ }
22741
+ return `Could not tell which package manager installed this copy (${install.dir}).
22742
+ ` + ` Upgrade it the way you installed it, e.g. npm install -g ${pkg}@latest`;
22743
+ }
22744
+ function describe2(install) {
22745
+ switch (install.kind) {
22746
+ case "global":
22747
+ return `${install.manager} global install at ${install.dir}`;
22748
+ case "project":
22749
+ return `${install.manager} dependency of ${install.projectDir}`;
22750
+ case "transient":
22751
+ return `${install.runner} temporary copy at ${install.dir}`;
22752
+ case "source":
22753
+ return `source checkout at ${install.dir}`;
22754
+ default:
22755
+ return `unrecognized install at ${install.dir}`;
22756
+ }
22757
+ }
22758
+ async function runUpdate(opts) {
22759
+ const entry = opts.entry ?? entryPath();
22760
+ const install = detectInstall(entry, opts.exists);
22761
+ const manifest = install.kind === "unknown" ? null : await readJson(join18(install.dir, "package.json"));
22762
+ const pkg = manifest?.name ?? PACKAGE_NAME;
22763
+ const latest = await latestVersion(pkg, opts.fetchFn);
22764
+ if ("error" in latest) {
22765
+ return { ok: false, message: `Could not reach the registry to check for updates: ${latest.error}` };
22766
+ }
22767
+ const behind = compareVersions2(opts.current, latest.version) < 0;
22768
+ const status = behind ? `autorouter ${opts.current} → ${latest.version} available` : `autorouter ${opts.current} is up to date (latest is ${latest.version})`;
22769
+ const command = updateCommand(install, pkg, latest.version);
22770
+ if (!command) {
22771
+ return { ok: !behind, message: `${status}
22772
+
22773
+ ${cannotUpdate(install, pkg)}` };
22774
+ }
22775
+ const printable = command.join(" ");
22776
+ if (opts.check) {
22777
+ return { ok: true, message: `${status}
22778
+ ${describe2(install)}
22779
+ Update with: ${printable}` };
22780
+ }
22781
+ if (!behind && !opts.force) {
22782
+ return { ok: true, message: `${status}
22783
+ ${describe2(install)}
22784
+ Re-install anyway with: --force` };
22785
+ }
22786
+ if (opts.dryRun) {
22787
+ return { ok: true, message: `${status}
22788
+ ${describe2(install)}
22789
+ Would run: ${printable}` };
22790
+ }
22791
+ console.log(`${status}
22792
+ ${describe2(install)}
22793
+ Running: ${printable}
22794
+ `);
22795
+ const code = await runStreaming(command[0], command.slice(1), {
22796
+ cwd: install.kind === "project" ? install.projectDir : undefined
22797
+ }).catch((err) => err);
22798
+ if (code instanceof Error) {
22799
+ return {
22800
+ ok: false,
22801
+ message: `Could not run ${command[0]}: ${code.message}
22802
+ ` + ` Run it yourself: ${printable}`
22803
+ };
22804
+ }
22805
+ if (code !== 0) {
22806
+ return { ok: false, message: `${command[0]} exited with code ${code}. Nothing was changed by autorouter.` };
22807
+ }
22808
+ return {
22809
+ ok: true,
22810
+ message: `
22811
+ Updated to ${latest.version}. Restart any harness with the router running to pick it up` + `
22812
+ (it is a long-lived stdio server, so an open session keeps the old build).`
22813
+ };
22814
+ }
22815
+
22588
22816
  // src/cli.ts
22589
- var VERSION2 = "0.2.5";
22817
+ var VERSION2 = "0.2.6";
22590
22818
  var USAGE = `autorouter — one search tool instead of every tool
22591
22819
 
22592
22820
  autorouter serve Run as an MCP server over stdio (default)
@@ -22596,6 +22824,9 @@ var USAGE = `autorouter — one search tool instead of every tool
22596
22824
  autorouter list [--kind K] List everything in the catalog
22597
22825
  autorouter reindex Rebuild the catalog now
22598
22826
  autorouter doctor Show what is reachable and what it saves
22827
+ autorouter update Upgrade via the package manager that
22828
+ installed this copy. --check to look
22829
+ without installing.
22599
22830
  autorouter login [server] Authorize an OAuth server (opens a browser,
22600
22831
  or prints a code on a headless box);
22601
22832
  with no argument, lists what needs one
@@ -22623,6 +22854,8 @@ Options
22623
22854
  --json machine-readable output
22624
22855
  --yes init/adopt: do not prompt
22625
22856
  --dry-run adopt: show what would move, change nothing
22857
+ update: print the upgrade command without running it
22858
+ --check update: report the available version, install nothing
22626
22859
  --force adopt: proceed even if a server is unreachable
22627
22860
  --keep S adopt: leave server S registered in the harness (comma-separated)
22628
22861
  --keep-skill S adopt: leave skill S loaded (comma-separated)
@@ -22678,6 +22911,17 @@ async function main(argv) {
22678
22911
  case "doctor":
22679
22912
  console.log(await runDoctor(process.cwd()));
22680
22913
  return 0;
22914
+ case "update":
22915
+ case "upgrade": {
22916
+ const result = await runUpdate({
22917
+ current: VERSION2,
22918
+ check: Boolean(flags.check),
22919
+ dryRun: Boolean(flags["dry-run"]),
22920
+ force: Boolean(flags.force)
22921
+ });
22922
+ console.log(result.message);
22923
+ return result.ok ? 0 : 1;
22924
+ }
22681
22925
  case "init":
22682
22926
  return await cmdInit(flags);
22683
22927
  case "login":
@@ -23082,7 +23326,7 @@ function parseArgs(argv) {
23082
23326
  command = `--${name}`;
23083
23327
  continue;
23084
23328
  }
23085
- const boolean = ["raw", "json", "yes", "dry-run", "force", "servers-only", "read-only", "all-scopes", "list-scopes", "device", "manual"].includes(name) && !(name === "json" && command === "add");
23329
+ const boolean = ["raw", "json", "yes", "dry-run", "force", "servers-only", "read-only", "all-scopes", "list-scopes", "device", "manual", "check"].includes(name) && !(name === "json" && command === "add");
23086
23330
  if (boolean) {
23087
23331
  flags[name] = true;
23088
23332
  } else if (inline !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autorouter-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "One search tool instead of every tool: an MCP capability router for Claude Code, Codex, Cursor and anything else that speaks MCP.",
5
5
  "mcpName": "io.github.Webb-Ventures/autorouter",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.Webb-Ventures/autorouter",
4
4
  "title": "autorouter",
5
5
  "description": "An MCP capability router: one search tool instead of every server's tool schema.",
6
- "version": "0.2.5",
6
+ "version": "0.2.6",
7
7
  "websiteUrl": "https://github.com/Webb-Ventures/autorouter",
8
8
  "repository": {
9
9
  "url": "https://github.com/Webb-Ventures/autorouter",
@@ -14,7 +14,7 @@
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "autorouter-mcp",
17
- "version": "0.2.5",
17
+ "version": "0.2.6",
18
18
  "transport": { "type": "stdio" },
19
19
  "packageArguments": [{ "type": "positional", "value": "serve" }],
20
20
  "environmentVariables": [