Files
dingtalk-workspace-cli/internal/transport/testdata/stdio_test_server.go
修雨 19f2ed5c69 fix(plugin): schema flag params, HTTP tool discovery, and integration tests
- Fix schema-derived flags not being collected into tool call params
- Add registerHTTPServer for plugin streamable-http tool discovery
- Include dev plugins in ListInstalled output
- Isolate settings path for test environments
- Add stdio and HTTP MCP end-to-end integration tests
2026-04-16 09:34:52 +08:00

127 lines
3.4 KiB
Go

// Minimal MCP stdio server for integration tests.
// Implements initialize, tools/list, tools/call over newline-delimited JSON-RPC.
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
)
type request struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type response struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id"`
Result any `json:"result,omitempty"`
Error *rpcError `json:"error,omitempty"`
}
type rpcError struct {
Code int `json:"code"`
Message string `json:"message"`
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 {
continue
}
var req request
if err := json.Unmarshal(line, &req); err != nil {
writeResp(response{JSONRPC: "2.0", Error: &rpcError{Code: -32700, Message: "parse error"}})
continue
}
switch req.Method {
case "initialize":
writeResp(response{JSONRPC: "2.0", ID: req.ID, Result: map[string]any{
"protocolVersion": "2025-03-26",
"capabilities": map[string]any{"tools": map[string]any{}},
"serverInfo": map[string]any{"name": "test-server", "version": "0.0.1"},
}})
case "tools/list":
writeResp(response{JSONRPC: "2.0", ID: req.ID, Result: map[string]any{
"tools": []map[string]any{
{
"name": "test_echo",
"description": "Echo the input message",
"inputSchema": map[string]any{
"type": "object",
"properties": map[string]any{
"message": map[string]any{"type": "string", "description": "Message to echo"},
},
"required": []string{"message"},
},
},
{
"name": "test_add",
"description": "Add two numbers",
"inputSchema": map[string]any{
"type": "object",
"properties": map[string]any{
"a": map[string]any{"type": "integer"},
"b": map[string]any{"type": "integer"},
},
"required": []string{"a", "b"},
},
},
},
}})
case "tools/call":
handleCall(req.ID, req.Params)
case "notifications/initialized":
// no response
continue
default:
writeResp(response{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32601, Message: "method not found"}})
}
}
}
func handleCall(id, params json.RawMessage) {
var p struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}
if err := json.Unmarshal(params, &p); err != nil {
writeResp(response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: -32602, Message: "invalid params"}})
return
}
switch p.Name {
case "test_echo":
msg, _ := p.Arguments["message"].(string)
writeResp(response{JSONRPC: "2.0", ID: id, Result: map[string]any{
"content": []map[string]any{{"type": "text", "text": fmt.Sprintf("Echo: %s", msg)}},
}})
case "test_add":
a, _ := p.Arguments["a"].(float64)
b, _ := p.Arguments["b"].(float64)
writeResp(response{JSONRPC: "2.0", ID: id, Result: map[string]any{
"content": []map[string]any{{"type": "text", "text": fmt.Sprintf("%.0f", a+b)}},
}})
default:
writeResp(response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: -32601, Message: "unknown tool: " + p.Name}})
}
}
func writeResp(resp response) {
data, _ := json.Marshal(resp)
fmt.Fprintf(os.Stdout, "%s\n", data)
}