Skip to content

Commit 9241fa6

Browse files
committed
add mcp-client for testing purpose
1 parent 5bc6619 commit 9241fa6

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

client.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//go:build client
2+
3+
// A simple mcp-client that can be used to test the mcp server.
4+
package main
5+
6+
import (
7+
"bufio"
8+
"encoding/json"
9+
"fmt"
10+
"os"
11+
"os/exec"
12+
"strings"
13+
"time"
14+
)
15+
16+
type MCPMessage struct {
17+
JSONRPC string `json:"jsonrpc"`
18+
ID int `json:"id"`
19+
Method string `json:"method,omitempty"`
20+
Params interface{} `json:"params,omitempty"`
21+
}
22+
23+
func main() {
24+
if len(os.Args) < 2 {
25+
fmt.Println("Usage: go run mcp-client.go <your-mcp-server-binary> [args...]")
26+
fmt.Println("Example: go run mcp-client.go ./my-mcp-server")
27+
fmt.Println("Example: go run mcp-client.go node my-server.js")
28+
os.Exit(1)
29+
}
30+
31+
// Start your MCP server
32+
args := os.Args[2:] // Skip program name and server binary
33+
cmd := exec.Command(os.Args[1], args...)
34+
35+
stdin, err := cmd.StdinPipe()
36+
if err != nil {
37+
fmt.Printf("❌ Error creating stdin pipe: %v\n", err)
38+
return
39+
}
40+
41+
stdout, err := cmd.StdoutPipe()
42+
if err != nil {
43+
fmt.Printf("❌ Error creating stdout pipe: %v\n", err)
44+
return
45+
}
46+
47+
stderr, err := cmd.StderrPipe()
48+
if err != nil {
49+
fmt.Printf("❌ Error creating stderr pipe: %v\n", err)
50+
return
51+
}
52+
53+
fmt.Printf("Starting MCP server: %s %s\n", os.Args[1], strings.Join(args, " "))
54+
if err := cmd.Start(); err != nil {
55+
fmt.Printf("❌ Error starting server: %v\n", err)
56+
return
57+
}
58+
59+
// Handle server logs (stderr)
60+
go func() {
61+
scanner := bufio.NewScanner(stderr)
62+
for scanner.Scan() {
63+
fmt.Printf("Server Stderr: %s\n", scanner.Text())
64+
}
65+
}()
66+
67+
// Handle server responses (stdout)
68+
responseChan := make(chan string)
69+
go func() {
70+
scanner := bufio.NewScanner(stdout)
71+
for scanner.Scan() {
72+
line := scanner.Text()
73+
if strings.TrimSpace(line) != "" {
74+
responseChan <- line
75+
}
76+
}
77+
close(responseChan)
78+
}()
79+
80+
messageID := 1
81+
reader := bufio.NewReader(os.Stdin)
82+
83+
for {
84+
fmt.Println("\n" + strings.Repeat("=", 60))
85+
fmt.Println(" MCP Client - Choose an action:")
86+
fmt.Println("1. Initialize connection")
87+
fmt.Println("2. List available tools")
88+
fmt.Println("3. Calculate 3 + 3")
89+
fmt.Println("4. Calculate custom expression")
90+
fmt.Println("5. Send custom JSON-RPC message")
91+
fmt.Println("6. Exit")
92+
fmt.Println(strings.Repeat("=", 60))
93+
94+
fmt.Print("Enter choice (1-6): ")
95+
choice, _ := reader.ReadString('\n')
96+
choice = strings.TrimSpace(choice)
97+
98+
var message MCPMessage
99+
100+
switch choice {
101+
case "1":
102+
message = MCPMessage{
103+
JSONRPC: "2.0",
104+
ID: messageID,
105+
Method: "initialize",
106+
Params: map[string]interface{}{
107+
"protocolVersion": "2024-11-05",
108+
"capabilities": map[string]interface{}{},
109+
"clientInfo": map[string]string{
110+
"name": "test-client",
111+
"version": "1.0.0",
112+
},
113+
},
114+
}
115+
116+
case "2":
117+
message = MCPMessage{
118+
JSONRPC: "2.0",
119+
ID: messageID,
120+
Method: "tools/list",
121+
Params: map[string]interface{}{},
122+
}
123+
124+
case "3":
125+
message = MCPMessage{
126+
JSONRPC: "2.0",
127+
ID: messageID,
128+
Method: "tools/call",
129+
Params: map[string]interface{}{
130+
"name": "local__calculator__calculate",
131+
"arguments": map[string]string{
132+
"expression": "3 + 3",
133+
},
134+
},
135+
}
136+
137+
case "4":
138+
fmt.Print("Enter mathematical expression: ")
139+
expr, _ := reader.ReadString('\n')
140+
expr = strings.TrimSpace(expr)
141+
142+
message = MCPMessage{
143+
JSONRPC: "2.0",
144+
ID: messageID,
145+
Method: "tools/call",
146+
Params: map[string]interface{}{
147+
"name": "local__calculator__calculate",
148+
"arguments": map[string]string{
149+
"expression": expr,
150+
},
151+
},
152+
}
153+
154+
case "5":
155+
fmt.Print("Enter JSON-RPC message: ")
156+
jsonMsg, _ := reader.ReadString('\n')
157+
jsonMsg = strings.TrimSpace(jsonMsg)
158+
159+
// Send raw JSON
160+
fmt.Println("\nSENDING RAW JSON:")
161+
fmt.Println(jsonMsg)
162+
stdin.Write([]byte(jsonMsg + "\n"))
163+
164+
// Wait for response
165+
waitForResponse(responseChan)
166+
continue
167+
168+
case "6":
169+
fmt.Println("Shutting down...")
170+
stdin.Close()
171+
cmd.Process.Kill()
172+
return
173+
174+
default:
175+
fmt.Println("Invalid choice")
176+
continue
177+
}
178+
179+
// Send message to server
180+
fmt.Println("\nSENDING TO SERVER:")
181+
prettyJSON, _ := json.MarshalIndent(message, "", " ")
182+
fmt.Println(string(prettyJSON))
183+
184+
jsonData, _ := json.Marshal(message)
185+
fmt.Printf("\nRAW JSON PAYLOAD:\n%s\n", string(jsonData))
186+
187+
stdin.Write(append(jsonData, '\n'))
188+
messageID++
189+
190+
// Wait for response
191+
waitForResponse(responseChan)
192+
}
193+
}
194+
195+
func waitForResponse(responseChan chan string) {
196+
fmt.Println("\nWaiting for server response...")
197+
198+
select {
199+
case response, ok := <-responseChan:
200+
if !ok {
201+
fmt.Println("Server closed connection")
202+
return
203+
}
204+
205+
fmt.Println("\nSERVER RESPONSE:")
206+
207+
// Try to pretty print JSON
208+
var jsonData interface{}
209+
if err := json.Unmarshal([]byte(response), &jsonData); err == nil {
210+
prettyJSON, _ := json.MarshalIndent(jsonData, "", " ")
211+
fmt.Println(string(prettyJSON))
212+
} else {
213+
fmt.Printf("Raw response: %s\n", response)
214+
}
215+
216+
case <-time.After(5 * time.Second):
217+
fmt.Println("Timeout waiting for response")
218+
}
219+
}

main.go renamed to server.go

File renamed without changes.

0 commit comments

Comments
 (0)