diff --git a/.muse-plugin/plugin.json b/.muse-plugin/plugin.json index 13bab52..2df19cf 100644 --- a/.muse-plugin/plugin.json +++ b/.muse-plugin/plugin.json @@ -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", diff --git a/hooks/recall-approve.js b/hooks/recall-approve.js index cf0eca7..53baaba 100644 --- a/hooks/recall-approve.js +++ b/hooks/recall-approve.js @@ -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', diff --git a/hooks/recall.js b/hooks/recall.js index 57771e7..03a6abb 100644 --- a/hooks/recall.js +++ b/hooks/recall.js @@ -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 diff --git a/mcp/proxy.js b/mcp/proxy.js index aa21a95..bd7ff20 100644 --- a/mcp/proxy.js +++ b/mcp/proxy.js @@ -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', @@ -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; diff --git a/package.json b/package.json index b8de3fe..189250d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/unit.mjs b/test/unit.mjs index 821841c..bb53b5e 100644 --- a/test/unit.mjs +++ b/test/unit.mjs @@ -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', @@ -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',