Skip to content

feat(mcp): minimal mem_find_project tool#661

Open
carlosmoradev wants to merge 1 commit into
Gentleman-Programming:mainfrom
carlosmoradev:feat/mcp-find-project-minimal
Open

feat(mcp): minimal mem_find_project tool#661
carlosmoradev wants to merge 1 commit into
Gentleman-Programming:mainfrom
carlosmoradev:feat/mcp-find-project-minimal

Conversation

@carlosmoradev

@carlosmoradev carlosmoradev commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Draft PR for #304. Dropped the preview field to keep the diff tiny. It only returns
Project, MatchCount, and TopRank (using FTS5 bm25). All MCP tool count tests updated.

Summary by CodeRabbit

  • New Features

    • Added project discovery through memory relevance search.
    • Search results show matching project names, relevance ranking, and match counts.
    • Supports matching all search terms or any search term.
    • Project discovery is available in the default agent toolset.
  • Bug Fixes

    • Added validation for empty searches and unsupported matching modes.
    • Search results are limited and consistently ranked.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds project-level memory relevance search through a new mem_find_project MCP tool. Store search results are grouped and ranked by project, while the agent profile, registration logic, validation, formatting, and related tests are updated.

Changes

Project relevance search

Layer / File(s) Summary
Store project search
internal/store/store.go, internal/store/store_test.go
Adds ProjectMatch and SearchProjects, which aggregate FTS observation matches by project and order them by rank and match count; tests cover grouped results.
MCP project discovery tool
internal/mcp/mcp.go, internal/mcp/mcp_test.go
Registers mem_find_project, adds it to the agent profile, validates inputs, formats ranked results, and updates tool-count expectations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant handleFindProject
  participant Store
  MCPClient->>handleFindProject: query and match_mode
  handleFindProject->>Store: SearchProjects(query, match_mode, limit)
  Store-->>handleFindProject: ranked ProjectMatch results
  handleFindProject-->>MCPClient: formatted project list or tool error
Loading

Possibly related issues

  • Gentleman-Programming/engram#304 — Directly covers the mem_find_project MCP tool and grouped project-relevance search added here.

Suggested labels: type:feature

Suggested reviewers: gentleman-programming, alan-thegentleman

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a minimal mem_find_project MCP tool.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@carlosmoradev
carlosmoradev marked this pull request as ready for review July 25, 2026 12:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/mcp/mcp_test.go`:
- Line 2257: Expand the tests around mem_find_project beyond tool registration
by invoking its handler with deterministic fixtures. Cover a successful search
result, missing query, invalid match_mode, no results, and store failures,
asserting each response and error behavior; keep the existing registration
assertion intact.

In `@internal/mcp/mcp.go`:
- Line 1199: Update the result formatting in the mem_find_project output around
TopRank so nearby computed ranks remain distinguishable and the returned value
preserves sufficient numeric precision; replace the fixed two-decimal formatting
in the fmt.Fprintf call with a precision-preserving representation while keeping
the existing project and match-count fields unchanged.

In `@internal/store/store_test.go`:
- Around line 8834-8906: Expand TestSearchProjects to cover empty queries,
any-mode matching, exclusion of deleted or blank-project observations,
zero/limited result bounds, query-error propagation, and deterministic TopRank
ordering. Use the existing test store and search-related helpers to create each
scenario, assert expected results and errors, and retain the current all-mode
grouping assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: acd92883-37d2-4a57-a6eb-7e1c4f617217

📥 Commits

Reviewing files that changed from the base of the PR and between 763a6ba and 485e7f4.

📒 Files selected for processing (4)
  • internal/mcp/mcp.go
  • internal/mcp/mcp_test.go
  • internal/store/store.go
  • internal/store/store_test.go

Comment thread internal/mcp/mcp_test.go

allTools := []string{
"mem_save", "mem_search", "mem_context", "mem_session_summary",
"mem_save", "mem_search", "mem_find_project", "mem_context", "mem_session_summary",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Test mem_find_project behavior, not only registration.

The new assertions only prove the tool is listed. Add deterministic handler tests for a successful search result, missing query, invalid match_mode, no results, and store failures.

As per path instructions, **/*_test.go must cover happy paths, error paths, and edge cases, and behavior changes without tests should be blocked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/mcp/mcp_test.go` at line 2257, Expand the tests around
mem_find_project beyond tool registration by invoking its handler with
deterministic fixtures. Cover a successful search result, missing query, invalid
match_mode, no results, and store failures, asserting each response and error
behavior; keep the existing registration assertion intact.

