Skip to content

Commit 12c0033

Browse files
committed
regenerate init file when adding tools
1 parent 3c4045e commit 12c0033

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

cmd/kmcp/cmd/add_tool.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ func generateTool(toolName, toolPath string) error {
175175

176176
fmt.Printf("✅ Successfully created tool: %s\n", toolName)
177177
fmt.Printf("📁 Generated file: %s\n", toolPath)
178+
179+
// Regenerate __init__.py file
180+
toolsDir := filepath.Dir(toolPath)
181+
if err := generator.RegenerateToolsInit(toolsDir); err != nil {
182+
fmt.Printf("⚠️ Warning: Failed to regenerate __init__.py: %v\n", err)
183+
} else {
184+
fmt.Printf("🔄 Updated tools/__init__.py with new tool import\n")
185+
}
186+
178187
fmt.Printf("📝 Edit the file to implement your tool logic\n")
179188
fmt.Printf("🚀 The tool will be automatically loaded when the server starts\n")
180189

cmd/kmcp/cmd/regenerate_init.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/spf13/cobra"
8+
"kagent.dev/kmcp/pkg/tools"
9+
)
10+
11+
var regenerateInitCmd = &cobra.Command{
12+
Use: "regenerate-init",
13+
Short: "Regenerate the tools/__init__.py file",
14+
Long: `Regenerate the tools/__init__.py file based on the current tools in the directory.
15+
16+
This command scans the src/tools/ directory for Python files and regenerates the __init__.py
17+
file with the appropriate imports and __all__ list. This is useful when:
18+
19+
- You've manually added tool files
20+
- The __init__.py file is out of sync
21+
- You need to refresh the tool imports
22+
23+
The command will:
24+
1. Scan src/tools/ for all .py files (excluding __init__.py)
25+
2. Generate import statements for each tool
26+
3. Update the __all__ list with all discovered tools
27+
4. Overwrite the existing __init__.py file
28+
29+
Examples:
30+
kmcp regenerate-init # Regenerate in current directory
31+
kmcp regenerate-init --tools-dir custom-tools # Use custom tools directory`,
32+
RunE: runRegenerateInit,
33+
}
34+
35+
var regenerateInitToolsDir string
36+
37+
func init() {
38+
rootCmd.AddCommand(regenerateInitCmd)
39+
regenerateInitCmd.Flags().StringVar(&regenerateInitToolsDir, "tools-dir", "src/tools", "Tools directory to regenerate __init__.py for")
40+
}
41+
42+
func runRegenerateInit(cmd *cobra.Command, args []string) error {
43+
// Check if we're in a valid KMCP project
44+
if !isKMCPProject() {
45+
return fmt.Errorf("not in a KMCP project directory. Run 'kmcp init' first")
46+
}
47+
48+
// Check if tools directory exists
49+
if !fileExists(regenerateInitToolsDir) {
50+
return fmt.Errorf("tools directory '%s' does not exist", regenerateInitToolsDir)
51+
}
52+
53+
// Get absolute path
54+
absToolsDir, err := filepath.Abs(regenerateInitToolsDir)
55+
if err != nil {
56+
return fmt.Errorf("failed to get absolute path: %w", err)
57+
}
58+
59+
if verbose {
60+
fmt.Printf("Regenerating __init__.py in: %s\n", absToolsDir)
61+
}
62+
63+
// Create generator and regenerate
64+
generator := tools.NewGenerator()
65+
if err := generator.RegenerateToolsInit(absToolsDir); err != nil {
66+
return fmt.Errorf("failed to regenerate __init__.py: %w", err)
67+
}
68+
69+
fmt.Printf("✅ Successfully regenerated %s/__init__.py\n", regenerateInitToolsDir)
70+
71+
return nil
72+
}

pkg/tools/generator.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,96 @@ func (g *Generator) GenerateToolFile(filePath, toolName string, config map[strin
5757
return fmt.Errorf("failed to execute template: %w", err)
5858
}
5959

60+
// After generating the tool file, regenerate the __init__.py file
61+
toolsDir := filepath.Dir(filePath)
62+
if err := g.RegenerateToolsInit(toolsDir); err != nil {
63+
return fmt.Errorf("failed to regenerate __init__.py: %w", err)
64+
}
65+
66+
return nil
67+
}
68+
69+
// RegenerateToolsInit regenerates the __init__.py file in the tools directory
70+
func (g *Generator) RegenerateToolsInit(toolsDir string) error {
71+
// Scan the tools directory for Python files
72+
tools, err := g.ScanToolsDirectory(toolsDir)
73+
if err != nil {
74+
return fmt.Errorf("failed to scan tools directory: %w", err)
75+
}
76+
77+
// Generate the __init__.py content
78+
content := g.generateInitContent(tools)
79+
80+
// Write the __init__.py file
81+
initPath := filepath.Join(toolsDir, "__init__.py")
82+
if err := os.WriteFile(initPath, []byte(content), 0644); err != nil {
83+
return fmt.Errorf("failed to write __init__.py: %w", err)
84+
}
85+
6086
return nil
6187
}
6288

89+
// ScanToolsDirectory scans the tools directory and returns a list of tool names
90+
func (g *Generator) ScanToolsDirectory(toolsDir string) ([]string, error) {
91+
var tools []string
92+
93+
// Read the directory
94+
entries, err := os.ReadDir(toolsDir)
95+
if err != nil {
96+
return nil, fmt.Errorf("failed to read tools directory: %w", err)
97+
}
98+
99+
// Find all Python files (excluding __init__.py)
100+
for _, entry := range entries {
101+
if entry.IsDir() {
102+
continue
103+
}
104+
105+
name := entry.Name()
106+
if strings.HasSuffix(name, ".py") && name != "__init__.py" {
107+
// Extract tool name (filename without .py extension)
108+
toolName := strings.TrimSuffix(name, ".py")
109+
tools = append(tools, toolName)
110+
}
111+
}
112+
113+
return tools, nil
114+
}
115+
116+
// generateInitContent generates the content for the __init__.py file
117+
func (g *Generator) generateInitContent(tools []string) string {
118+
var content strings.Builder
119+
120+
// Add the header comment
121+
content.WriteString(`"""Tools package for knowledge-assistant MCP server.
122+
123+
This file is automatically generated by the dynamic loading system.
124+
Do not edit manually - it will be overwritten when tools are loaded.
125+
"""
126+
127+
`)
128+
129+
// Add import statements
130+
for _, tool := range tools {
131+
content.WriteString(fmt.Sprintf("from .%s import %s\n", tool, tool))
132+
}
133+
134+
// Add empty line
135+
content.WriteString("\n")
136+
137+
// Add __all__ list
138+
content.WriteString("__all__ = [")
139+
for i, tool := range tools {
140+
if i > 0 {
141+
content.WriteString(", ")
142+
}
143+
content.WriteString(fmt.Sprintf(`"%s"`, tool))
144+
}
145+
content.WriteString("]\n")
146+
147+
return content.String()
148+
}
149+
63150
// getUnifiedTemplate returns the unified tool template with commented examples
64151
func (g *Generator) getUnifiedTemplate() string {
65152
return `"""{{.ToolNameTitle}} tool for MCP server.{{if .description}}

0 commit comments

Comments
 (0)