Skip to content

fix(mcp): audit fixes — stale instructions, validation, safety gaps#129

Merged
wcatz merged 2 commits intomainfrom
fix/mcp-audit-stale-instructions
Mar 25, 2026
Merged

fix(mcp): audit fixes — stale instructions, validation, safety gaps#129
wcatz merged 2 commits intomainfrom
fix/mcp-audit-stale-instructions

Conversation

@wcatz
Copy link
Copy Markdown
Owner

@wcatz wcatz commented Mar 25, 2026

Summary

  • MCP instructions no longer tell Claude to call tools at session start (hook injects context)
  • ghost_project_context and ghost_list_projects descriptions updated
  • buildProjectContext now includes _global memories
  • ghost_save_global gets category validation (was missing)
  • All Limit params capped at 100
  • MCP server version from build, not hardcoded 0.1.0
  • Hook: symlink resolution, UTF-8 safe truncation, updated fallback message
  • Tests updated to match new behavior

Test plan

  • go test ./... passes
  • go vet ./... clean
  • 5 parallel audit agents verified all fixes

Summary by CodeRabbit

  • New Features

    • Project context now includes a "Global" section with preferences that apply to all projects.
    • Added a 100-result limit to search and list operations for improved performance.
  • Bug Fixes

    • Fixed memory content truncation to properly handle UTF-8 characters without breaking text boundaries.
  • Documentation

    • Clarified that the SessionStart hook automatically injects project memories and global preferences into Claude's context.

wcatz added 2 commits March 25, 2026 13:08
…y gaps

- Update MCP instructions: stop telling Claude to call tools at session
  start (hook injects context automatically)
- Add global memories to buildProjectContext
- Add category validation to ghost_save_global
- Cap Limit parameters at 100
- Pass version to MCP server (was hardcoded 0.1.0)
- Fix hook fallback, add symlink resolution, UTF-8 safe truncation
- Fix stale test assertions
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

The PR threads a version parameter through the MCP server initialization, updates the SessionStart hook to resolve symlinks and output cleaner messages, implements UTF-8-safe memory truncation, adds input validation for memory categories and tool pagination limits, and refactors tool descriptions and context building to reflect that SessionStart now injects project context (including global memories) directly.

Changes

Cohort / File(s) Summary
Documentation Update
README.md
Updated SessionStart hook description to explicitly state it injects project memories and global preferences into Claude's context at startup, replacing generic reminder language.
SessionStart Hook Implementation
internal/mcpinit/hook.go, internal/mcpinit/init_test.go
Hook now resolves cwd symlinks via filepath.EvalSymlinks, implements UTF-8-safe truncation for memory content via new truncateUTF8 helper, and emits simplified startup message. Updated test assertions to verify new output.
MCP Server Core Changes
cmd/ghost/main.go, internal/mcpserver/mcpserver.go, internal/mcpserver/mcpserver_test.go
New() constructor accepts additional version parameter and sets MCP Version field dynamically. Added upper bound (Limit capped at 100) to tool pagination. Strengthened validation in memory-save tools to reject disallowed categories. Enhanced buildProjectContext to append global (_global project) memories. Updated tool descriptions and workflow instructions to reflect SessionStart hook improvements.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A version flows through the server's veins,
UTF-8 boundaries now held in reins,
Symlinks unfold where the context begins,
Global memories fold gently within,
SessionStart whispers the simplest refrain. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(mcp): audit fixes — stale instructions, validation, safety gaps' accurately reflects the main changes: removing stale MCP instructions, adding validation, and addressing safety gaps across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mcp-audit-stale-instructions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/mcpinit/hook.go (1)

34-43: ⚠️ Potential issue | 🟠 Major

Don't canonicalize only on the read path.

Line 39 resolves cwd, but internal/memory/store.go, Lines 154-171, still persist projects.path exactly as provided. A project first registered through a symlinked workspace now drops to the weaker name = cwdBase lookup here, which can miss or inject the wrong project's memories when leaf names collide.

