|
package main |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"log" |
|
"net/http" |
|
"sync" |
|
) |
|
|
|
// JSON-RPC 2.0 Request |
|
type JSONRPCRequest struct { |
|
JSONRPC string `json:"jsonrpc"` |
|
Method string `json:"method"` |
|
Params json.RawMessage `json:"params,omitempty"` |
|
ID interface{} `json:"id"` |
|
} |
|
|
|
// JSON-RPC 2.0 Response |
|
type JSONRPCResponse struct { |
|
JSONRPC string `json:"jsonrpc"` |
|
Result interface{} `json:"result,omitempty"` |
|
Error *RPCError `json:"error,omitempty"` |
|
ID interface{} `json:"id"` |
|
} |
|
|
|
// JSON-RPC 2.0 Error |
|
type RPCError struct { |
|
Code int `json:"code"` |
|
Message string `json:"message"` |
|
Data interface{} `json:"data,omitempty"` |
|
} |
|
|
|
// MCP Tool Definition |
|
type Tool struct { |
|
Name string `json:"name"` |
|
Description string `json:"description"` |
|
InputSchema map[string]interface{} `json:"inputSchema"` |
|
} |
|
|
|
// MCP Server |
|
type MCPServer struct { |
|
tools map[string]Tool |
|
mu sync.RWMutex |
|
} |
|
|
|
func NewMCPServer() *MCPServer { |
|
server := &MCPServer{ |
|
tools: make(map[string]Tool), |
|
} |
|
|
|
// サンプルツールの登録 |
|
server.RegisterTool(Tool{ |
|
Name: "echo", |
|
Description: "Echo back the input message", |
|
InputSchema: map[string]interface{}{ |
|
"type": "object", |
|
"properties": map[string]interface{}{ |
|
"message": map[string]interface{}{ |
|
"type": "string", |
|
"description": "The message to echo", |
|
}, |
|
}, |
|
"required": []string{"message"}, |
|
}, |
|
}) |
|
|
|
server.RegisterTool(Tool{ |
|
Name: "add", |
|
Description: "Add two numbers", |
|
InputSchema: map[string]interface{}{ |
|
"type": "object", |
|
"properties": map[string]interface{}{ |
|
"a": map[string]interface{}{ |
|
"type": "number", |
|
"description": "First number", |
|
}, |
|
"b": map[string]interface{}{ |
|
"type": "number", |
|
"description": "Second number", |
|
}, |
|
}, |
|
"required": []string{"a", "b"}, |
|
}, |
|
}) |
|
|
|
return server |
|
} |
|
|
|
func (s *MCPServer) RegisterTool(tool Tool) { |
|
s.mu.Lock() |
|
defer s.mu.Unlock() |
|
s.tools[tool.Name] = tool |
|
} |
|
|
|
func (s *MCPServer) HandleRequest(req JSONRPCRequest) JSONRPCResponse { |
|
switch req.Method { |
|
case "tools/list": |
|
return s.handleToolsList(req) |
|
case "tools/call": |
|
return s.handleToolsCall(req) |
|
case "initialize": |
|
return s.handleInitialize(req) |
|
default: |
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Error: &RPCError{ |
|
Code: -32601, |
|
Message: "Method not found", |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
} |
|
|
|
func (s *MCPServer) handleInitialize(req JSONRPCRequest) JSONRPCResponse { |
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Result: map[string]interface{}{ |
|
"protocolVersion": "2024-11-05", |
|
"serverInfo": map[string]interface{}{ |
|
"name": "sample-mcp-server", |
|
"version": "1.0.0", |
|
}, |
|
"capabilities": map[string]interface{}{ |
|
"tools": map[string]interface{}{}, |
|
}, |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
|
|
func (s *MCPServer) handleToolsList(req JSONRPCRequest) JSONRPCResponse { |
|
s.mu.RLock() |
|
defer s.mu.RUnlock() |
|
|
|
tools := make([]Tool, 0, len(s.tools)) |
|
for _, tool := range s.tools { |
|
tools = append(tools, tool) |
|
} |
|
|
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Result: map[string]interface{}{ |
|
"tools": tools, |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
|
|
func (s *MCPServer) handleToolsCall(req JSONRPCRequest) JSONRPCResponse { |
|
var params struct { |
|
Name string `json:"name"` |
|
Arguments map[string]interface{} `json:"arguments"` |
|
} |
|
|
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil { |
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Error: &RPCError{ |
|
Code: -32602, |
|
Message: "Invalid params", |
|
Data: err.Error(), |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
|
|
// ツールの実行 |
|
result, err := s.executeTool(params.Name, params.Arguments) |
|
if err != nil { |
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Error: &RPCError{ |
|
Code: -32603, |
|
Message: "Internal error", |
|
Data: err.Error(), |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
|
|
return JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Result: map[string]interface{}{ |
|
"content": []map[string]interface{}{ |
|
{ |
|
"type": "text", |
|
"text": result, |
|
}, |
|
}, |
|
}, |
|
ID: req.ID, |
|
} |
|
} |
|
|
|
func (s *MCPServer) executeTool(name string, args map[string]interface{}) (string, error) { |
|
s.mu.RLock() |
|
_, exists := s.tools[name] |
|
s.mu.RUnlock() |
|
|
|
if !exists { |
|
return "", fmt.Errorf("tool not found: %s", name) |
|
} |
|
|
|
switch name { |
|
case "echo": |
|
message, ok := args["message"].(string) |
|
if !ok { |
|
return "", fmt.Errorf("invalid message parameter") |
|
} |
|
return message, nil |
|
|
|
case "add": |
|
a, ok1 := args["a"].(float64) |
|
b, ok2 := args["b"].(float64) |
|
if !ok1 || !ok2 { |
|
return "", fmt.Errorf("invalid number parameters") |
|
} |
|
return fmt.Sprintf("%.2f", a+b), nil |
|
|
|
default: |
|
return "", fmt.Errorf("tool execution not implemented: %s", name) |
|
} |
|
} |
|
|
|
// HTTP Handler |
|
func (s *MCPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
if r.Method != http.MethodPost { |
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) |
|
return |
|
} |
|
|
|
body, err := io.ReadAll(r.Body) |
|
if err != nil { |
|
http.Error(w, "Failed to read request body", http.StatusBadRequest) |
|
return |
|
} |
|
defer r.Body.Close() |
|
|
|
var req JSONRPCRequest |
|
if err := json.Unmarshal(body, &req); err != nil { |
|
resp := JSONRPCResponse{ |
|
JSONRPC: "2.0", |
|
Error: &RPCError{ |
|
Code: -32700, |
|
Message: "Parse error", |
|
}, |
|
ID: nil, |
|
} |
|
s.writeResponse(w, resp) |
|
return |
|
} |
|
|
|
resp := s.HandleRequest(req) |
|
s.writeResponse(w, resp) |
|
} |
|
|
|
func (s *MCPServer) writeResponse(w http.ResponseWriter, resp JSONRPCResponse) { |
|
w.Header().Set("Content-Type", "application/json") |
|
if err := json.NewEncoder(w).Encode(resp); err != nil { |
|
log.Printf("Failed to encode response: %v", err) |
|
} |
|
} |
|
|
|
func main() { |
|
server := NewMCPServer() |
|
|
|
http.Handle("/mcp", server) |
|
|
|
port := ":8080" |
|
log.Printf("MCP Server starting on %s", port) |
|
if err := http.ListenAndServe(port, nil); err != nil { |
|
log.Fatal(err) |
|
} |
|
} |