Files

158 lines
3.8 KiB
Go
Raw Permalink Normal View History

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)
Rich func(ctx context.Context, args json.RawMessage) Result
}
type Content struct {
Type string `json:"type"`
Text string `json:"text"`
Data string `json:"data"`
MimeType string `json:"mimeType"`
}
func (c Content) MarshalJSON() ([]byte, error) {
if c.Type == "image" {
return json.Marshal(map[string]string{"type": c.Type, "data": c.Data, "mimeType": c.MimeType})
}
return json.Marshal(map[string]string{"type": c.Type, "text": c.Text})
}
type Result struct {
Content []Content `json:"content"`
IsError bool `json:"isError,omitempty"`
}
func TextResult(text string, isError bool) Result {
return Result{Content: []Content{{Type: "text", Text: text}}, IsError: isError}
}
func (t Tool) Call(ctx context.Context, args json.RawMessage) Result {
if t.Rich != nil {
return t.Rich(ctx, args)
}
text, err := t.Handler(ctx, args)
if err != nil {
return TextResult(err.Error(), true)
}
return TextResult(text, false)
}
type Server struct {
Name string
Version string
Tools []Tool
}
type Stdio struct {
Name string
Command string
Args []string
}
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"`
}
func (s *Server) Tool(name string) (Tool, bool) {
for _, t := range s.Tools {
if t.Name == name {
return t, true
}
}
return Tool{}, false
}
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()}
}
t, ok := s.Tool(p.Name)
if !ok {
return nil, &rpcError{Code: -32602, Message: fmt.Sprintf("unknown tool: %s", p.Name)}
}
return t.Call(ctx, p.Arguments), nil
}