Skip to content

Commit b2ac4ea

Browse files
- Added robot test MCP HTTP Server Run List Tools.
- Added robot test `MCP HTTP Server Verify Greeting Tool`. - Added robot test `MCP HTTP Server List Providers Tool`.
1 parent ff9bbe6 commit b2ac4ea

File tree

6 files changed

+138
-45
lines changed

6 files changed

+138
-45
lines changed

mcp_client/cmd/exec.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ import (
2323
"github.com/stackql/stackql/pkg/mcp_server"
2424
)
2525

26+
var (
27+
actionName string // overwritten by flag
28+
actionArgs string // overwritten by flag
29+
)
30+
31+
const (
32+
listToolsAction = "list_tools"
33+
listProvidersAction = "list_providers"
34+
)
35+
2636
// execCmd represents the exec command.
2737
//
2838
//nolint:gochecknoglobals // cobra pattern
@@ -40,15 +50,31 @@ var execCmd = &cobra.Command{
4050
if setupErr != nil {
4151
panic(fmt.Sprintf("error setting up mcp client: %v", setupErr))
4252
}
43-
rv, rvErr := client.InspectTools()
44-
if rvErr != nil {
45-
panic(fmt.Sprintf("error inspecting tools: %v", rvErr))
46-
}
47-
output, outPutErr := json.MarshalIndent(rv, "", " ")
48-
if outPutErr != nil {
49-
panic(fmt.Sprintf("error marshaling output: %v", outPutErr))
53+
var outputString string
54+
switch actionName {
55+
case listToolsAction:
56+
rv, rvErr := client.InspectTools()
57+
if rvErr != nil {
58+
panic(fmt.Sprintf("error inspecting tools: %v", rvErr))
59+
}
60+
output, outPutErr := json.MarshalIndent(rv, "", " ")
61+
if outPutErr != nil {
62+
panic(fmt.Sprintf("error marshaling output: %v", outPutErr))
63+
}
64+
outputString = string(output)
65+
default:
66+
var args map[string]any
67+
jsonErr := json.Unmarshal([]byte(actionArgs), &args)
68+
if jsonErr != nil {
69+
panic(fmt.Sprintf("error unmarshaling action args: %v", jsonErr))
70+
}
71+
rv, rvErr := client.CallToolText(actionName, args)
72+
if rvErr != nil {
73+
panic(fmt.Sprintf("error calling tool %s: %v", actionName, rvErr))
74+
}
75+
outputString = rv
5076
}
5177
//nolint:forbidigo // legacy
52-
fmt.Println(string(output))
78+
fmt.Println(outputString)
5379
},
5480
}

mcp_client/cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ func init() {
7979

8080
rootCmd.CompletionOptions.DisableDefaultCmd = true
8181
rootCmd.AddCommand(execCmd)
82+
execCmd.PersistentFlags().StringVar(&actionName, "exec.action", "list_tools", "MCP server action name")
83+
execCmd.PersistentFlags().StringVar(&actionArgs, "exec.args", "{}", "MCP server action arguments as JSON string")
8284
}
8385

8486
// initConfig reads in config file and ENV variables if set.

pkg/mcp_server/client.go

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818

1919
type MCPClient interface {
2020
InspectTools() ([]map[string]any, error)
21+
CallToolText(toolName string, args map[string]any) (string, error)
2122
}
2223

