Skip to content

Commit b3ada6a

Browse files
committed
fix: project id optional
1 parent ff78607 commit b3ada6a

File tree

9 files changed

+544
-165
lines changed

9 files changed

+544
-165
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,6 @@ tramp
242242
# Project specific
243243
gograph
244244
gograph.exe
245+
246+
# Test directory for auto-discovery feature
247+
test-project/

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ cd /path/to/your/go/project
166166
# Initialize gograph configuration with required project ID
167167
gograph init --project-id my-awesome-project --project-name "My Awesome Project"
168168

169-
# Analyze the entire codebase (project ID is read from config)
169+
# Analyze the entire codebase
170+
# Note: gograph automatically finds and uses the project ID from gograph.yaml
171+
# in the project directory or any parent directory
170172
gograph analyze . \
171173
--include-tests \
172174
--include-vendor \
@@ -620,6 +622,8 @@ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/
620622

621623
### Available MCP Tools
622624

625+
**Note:** All MCP tools now support automatic project ID discovery from your `gograph.yaml` configuration file. When using MCP tools, you no longer need to provide the `project_id` parameter - it will be automatically derived from the project's configuration file.
626+
623627
**Project Management:**
624628
- `list_projects`: List all projects in the database
625629
- `validate_project`: Validate project existence and configuration

cmd/gograph/commands/analyze.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ The resulting graph allows you to:
6060

