Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/mcp/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Set `MEMWAL_MCP_DEBUG=1` to enable verbose stderr logging.

## Default namespace

Set a default memory namespace once in your client config instead of having the agent pass `namespace` on every call. The package injects it into `memwal_remember`, `memwal_recall`, `memwal_analyze`, and `memwal_restore` calls that don't already carry one.
Set a default memory namespace once in your client config instead of having the agent pass `namespace` on every call. The package injects it into `memwal_remember`, `memwal_remember_bulk`, `memwal_recall`, `memwal_analyze`, and `memwal_restore` calls that don't already carry one.

Precedence, highest first:

Expand Down
6 changes: 3 additions & 3 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Enable verbose stderr logging with `MEMWAL_MCP_DEBUG=1`.
## Default Namespace

By default the MCP tool schemas expose an optional `namespace` argument and the
agent has to pass it on every `memwal_remember` / `memwal_recall` /
`memwal_analyze` call (and `memwal_restore` requires it). Set a default once in
your client config instead:
agent has to pass it on every `memwal_remember` / `memwal_remember_bulk` /
`memwal_recall` / `memwal_analyze` call (and `memwal_restore` requires it). Set
a default once in your client config instead:

```json
{
Expand Down
8 changes: 4 additions & 4 deletions packages/mcp/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export interface BridgeConfig {
}

/** Memory tools that take a `namespace` argument. `memwal_remember`,
* `memwal_recall`, and `memwal_analyze` treat it as optional; `memwal_restore`
* requires it (its upstream schema still lists `namespace` as required, so
* agents normally pass one — but a configured default is filled in if the
* agent calls it without). */
* `memwal_remember_bulk`, `memwal_recall`, and `memwal_analyze` treat it as
Comment thread
nikola0x0 marked this conversation as resolved.
* optional; `memwal_restore` requires it (its upstream schema still lists
* `namespace` as required, so agents normally pass one — but a configured
* default is filled in if the agent calls it without). */
const NAMESPACE_TOOLS = new Set([
"memwal_remember",
"memwal_remember_bulk",
Expand Down
123 changes: 123 additions & 0 deletions packages/mcp/test/default-namespace.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import assert from "node:assert/strict";
import test from "node:test";

import { applyDefaultNamespace } from "../dist/bridge.js";

test("applyDefaultNamespace injects configured namespace into memwal_remember_bulk", () => {
const msg = {
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: {
name: "memwal_remember_bulk",
arguments: {
facts: ["fact 1", "fact 2"],
},
},
};

const updated = applyDefaultNamespace(msg, "project-alpha");
Comment thread
nikola0x0 marked this conversation as resolved.
assert.equal(updated.params.arguments.namespace, "project-alpha");
assert.deepEqual(updated.params.arguments.facts, ["fact 1", "fact 2"]);
// The call site ignores the return value — in-place mutation is the real contract.
assert.equal(msg.params.arguments.namespace, "project-alpha");
});

test("applyDefaultNamespace respects explicit namespace on memwal_remember_bulk", () => {
const msg = {
jsonrpc: "2.0",
id: 2,
method: "tools/call",
params: {
name: "memwal_remember_bulk",
arguments: {
facts: ["fact 1"],
namespace: "explicit-scope",
},
},
};

const updated = applyDefaultNamespace(msg, "project-alpha");
assert.equal(updated.params.arguments.namespace, "explicit-scope");
});

test("applyDefaultNamespace injects into all namespace-aware tools", () => {
const tools = [
"memwal_remember",
"memwal_remember_bulk",
"memwal_recall",
"memwal_analyze",
"memwal_restore",
];

for (const toolName of tools) {
const msg = {
jsonrpc: "2.0",
id: 3,
method: "tools/call",
params: {
name: toolName,
arguments: {},
},
};

const updated = applyDefaultNamespace(msg, "shared-namespace");
assert.equal(
updated.params.arguments.namespace,
"shared-namespace",
`expected default namespace to be injected for ${toolName}`
);
}
});

test("applyDefaultNamespace does not touch unrelated tools or non-call RPC messages", () => {
const loginMsg = {
jsonrpc: "2.0",
id: 4,
method: "tools/call",
params: {
name: "memwal_login",
arguments: {},
},
};
const updatedLogin = applyDefaultNamespace(loginMsg, "test-ns");
assert.equal(updatedLogin.params.arguments.namespace, undefined);

const listMsg = {
jsonrpc: "2.0",
id: 5,
method: "tools/list",
params: { name: "memwal_remember_bulk", arguments: {} },
};
const updatedList = applyDefaultNamespace(listMsg, "test-ns");
assert.equal(updatedList.params.arguments.namespace, undefined);
});

test("applyDefaultNamespace is a no-op when no default is configured", () => {
const msg = {
jsonrpc: "2.0",
id: 6,
method: "tools/call",
params: { name: "memwal_remember_bulk", arguments: { facts: ["fact 1"] } },
};

applyDefaultNamespace(msg, undefined);
assert.equal(msg.params.arguments.namespace, undefined);
});

test("applyDefaultNamespace overrides a blank explicit namespace", () => {
for (const blank of ["", " "]) {
const msg = {
jsonrpc: "2.0",
id: 7,
method: "tools/call",
params: {
name: "memwal_remember_bulk",
arguments: { facts: ["fact 1"], namespace: blank },
},
};

applyDefaultNamespace(msg, "project-alpha");
assert.equal(msg.params.arguments.namespace, "project-alpha");
}
});
Comment thread
nikola0x0 marked this conversation as resolved.
Loading