Skip to content

Commit 341170b

Browse files
committed
fix(mcp): resolve project_id by name for MCP clients
MCP clients pass project name ("ghost") but DB stores project_id as sha256(abs_path)[:12]. Every MCP query returned empty results. Add ResolveProjectByName to Store and resolveProjectID helper to MCP server. All tool handlers now resolve name→hash before querying.
1 parent 65801bd commit 341170b

4 files changed

Lines changed: 53 additions & 0 deletions

File tree

internal/mcpserver/mcpserver.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ func (s *Server) Run(ctx context.Context) error {
4747
return s.mcp.Run(ctx, &mcp.StdioTransport{})
4848
}
4949

50+
// resolveProjectID resolves a project_id that may be a name (e.g. "ghost")
51+
// into the actual hash ID (e.g. "6bdc098af7f5") stored in the database.
52+
// Tries direct ID match first, then falls back to name lookup.
53+
func (s *Server) resolveProjectID(ctx context.Context, input string) string {
54+
// Check if input directly matches a project ID.
55+
projects, err := s.store.ListProjects(ctx)
56+
if err == nil {
57+
for _, p := range projects {
58+
if p.ID == input {
59+
return input
60+
}
61+
}
62+
}
63+
64+
// Try name lookup.
65+
resolved, err := s.store.ResolveProjectByName(ctx, input)
66+
if err == nil && resolved != "" {
67+
return resolved
68+
}
69+
70+
// Return as-is if nothing matched — let downstream handle the miss.
71+
return input
72+
}
73+
5074
func (s *Server) registerTools() {
5175
// ghost_memory_search — search memories by keyword or semantic query.
5276
type searchArgs struct {
@@ -66,6 +90,8 @@ func (s *Server) registerTools() {
6690
args.Limit = 10
6791
}
6892

93+
args.ProjectID = s.resolveProjectID(ctx, args.ProjectID)
94+
6995
memories, err := s.store.SearchFTS(ctx, args.ProjectID, args.Query, args.Limit)
7096
if err != nil {
7197
return nil, nil, fmt.Errorf("search failed: %w", err)
@@ -118,6 +144,8 @@ func (s *Server) registerTools() {
118144
args.Tags = []string{}
119145
}
120146

147+
args.ProjectID = s.resolveProjectID(ctx, args.ProjectID)
148+
121149
if err := s.store.EnsureProject(ctx, args.ProjectID, "", args.ProjectID); err != nil {
122150
return nil, nil, fmt.Errorf("ensure project: %w", err)
123151
}
@@ -155,6 +183,8 @@ func (s *Server) registerTools() {
155183
args.Limit = 20
156184
}
157185

186+
args.ProjectID = s.resolveProjectID(ctx, args.ProjectID)
187+
158188
var sb strings.Builder
159189

160190
memories, err := s.store.GetTopMemories(ctx, args.ProjectID, args.Limit)
@@ -203,6 +233,8 @@ func (s *Server) registerTools() {
203233
args.Limit = 30
204234
}
205235

236+
args.ProjectID = s.resolveProjectID(ctx, args.ProjectID)
237+
206238
var memories []memory.Memory
207239
var err error
208240

internal/memory/store.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ func (s *Store) EnsureProject(ctx context.Context, id, path, name string) error
9797
return err
9898
}
9999

100+
// ResolveProjectByName looks up a project by name and returns its hash ID.
101+
// Returns empty string if no project with that name exists.
102+
func (s *Store) ResolveProjectByName(ctx context.Context, name string) (string, error) {
103+
s.mu.RLock()
104+
defer s.mu.RUnlock()
105+
106+
var id string
107+
err := s.db.QueryRowContext(ctx, `SELECT id FROM projects WHERE name = ? LIMIT 1`, name).Scan(&id)
108+
if err == sql.ErrNoRows {
109+
return "", nil
110+
}
111+
if err != nil {
112+
return "", fmt.Errorf("resolve project by name: %w", err)
113+
}
114+
return id, nil
115+
}
116+
100117
// Create inserts a new memory and returns its ID.
101118
func (s *Store) Create(ctx context.Context, projectID string, m Memory) (string, error) {
102119
s.mu.Lock()

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type MemoryStore interface {
6666
// Project management
6767
ListProjects(ctx context.Context) ([]memory.Project, error)
6868
EnsureProject(ctx context.Context, id, path, name string) error
69+
ResolveProjectByName(ctx context.Context, name string) (string, error)
6970

7071
// Conversation persistence
7172
CreateConversation(ctx context.Context, projectID, mode string) (string, error)

internal/server/server_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ func (m *mockStore) ReplaceNonManual(_ context.Context, _ string, _ []memory.Mem
8080
}
8181
func (m *mockStore) ListProjects(_ context.Context) ([]memory.Project, error) { return nil, nil }
8282
func (m *mockStore) EnsureProject(_ context.Context, _, _, _ string) error { return nil }
83+
func (m *mockStore) ResolveProjectByName(_ context.Context, _ string) (string, error) {
84+
return "", nil
85+
}
8386
func (m *mockStore) CreateConversation(_ context.Context, _, _ string) (string, error) {
8487
return "", nil
8588
}

0 commit comments

Comments
 (0)