From 3cd434f33fcc195b4f68f9cbb19876c901c76fca Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Sun, 7 Jun 2026 11:39:10 +0100 Subject: [PATCH 1/3] fix(mcp): correct tool-count drift and add count-consistency guard Correct stale 51-tool references to the registry truth of 53 across the standalone shim comments and the registry comment, derive the visible tool count from the registry, update README test-count stats to 1,423+, add the demo --serve README line, and add a vitest guard that fails CI if the registry count, README, or CLI help drift out of sync. --- README.md | 7 +++-- src/mcp/standalone.ts | 5 ++-- src/mcp/tools-registry.ts | 8 +++--- test/tool-count-consistency.test.ts | 43 +++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 test/tool-count-consistency.test.ts diff --git a/README.md b/README.md index 87e6b3f0d..30483919b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ 53 MCP tools 12 auto hooks 0 external DBs - 1,390+ tests passing + 1,423+ tests passing

@@ -93,6 +93,7 @@ npm install -g @agentmemory/agentmemory # once — bare `agentmemory` o # sudo npm install -g @agentmemory/agentmemory agentmemory # start the memory server on :3111 agentmemory demo # seed sample sessions + prove recall +agentmemory demo --serve # one command: boot server, run demo, tear down (no second terminal) agentmemory connect claude-code # wire MCP into your agent (also: copilot-cli, codex, cursor, gemini-cli, ...) npx skills add rohitg00/agentmemory -y # install 8 native skills so your agent knows when to use the tools ``` @@ -1141,7 +1142,7 @@ Full registry: [workers.iii.dev](https://workers.iii.dev). Every worker there co | Prometheus / Grafana | iii OTEL + health monitor | | Custom plugin systems | `iii worker add ` | -**174 source files · ~37,800 LOC · 1,390+ tests · 258 functions · 44 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself. +**174 source files · ~37,800 LOC · 1,423+ tests · 258 functions · 44 KV scopes** — all on three primitives. No `agentmemory plugin install`. The plugin system is iii itself. --- @@ -1465,7 +1466,7 @@ Full endpoint list: [`src/triggers/api.ts`](src/triggers/api.ts) ```bash npm run dev # Hot reload npm run build # Production build -npm test # 1,390+ tests +npm test # 1,423+ tests npm run test:integration # API tests (requires running services) ``` diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts index f4683747f..dd66ecb1d 100644 --- a/src/mcp/standalone.ts +++ b/src/mcp/standalone.ts @@ -51,8 +51,9 @@ function announceMode(handle: Handle): void { `[@agentmemory/mcp] proxying to agentmemory server at ${handle.baseUrl}\n`, ); } else { + const fullToolCount = getAllTools().length; process.stderr.write( - `[@agentmemory/mcp] no server reachable at ${displayAgentmemoryUrl()}; falling back to local InMemoryKV\n`, + `[@agentmemory/mcp] no server reachable at ${displayAgentmemoryUrl()}; running reduced LOCAL FALLBACK with ${IMPLEMENTED_TOOLS.size} of ${fullToolCount} tools. Start 'npx @agentmemory/agentmemory' (and point AGENTMEMORY_URL at it) to unlock all ${fullToolCount} tools.\n`, ); } } @@ -338,7 +339,7 @@ async function handleProxyGeneric( handle: ProxyHandle, ): Promise<{ content: Array<{ type: string; text: string }> }> { // Forward to the server's full MCP surface so non-Claude clients can - // reach all 51 tools (lessons, sentinels, slots, signals, graph, …) + // reach all 53 tools (lessons, sentinels, slots, signals, graph, …) // instead of being capped at the 7 IMPLEMENTED_TOOLS set baked into // this shim. The server validates arguments per tool. const result = (await handle.call("/agentmemory/mcp/call", { diff --git a/src/mcp/tools-registry.ts b/src/mcp/tools-registry.ts index c86d899bb..c4df3499c 100644 --- a/src/mcp/tools-registry.ts +++ b/src/mcp/tools-registry.ts @@ -925,7 +925,7 @@ export const V010_SLOTS_TOOLS: McpToolDef[] = [ }, ]; -const ESSENTIAL_TOOLS = new Set([ +export const ESSENTIAL_TOOLS = new Set([ "memory_save", "memory_recall", "memory_consolidate", @@ -950,9 +950,9 @@ export function getAllTools(): McpToolDef[] { } // default switched from "core" (8 essential tools) to "all" -// (full 51-tool surface). README and plugin manifests have always -// advertised 51 tools "in proxy mode"; the old default left OpenCode / -// Claude Code users seeing 8 with no indication the other 43 existed. +// (full 53-tool surface). README and plugin manifests have always +// advertised 53 tools "in proxy mode"; the old default left OpenCode / +// Claude Code users seeing 8 with no indication the other tools existed. // Users who want the lean essentials can still set AGENTMEMORY_TOOLS=core. export function getVisibleTools(): McpToolDef[] { const mode = process.env["AGENTMEMORY_TOOLS"] || "all"; diff --git a/test/tool-count-consistency.test.ts b/test/tool-count-consistency.test.ts new file mode 100644 index 000000000..19f0e3a5e --- /dev/null +++ b/test/tool-count-consistency.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +vi.mock("../src/logger.js", () => ({ + logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, +})); + +import { getAllTools, ESSENTIAL_TOOLS } from "../src/mcp/tools-registry.js"; + +const ROOT = join(import.meta.dirname, ".."); +const EXPECTED_TOOL_COUNT = 53; + +function readText(relativePath: string): string { + return readFileSync(join(ROOT, relativePath), "utf-8"); +} + +describe("Tool count consistency", () => { + it("registry exposes the expected number of tools", () => { + expect(getAllTools().length).toBe(EXPECTED_TOOL_COUNT); + }); + + it("cli help derives the tool counts from the registry", () => { + const cli = readText("src/cli.ts"); + expect(cli).toContain("const ALL_TOOLS_COUNT = getAllTools().length;"); + expect(cli).toContain( + "(default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials)", + ); + expect(cli).not.toMatch(/all\s*=\s*51 tools/); + }); + + it("core tool count derives from the registry", () => { + const coreCount = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length; + expect(coreCount).toBe(ESSENTIAL_TOOLS.size); + expect(coreCount).toBeGreaterThan(0); + }); + + it("README advertises the same tool count as the registry", () => { + const readme = readText("README.md"); + expect(readme).toContain(`${EXPECTED_TOOL_COUNT} MCP tools`); + expect(readme).not.toContain("51 MCP tools"); + }); +}); From 87964c3341f07cf4765e0a58ebfc00291eff786e Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Sun, 7 Jun 2026 11:39:16 +0100 Subject: [PATCH 2/3] fix(cli): relax engine version gate and add demo --serve one-shot Convert the hard process.exit on a PATH iii version mismatch into a non-fatal warning so a mismatched iii on PATH no longer blocks first run; agentmemory continues with its own pinned engine in ~/.agentmemory/bin and honors AGENTMEMORY_III_VERSION. Add a --serve flag to the demo subcommand that boots the server, waits for health, runs the seed and query demo, and tears the server down on exit, all in one invocation. Derive the --tools help counts from the registry. --- src/cli.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index acac3d971..c6087d891 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,6 +43,10 @@ import { isFirstRun, readPrefs, resetPrefs, writePrefs } from "./cli/preferences import { runOnboarding } from "./cli/onboarding.js"; import { setBootVerbose } from "./logger.js"; import { VERSION } from "./version.js"; +import { getAllTools, ESSENTIAL_TOOLS } from "./mcp/tools-registry.js"; + +const ALL_TOOLS_COUNT = getAllTools().length; +const CORE_TOOLS_COUNT = getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)).length; const __dirname = dirname(fileURLToPath(import.meta.url)); const args = process.argv.slice(2); @@ -136,7 +140,9 @@ Commands: --dry-run: show what each fix would do, don't execute remove Cleanly uninstall agentmemory (pidfile, state, .env, binaries). --force: skip confirmations · --keep-data: keep memory data - demo Seed sample sessions and show recall in action + demo [--serve] Seed sample sessions and show recall in action. + --serve boots the server, runs the demo, and stops it + in one command (no second terminal). upgrade Upgrade local deps + iii runtime (best effort) stop [--force] Stop the running iii-engine started by this CLI. --force bypasses the Docker-heuristic guard and signals @@ -152,7 +158,7 @@ Options: --help, -h Show this help --verbose, -v Show engine stderr, boot log, and diagnostic info --reset Wipe ~/.agentmemory/preferences.json and re-run onboarding - --tools all|core Tool visibility (default: all = 51 tools; core = 8 essentials) + --tools all|core Tool visibility (default: all = ${ALL_TOOLS_COUNT} tools; core = ${CORE_TOOLS_COUNT} essentials) --no-engine Skip auto-starting iii-engine --port Override REST port (default: 3111). Streams (N+1), viewer (N+2), and iii engine (N+46023) auto-derive from N so a @@ -1197,15 +1203,11 @@ async function main() { if (attachedBin) { const detected = iiiBinVersion(attachedBin); if (detected && detected !== IIPINNED_VERSION) { - p.log.error( - `Attached iii-engine appears to be v${detected} (from ${attachedBin}) ` + - `but agentmemory v${VERSION} hard-pins v${IIPINNED_VERSION}. ` + - `Engine API drift causes runtime failures (e.g. state::list-not-found on v0.13.0+). ` + - `Stop the running engine (\`agentmemory stop --force\`) and re-run \`agentmemory\` ` + - `to install the pinned engine into ~/.agentmemory/bin without touching ${attachedBin}. ` + - `Or set AGENTMEMORY_III_VERSION=${detected} to override at your own risk.`, + p.log.warn( + `iii on PATH is v${detected} (from ${attachedBin}) but agentmemory v${VERSION} pins v${IIPINNED_VERSION}. ` + + `agentmemory will use its own pinned engine in ~/.agentmemory/bin and leaves ${attachedBin} untouched. ` + + `If you want agentmemory to track a different engine, set AGENTMEMORY_III_VERSION=${detected}.`, ); - process.exit(1); } } adoptRunningEngine(); @@ -2100,19 +2102,78 @@ async function runInit() { p.outro(`Edit ${target} and you're set.`); } +async function startServerForDemo(): Promise<() => Promise> { + if (await isAgentmemoryReady()) { + return async () => {}; + } + + const startedEngine = !(await isEngineRunning()); + if (startedEngine) { + const ok = await startEngine(); + if (!ok) { + p.log.error("Could not start iii-engine for the demo."); + p.note(installInstructions().join("\n"), "Setup required"); + process.exit(1); + } + if (!(await waitForEngine(15000))) { + p.log.error("iii-engine did not become ready within 15s."); + process.exit(1); + } + } + + await import("./index.js"); + if (!(await waitForAgentmemoryReady(15000))) { + p.log.error("agentmemory worker did not become ready within 15s."); + process.exit(1); + } + + return async () => { + if (!startedEngine) return; + const port = getRestPort(); + const state = readEngineState(); + if (state?.kind === "docker") { + await stopDockerEngine(state.composeFile, port).catch(() => {}); + return; + } + const pids = new Set(findEnginePidsByPort(port)); + const pidfilePid = readEnginePidfile(); + if (pidfilePid) pids.add(pidfilePid); + for (const pid of pids) { + await signalAndWait(pid, "SIGTERM", 3000).catch(() => {}); + } + clearEnginePidfile(); + clearEngineState(); + clearWorkerPidfile(); + }; +} + async function runDemo() { const port = getRestPort(); const base = `http://localhost:${port}`; p.intro("agentmemory demo"); - if (!(await isAgentmemoryReady())) { + const serve = args.includes("--serve"); + let teardown: () => Promise = async () => {}; + + if (serve) { + teardown = await startServerForDemo(); + } else if (!(await isAgentmemoryReady())) { p.log.error( `agentmemory worker not reachable on port ${port} (livez probe failed). Something may be on the port but it isn't serving /agentmemory/*.`, ); p.log.info("Start it with: npx @agentmemory/agentmemory"); + p.log.info("Or run a one-command demo with: npx @agentmemory/agentmemory demo --serve"); process.exit(1); } + try { + await runDemoBody(base); + } finally { + await teardown(); + } +} + +async function runDemoBody(base: string) { const demoProject = "/tmp/agentmemory-demo"; const sessions = buildDemoSessions(); From 7189096ed4d6f134c38a98dddcf64f46f51a3199 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Sun, 7 Jun 2026 12:09:26 +0100 Subject: [PATCH 3/3] fix(cli): exit cleanly after demo --serve so in-process worker stops --- src/cli.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index c6087d891..ae5be0035 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2171,6 +2171,10 @@ async function runDemo() { } finally { await teardown(); } + + if (serve) { + process.exit(0); + } } async function runDemoBody(base: string) {