6161
// Wrap the entire command execution with panic recovery
6262
return errors.WithRecover("analyze_command", func() error {
63-
// Load configuration
64-
cfg, err := config.Load("")
63+
// Load configuration from project path
64+
cfg, err := config.LoadProjectConfig(projectPath)
6565
if err != nil {
6666
return fmt.Errorf("failed to load config: %w", err)
6767
}

engine/mcp/handlers.go

Lines changed: 107 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,55 @@ import (
1313
"github.com/compozy/gograph/engine/core"
1414
"github.com/compozy/gograph/engine/graph"
1515
"github.com/compozy/gograph/engine/query"
16+
"github.com/compozy/gograph/pkg/config"
1617
"github.com/compozy/gograph/pkg/logger"
1718
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
1819
)
1920

2021
// Tool handler implementations - These replace the stub implementations
2122

23+
// getProjectID extracts project ID from input, with fallback to loading from config
24+
func (s *Server) getProjectID(input map[string]any) (string, error) {
25+
// First, try to get explicit project_id
26+
if projectID, ok := input["project_id"].(string); ok && projectID != "" {
27+
return projectID, nil
28+
}
29+
30+
// If not provided, try to load from project_path config
31+
projectPath, ok := input["project_path"].(string)
32+
if !ok || projectPath == "" {
33+
// For tools that don't have project_path, try current directory
34+
cwd, err := os.Getwd()
35+
if err != nil {
36+
return "", fmt.Errorf("project_id not provided and cannot determine current directory: %w", err)
37+
}
38+
projectPath = cwd
39+
}
40+
41+
// Try to load from config
42+
cfg, err := config.LoadProjectConfig(projectPath)
43+
if err != nil {
44+
return "", fmt.Errorf("project_id not provided and failed to load config from %s: %w", projectPath, err)
45+
}
46+
47+
if cfg.Project.ID == "" {
48+
return "", fmt.Errorf("project_id not provided and not found in config at %s", projectPath)
49+
}
50+
51+
return cfg.Project.ID, nil
52+
}
53+
2254
// HandleAnalyzeProjectInternal analyzes a Go project and stores results in Neo4j
2355
func (s *Server) HandleAnalyzeProjectInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
2456
projectPath, ok := input["project_path"].(string)
2557
if !ok {
2658
return nil, fmt.Errorf("project_path is required")
2759
}
28-
projectID, ok := input["project_id"].(string)
29-
if !ok {
30-
return nil, fmt.Errorf("project_id is required")
60+
61+
// Get project ID using helper
62+
projectID, err := s.getProjectID(input)
63+
if err != nil {
64+
return nil, err
3165
}
3266

3367
// Validate path is allowed
@@ -108,10 +142,12 @@ func (s *Server) HandleAnalyzeProjectInternal(ctx context.Context, input map[str
108142

109143
// HandleExecuteCypherInternal executes a custom Cypher query
110144
func (s *Server) HandleExecuteCypherInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
111-
projectID, ok := input["project_id"].(string)
112-
if !ok {
113-
return nil, fmt.Errorf("project_id is required")
145+
// Get project ID using helper
146+
projectID, err := s.getProjectID(input)
147+
if err != nil {
148+
return nil, err
114149
}
150+
115151
query, ok := input["query"].(string)
116152
if !ok {
117153
return nil, fmt.Errorf("query is required")
@@ -161,9 +197,10 @@ func (s *Server) HandleExecuteCypherInternal(ctx context.Context, input map[stri
161197
//
162198
//nolint:funlen,gocyclo // MCP tool handlers can be longer and have complex logic
163199
func (s *Server) HandleGetFunctionInfoInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
164-
projectID, ok := input["project_id"].(string)
165-
if !ok {
166-
return nil, fmt.Errorf("project_id is required")
200+
// Get project ID using helper
201+
projectID, err := s.getProjectID(input)
202+
if err != nil {
203+
return nil, err
167204
}
168205
functionName, ok := input["function_name"].(string)
169206
if !ok {
@@ -343,9 +380,10 @@ func (s *Server) AddFunctionRelationships(
343380
//
344381
//nolint:funlen // MCP tool handlers can be longer for comprehensive functionality
345382
func (s *Server) HandleQueryDependenciesInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
346-
projectID, ok := input["project_id"].(string)
347-
if !ok {
348-
return nil, fmt.Errorf("project_id is required")
383+
// Get project ID using helper
384+
projectID, err := s.getProjectID(input)
385+
if err != nil {
386+
return nil, err
349387
}
350388
path, ok := input["path"].(string)
351389
if !ok {
@@ -489,9 +527,10 @@ func (s *Server) IsPathAllowed(path string) bool {
489527
//
490528
//nolint:funlen // MCP tool handlers can be longer for comprehensive functionality
491529
func (s *Server) HandleFindImplementationsInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
492-
projectID, ok := input["project_id"].(string)
493-
if !ok {
494-
return nil, fmt.Errorf("project_id is required")
530+
// Get project ID using helper
531+
projectID, err := s.getProjectID(input)
532+
if err != nil {
533+
return nil, err
495534
}
496535
interfaceName, ok := input["interface_name"].(string)
497536
if !ok {
@@ -578,9 +617,10 @@ func (s *Server) HandleFindImplementationsInternal(ctx context.Context, input ma
578617
//
579618
//nolint:funlen // MCP tool handlers can be longer for comprehensive functionality
580619
func (s *Server) HandleTraceCallChainInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
581-
projectID, ok := input["project_id"].(string)
582-
if !ok {
583-
return nil, fmt.Errorf("project_id is required")
620+
// Get project ID using helper
621+
projectID, err := s.getProjectID(input)
622+
if err != nil {
623+
return nil, err
584624
}
585625
fromFunction, ok := input["from_function"].(string)
586626
if !ok {
@@ -682,9 +722,10 @@ func (s *Server) HandleTraceCallChainInternal(ctx context.Context, input map[str
682722

683723
// handleDetectCircularDeps detects circular dependencies
684724
func (s *Server) HandleDetectCircularDepsInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
685-
projectID, ok := input["project_id"].(string)
686-
if !ok {
687-
return nil, fmt.Errorf("project_id is required")
725+
// Get project ID using helper
726+
projectID, err := s.getProjectID(input)
727+
if err != nil {
728+
return nil, err
688729
}
689730
scope := "packages"
690731
if s, ok := input["scope"].(string); ok {
@@ -745,9 +786,10 @@ func (s *Server) HandleDetectCircularDepsInternal(ctx context.Context, input map
745786

746787
// handleListPackages lists all packages in the project
747788
func (s *Server) HandleListPackagesInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
748-
projectID, ok := input["project_id"].(string)
749-
if !ok {
750-
return nil, fmt.Errorf("project_id is required")
789+
// Get project ID using helper
790+
projectID, err := s.getProjectID(input)
791+
if err != nil {
792+
return nil, err
751793
}
752794
pattern := ""
753795
if p, ok := input["pattern"].(string); ok {
@@ -810,9 +852,10 @@ func (s *Server) HandleListPackagesInternal(ctx context.Context, input map[strin
810852
//
811853
//nolint:funlen // MCP tool handlers can be longer for comprehensive functionality
812854
func (s *Server) HandleGetPackageStructureInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
813-
projectID, ok := input["project_id"].(string)
814-
if !ok {
815-
return nil, fmt.Errorf("project_id is required")
855+
// Get project ID using helper
856+
projectID, err := s.getProjectID(input)
857+
if err != nil {
858+
return nil, err
816859
}
817860
packageName, ok := input["package"].(string)
818861
if !ok {
@@ -915,9 +958,10 @@ func (s *Server) HandleGetPackageStructureInternal(ctx context.Context, input ma
915958

916959
// handleNaturalLanguageQuery converts natural language to Cypher and executes
917960
func (s *Server) HandleNaturalLanguageQueryInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
918-
projectID, ok := input["project_id"].(string)
919-
if !ok {
920-
return nil, fmt.Errorf("project_id is required")
961+
// Get project ID using helper
962+
projectID, err := s.getProjectID(input)
963+
if err != nil {
964+
return nil, err
921965
}
922966
query, ok := input["query"].(string)
923967
if !ok {
@@ -936,7 +980,6 @@ func (s *Server) HandleNaturalLanguageQueryInternal(ctx context.Context, input m
936980
// Use LLM service to translate natural language to Cypher
937981
var cypherQuery string
938982
var params map[string]any
939-
var err error
940983

941984
if s.llmService != nil {
942985
// Get schema for the project
@@ -994,9 +1037,10 @@ func (s *Server) HandleNaturalLanguageQueryInternal(ctx context.Context, input m
9941037
//
9951038
//nolint:gocyclo,funlen // MCP tool handlers need multiple branches for different element types
9961039
func (s *Server) HandleVerifyCodeExistsInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
997-
projectID, ok := input["project_id"].(string)
998-
if !ok {
999-
return nil, fmt.Errorf("project_id is required")
1040+
// Get project ID using helper
1041+
projectID, err := s.getProjectID(input)
1042+
if err != nil {
1043+
return nil, err
10001044
}
10011045
elementType, ok := input["element_type"].(string)
10021046
if !ok {
@@ -1374,11 +1418,12 @@ func (s *Server) BuildElementLocationQuery(elementType string) (string, error) {
13741418
func (s *Server) ParseCodeContextInput(
13751419
input map[string]any,
13761420
) (projectID, elementType, name string, contextLines int, err error) {
1377-
projectID, ok := input["project_id"].(string)
1378-
if !ok {
1379-
return "", "", "", 0, fmt.Errorf("project_id is required")
1421+
// Get project ID using helper
1422+
projectID, err = s.getProjectID(input)
1423+
if err != nil {
1424+
return "", "", "", 0, err
13801425
}
1381-
elementType, ok = input["element_type"].(string)
1426+
elementType, ok := input["element_type"].(string)
13821427
if !ok {
13831428
return "", "", "", 0, fmt.Errorf("element_type is required")
13841429
}
@@ -1507,9 +1552,10 @@ func (s *Server) ExtractCodeContextFromResults(
15071552

15081553
// handleValidateImportPath validates an import path
15091554
func (s *Server) HandleValidateImportPathInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
1510-
projectID, ok := input["project_id"].(string)
1511-
if !ok {
1512-
return nil, fmt.Errorf("project_id is required")
1555+
// Get project ID using helper
1556+
projectID, err := s.getProjectID(input)
1557+
if err != nil {
1558+
return nil, err
15131559
}
15141560
importPath, ok := input["import_path"].(string)
15151561
if !ok {
@@ -1594,9 +1640,10 @@ func (s *Server) HandleValidateImportPathInternal(ctx context.Context, input map
15941640

15951641
// handleDetectCodePatterns detects code patterns
15961642
func (s *Server) HandleDetectCodePatternsInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
1597-
projectID, ok := input["project_id"].(string)
1598-
if !ok {
1599-
return nil, fmt.Errorf("project_id is required")
1643+
// Get project ID using helper
1644+
projectID, err := s.getProjectID(input)
1645+
if err != nil {
1646+
return nil, err
16001647
}
16011648
// Extract patterns filter (optional)
16021649
_ = input["patterns"] // TODO: Use specific patterns filter in future
@@ -1635,9 +1682,10 @@ func (s *Server) HandleDetectCodePatternsInternal(ctx context.Context, input map
16351682

16361683
// handleGetNamingConventions analyzes naming conventions
16371684
func (s *Server) HandleGetNamingConventionsInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
1638-
projectID, ok := input["project_id"].(string)
1639-
if !ok {
1640-
return nil, fmt.Errorf("project_id is required")
1685+
// Get project ID using helper
1686+
projectID, err := s.getProjectID(input)
1687+
if err != nil {
1688+
return nil, err
16411689
}
16421690
scope := ""
16431691
if s, ok := input["scope"].(string); ok {
@@ -1685,9 +1733,10 @@ func (s *Server) HandleGetNamingConventionsInternal(ctx context.Context, input m
16851733

16861734
// handleFindTestsForCode finds tests for code elements
16871735
func (s *Server) HandleFindTestsForCodeInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
1688-
projectID, ok := input["project_id"].(string)
1689-
if !ok {
1690-
return nil, fmt.Errorf("project_id is required")
1736+
// Get project ID using helper
1737+
projectID, err := s.getProjectID(input)
1738+
if err != nil {
1739+
return nil, err
16911740
}
16921741
elementType, ok := input["element_type"].(string)
16931742
if !ok {
@@ -1738,9 +1787,10 @@ func (s *Server) HandleFindTestsForCodeInternal(ctx context.Context, input map[s
17381787

17391788
// handleCheckTestCoverage checks test coverage
17401789
func (s *Server) HandleCheckTestCoverageInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
1741-
projectID, ok := input["project_id"].(string)
1742-
if !ok {
1743-
return nil, fmt.Errorf("project_id is required")
1790+
// Get project ID using helper
1791+
projectID, err := s.getProjectID(input)
1792+
if err != nil {
1793+
return nil, err
17441794
}
17451795
path := ""
17461796
if p, ok := input["path"].(string); ok {
@@ -2523,9 +2573,10 @@ func (s *Server) HandleListProjectsInternal(ctx context.Context, _ map[string]an
25232573

25242574
// HandleValidateProjectInternal validates if a project exists in the database
25252575
func (s *Server) HandleValidateProjectInternal(ctx context.Context, input map[string]any) (*ToolResponse, error) {
2526-
projectID, ok := input["project_id"].(string)
2527-
if !ok {
2528-
return nil, fmt.Errorf("project_id is required")
2576+
// Get project ID using helper
2577+
projectID, err := s.getProjectID(input)
2578+
if err != nil {
2579+
return nil, err
25292580
}
25302581

25312582
logger.Info("validating project existence", "project_id", projectID)

0 commit comments

Comments
 (0)