Add Claude Code stream-json client, report gate, and terminal driver
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const protocolVersion = "2025-06-18"
|
||||
|
||||
type Tool struct {
|
||||
Name string
|
||||
Description string
|
||||
InputSchema json.RawMessage
|
||||
Handler func(ctx context.Context, args json.RawMessage) (string, error)
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Name string
|
||||
Version string
|
||||
Tools []Tool
|
||||
}
|
||||
|
||||
type request struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type rpcError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id"`
|
||||
Result any `json:"result,omitempty"`
|
||||
Error *rpcError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type toolDesc struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
}
|
||||
|
||||
type content struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type callResult struct {
|
||||
Content []content `json:"content"`
|
||||
IsError bool `json:"isError,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) Handle(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) {
|
||||
req := request{}
|
||||
err := json.Unmarshal(raw, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse jsonrpc: %w", err)
|
||||
}
|
||||
|
||||
if req.ID == nil {
|
||||
return json.Marshal(response{JSONRPC: "2.0", Result: map[string]any{}})
|
||||
}
|
||||
|
||||
result, rerr := s.dispatch(ctx, req)
|
||||
resp := response{JSONRPC: "2.0", ID: req.ID, Result: result, Error: rerr}
|
||||
return json.Marshal(resp)
|
||||
}
|
||||
|
||||
func (s *Server) dispatch(ctx context.Context, req request) (any, *rpcError) {
|
||||
switch req.Method {
|
||||
case "initialize":
|
||||
return map[string]any{
|
||||
"protocolVersion": protocolVersion,
|
||||
"capabilities": map[string]any{"tools": map[string]any{}},
|
||||
"serverInfo": map[string]any{"name": s.Name, "version": s.Version},
|
||||
}, nil
|
||||
|
||||
case "ping":
|
||||
return map[string]any{}, nil
|
||||
|
||||
case "tools/list":
|
||||
tools := []toolDesc{}
|
||||
for _, t := range s.Tools {
|
||||
tools = append(tools, toolDesc{Name: t.Name, Description: t.Description, InputSchema: t.InputSchema})
|
||||
}
|
||||
return map[string]any{"tools": tools}, nil
|
||||
|
||||
case "tools/call":
|
||||
return s.call(ctx, req.Params)
|
||||
}
|
||||
|
||||
return nil, &rpcError{Code: -32601, Message: fmt.Sprintf("method not found: %s", req.Method)}
|
||||
}
|
||||
|
||||
func (s *Server) call(ctx context.Context, params json.RawMessage) (any, *rpcError) {
|
||||
p := struct {
|
||||
Name string `json:"name"`
|
||||
Arguments json.RawMessage `json:"arguments"`
|
||||
}{}
|
||||
err := json.Unmarshal(params, &p)
|
||||
if err != nil {
|
||||
return nil, &rpcError{Code: -32602, Message: err.Error()}
|
||||
}
|
||||
|
||||
for _, t := range s.Tools {
|
||||
if t.Name != p.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
text, err := t.Handler(ctx, p.Arguments)
|
||||
if err != nil {
|
||||
return callResult{Content: []content{{Type: "text", Text: err.Error()}}, IsError: true}, nil
|
||||
}
|
||||
|
||||
return callResult{Content: []content{{Type: "text", Text: text}}}, nil
|
||||
}
|
||||
|
||||
return nil, &rpcError{Code: -32602, Message: fmt.Sprintf("unknown tool: %s", p.Name)}
|
||||
}
|
||||
Reference in New Issue
Block a user