Possible stopgap in this handler
  cwd := input.CWD
  if cwd == "" {
  	cwd, _ = os.Getwd()
  }
+	rawCWD := cwd
  // Resolve symlinks so cwd matches the canonical path stored in the DB.
  if resolved, err := filepath.EvalSymlinks(cwd); err == nil {
  	cwd = resolved
  }

  project, memories, learned := loadSessionContext(cwd)
+	if project == "" && rawCWD != cwd {
+		project, memories, learned = loadSessionContext(rawCWD)
+	}

Longer-term, EnsureProject should normalize paths before persisting them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcpinit/hook.go` around lines 34 - 43, The handler canonicalizes cwd
only on read (filepath.EvalSymlinks in loadSessionContext), causing mismatches
when projects were persisted with symlinked paths; update this handler to
canonicalize input.CWD before any lookup and ensure project creation/update uses
the canonical path: call filepath.EvalSymlinks (and fallback to os.Getwd when
empty) before invoking loadSessionContext/EnsureProject so the same normalized
path is used for both lookup and persistence; also update EnsureProject in
internal/memory/store.go to normalize incoming project paths before saving to
projects.path so persisted paths are always canonicalized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/mcpserver/mcpserver.go`:
- Around line 301-304: The ghost_project_context tool handler currently builds
its response from project-only memories; update the handler(s) that assemble the
tool response (the ghost_project_context tool handler around the
buildProjectContext call and the duplicate assembly at the later occurrence) to
include the `_global` memories that buildProjectContext now returns—i.e., call
buildProjectContext (or use its returned struct) and merge both project memories
and the returned global memories into the tool response so globals aren't
dropped during mid-session refreshes and project switches.
- Around line 67-75: The startup instructions in the "Workflow" block are not
client-agnostic and claim behavior specific to Claude Code; edit the text (the
"Workflow" block and sentences mentioning auto-import) to make it
conditional/neutral: reference the SessionStart hook generically and remove the
absolute claim that "Line 68 is only true for Claude Code"; instead say "If a
client implements the SessionStart hook (e.g., Claude Code), the project context
may be injected — otherwise clients should call ghost_project_context as
needed." Also change the "Ghost auto-imports Claude Code memory files..."
sentence to a conditional note like "Some clients may auto-import memory files
(for example Claude Code when the SessionStart hook is present)." Ensure
references to ghost_project_context, SessionStart hook, and
ghost_memory_save/search remain unchanged so behavior guidance is preserved.

---

Outside diff comments:
In `@internal/mcpinit/hook.go`:
- Around line 34-43: The handler canonicalizes cwd only on read
(filepath.EvalSymlinks in loadSessionContext), causing mismatches when projects
were persisted with symlinked paths; update this handler to canonicalize
input.CWD before any lookup and ensure project creation/update uses the
canonical path: call filepath.EvalSymlinks (and fallback to os.Getwd when empty)
before invoking loadSessionContext/EnsureProject so the same normalized path is
used for both lookup and persistence; also update EnsureProject in
internal/memory/store.go to normalize incoming project paths before saving to
projects.path so persisted paths are always canonicalized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65b52cde-0551-4a17-8534-3480c7ea5aea

📥 Commits

Reviewing files that changed from the base of the PR and between 525d100 and 44f574b.

📒 Files selected for processing (6)
  • README.md
  • cmd/ghost/main.go
  • internal/mcpinit/hook.go
  • internal/mcpinit/init_test.go
  • internal/mcpserver/mcpserver.go
  • internal/mcpserver/mcpserver_test.go

Comment on lines 67 to +75
## Workflow
1. At session start, call ghost_list_projects to discover known projects.
2. Call ghost_project_context with the project name to load accumulated knowledge.
3. During work, save important discoveries with ghost_memory_save. Do NOT wait until the end of the session.
4. Use ghost_memory_search to recall specific facts.
5. No special action needed at session end — Ghost persists automatically.
1. At session start, Ghost's SessionStart hook has ALREADY injected your project context (memories + global preferences) into this conversation. READ IT — do not call ghost_project_context redundantly.
2. During work, save important discoveries with ghost_memory_save. Do NOT wait until the end of the session.
3. Use ghost_memory_search to recall specific facts not in the injected context.
4. No special action needed at session end — Ghost persists automatically.

