Skip to content
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <query>` | Search memories |
| `engram save <title> <msg>` | Save a memory |
Expand Down
2 changes: 1 addition & 1 deletion docs/AGENT-SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
44 changes: 29 additions & 15 deletions internal/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"

"github.com/Gentleman-Programming/engram/internal/mcp"
)

var (
Expand Down Expand Up @@ -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.
Expand Down
47 changes: 46 additions & 1 deletion internal/setup/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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],
Expand Down
8 changes: 4 additions & 4 deletions plugin/claude-code/skills/memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Loading