Source: Path instructions

Comment thread internal/mcp/mcp.go
var b strings.Builder
fmt.Fprintf(&b, "Found %d project(s) matching %q:\n", len(matches), query)
for _, m := range matches {
fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate mcp.go =="
fd -a 'mcp\.go$' . | sed 's#^\./##'

echo "== context around line 1199 =="
if [ -f internal/mcp/mcp.go ]; then
  nl -ba internal/mcp/mcp.go | sed -n '1160,1220p'
fi

echo "== TopRank definitions/usages =="
rg -n "TopRank|MatchCount|Project|Rank" internal/mcp/mcp.go | sed -n '1,220p'

echo "== behavioral probe for %.2f vs %.6g precision loss =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568]
print("input -> %.2f -> %.6g")
for x in samples:
    print(f"{x:.7f} {x:.2f} {x:.6g}")
print("collapsed distinct values for %.2f:", len(set(f"{x:.2f}" for x in samples)))
print("collapses for %.6g:", len(set(f"{x:.6g}" for x in samples)))
PY

Repository: Gentleman-Programming/engram

Length of output: 296


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== mcp.go relevant definitions =="
rg -n "type .*struct|func .*TopRank|TopRank|MatchCount|Project" internal/mcp/mcp.go -C 3 | sed -n '1,260p'

echo "== compute/top-ranked value generation =="
rg -n "TopRank|MatchCount|Project" internal/mcp/mcp.go -C 5 | sed -n '1,260p'

Repository: Gentleman-Programming/engram

Length of output: 22281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SearchProjects definitions/usages =="
rg -n "SearchProjects|SearchProject|TopRank|MatchCount" . -C 4 --include='*.go' | sed -n '1,260p'

echo "== Precision loss examples =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568, -0.001, -0.0015]
fmt2 = [f"{x:.2f}" for x in samples]
fmt6g = [f"{x:.6g}" for x in samples]
print("input -> %.2f -> %.6g")
for x, a, b in zip(samples, fmt2, fmt6g):
    print(f"{x:.8f} {a} {b}")
print("distinct %.2f:", len(set(fmt2)), f"{', '.join(set(fmt2))}" if len(set(fmt2)) < 8 else "")
print("distinct %.6g:", len(set(fmt6g)))
PY

Repository: Gentleman-Programming/engram

Length of output: 648


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== SearchProjects definitions/usages =="
rg -n "SearchProjects|SearchProject|TopRank|MatchCount" . --glob '*.go' -C 4 | sed -n '1,280p'

echo "== Precision loss examples =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568, -0.001, -0.0015]
fmt2 = [f"{x:.2f}" for x in samples]
fmt6g = [f"{x:.6g}" for x in samples]
print("input -> %.2f -> %.6g")
for x, a, b in zip(samples, fmt2, fmt6g):
    print(f"{x:.8f} {a} {b}")
print("distinct %.2f:", len(set(fmt2)))
print("distinct %.6g:", len(set(fmt6g)))
PY

Repository: Gentleman-Programming/engram

Length of output: 5421


Preserve usable TopRank precision.

%.2f can collapse nearby computed ranks to the same displayed value, so mem_find_project does not return the actual TopRank. Use a precision-preserving format or structured numeric metadata.

Proposed fix
- fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank)
+ fmt.Fprintf(&b, "- %s (%d matches, rank: %.6g)\n", m.Project, m.MatchCount, m.TopRank)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank)
fmt.Fprintf(&b, "- %s (%d matches, rank: %.6g)\n", m.Project, m.MatchCount, m.TopRank)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/mcp/mcp.go` at line 1199, Update the result formatting in the
mem_find_project output around TopRank so nearby computed ranks remain
distinguishable and the returned value preserves sufficient numeric precision;
replace the fixed two-decimal formatting in the fmt.Fprintf call with a
precision-preserving representation while keeping the existing project and
match-count fields unchanged.

Comment on lines +8834 to +8906
func TestSearchProjects(t *testing.T) {
st := newTestStore(t)

st.CreateSession("s1", "project-a", "/tmp/a")
st.CreateSession("s2", "project-b", "/tmp/b")
st.CreateSession("s3", "project-c", "/tmp/c")

// Seed 3 observations for project A
_, err := st.AddObservation(AddObservationParams{
SessionID: "s1", Type: "bugfix", Title: "Fix auth token expiration",
Content: "The auth middleware was dropping tokens", Project: "project-a",
})
if err != nil { t.Fatal(err) }
_, err = st.AddObservation(AddObservationParams{
SessionID: "s1", Type: "bugfix", Title: "Auth token validation",
Content: "Middleware should validate auth tokens", Project: "project-a",
})
if err != nil { t.Fatal(err) }
_, err = st.AddObservation(AddObservationParams{
SessionID: "s1", Type: "bugfix", Title: "Minor fix",
Content: "Just a minor auth fix in the middleware", Project: "project-a",
})
if err != nil { t.Fatal(err) }

// Seed 1 highly relevant observation for project B
_, err = st.AddObservation(AddObservationParams{
SessionID: "s2", Type: "bugfix", Title: "Auth middleware completely rewritten",
Content: "Auth middleware auth middleware auth middleware tokens", Project: "project-b",
})
if err != nil { t.Fatal(err) }

// Seed an irrelevant observation for project C
_, err = st.AddObservation(AddObservationParams{
SessionID: "s3", Type: "feature", Title: "Database migration",
Content: "Added new tables", Project: "project-c",
})
if err != nil { t.Fatal(err) }

// Force FTS sync if async (test setup normally does this, but just in case)
// We'll just search directly.

matches, err := st.SearchProjects("auth middleware", "all", 10)
if err != nil {
t.Fatalf("SearchProjects failed: %v", err)
}

if len(matches) != 2 {
t.Fatalf("Expected 2 projects, got %d: %+v", len(matches), matches)
}

// project-a should have 3 matches. project-b should have 1 match.
// project-b has more occurrences of the terms, so its top_rank might be better (more negative).
// Let's assert on the project names and counts.
hasA := false
hasB := false
for _, m := range matches {
if m.Project == "project-a" {
hasA = true
if m.MatchCount != 3 {
t.Errorf("project-a: expected 3 matches, got %d", m.MatchCount)
}
}
if m.Project == "project-b" {
hasB = true
if m.MatchCount != 1 {
t.Errorf("project-b: expected 1 match, got %d", m.MatchCount)
}
}
}
if !hasA || !hasB {
t.Errorf("Missing expected projects in results: %+v", matches)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Cover SearchProjects error and edge paths.

This only tests normal all-mode grouping. Add deterministic coverage for empty input, any mode, deleted/blank-project exclusion, limit bounds, query failures, and TopRank ordering.

As per path instructions, **/*_test.go must cover happy paths, error paths, and edge cases, and behavior changes without tests should be blocked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/store/store_test.go` around lines 8834 - 8906, Expand
TestSearchProjects to cover empty queries, any-mode matching, exclusion of
deleted or blank-project observations, zero/limited result bounds, query-error
propagation, and deterministic TopRank ordering. Use the existing test store and
search-related helpers to create each scenario, assert expected results and
errors, and retain the current all-mode grouping assertions.

Source: Path instructions

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