Ghost auto-imports Claude Code memory files (~/.claude/projects/*/memory/*.md) on first project contact. No manual migration is needed. Ghost has built-in upsert/merge deduplication — it is always safe to save, even if similar knowledge already exists.

IMPORTANT: Global memories (preferences, conventions) are included in the SessionStart hook output under "Global (applies to all projects)". These are non-negotiable rules from the user. Follow them.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the startup instructions client-agnostic.

Line 68 is only true for Claude Code with the session-start hook installed. These instructions are emitted by the MCP server itself, so any non-Claude client that honors them will skip its initial ghost_project_context call and start without project context.

Suggested wording
-1. At session start, Ghost's SessionStart hook has ALREADY injected your project context (memories + global preferences) into this conversation. READ IT — do not call ghost_project_context redundantly.
-2. During work, save important discoveries with ghost_memory_save. Do NOT wait until the end of the session.
-3. Use ghost_memory_search to recall specific facts not in the injected context.
-4. No special action needed at session end — Ghost persists automatically.
+1. If the client already injected Ghost SessionStart context, read it and do not call ghost_project_context redundantly.
+2. Otherwise, call ghost_project_context once at session start to load project context.
+3. During work, save important discoveries with ghost_memory_save. Do NOT wait until the end of the session.
+4. Use ghost_memory_search to recall specific facts not already in context.
+5. No special action needed at session end — Ghost persists automatically.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcpserver/mcpserver.go` around lines 67 - 75, The startup
instructions in the "Workflow" block are not client-agnostic and claim behavior
specific to Claude Code; edit the text (the "Workflow" block and sentences
mentioning auto-import) to make it conditional/neutral: reference the
SessionStart hook generically and remove the absolute claim that "Line 68 is
only true for Claude Code"; instead say "If a client implements the SessionStart
hook (e.g., Claude Code), the project context may be injected — otherwise
clients should call ghost_project_context as needed." Also change the "Ghost
auto-imports Claude Code memory files..." sentence to a conditional note like
"Some clients may auto-import memory files (for example Claude Code when the
SessionStart hook is present)." Ensure references to ghost_project_context,
SessionStart hook, and ghost_memory_save/search remain unchanged so behavior
guidance is preserved.

Comment on lines 301 to 304
mcp.AddTool(s.mcp, &mcp.Tool{
Name: "ghost_project_context",
Description: "Get Ghost's accumulated knowledge about a project: top memories ranked by importance and recency, plus any learned context summaries. Use this at the start of a session to recall what Ghost knows.",
Description: "Get Ghost's accumulated knowledge about a project: top memories ranked by importance and recency, plus global memories and learned context. Usually NOT needed at session start — the SessionStart hook already injects this. Use when switching projects mid-session or refreshing after saves.",
Annotations: &mcp.ToolAnnotations{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return globals from ghost_project_context too.

These changes add _global memories to buildProjectContext, but the ghost_project_context tool handler still assembles its own response from project memories only at Lines 335-351. Mid-session refreshes and project switches will therefore drop the same global preferences that SessionStart and resource reads now include.

Also applies to: 833-840

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcpserver/mcpserver.go` around lines 301 - 304, The
ghost_project_context tool handler currently builds its response from
project-only memories; update the handler(s) that assemble the tool response
(the ghost_project_context tool handler around the buildProjectContext call and
the duplicate assembly at the later occurrence) to include the `_global`
memories that buildProjectContext now returns—i.e., call buildProjectContext (or
use its returned struct) and merge both project memories and the returned global
memories into the tool response so globals aren't dropped during mid-session
refreshes and project switches.

@wcatz wcatz merged commit 4be18a6 into main Mar 25, 2026
4 checks passed
@wcatz wcatz deleted the fix/mcp-audit-stale-instructions branch March 25, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant