diff --git a/README.md b/README.md index c26d4d13..506ba206 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ For authenticated mode, upgrade flow, dashboard behavior, reason codes, and full |---------|-------------| | `engram setup [agent]` | Install agent integration | | `engram serve [port]` | Start HTTP API (default: 7437) | -| `engram mcp` | Start MCP server (stdio) | +| `engram mcp [--tools=PROFILE]` | Start MCP server (stdio transport) | | `engram tui` | Launch terminal UI | | `engram search ` | Search memories | | `engram save <msg>` | Save a memory | diff --git a/docs/AGENT-SETUP.md b/docs/AGENT-SETUP.md index 4cb81540..5543bbf9 100644 --- a/docs/AGENT-SETUP.md +++ b/docs/AGENT-SETUP.md @@ -97,7 +97,7 @@ That's it. The plugin registers the MCP server, hooks, and Memory Protocol skill engram setup claude-code ``` -During setup, you'll be asked whether to add engram tools to `~/.claude/settings.json` permissions allowlist — this prevents Claude Code from prompting for confirmation on every memory operation. +During setup, you'll be asked whether to add engram's agent-profile MCP tools to `~/.claude/settings.json` `permissions.allow`. The setup writes entries for both the durable user-level MCP server id (`mcp__engram__...`) and the plugin-scoped server id used by older Claude Code plugin installs, so re-running setup repairs stale or incomplete allowlists without adding startup delay. **Option C: Bare MCP** — all 17 tools by default, no session management: diff --git a/internal/setup/setup.go b/internal/setup/setup.go index d0a8453f..ae0ed179 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -20,7 +20,10 @@ import ( "os/exec" "path/filepath" "runtime" + "sort" "strings" + + "github.com/Gentleman-Programming/engram/internal/mcp" ) var ( @@ -73,21 +76,32 @@ const claudeCodeMarketplace = "Gentleman-Programming/engram" const openCodeSubagentStatuslinePlugin = "opencode-subagent-statusline" -// claudeCodeMCPTools are the MCP tool names registered by the engram plugin -// in Claude Code. Adding these to ~/.claude/settings.json permissions.allow -// prevents Claude Code from prompting for confirmation on every tool call. -var claudeCodeMCPTools = []string{ - "mcp__plugin_engram_engram__mem_capture_passive", - "mcp__plugin_engram_engram__mem_context", - "mcp__plugin_engram_engram__mem_get_observation", - "mcp__plugin_engram_engram__mem_save", - "mcp__plugin_engram_engram__mem_save_prompt", - "mcp__plugin_engram_engram__mem_search", - "mcp__plugin_engram_engram__mem_session_end", - "mcp__plugin_engram_engram__mem_session_start", - "mcp__plugin_engram_engram__mem_session_summary", - "mcp__plugin_engram_engram__mem_suggest_topic_key", - "mcp__plugin_engram_engram__mem_update", +// claudeCodeMCPTools are the MCP tool permission names for the agent profile +// registered by the engram Claude Code plugin and durable user-level MCP config. +// Adding these to ~/.claude/settings.json permissions.allow prevents Claude Code +// from prompting for confirmation on every tool call. +var claudeCodeMCPTools = claudeCodePermissionTools(mcp.ResolveTools("agent")) + +func claudeCodePermissionTools(agentTools map[string]bool) []string { + toolNames := make([]string, 0, len(agentTools)) + for toolName, enabled := range agentTools { + if enabled { + toolNames = append(toolNames, toolName) + } + } + sort.Strings(toolNames) + + // Claude Code's bare/user-level MCP config uses the server id "engram". + // Older plugin installs have been observed with a plugin-scoped server id; + // allowlisting both forms is harmless and keeps re-running setup idempotent. + prefixes := []string{"mcp__engram__", "mcp__plugin_engram_engram__"} + permissions := make([]string, 0, len(toolNames)*len(prefixes)) + for _, prefix := range prefixes { + for _, toolName := range toolNames { + permissions = append(permissions, prefix+toolName) + } + } + return permissions } // codexEngramBlock is the canonical Codex TOML MCP block. diff --git a/internal/setup/setup_test.go b/internal/setup/setup_test.go index d5c8bc43..80ebd770 100644 --- a/internal/setup/setup_test.go +++ b/internal/setup/setup_test.go @@ -5,6 +5,8 @@ import ( "errors" "os" "path/filepath" + "reflect" + "slices" "strings" "testing" ) @@ -1735,6 +1737,49 @@ func TestAdditionalHelperBranches(t *testing.T) { }) } +func TestClaudeCodePermissionTools(t *testing.T) { + tools := claudeCodePermissionTools(map[string]bool{ + "mem_search": true, + "mem_current_project": true, + "mem_stats": false, + }) + + want := []string{ + "mcp__engram__mem_current_project", + "mcp__engram__mem_search", + "mcp__plugin_engram_engram__mem_current_project", + "mcp__plugin_engram_engram__mem_search", + } + if !reflect.DeepEqual(tools, want) { + t.Fatalf("unexpected permissions:\nwant %#v\n got %#v", want, tools) + } + + for _, tool := range []string{ + "mcp__engram__mem_current_project", + "mcp__engram__mem_judge", + "mcp__plugin_engram_engram__mem_current_project", + "mcp__plugin_engram_engram__mem_judge", + } { + if !slices.Contains(claudeCodeMCPTools, tool) { + t.Fatalf("claudeCodeMCPTools missing current agent permission %q", tool) + } + } +} + +func TestClaudeCodeMemorySkillDoesNotHardcodePluginScopedToolSearch(t *testing.T) { + data, err := os.ReadFile(filepath.Join("..", "..", "plugin", "claude-code", "skills", "memory", "SKILL.md")) + if err != nil { + t.Fatalf("read memory skill: %v", err) + } + text := string(data) + if strings.Contains(text, "select:mcp__plugin_engram_engram__") { + t.Fatalf("memory skill must not hardcode plugin-scoped ToolSearch names") + } + if !strings.Contains(text, "engram setup claude-code") { + t.Fatalf("memory skill fallback should direct users to repair Claude Code setup") + } +} + func TestAddClaudeCodeAllowlist(t *testing.T) { t.Run("creates file from scratch", func(t *testing.T) { resetSetupSeams(t) @@ -1869,7 +1914,7 @@ func TestAddClaudeCodeAllowlist(t *testing.T) { t.Fatalf("mkdir: %v", err) } - // Include 3 of 11 tools + // Include 3 tools and verify only the missing permissions are appended. partial := []string{ claudeCodeMCPTools[0], claudeCodeMCPTools[3], diff --git a/plugin/claude-code/skills/memory/SKILL.md b/plugin/claude-code/skills/memory/SKILL.md index cde2b97d..c281c13b 100644 --- a/plugin/claude-code/skills/memory/SKILL.md +++ b/plugin/claude-code/skills/memory/SKILL.md @@ -17,10 +17,10 @@ They are available immediately — no manual ToolSearch needed. - `mem_get_observation`, `mem_suggest_topic_key`, `mem_update` - `mem_session_start`, `mem_session_end`, `mem_save_prompt` -**Fallback**: If tools are unexpectedly unavailable, trigger ToolSearch manually: -``` -select:mcp__plugin_engram_engram__mem_save,mcp__plugin_engram_engram__mem_search,mcp__plugin_engram_engram__mem_context,mcp__plugin_engram_engram__mem_session_summary,mcp__plugin_engram_engram__mem_get_observation,mcp__plugin_engram_engram__mem_suggest_topic_key,mcp__plugin_engram_engram__mem_update,mcp__plugin_engram_engram__mem_session_start,mcp__plugin_engram_engram__mem_session_end,mcp__plugin_engram_engram__mem_save_prompt -``` +**Fallback**: If tools are unexpectedly unavailable, run `engram setup claude-code` +again and restart Claude Code. Setup repairs the durable MCP config and +permissions allowlist for both current (`mcp__engram__...`) and older +plugin-scoped (`mcp__plugin_engram_engram__...`) server ids. Admin tools (deferred — use ToolSearch only if needed): - `mem_stats`, `mem_delete`, `mem_timeline`, `mem_capture_passive`