Skip to content

Commit 910955d

Browse files
authored
Merge pull request #14 from akihikokuroda/createtools
bug: add tool create command
2 parents 735bb9d + 51184b6 commit 910955d

File tree

6 files changed

+293
-11
lines changed

6 files changed

+293
-11
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,52 @@ spec:
12201220
./maestro workflow deploy agent-config.yaml workflow-config.yaml --dry-run
12211221
```
12221222

1223+
## Tool Management
1224+
1225+
The Maestro CLI provides commands for creating Tools for agents.
1226+
1227+
### Create Tool
1228+
1229+
```bash
1230+
# Create tool from YAML
1231+
./maestro tool create tool-config.yaml
1232+
1233+
# Test without creating (dry run)
1234+
./maestro tool create tool-config.yaml --dry-run
1235+
```
1236+
1237+
The command automatically:
1238+
- Sets the API version to `maestro.ai4quantum.com/v1alpha1`
1239+
- Sanitizes resource names for Kubernetes compatibility
1240+
- Processes workflow-specific fields for proper deployment
1241+
1242+
### Tool Examples
1243+
Tool example defined in yaml format is:
1244+
```yaml
1245+
apiVersion: maestro/v1alpha1
1246+
kind: MCPTool
1247+
metadata:
1248+
name: fetch
1249+
namespace: default
1250+
spec:
1251+
image: ghcr.io/stackloklabs/gofetch/server:latest
1252+
transport: streamable-http
1253+
```
1254+
The syntax of the agent definition is defined in the [json schema](https://github.com/AI4quantum/maestro/blob/main/schemas/tool_schema.json).
1255+
The schema is same as ToolHive CRD definition except `apiVersion` and `kind`.
1256+
Maestro deploy MCP servers for the defined tools. The available tools are listed by [ToolHive `thv list`](https://docs.stacklok.com/toolhive/reference/cli/thv_list) command.
1257+
1258+
- **apiVersion**: version of agent definition format. This must be `maestro/v1alpha1` now.
1259+
- **kind**: type of object. `MCPTool` for agent definition
1260+
- **metadata**:
1261+
- **name**: name of tool
1262+
- **labels**: array of key, value pairs. This is optional and can be used to associate any information to this agent
1263+
- **spec**:
1264+
- **image**: Image is the container image for the MCP server. The image location is in [`thv registry info [server] [flags]`](https://docs.stacklok.com/toolhive/reference/cli/thv_registry_info) output
1265+
- **transport**: Transport is the transport method for the MCP server (stdio, streamable-http, sse)
1266+
1267+
The full schema is documeted in [ToolHive Docs](https://docs.stacklok.com/toolhive/reference/crd-spec)
1268+
12231269
## Custom Resource Management
12241270

12251271
The CLI provides commands for creating Kubernetes custom resources:

docs/USER_GUIDE.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Welcome to the Maestro CLI! This guide will help you get started with managing v
1313
- [Document Management](#document-management)
1414
- [Agent Management](#agent-management)
1515
- [Workflow Management](#workflow-management)
16+
- [Tool Management](#tool-management)
1617
- [Custom Resource Management](#custom-resource-management)
1718
- [Mermaid Diagram Generation](#mermaid-diagram-generation)
1819
- [Validation](#validation)
@@ -357,6 +358,52 @@ spec:
357358
./maestro workflow deploy agent-config.yaml workflow-config.yaml --dry-run
358359
```
359360

