Files
patterm/internal/harness/mcp_client.go

83 lines
1.6 KiB
Go

package harness
import (
"bufio"
"encoding/json"
"fmt"
"net"
"sync"
)
type MCPClient struct {
conn net.Conn
r *bufio.Reader
mu sync.Mutex
next int
}
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
func (e *RPCError) Error() string {
return fmt.Sprintf("json-rpc error %d: %s", e.Code, e.Message)
}
func DialMCP(socket string) (*MCPClient, error) {
conn, err := net.Dial("unix", socket)
if err != nil {
return nil, err
}
if _, err := conn.Write([]byte("{\"patterm_identity\":\"harness\"}\n")); err != nil {
_ = conn.Close()
return nil, err
}
return &MCPClient{conn: conn, r: bufio.NewReader(conn)}, nil
}
func (c *MCPClient) Close() error {
if c == nil || c.conn == nil {
return nil
}
return c.conn.Close()
}
func (c *MCPClient) Call(method string, params any) (json.RawMessage, error) {
c.mu.Lock()
defer c.mu.Unlock()
c.next++
req := map[string]any{
"jsonrpc": "2.0",
"id": c.next,
"method": method,
}
if params != nil {
req["params"] = params
}
b, err := json.Marshal(req)
if err != nil {
return nil, err
}
b = append(b, '\n')
if _, err := c.conn.Write(b); err != nil {
return nil, err
}
line, err := c.r.ReadBytes('\n')
if err != nil {
return nil, err
}
var resp struct {
Result json.RawMessage `json:"result"`
Error *RPCError `json:"error"`
}
if err := json.Unmarshal(line, &resp); err != nil {
return nil, fmt.Errorf("parse response: %w: %s", err, string(line))
}
if resp.Error != nil {
return nil, resp.Error
}
return resp.Result, nil
}