Skip to content

Commit f503a06

Browse files
authored
Merge pull request #1 from FreePeak/fix/add-paramter-tools
Fix/add paramter tools
2 parents 8fd8f52 + 2f79450 commit f503a06

File tree

14 files changed

+287
-2
lines changed

14 files changed

+287
-2
lines changed

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,30 @@ func main() {
8989
),
9090
)
9191

92-
// Add the tool to the server with a handler
92+
// Example of a tool with array parameter
93+
arrayExampleTool := tools.NewTool("array_example",
94+
tools.WithDescription("Example tool with array parameter"),
95+
tools.WithArray("values",
96+
tools.Description("Array of string values"),
97+
tools.Required(),
98+
tools.Items(map[string]interface{}{
99+
"type": "string",
100+
}),
101+
),
102+
)
103+
104+
// Add the tools to the server with handlers
93105
ctx := context.Background()
94106
err := mcpServer.AddTool(ctx, echoTool, handleEcho)
95107
if err != nil {
96108
logger.Fatalf("Error adding tool: %v", err)
97109
}
98110

111+
err = mcpServer.AddTool(ctx, arrayExampleTool, handleArrayExample)
112+
if err != nil {
113+
logger.Fatalf("Error adding array example tool: %v", err)
114+
}
115+
99116
// Write server status to stderr instead of stdout to maintain clean JSON protocol
100117
fmt.Fprintf(os.Stderr, "Starting Echo Server...\n")
101118
fmt.Fprintf(os.Stderr, "Send JSON-RPC messages via stdin to interact with the server.\n")
@@ -126,6 +143,26 @@ func handleEcho(ctx context.Context, request server.ToolCallRequest) (interface{
126143
},
127144
}, nil
128145
}
146+
147+
// Array example tool handler
148+
func handleArrayExample(ctx context.Context, request server.ToolCallRequest) (interface{}, error) {
149+
// Extract the values parameter
150+
values, ok := request.Parameters["values"].([]interface{})
151+
if !ok {
152+
return nil, fmt.Errorf("missing or invalid 'values' parameter")
153+
}
154+
155+
// Convert values to string array
156+
stringValues := make([]string, len(values))
157+
for i, v := range values {
158+
stringValues[i] = v.(string)
159+
}
160+
161+
// Return the array response in the format expected by the MCP protocol
162+
return map[string]interface{}{
163+
"content": stringValues,
164+
}, nil
165+
}
129166
```
130167

131168
## What is MCP?

agent-sdk-test

8.69 MB
Binary file not shown.

array-parameter-test

7.86 MB
Binary file not shown.

examples/agent-sdk-test/main.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"os/signal"
9+
"syscall"
10+
"time"
11+
12+
"github.com/FreePeak/cortex/pkg/server"
13+
"github.com/FreePeak/cortex/pkg/tools"
14+
)
15+
16+
func main() {
17+
// Create a logger that writes to stderr
18+
logger := log.New(os.Stderr, "[agent-sdk-test] ", log.LstdFlags)
19+
20+
// Create the server
21+
mcpServer := server.NewMCPServer("Agent SDK Test", "1.0.0", logger)
22+
23+
// Configure HTTP address
24+
mcpServer.SetAddress(":9095")
25+
26+
// Create a tool with array parameter (compatible with OpenAI Agent SDK)
27+
queryTool := tools.NewTool("query_database",
28+
tools.WithDescription("Execute SQL query on a database"),
29+
tools.WithString("query",
30+
tools.Description("SQL query to execute"),
31+
tools.Required(),
32+
),
33+
tools.WithArray("params",
34+
tools.Description("Query parameters"),
35+
tools.Items(map[string]interface{}{
36+
"type": "string",
37+
}),
38+
),
39+
)
40+
41+
// Add tool to the server
42+
ctx := context.Background()
43+
err := mcpServer.AddTool(ctx, queryTool, handleQuery)
44+
if err != nil {
45+
logger.Fatalf("Error adding tool: %v", err)
46+
}
47+
48+
// Start HTTP server in a goroutine
49+
go func() {
50+
logger.Printf("Starting Agent SDK Test server on %s", mcpServer.GetAddress())
51+
logger.Printf("Use the following URL in your OpenAI Agent SDK configuration: http://localhost:9095/sse")
52+
53+
if err := mcpServer.ServeHTTP(); err != nil {
54+
logger.Fatalf("HTTP server error: %v", err)
55+
}
56+
}()
57+
58+
// Wait for shutdown signal
59+
stop := make(chan os.Signal, 1)
60+
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
61+
<-stop
62+
63+
// Shutdown gracefully
64+
logger.Println("Shutting down server...")
65+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
66+
defer cancel()
67+
68+
if err := mcpServer.Shutdown(shutdownCtx); err != nil {
69+
logger.Fatalf("Server shutdown error: %v", err)
70+
}
71+
72+
logger.Println("Server shutdown complete")
73+
}
74+
75+
// Handler for the query tool
76+
func handleQuery(ctx context.Context, request server.ToolCallRequest) (interface{}, error) {
77+
// Extract the query parameter
78+
query, ok := request.Parameters["query"].(string)
79+
if !ok {
80+
return nil, fmt.Errorf("missing or invalid 'query' parameter")
81+
}
82+
83+
// Get optional parameters
84+
var params []interface{}
85+
if paramsVal, ok := request.Parameters["params"]; ok {
86+
params, _ = paramsVal.([]interface{})
87+
}
88+
89+
// In a real implementation, you would execute the query with the parameters
90+
// For this example, we'll just return mock data
91+
92+
// Log the request
93+
log.Printf("Query received: %s", query)
94+
log.Printf("Parameters: %v", params)
95+
96+
// Return a mock response
97+
return map[string]interface{}{
98+
"content": []map[string]interface{}{
99+
{
100+
"type": "text",
101+
"text": fmt.Sprintf("Executed query: %s\nParameters: %v\n\nID\tName\tValue\n1\tItem1\t100\n2\tItem2\t200", query, params),
102+
},
103+
},
104+
}, nil
105+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"os"
9+
10+
"github.com/FreePeak/cortex/pkg/server"
11+
"github.com/FreePeak/cortex/pkg/tools"
12+
"github.com/FreePeak/cortex/pkg/types"
13+
)
14+
15+
func main() {
16+
// Create a logger that writes to stderr
17+
logger := log.New(os.Stderr, "[array-test] ", log.LstdFlags)
18+
19+
// Create the server
20+
mcpServer := server.NewMCPServer("Array Parameter Test", "1.0.0", logger)
21+
22+
// Create a tool with array parameter
23+
arrayTool := tools.NewTool("array_test",
24+
tools.WithDescription("Test tool with array parameter"),
25+
tools.WithArray("string_array",
26+
tools.Description("Array of strings"),
27+
tools.Required(),
28+
tools.Items(map[string]interface{}{
29+
"type": "string",
30+
}),
31+
),
32+
tools.WithArray("number_array",
33+
tools.Description("Array of numbers"),
34+
tools.Items(map[string]interface{}{
35+
"type": "number",
36+
}),
37+
),
38+
)
39+
40+
// Add the tool to the server
41+
ctx := context.Background()
42+
err := mcpServer.AddTool(ctx, arrayTool, handleArrayTest)
43+
if err != nil {
44+
logger.Fatalf("Error adding tool: %v", err)
45+
}
46+
47+
// Print tool schema for debugging
48+
printToolSchema(arrayTool)
49+
50+
// Write server status to stderr
51+
fmt.Fprintf(os.Stderr, "Starting Array Parameter Test Server...\n")
52+
fmt.Fprintf(os.Stderr, "Send JSON-RPC messages via stdin to interact with the server.\n")
53+
fmt.Fprintf(os.Stderr, `Try: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"array_test","parameters":{"string_array":["a","b","c"]}}}\n`)
54+
55+
// Serve over stdio
56+
if err := mcpServer.ServeStdio(); err != nil {
57+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
58+
os.Exit(1)
59+
}
60+
}
61+
62+
// Handler for the array test tool
63+
func handleArrayTest(ctx context.Context, request server.ToolCallRequest) (interface{}, error) {
64+
// Extract the string array parameter
65+
stringArray, ok := request.Parameters["string_array"].([]interface{})
66+
if !ok {
67+
return nil, fmt.Errorf("missing or invalid 'string_array' parameter")
68+
}
69+
70+
// Get the optional number array parameter
71+
var numberArray []interface{}
72+
if val, ok := request.Parameters["number_array"]; ok {
73+
numberArray, _ = val.([]interface{})
74+
}
75+
76+
// Return the arrays in the response
77+
return map[string]interface{}{
78+
"content": []map[string]interface{}{
79+
{
80+
"type": "text",
81+
"text": fmt.Sprintf("Received string array: %v\nReceived number array: %v", stringArray, numberArray),
82+
},
83+
},
84+
}, nil
85+
}
86+
87+
// Print the tool schema
88+
func printToolSchema(tool *types.Tool) {
89+
schema := map[string]interface{}{
90+
"type": "object",
91+
"properties": make(map[string]interface{}),
92+
}
93+
94+
for _, param := range tool.Parameters {
95+
paramSchema := map[string]interface{}{
96+
"type": param.Type,
97+
"description": param.Description,
98+
}
99+
100+
if param.Type == "array" && param.Items != nil {
101+
paramSchema["items"] = param.Items
102+
}
103+
104+
schema["properties"].(map[string]interface{})[param.Name] = paramSchema
105+
}
106+
107+
schemaJSON, _ := json.MarshalIndent(schema, "", " ")
108+
fmt.Fprintf(os.Stderr, "Tool schema:\n%s\n", schemaJSON)
109+
}

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
88
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
99
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1010
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
12+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
1113
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
1214
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
1315
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
1416
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1518
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1619
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1720
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/domain/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type ToolParameter struct {
5050
Description string
5151
Type string
5252
Required bool
53+
Items map[string]interface{}
5354
}
5455

5556
// ToolCall represents a request to execute a tool.

internal/infrastructure/logging/logger.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type Config struct {
4040
Development bool
4141
OutputPaths []string
4242
InitialFields Fields
43+
LogToFile bool
44+
LogDir string
4345
}
4446

4547
// DefaultConfig returns a default configuration for the logger
@@ -105,7 +107,10 @@ func getStdioSafeOutputs() []string {
105107
// Create a log file in the logs directory
106108
logsDir := "logs"
107109
if _, err := os.Stat(logsDir); os.IsNotExist(err) {
108-
os.MkdirAll(logsDir, 0755)
110+
if err := os.MkdirAll(logsDir, 0755); err != nil {
111+
// If we can't create the directory, just use stderr
112+
return []string{"stderr"}
113+
}
109114
}
110115

111116
// Create a timestamped log file

internal/interfaces/rest/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,12 @@ func (s *MCPServer) processToolsList(ctx context.Context, request domain.JSONRPC
358358
"type": param.Type,
359359
"description": param.Description,
360360
}
361+
362+
// Add items schema for array parameters
363+
if param.Type == "array" && param.Items != nil {
364+
paramObj["items"] = param.Items
365+
}
366+
361367
properties[param.Name] = paramObj
362368

363369
if param.Required {

internal/interfaces/stdio/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ func (p *MessageProcessor) handleToolsList(ctx context.Context, params interface
440440
"type": param.Type,
441441
"description": param.Description,
442442
}
443+
444+
// Add items schema for array parameters
445+
if param.Type == "array" && param.Items != nil {
446+
paramObj["items"] = param.Items
447+
}
448+
443449
properties[param.Name] = paramObj
444450

445451
if param.Required {

0 commit comments

Comments
 (0)