From c597767a363b6e8d0100b17c4a6c565550d6ad99 Mon Sep 17 00:00:00 2001 From: vreshch Date: Sat, 20 Jun 2026 22:32:18 +0200 Subject: [PATCH] chore: rename to @agentage/server-memory + add server config params (vault, vaultsDir, readonly, scope) --- .github/workflows/pr-validation.yml | 26 ++------------ .github/workflows/publish.yml | 2 +- README.md | 8 ++--- package-lock.json | 14 ++++---- package.json | 12 ++++--- src/bin/{mcp-memory.ts => server-memory.ts} | 12 +++---- src/index.ts | 2 +- src/server/create-memory-server.ts | 7 ++-- src/server/local-server.ts | 39 +++++++++++++++++---- src/server/register-tools.ts | 13 ++++++- test/e2e.test.ts | 11 +++--- 11 files changed, 82 insertions(+), 64 deletions(-) rename src/bin/{mcp-memory.ts => server-memory.ts} (57%) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index e8078a8..da4fa04 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -19,51 +19,29 @@ jobs: timeout-minutes: 10 steps: - - name: Checkout mcp-memory - uses: actions/checkout@v7 - with: - path: mcp-memory - - # @agentage/memory-core is a local file: dependency until it publishes to npm, - # so build it as a sibling here (memory-core is a public repo, default token reads it). - - name: Checkout memory-core (engine dependency) - uses: actions/checkout@v7 - with: - repository: agentage/memory-core - ref: master - path: memory-core + - name: Checkout code + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - cache-dependency-path: mcp-memory/package-lock.json - - - name: Build engine dependency - working-directory: memory-core - run: npm ci && npm run build - name: Install dependencies - working-directory: mcp-memory run: npm ci - name: Type check - working-directory: mcp-memory run: npm run type-check - name: Lint - working-directory: mcp-memory run: npm run lint - name: Format check - working-directory: mcp-memory run: npm run format:check - name: Test with coverage - working-directory: mcp-memory run: npm run test:coverage - name: Build - working-directory: mcp-memory run: npm run build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 83f444f..aeef505 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,6 @@ name: Publish Package -# Publishes @agentage/mcp-memory to npm. Manual (workflow_dispatch) or on a release +# Publishes @agentage/server-memory to npm. Manual (workflow_dispatch) or on a release # commit to master; never on an ordinary push. Requires an NPM_TOKEN repo secret. # (The first publish was bootstrapped manually; --provenance is safe now the pkg exists.) diff --git a/README.md b/README.md index 77d174f..8069903 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @agentage/mcp-memory +# @agentage/server-memory The **MCP server** for agentage Memory: exposes your local vaults (`~/.agentage/vaults.json`, read through [`@agentage/memory-core`](https://github.com/agentage/memory-core)) @@ -14,10 +14,10 @@ All memory logic (backends, git, search, routing) lives in `@agentage/memory-cor ```bash # one-time, offline: scaffold ~/.agentage + a starter vault # (memory-core's `init`, also surfaced by the agentage CLI) -npx @agentage/mcp-memory # serves ~/.agentage/vaults.json over stdio +npx @agentage/server-memory # serves ~/.agentage/vaults.json over stdio ``` -Point any stdio MCP client (Windsurf, Zed, Claude Desktop) at `npx @agentage/mcp-memory`. +Point any stdio MCP client (Windsurf, Zed, Claude Desktop) at `npx @agentage/server-memory`. ## Reused by the CLI daemon @@ -25,7 +25,7 @@ The server builder is transport-agnostic, so the agentage CLI reuses the exact s pieces and only swaps the transport: ```ts -import { createMemoryServer, loadLocalServer } from '@agentage/mcp-memory'; +import { createMemoryServer, loadLocalServer } from '@agentage/server-memory'; // stdio bin: await (await loadLocalServer()).connect(new StdioServerTransport()); // CLI daemon: const server = createMemoryServer(registry, { scope: 'local' }); diff --git a/package-lock.json b/package-lock.json index 2460cbc..2564423 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { - "name": "@agentage/mcp-memory", + "name": "@agentage/server-memory", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@agentage/mcp-memory", + "name": "@agentage/server-memory", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@agentage/memory-core": "^0.0.1", + "@agentage/memory-core": "^0.1.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.4.3" }, "bin": { - "agentage-mcp-memory": "dist/bin/mcp-memory.js" + "agentage-server-memory": "dist/bin/server-memory.js" }, "devDependencies": { "@types/node": "^22.10.0", @@ -34,9 +34,9 @@ } }, "node_modules/@agentage/memory-core": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@agentage/memory-core/-/memory-core-0.0.1.tgz", - "integrity": "sha512-2siRlh4sscOYmCLlEgug/NypyClAXBhz+vR2MhP5CVN96vxFqgiedcYDYcioZ+WONH8WQ6p2yP2IBCMUbhxlJw==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@agentage/memory-core/-/memory-core-0.1.0.tgz", + "integrity": "sha512-ontC1Zs1YRgmGtrBuZ9qIB5VYPabYdiJhr/FSjsFPvnT6Gn3jaLZJP/QIdJzNlB8upNydByiuP7BfUkjRDc0VA==", "license": "UNLICENSED", "dependencies": { "yaml": "^2.7.0", diff --git a/package.json b/package.json index 5e52573..57abeda 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,13 @@ { - "name": "@agentage/mcp-memory", + "name": "@agentage/server-memory", "version": "0.0.1", - "description": "The MCP server for agentage Memory: exposes @agentage/memory-core vaults as the frozen 6 memory__* tools over stdio. The open, cross-vendor counterpart to @modelcontextprotocol/server-memory.", + "description": "The agentage Memory MCP server: exposes your local vaults as the frozen 6 memory__* tools over stdio. The open, cross-vendor counterpart to @modelcontextprotocol/server-memory.", "type": "module", "license": "UNLICENSED", + "repository": { + "type": "git", + "url": "git+https://github.com/agentage/server-memory.git" + }, "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { @@ -13,7 +17,7 @@ "access": "public" }, "bin": { - "agentage-mcp-memory": "dist/bin/mcp-memory.js" + "agentage-server-memory": "dist/bin/server-memory.js" }, "files": [ "dist" @@ -37,7 +41,7 @@ "prepublishOnly": "npm run verify" }, "dependencies": { - "@agentage/memory-core": "^0.0.1", + "@agentage/memory-core": "^0.1.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.4.3" }, diff --git a/src/bin/mcp-memory.ts b/src/bin/server-memory.ts similarity index 57% rename from src/bin/mcp-memory.ts rename to src/bin/server-memory.ts index 41e1e34..fe1a167 100644 --- a/src/bin/mcp-memory.ts +++ b/src/bin/server-memory.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node -// @agentage/mcp-memory - the stdio MCP keystone. `npx @agentage/mcp-memory` exposes -// the user's local vaults (~/.agentage/vaults.json, read via @agentage/memory-core) -// as the 6 memory__* tools over stdio, for stdio-only clients (Windsurf, Zed) and as -// the published npm artifact. Zero memory logic - it binds the memory-core server to -// a StdioServerTransport. +// @agentage/server-memory - the stdio MCP keystone. `npx @agentage/server-memory` +// exposes the user's local vaults (~/.agentage/vaults.json, read via +// @agentage/memory-core) as the 6 memory__* tools over stdio, for stdio-only clients +// (Windsurf, Zed) and as the published npm artifact. Zero memory logic - it binds the +// memory-core server to a StdioServerTransport. // // stdout is the JSON-RPC wire; all diagnostics MUST go to stderr. @@ -18,6 +18,6 @@ const main = async (): Promise => { main().catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err); - process.stderr.write(`[mcp-memory] fatal: ${message}\n`); + process.stderr.write(`[server-memory] fatal: ${message}\n`); process.exit(1); }); diff --git a/src/index.ts b/src/index.ts index 5addb46..f68d550 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -// @agentage/mcp-memory public API - the MCP layer over @agentage/memory-core. +// @agentage/server-memory public API - the MCP layer over @agentage/memory-core. export { MEMORY_TOOLS, type MemoryToolDef } from './server/memory-tools.schema.js'; export { createMemoryServer, diff --git a/src/server/create-memory-server.ts b/src/server/create-memory-server.ts index 7bfe298..683b176 100644 --- a/src/server/create-memory-server.ts +++ b/src/server/create-memory-server.ts @@ -9,6 +9,8 @@ export const SERVER_VERSION = '0.0.1'; export interface CreateServerOptions { scope: McpScope; // which vaults to surface: only those whose mcp includes this scope version?: string; + readOnly?: boolean; // expose only search/read/list (hide write/edit/delete) + only?: string; // surface just this one vault id } const instructionsFor = (vaultIds: string[]): string => { @@ -27,7 +29,8 @@ const instructionsFor = (vaultIds: string[]): string => { // vaults (those whose mcp scope includes opts.scope). Transport-agnostic: connect // it to a stdio or Streamable-HTTP transport. The vault router federates per-call. export const createMemoryServer = (reg: VaultRegistry, opts: CreateServerOptions): McpServer => { - const surfaced = reg.surfaced(opts.scope); + const scoped = reg.surfaced(opts.scope); + const surfaced = opts.only ? scoped.filter((h) => h.id === opts.only) : scoped; const fallbackDefault = reg.default(); const defaultHandle = surfaced.find((h) => h.id === fallbackDefault?.id) ?? surfaced[0]; const router = createRouter(surfaced, defaultHandle); @@ -40,6 +43,6 @@ export const createMemoryServer = (reg: VaultRegistry, opts: CreateServerOptions }, { capabilities: { tools: {} }, instructions: instructionsFor(surfaced.map((h) => h.id)) } ); - registerTools(server, router); + registerTools(server, router, { readOnly: opts.readOnly }); return server; }; diff --git a/src/server/local-server.ts b/src/server/local-server.ts index 227307b..64b57c4 100644 --- a/src/server/local-server.ts +++ b/src/server/local-server.ts @@ -1,12 +1,37 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { createRegistry, loadConfig } from '@agentage/memory-core'; +import { + createRegistry, + loadConfig, + type McpScope, + type VaultsConfig, +} from '@agentage/memory-core'; import { createMemoryServer } from './create-memory-server.js'; -// The whole local stack in one call: read vaults.json -> registry (memory-core) -> -// a server exposing the local-scoped vaults via the 6 tools. The transport (stdio, -// HTTP) is the caller's choice. Used by the stdio bin and the daemon. -export const loadLocalServer = async (opts: { configDir?: string } = {}): Promise => { - const config = await loadConfig({ configDir: opts.configDir }); +export interface LocalServerOptions { + configDir?: string; +} + +const truthy = (v: string | undefined): boolean => v === '1' || v === 'true' || v === 'yes'; + +// Resolve config from the environment first (so `npx` works with no file): +// AGENTAGE_VAULTS_DIR -> autodiscover every subfolder of that dir as a vault; +// otherwise -> read ~/.agentage/vaults.json (or the zero-config default). +const resolveConfig = async (configDir?: string): Promise => { + const vaultsDir = process.env.AGENTAGE_VAULTS_DIR; + if (vaultsDir) return { version: 1, vaultsDir, autodiscover: true, autoInit: true }; + return loadConfig({ configDir }); +}; + +// The whole local stack in one call: resolve config -> registry (memory-core) -> an +// MCP server over the surfaced vaults. Honors env knobs: AGENTAGE_VAULTS_DIR, +// AGENTAGE_VAULT (one vault), AGENTAGE_SCOPE (local|remote), AGENTAGE_READONLY. +export const loadLocalServer = async (opts: LocalServerOptions = {}): Promise => { + const config = await resolveConfig(opts.configDir); const registry = await createRegistry(config); - return createMemoryServer(registry, { scope: 'local' }); + const scope = (process.env.AGENTAGE_SCOPE as McpScope) || 'local'; + return createMemoryServer(registry, { + scope, + readOnly: truthy(process.env.AGENTAGE_READONLY), + only: process.env.AGENTAGE_VAULT || undefined, + }); }; diff --git a/src/server/register-tools.ts b/src/server/register-tools.ts index 99e68c9..53a87a9 100644 --- a/src/server/register-tools.ts +++ b/src/server/register-tools.ts @@ -54,9 +54,18 @@ const guard = async (fn: () => Promise): Promise } }; +export interface RegisterOptions { + readOnly?: boolean; // skip the mutating tools (write/edit/delete) +} + // Register the frozen 6-tool surface onto the federated router. write/edit return // {path,updated} (the commit SHA is internal, stripped); errors surface as isError. -export const registerTools = (server: McpServer, router: Router): void => { +// With readOnly, only search/read/list are registered. +export const registerTools = ( + server: McpServer, + router: Router, + opts: RegisterOptions = {} +): void => { server.registerTool('memory__search', toolConfig('memory__search'), (args) => guard(async () => { const query = args as unknown as SearchQuery; @@ -81,6 +90,8 @@ export const registerTools = (server: McpServer, router: Router): void => { }) ); + if (opts.readOnly) return; + server.registerTool('memory__write', toolConfig('memory__write'), (args) => guard(async () => { const input = args as unknown as WriteInput; diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 2ecc3d8..0e304bd 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -10,9 +10,9 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { call } from './fixtures/mcp.js'; // The whole point: it works out of the box. `init` scaffolds ~/.agentage + a vault, -// then `npx @agentage/mcp-memory` (here: the built bin) serves it - nothing else. +// then `npx @agentage/server-memory` (here: the built bin) serves it - nothing else. const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); -const bin = join(repoRoot, 'dist/bin/mcp-memory.js'); +const bin = join(repoRoot, 'dist/bin/server-memory.js'); const tmps: string[] = []; const mk = (p: string) => { const d = mkdtempSync(join(tmpdir(), p)); @@ -22,11 +22,8 @@ const mk = (p: string) => { describe('e2e: init, then the stdio server just works', () => { beforeAll(() => { - // build memory-core (the file: dep) + this package so the spawned bin can run. - execFileSync('npm', ['run', 'build'], { - cwd: join(repoRoot, '../memory-core'), - stdio: 'ignore', - }); + // @agentage/memory-core resolves from npm (a normal dependency); just build this + // package so the spawned bin exists. execFileSync('npm', ['run', 'build'], { cwd: repoRoot, stdio: 'ignore' }); }, 60_000);