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 .muse-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"name": "supermemory",
"displayName": "Supermemory",
"version": "0.2.0",
"version": "0.3.1",
"description": "Persistent memory across Muse Code sessions using Supermemory.",
"compat": {
"source": "native",
Expand Down
6 changes: 6 additions & 0 deletions hooks/recall-approve.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ const { readStdin, writeOutput } = require('./lib/stdin');

const READ_ONLY_TOOLS = new Set([
'search_memory',
'get_profile',
'list_documents',
'get_document',
'list_memories',
'list_spaces',
'who_am_i',
'listSpaces',
'listMemories',
'listDocuments',
Expand Down
4 changes: 2 additions & 2 deletions hooks/recall.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ No stored memories matched this prompt for this project.

Deeper history may still exist. Search it with the supermemory search_memory tool.

- Scope to this project with containerTag: "${containerTag}"
- Omit containerTag to search the account's active/shared space instead.
- Calls without a containerTag search this project ("${containerTag}").
- To search another space, pass its containerTag (find it with list_spaces).
- Read-only supermemory calls are auto-approved; they never prompt the user.

Worth a call when the user refers to past decisions, earlier sessions, or says
Expand Down
11 changes: 10 additions & 1 deletion mcp/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ const REQUEST_TIMEOUT_MS = 30000;

const REPO_SCOPED_TOOLS = new Set([
'search_memory',
'get_profile',
'add_memory',
'forget_memory',
'list_documents',
'listDocuments',
'get_document',
'getDocument',
'list_memories',
'listMemories',
'memory-graph',
'fetch-graph-data',
Expand Down Expand Up @@ -42,7 +48,10 @@ function injectRepoContainerTag(message, containerTag) {
}
}
if (!args || typeof args !== 'object' || Array.isArray(args)) return;
if (typeof args.containerTag === 'string' && args.containerTag.trim()) return;
// The hosted server reads `namespace ?? containerTag`; either one means the caller chose a space.
for (const key of ['namespace', 'containerTag']) {
if (typeof args[key] === 'string' && args[key].trim()) return;
}

args.containerTag = containerTag;
params.arguments = encoded ? JSON.stringify(args) : args;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "muse-supermemory",
"version": "0.2.0",
"version": "0.3.1",
"description": "Muse Code plugin by Supermemory AI",
"private": true,
"type": "commonjs",
Expand Down
37 changes: 37 additions & 0 deletions test/unit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ describe('hooks', () => {
assert.equal(out.hookSpecificOutput.permissionDecision, 'allow');
});

test('PreToolUse allows the snake_case read tools', () => {
for (const tool of ['get_profile', 'list_documents', 'get_document', 'list_memories', 'list_spaces', 'who_am_i']) {
const result = spawnSync('node', [join(root, 'hooks/recall-approve.js')], {
encoding: 'utf8',
input: JSON.stringify({ tool_name: `mcp__supermemory__${tool}`, tool_input: {} }),
});
assert.equal(result.status, 0, result.stderr);
const out = JSON.parse(result.stdout.trim().split('\n').at(-1));
assert.equal(out.hookSpecificOutput?.permissionDecision, 'allow', tool);
}
});

test('PreToolUse does not auto-allow writes', () => {
const result = spawnSync('node', [join(root, 'hooks/recall-approve.js')], {
encoding: 'utf8',
Expand All @@ -129,6 +141,31 @@ describe('mcp proxy', () => {
assert.equal(message.params.arguments.containerTag, 'repo_example__abc');
});

test('defaults snake_case read and forget tools to the repo container', () => {
for (const name of ['list_documents', 'list_memories', 'get_document', 'get_profile', 'forget_memory']) {
const message = { method: 'tools/call', params: { name, arguments: {} } };
injectRepoContainerTag(message, 'repo_example__abc');
assert.equal(message.params.arguments.containerTag, 'repo_example__abc', name);
}
});

test('leaves account-level tools alone', () => {
for (const name of ['list_spaces', 'who_am_i', 'set-active-tag', 'select-space']) {
const message = { method: 'tools/call', params: { name, arguments: {} } };
injectRepoContainerTag(message, 'repo_example__abc');
assert.deepEqual(message.params.arguments, {}, name);
}
});

test('does not override an explicit namespace', () => {
const message = {
method: 'tools/call',
params: { name: 'list_documents', arguments: { namespace: 'team' } },
};
injectRepoContainerTag(message, 'repo_example__abc');
assert.deepEqual(message.params.arguments, { namespace: 'team' });
});

test('does not overwrite an explicit containerTag', () => {
const message = {
method: 'tools/call',
Expand Down
Loading