Skip to content

Commit e04f5f9

Browse files
committed
update
1 parent 416aecc commit e04f5f9

File tree

2 files changed

+374
-1
lines changed

2 files changed

+374
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# MCP
1+
# MCP Tools
22

33
A command-line interface for interacting with MCP (Model Context Protocol) servers using both stdio and HTTP transport.
44

cmd/mcptools/main_test.go

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"strings"
10+
"testing"
11+
12+
"github.com/f/mcptools/pkg/transport"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
// MockTransport implements the Transport interface for testing
17+
type MockTransport struct {
18+
Responses map[string]map[string]interface{}
19+
}
20+
21+
func NewMockTransport() *MockTransport {
22+
return &MockTransport{
23+
Responses: map[string]map[string]interface{}{},
24+
}
25+
}
26+
27+
func (t *MockTransport) Execute(method string, params interface{}) (map[string]interface{}, error) {
28+
if resp, ok := t.Responses[method]; ok {
29+
return resp, nil
30+
}
31+
32+
if method == "tools/list" {
33+
return map[string]interface{}{
34+
"tools": []map[string]interface{}{
35+
{
36+
"name": "test_tool",
37+
"description": "A test tool",
38+
},
39+
{
40+
"name": "another_tool",
41+
"description": "Another test tool",
42+
},
43+
},
44+
}, nil
45+
}
46+
47+
if method == "tools/call" {
48+
paramsMap := params.(map[string]interface{})
49+
toolName := paramsMap["name"].(string)
50+
return map[string]interface{}{
51+
"result": fmt.Sprintf("Called tool: %s", toolName),
52+
}, nil
53+
}
54+
55+
if method == "resources/list" {
56+
return map[string]interface{}{
57+
"resources": []map[string]interface{}{
58+
{
59+
"uri": "test_resource",
60+
"description": "A test resource",
61+
},
62+
},
63+
}, nil
64+
}
65+
66+
if method == "resources/read" {
67+
paramsMap := params.(map[string]interface{})
68+
uri := paramsMap["uri"].(string)
69+
return map[string]interface{}{
70+
"content": fmt.Sprintf("Content of resource: %s", uri),
71+
}, nil
72+
}
73+
74+
if method == "prompts/list" {
75+
return map[string]interface{}{
76+
"prompts": []map[string]interface{}{
77+
{
78+
"name": "test_prompt",
79+
"description": "A test prompt",
80+
},
81+
},
82+
}, nil
83+
}
84+
85+
if method == "prompts/get" {
86+
paramsMap := params.(map[string]interface{})
87+
promptName := paramsMap["name"].(string)
88+
return map[string]interface{}{
89+
"content": fmt.Sprintf("Content of prompt: %s", promptName),
90+
}, nil
91+
}
92+
93+
return map[string]interface{}{}, fmt.Errorf("unknown method: %s", method)
94+
}
95+
96+
// Helper function to create a root command with a mock transport
97+
func setupTestCommand() (*cobra.Command, *bytes.Buffer) {
98+
// Setup output buffer
99+
outBuf := &bytes.Buffer{}
100+
101+
// Create root command
102+
rootCmd := &cobra.Command{
103+
Use: "mcp",
104+
Short: "MCP CLI",
105+
}
106+
107+
// Create shell command
108+
shellCmd := &cobra.Command{
109+
Use: "shell",
110+
Short: "Interactive shell",
111+
Run: func(cmd *cobra.Command, args []string) {
112+
// Create mock transport
113+
mockTransport := NewMockTransport()
114+
115+
// Create shell instance
116+
shell := &Shell{
117+
Transport: mockTransport,
118+
Format: "table",
119+
Reader: strings.NewReader("tools\ncall test_tool --params '{\"foo\":\"bar\"}'\ntest_tool {\"foo\":\"bar\"}\nresource:test_resource\nprompt:test_prompt\n/q\n"),
120+
Writer: outBuf,
121+
}
122+
123+
// Run shell
124+
shell.Run()
125+
},
126+
}
127+
128+
rootCmd.AddCommand(shellCmd)
129+
130+
// Return command and output buffer
131+
return rootCmd, outBuf
132+
}
133+
134+
// Shell represents a test shell instance
135+
type Shell struct {
136+
Transport transport.Transport
137+
Format string
138+
Reader io.Reader
139+
Writer io.Writer
140+
}
141+
142+
// Run executes the shell with predefined inputs
143+
func (s *Shell) Run() {
144+
scanner := bufio.NewScanner(s.Reader)
145+
146+
for scanner.Scan() {
147+
input := scanner.Text()
148+
149+
if input == "/q" || input == "/quit" || input == "exit" {
150+
fmt.Fprintln(s.Writer, "Exiting MCP shell")
151+
break
152+
}
153+
154+
parts := strings.Fields(input)
155+
if len(parts) == 0 {
156+
continue
157+
}
158+
159+
command := parts[0]
160+
args := parts[1:]
161+
162+
switch command {
163+
case "tools":
164+
resp, _ := s.Transport.Execute("tools/list", nil)
165+
fmt.Fprintln(s.Writer, "Tools:", resp)
166+
167+
case "resources":
168+
resp, _ := s.Transport.Execute("resources/list", nil)
169+
fmt.Fprintln(s.Writer, "Resources:", resp)
170+
171+
case "prompts":
172+
resp, _ := s.Transport.Execute("prompts/list", nil)
173+
fmt.Fprintln(s.Writer, "Prompts:", resp)
174+
175+
case "call":
176+
if len(args) < 1 {
177+
fmt.Fprintln(s.Writer, "Usage: call <entity> [--params '{...}']")
178+
continue
179+
}
180+
181+
entityName := args[0]
182+
entityType := "tool"
183+
184+
parts := strings.SplitN(entityName, ":", 2)
185+
if len(parts) == 2 {
186+
entityType = parts[0]
187+
entityName = parts[1]
188+
}
189+
190+
params := map[string]interface{}{}
191+
192+
for i := 1; i < len(args); i++ {
193+
if args[i] == "--params" || args[i] == "-p" {
194+
if i+1 < len(args) {
195+
_ = json.Unmarshal([]byte(args[i+1]), &params)
196+
break
197+
}
198+
}
199+
}
200+
201+
var resp map[string]interface{}
202+
203+
switch entityType {
204+
case "tool":
205+
resp, _ = s.Transport.Execute("tools/call", map[string]interface{}{
206+
"name": entityName,
207+
"arguments": params,
208+
})
209+
case "resource":
210+
resp, _ = s.Transport.Execute("resources/read", map[string]interface{}{
211+
"uri": entityName,
212+
})
213+
case "prompt":
214+
resp, _ = s.Transport.Execute("prompts/get", map[string]interface{}{
215+
"name": entityName,
216+
})
217+
}
218+
219+
fmt.Fprintln(s.Writer, "Call result:", resp)
220+
221+
default:
222+
// Try to interpret as a direct tool call
223+
entityName := command
224+
entityType := "tool"
225+
226+
parts := strings.SplitN(entityName, ":", 2)
227+
if len(parts) == 2 {
228+
entityType = parts[0]
229+
entityName = parts[1]
230+
}
231+
232+
params := map[string]interface{}{}
233+
234+
if len(args) > 0 {
235+
firstArg := args[0]
236+
if strings.HasPrefix(firstArg, "{") && strings.HasSuffix(firstArg, "}") {
237+
_ = json.Unmarshal([]byte(firstArg), &params)
238+
} else {
239+
for i := 0; i < len(args); i++ {
240+
if args[i] == "--params" || args[i] == "-p" {
241+
if i+1 < len(args) {
242+
_ = json.Unmarshal([]byte(args[i+1]), &params)
243+
break
244+
}
245+
}
246+
}
247+
}
248+
}
249+
250+
var resp map[string]interface{}
251+
252+
switch entityType {
253+
case "tool":
254+
resp, _ = s.Transport.Execute("tools/call", map[string]interface{}{
255+
"name": entityName,
256+
"arguments": params,
257+
})
258+
fmt.Fprintln(s.Writer, "Direct tool call result:", resp)
259+
case "resource":
260+
resp, _ = s.Transport.Execute("resources/read", map[string]interface{}{
261+
"uri": entityName,
262+
})
263+
fmt.Fprintln(s.Writer, "Direct resource read result:", resp)
264+
case "prompt":
265+
resp, _ = s.Transport.Execute("prompts/get", map[string]interface{}{
266+
"name": entityName,
267+
})
268+
fmt.Fprintln(s.Writer, "Direct prompt get result:", resp)
269+
default:
270+
fmt.Fprintln(s.Writer, "Unknown command:", command)
271+
}
272+
}
273+
}
274+
}
275+
276+
// TestDirectToolCalling tests the direct tool calling functionality
277+
func TestDirectToolCalling(t *testing.T) {
278+
testCases := []struct {
279+
input string
280+
expectedOutput string
281+
}{
282+
{
283+
input: "test_tool {\"param\": \"value\"}",
284+
expectedOutput: "Called tool: test_tool",
285+
},
286+
{
287+
input: "resource:test_resource",
288+
expectedOutput: "Content of resource: test_resource",
289+
},
290+
{
291+
input: "prompt:test_prompt",
292+
expectedOutput: "Content of prompt: test_prompt",
293+
},
294+
}
295+
296+
// Create mock transport
297+
mockTransport := NewMockTransport()
298+
299+
for _, tc := range testCases {
300+
t.Run(tc.input, func(t *testing.T) {
301+
// Setup output capture
302+
outBuf := &bytes.Buffer{}
303+
304+
// Create shell with input
305+
shell := &Shell{
306+
Transport: mockTransport,
307+
Format: "table",
308+
Reader: strings.NewReader(tc.input + "\n/q\n"),
309+
Writer: outBuf,
310+
}
311+
312+
// Run shell
313+
shell.Run()
314+
315+
// Check output
316+
if !strings.Contains(outBuf.String(), tc.expectedOutput) {
317+
t.Errorf("Expected output to contain %q, got: %s", tc.expectedOutput, outBuf.String())
318+
}
319+
})
320+
}
321+
}
322+
323+
// TestExecuteShell tests the shell command execution with various commands
324+
func TestExecuteShell(t *testing.T) {
325+
// Create mock transport
326+
mockTransport := NewMockTransport()
327+
328+
// Test inputs
329+
inputs := []string{
330+
"tools",
331+
"resources",
332+
"prompts",
333+
"call test_tool --params '{\"foo\":\"bar\"}'",
334+
"test_tool {\"foo\":\"bar\"}",
335+
"resource:test_resource",
336+
"prompt:test_prompt",
337+
"/q",
338+
}
339+
340+
// Expected outputs for each input
341+
expectedOutputs := []string{
342+
"A test tool", // tools command
343+
"A test resource", // resources command
344+
"A test prompt", // prompts command
345+
"Called tool: test_tool", // call command
346+
"Called tool: test_tool", // direct tool call
347+
"Content of resource: test_resource", // direct resource read
348+
"Content of prompt: test_prompt", // direct prompt get
349+
"Exiting MCP shell", // quit command
350+
}
351+
352+
// Setup output capture
353+
outBuf := &bytes.Buffer{}
354+
355+
// Create shell with all inputs
356+
shell := &Shell{
357+
Transport: mockTransport,
358+
Format: "table",
359+
Reader: strings.NewReader(strings.Join(inputs, "\n") + "\n"),
360+
Writer: outBuf,
361+
}
362+
363+
// Run shell
364+
shell.Run()
365+
366+
// Check all expected outputs
367+
output := outBuf.String()
368+
for _, expected := range expectedOutputs {
369+
if !strings.Contains(output, expected) {
370+
t.Errorf("Expected output to contain %q, but it doesn't.\nFull output: %s", expected, output)
371+
}
372+
}
373+
}

0 commit comments

Comments
 (0)