361+
## Tool Management
362+
363+
The Maestro CLI provides commands for creating Tools for agents.
364+
365+
### Create Tool
366+
367+
```bash
368+
# Create tool from YAML
369+
./maestro tool create tool-config.yaml
370+
371+
# Test without creating (dry run)
372+
./maestro tool create tool-config.yaml --dry-run
373+
```
374+
375+
The command automatically:
376+
- Sets the API version to `maestro.ai4quantum.com/v1alpha1`
377+
- Sanitizes resource names for Kubernetes compatibility
378+
- Processes workflow-specific fields for proper deployment
379+
380+
### Tool Examples
381+
Tool example defined in yaml format is:
382+
```yaml
383+
apiVersion: maestro/v1alpha1
384+
kind: MCPTool
385+
metadata:
386+
name: fetch
387+
namespace: default
388+
spec:
389+
image: ghcr.io/stackloklabs/gofetch/server:latest
390+
transport: streamable-http
391+
```
392+
The syntax of the agent definition is defined in the [json schema](https://github.com/AI4quantum/maestro/blob/main/schemas/tool_schema.json).
393+
The schema is same as ToolHive CRD definition except `apiVersion` and `kind`.
394+
Maestro deploy MCP servers for the defined tools. The available tools are listed by [ToolHive `thv list`](https://docs.stacklok.com/toolhive/reference/cli/thv_list) command.
395+
396+
- **apiVersion**: version of agent definition format. This must be `maestro/v1alpha1` now.
397+
- **kind**: type of object. `MCPTool` for agent definition
398+
- **metadata**:
399+
- **name**: name of tool
400+
- **labels**: array of key, value pairs. This is optional and can be used to associate any information to this agent
401+
- **spec**:
402+
- **image**: Image is the container image for the MCP server. The image location is in [`thv registry info [server] [flags]`](https://docs.stacklok.com/toolhive/reference/cli/thv_registry_info) output
403+
- **transport**: Transport is the transport method for the MCP server (stdio, streamable-http, sse)
404+
405+
The full schema is documeted in [ToolHive Docs](https://docs.stacklok.com/toolhive/reference/crd-spec)
406+
360407
## Custom Resource Management
361408

362409
The Maestro CLI provides commands for creating Kubernetes custom resources for agents and workflows.

internal/commands/create.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,63 @@ func (c *CreateCommand) createAgentsFromYAML(agentsYaml []common.YAMLDocument) e
146146
}
147147

148148
// createMCPToolsFromYAML creates MCP tools from the YAML configuration
149-
func (c *CreateCommand) createMCPToolsFromYAML(agentsYaml []common.YAMLDocument) error {
150-
// In the Python implementation, this calls create_mcptools from maestro.mcptool
151-
// We'll need to implement the equivalent functionality in Go
152-
149+
func (c *CreateCommand) createMCPToolsFromYAML(toolsYaml []common.YAMLDocument) error {
153150
// For now, we'll just print a message
154151
c.Console().Ok("Creating MCP tools from YAML configuration")
155152

156-
// TODO: Implement the actual MCP tool creation logic
157-
// This would involve:
158-
// 1. Parsing the tool definitions
159-
// 2. Creating the tool instances
160-
// 3. Registering them with the system
153+
// Get MCP server URI
154+
serverURI, err := common.GetMaestroMCPServerURI(c.mcpServerURI)
155+
if err != nil {
156+
if common.Progress != nil {
157+
common.Progress.StopWithError("Failed to get MCP server URI")
158+
}
159+
return err
160+
}
161+
162+
if common.Verbose {
163+
fmt.Printf("Connecting to MCP server at: %s\n", serverURI)
164+
}
165+
166+
// Create MCP client
167+
client, _ := common.NewMCPClient(serverURI)
168+
if err != nil {
169+
if common.Progress != nil {
170+
common.Progress.StopWithError("Failed to create MCP client")
171+
}
172+
return err
173+
}
174+
defer client.Close()
175+
176+
if common.Progress != nil {
177+
common.Progress.Update("Executing create tools...")
178+
}
179+
180+
// Call the run_workflow tool
181+
tool_strings, err := common.YamlToString(toolsYaml)
182+
if err != nil {
183+
fmt.Println("tool file error")
184+
}
185+
186+
params := map[string]interface{}{
187+
"tools": tool_strings,
188+
}
189+
190+
result, err := client.CallMCPServer("create_tools", params)
191+
if err != nil {
192+
if common.Progress != nil {
193+
common.Progress.StopWithError("Create tool failed")
194+
}
195+
return err
196+
}
197+
198+
if common.Progress != nil {
199+
common.Progress.Stop("Create tools completed successfully")
200+
}
201+
202+
if !common.Silent {
203+
fmt.Println("OK")
204+
}
205+
fmt.Println(result)
161206

162207
return nil
163208
}

src/commands.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@ var agentCmd = &cobra.Command{
354354
Short: "Manage agents",
355355
Long: `Manage agent including creating, and serving.`,
356356
Aliases: []string{"agent"},
357-
Example: ` maestro create agents.yaml
358-
maestro agent create agents.yaml`,
357+
Example: ` maestro agent create agents.yaml
358+
maestro agent serve agents.yaml`,
359359
}
360360

361361
// Workflow commands - run, serve, deploy
@@ -369,6 +369,15 @@ var workflowCmd = &cobra.Command{
369369
maestro workflow deploy agents.yaml workflow.yaml`,
370370
}
371371

372+
// Tool commands - create
373+
var toolCmd = &cobra.Command{
374+
Use: "tool",
375+
Short: "Manage tools",
376+
Long: `Manage tool including creating.`,
377+
Aliases: []string{"tool"},
378+
Example: ` maestro tool create tools.yaml`,
379+
}
380+
372381
// CustomResource commands - create
373382
var customResourceCmd = &cobra.Command{
374383
Use: "customresource",

src/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ A command-line interface for working with Maestro configurations.`,
124124
)
125125
rootCmd.AddCommand(agentCmd)
126126
rootCmd.AddCommand(workflowCmd)
127+
rootCmd.AddCommand(toolCmd)
127128
rootCmd.AddCommand(customResourceCmd)
128129
rootCmd.AddCommand(metaAgentCmd)
129130
rootCmd.AddCommand(deprecatedCreateCmd)
@@ -181,6 +182,8 @@ A command-line interface for working with Maestro configurations.`,
181182
workflowCmd.AddCommand(commands.NewRunCommand())
182183
workflowCmd.AddCommand(commands.NewDeployCommand())
183184

185+
toolCmd.AddCommand(commands.NewCreateCommand())
186+
184187
customResourceCmd.AddCommand(commands.NewCreateCrCommand())
185188

186189
// Chunking
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Package tool contains integration tests for the tool command
2+
//
3+
// Test Assumptions and Limitations:
4+
// 1. These tests are integration tests that execute the CLI commands as external processes
5+
// 2. Tests use the --dry-run flag when possible to avoid actual resource creation
6+
// 3. Tests are designed to be resilient to different environments (CI, local dev)
7+
// 4. The tests focus on command execution rather than specific output validation
8+
// 5. Some tests may pass even if the underlying functionality has issues, as they
9+
// primarily test that the command doesn't panic or crash
10+
package tool
11+
12+
import (
13+
"os"
14+
"os/exec"
15+
"strings"
16+
"testing"
17+
)
18+
19+
// TestToolCreate tests the tool create command
20+
func TestToolCreate(t *testing.T) {
21+
// Create a valid YAML file for testing
22+
validYAML := `---
23+
apiVersion: maestro/v1alpha1
24+
kind: MCPTool
25+
metadata:
26+
name: test-tool
27+
spec:
28+
description: "Test tool for unit tests"
29+
parameters:
30+
- name: param1
31+
description: "A test parameter"
32+
required: true
33+
type: string
34+
returns:
35+
description: "Test return value"
36+
type: string
37+
`
38+
39+
tempFile := createTempFile(t, "valid-tool-*.yaml", validYAML)
40+
defer os.Remove(tempFile)
41+
42+
cmd := exec.Command("../../../maestro", "tool", "create", tempFile, "--dry-run")
43+
output, err := cmd.CombinedOutput()
44+
45+
outputStr := string(output)
46+
47+
// This test is expected to fail if no MCP server is running
48+
if err != nil {
49+
// Check if the error is due to MCP server not being available
50+
if strings.Contains(outputStr, "MCP server could not be reached") {
51+
t.Logf("Test skipped: No MCP server running (expected): %s", outputStr)
52+
return
53+
}
54+
t.Fatalf("Tool create command failed with unexpected error: %v, output: %s", err, string(output))
55+
}
56+
57+
if !strings.Contains(outputStr, "Creating MCP tools from YAML configuration") {
58+
t.Errorf("Should show MCP tools creation message, got: %s", outputStr)
59+
}
60+
}
61+
62+
// TestToolCreateWithNonExistentFile tests with non-existent file
63+
func TestToolCreateWithNonExistentFile(t *testing.T) {
64+
cmd := exec.Command("../../../maestro", "tool", "create", "nonexistent.yaml")
65+
output, err := cmd.CombinedOutput()
66+
67+
outputStr := string(output)
68+
69+
// Should fail with non-existent file
70+
if err == nil {
71+
t.Error("Tool create command should fail with non-existent file")
72+
}
73+
74+
if !strings.Contains(outputStr, "no such file or directory") {
75+
t.Errorf("Error message should mention file not found, got: %s", outputStr)
76+
}
77+
}
78+
79+
// TestToolCreateWithInvalidYAML tests with invalid YAML
80+
func TestToolCreateWithInvalidYAML(t *testing.T) {
81+
// Create an invalid YAML file
82+
invalidYAML := `---
83+
apiVersion: maestro/v1alpha1
84+
kind: MCPTool
85+
metadata:
86+
name: test-tool
87+
spec:
88+
description: "Test tool with invalid YAML
89+
parameters:
90+
- name: param1
91+
description: "A test parameter"
92+
required: true
93+
type: string
94+
`
95+
96+
tempFile := createTempFile(t, "invalid-tool-*.yaml", invalidYAML)
97+
defer os.Remove(tempFile)
98+
99+
cmd := exec.Command("../../../maestro", "tool", "create", tempFile)
100+
output, err := cmd.CombinedOutput()
101+
102+
outputStr := string(output)
103+
104+
// Should fail with invalid YAML
105+
if err == nil {
106+
t.Error("Tool create command should fail with invalid YAML")
107+
}
108+
109+
if !strings.Contains(outputStr, "no valid YAML documents found") {
110+
t.Errorf("Error message should mention YAML parsing error, got: %s", outputStr)
111+
}
112+
}
113+
114+
// Helper function to create a temporary file with content
115+
func createTempFile(t *testing.T, pattern string, content string) string {
116+
tmpfile, err := os.CreateTemp("", pattern)
117+
if err != nil {
118+
t.Fatalf("Failed to create temp file: %v", err)
119+
}
120+
121+
if _, err := tmpfile.Write([]byte(content)); err != nil {
122+
t.Fatalf("Failed to write to temp file: %v", err)
123+
}
124+
125+
if err := tmpfile.Close(); err != nil {
126+
t.Fatalf("Failed to close temp file: %v", err)
127+
}
128+
129+
return tmpfile.Name()
130+
}
131+
132+
// Made with Bob

0 commit comments

Comments
 (0)