2324
func NewMCPClient(clientType string, baseURL string, logger *logrus.Logger) (MCPClient, error) {
@@ -49,7 +50,7 @@ type httpMCPClient struct {
4950
logger *logrus.Logger
5051
}
5152

52-
func (c *httpMCPClient) InspectTools() ([]map[string]any, error) {
53+
func (c *httpMCPClient) connect() (*mcp.ClientSession, error) {
5354
url := c.baseURL
5455
ctx := context.Background()
5556

@@ -63,17 +64,26 @@ func (c *httpMCPClient) InspectTools() ([]map[string]any, error) {
6364
}, nil)
6465

6566
// Connect to the server.
66-
session, err := client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: url}, nil)
67+
return client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: url}, nil)
68+
}
69+
70+
func (c *httpMCPClient) connectOrDie() *mcp.ClientSession {
71+
session, err := c.connect()
6772
if err != nil {
6873
c.logger.Fatalf("Failed to connect: %v", err)
6974
}
75+
return session
76+
}
77+
78+
func (c *httpMCPClient) InspectTools() ([]map[string]any, error) {
79+
session := c.connectOrDie()
7080
defer session.Close()
7181

7282
c.logger.Infof("Connected to server (session ID: %s)", session.ID())
7383

7484
// First, list available tools.
7585
c.logger.Infof("Listing available tools...")
76-
toolsResult, err := session.ListTools(ctx, nil)
86+
toolsResult, err := session.ListTools(context.Background(), nil)
7787
if err != nil {
7888
c.logger.Fatalf("Failed to list tools: %v", err)
7989
}
@@ -87,33 +97,42 @@ func (c *httpMCPClient) InspectTools() ([]map[string]any, error) {
8797
rv = append(rv, toolInfo)
8898
}
8999

90-
// Call the cityTime tool for each city.
91-
cities := []string{"nyc", "sf", "boston"}
92-
93-
c.logger.Println("Getting time for each city...")
94-
for _, city := range cities {
95-
// Call the tool.
96-
result, resultErr := session.CallTool(ctx, &mcp.CallToolParams{
97-
Name: "cityTime",
98-
Arguments: map[string]any{
99-
"city": city,
100-
},
101-
})
102-
if resultErr != nil {
103-
c.logger.Infof("Failed to get time for %s: %v\n", city, resultErr)
104-
continue
105-
}
100+
c.logger.Infof("Client completed successfully")
101+
return rv, nil
102+
}
106103

107-
// Print the result.
108-
for _, content := range result.Content {
109-
if textContent, ok := content.(*mcp.TextContent); ok {
110-
c.logger.Infof(" %s", textContent.Text)
111-
}
112-
}
104+
func (c *httpMCPClient) callTool(toolName string, args map[string]any) (*mcp.CallToolResult, error) {
105+
session := c.connectOrDie()
106+
defer session.Close()
107+
108+
c.logger.Infof("Connected to server (session ID: %s)", session.ID())
109+
110+
c.logger.Infof("Calling tool %s...", toolName)
111+
result, err := session.CallTool(context.Background(), &mcp.CallToolParams{
112+
Name: toolName,
113+
Arguments: args,
114+
})
115+
if err != nil {
116+
c.logger.Errorf("Failed to call tool %s: %v\n", toolName, err)
117+
return result, err
113118
}
114119

115120
c.logger.Infof("Client completed successfully")
116-
return rv, nil
121+
return result, nil
122+
}
123+
124+
func (c *httpMCPClient) CallToolText(toolName string, args map[string]any) (string, error) {
125+
toolCall, toolCallErr := c.callTool(toolName, args)
126+
if toolCallErr != nil {
127+
return "", toolCallErr
128+
}
129+
var result string
130+
for _, content := range toolCall.Content {
131+
if textContent, ok := content.(*mcp.TextContent); ok {
132+
result += textContent.Text + "\n"
133+
}
134+
}
135+
return result, nil
117136
}
118137

119138
type stdioMCPClient struct {
@@ -134,3 +153,8 @@ func (c *stdioMCPClient) InspectTools() ([]map[string]any, error) {
134153
c.logger.Infof("stdio MCP client not implemented yet")
135154
return nil, nil
136155
}
156+
157+
func (c *stdioMCPClient) CallToolText(toolName string, args map[string]any) (string, error) {
158+
c.logger.Infof("stdio MCP client not implemented yet")
159+
return "", nil
160+
}

pkg/mcp_server/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func newMCPServer(config *Config, backend Backend, logger *logrus.Logger) (MCPSe
127127
Name: "server_info",
128128
Description: "Get server information",
129129
},
130-
func(ctx context.Context, req *mcp.CallToolRequest, args GreetInput) (*mcp.CallToolResult, ServerInfoOutput, error) {
130+
func(ctx context.Context, req *mcp.CallToolRequest, args any) (*mcp.CallToolResult, ServerInfoOutput, error) {
131131
rv, rvErr := backend.ServerInfo(ctx, args)
132132
if rvErr != nil {
133133
return nil, ServerInfoOutput{}, rvErr
@@ -141,7 +141,7 @@ func newMCPServer(config *Config, backend Backend, logger *logrus.Logger) (MCPSe
141141
Name: "db_identity",
142142
Description: "get current database identity",
143143
},
144-
func(ctx context.Context, req *mcp.CallToolRequest, args GreetInput) (*mcp.CallToolResult, map[string]any, error) {
144+
func(ctx context.Context, req *mcp.CallToolRequest, args any) (*mcp.CallToolResult, map[string]any, error) {
145145
rv, rvErr := backend.DBIdentity(ctx, args)
146146
if rvErr != nil {
147147
return nil, nil, rvErr

test/python/stackql_test_tooling/stackql_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,7 @@ def get_registry_mock_url(execution_env :str) -> str:
941941
'REGISTRY_DEPRECATED_CFG_STR': _REGISTRY_DEPRECATED,
942942
'REGISTRY_MOCKED_CFG_STR': get_registry_mocked(execution_env),
943943
'REGISTRY_NO_VERIFY_CFG_STR': _get_registry_no_verify(_sundry_config.get('registry_path')),
944+
'REGISTRY_NO_VERIFY_CFG_JSON_STR': _get_registry_no_verify(_sundry_config.get('registry_path')).get_config_str(execution_environment=execution_env),
944945
'REGISTRY_NULL': _REGISTRY_NULL,
945946
'REPOSITORY_ROOT': repository_root,
946947
'SQL_BACKEND_CFG_STR_ANALYTICS': get_analytics_sql_backend(execution_env, sql_backend_str),

test/robot/functional/mcp.robot

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,62 @@
11
*** Settings ***
2-
Resource ${CURDIR}/stackql.resource
2+
Resource ${CURDIR}${/}stackql.resource
33

4-
*** Test Cases ***
5-
MCP HTTP Server List Tools
6-
Pass Execution If "%{IS_SKIP_MCP_TEST=false}" == "true" Debian testing does not have the MCP client available
7-
${serverProcess}= Start Process ${STACKQL_EXE}
4+
5+
*** Keywords ***
6+
Start MCP HTTP Server
7+
Pass Execution If "%{IS_SKIP_MCP_TEST=false}" == "true" Some platforms do not have the MCP client available
8+
Start Process ${STACKQL_EXE}
89
... mcp
9-
... \-\-mcp.server.type\=http
10+
... \-\-mcp.server.type\=http
1011
... \-\-mcp.config
1112
... {"server": {"transport": "http", "address": "127.0.0.1:9912"} }
13+
... \-\-registry
14+
... ${REGISTRY_NO_VERIFY_CFG_JSON_STR}
15+
Sleep 5s
16+
17+
*** Settings ***
18+
Suite Setup Start MCP HTTP Server
19+
20+
21+
*** Test Cases ***
22+
MCP HTTP Server Run List Tools
23+
Pass Execution If "%{IS_SKIP_MCP_TEST=false}" == "true" Some platforms do not have the MCP client available
1224
Sleep 5s
1325
${result}= Run Process ${STACKQL_MCP_CLIENT_EXE}
1426
... exec
1527
... \-\-client\-type\=http
1628
... \-\-url\=http://127.0.0.1:9912
17-
... stdout=${CURDIR}/tmp/MCP-HTTP-Server-List-Tools.txt
18-
... stderr=${CURDIR}/tmp/MCP-HTTP-Server-List-Tools-stderr.txt
29+
... stdout=${CURDIR}${/}tmp${/}MCP-HTTP-Server-Run-List-Tools.txt
30+
... stderr=${CURDIR}${/}tmp${/}MCP-HTTP-Server-Run-List-Tools-stderr.txt
1931
Should Contain ${result.stdout} Get server information
2032
Should Be Equal As Integers ${result.rc} 0
21-
Terminate Process ${serverProcess} kill=True
33+
34+
35+
MCP HTTP Server Verify Greeting Tool
36+
Pass Execution If "%{IS_SKIP_MCP_TEST=false}" == "true" Some platforms do not have the MCP client available
37+
Sleep 5s
38+
${result}= Run Process ${STACKQL_MCP_CLIENT_EXE}
39+
... exec
40+
... \-\-client\-type\=http
41+
... \-\-url\=http://127.0.0.1:9912
42+
... \-\-exec.action greet
43+
... \-\-exec.args {"name": "JOE BLOW"}
44+
... stdout=${CURDIR}${/}tmp${/}MCP-HTTP-Server-Verify-Greeting-Tool.txt
45+
... stderr=${CURDIR}${/}tmp${/}MCP-HTTP-Server-Verify-Greeting-Tool-stderr.txt
46+
Should Contain ${result.stdout} JOE BLOW
47+
Should Be Equal As Integers ${result.rc} 0
48+
49+
50+
MCP HTTP Server List Providers Tool
51+
Pass Execution If "%{IS_SKIP_MCP_TEST=false}" == "true" Some platforms do not have the MCP client available
52+
Sleep 5s
53+
${result}= Run Process ${STACKQL_MCP_CLIENT_EXE}
54+
... exec
55+
... \-\-client\-type\=http
56+
... \-\-url\=http://127.0.0.1:9912
57+
... \-\-exec.action list_providers
58+
... stdout=${CURDIR}${/}tmp${/}MCP-HTTP-Server-List-Providers.txt
59+
... stderr=${CURDIR}${/}tmp${/}MCP-HTTP-Server-List-Providers-stderr.txt
60+
Should Contain ${result.stdout} local_openssl
61+
Should Be Equal As Integers ${result.rc} 0
2262

0 commit comments

Comments
 (0)