mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
feat: add typed MCP 2026-07-28 server
This commit is contained in:
parent
14cc617575
commit
d032a5354e
16
mcp/server20260728/doc.go
Normal file
16
mcp/server20260728/doc.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Package mcpserver implements the stateless MCP 2026-07-28 protocol.
|
||||
//
|
||||
// The HTTP transport checks Mcp-Method and Mcp-Name before decoding the JSON
|
||||
// body. Registered handlers receive concrete Go parameter and result types;
|
||||
// application code does not need a JSON Schema validator or map[string]any.
|
||||
//
|
||||
// A typical server registers typed tools and resources:
|
||||
//
|
||||
// srv := &mcpserver.MCPServer{Name: "example", Version: "1"}
|
||||
// mcpserver.RegisterTypedTool(srv, mcptypes.Tool{Name: "echo"}, func(ctx context.Context, headers mcpserver.Headers, args EchoArgs) (mcptypes.CallToolResult, error) {
|
||||
// return TextResult(args.Message), nil
|
||||
// })
|
||||
//
|
||||
// Use the request headers for transport-level routing and custom tool headers;
|
||||
// use typed request decoding for the JSON parameters.
|
||||
package mcpserver
|
||||
284
mcp/server20260728/server.go
Normal file
284
mcp/server20260728/server.go
Normal file
@ -0,0 +1,284 @@
|
||||
// Package mcpserver implements the stateless MCP 2026-07-28 server.
|
||||
//
|
||||
// HTTP headers are inspected before the JSON body is decoded. Mcp-Method and
|
||||
// Mcp-Name select the protocol route; x-mcp-header values are made available
|
||||
// to the selected handler.
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
mcptypes "github.com/therootcompany/golib/mcp/types20260728"
|
||||
)
|
||||
|
||||
const ProtocolVersion = mcptypes.ProtocolVersion
|
||||
|
||||
type Headers struct {
|
||||
Method string
|
||||
Name string
|
||||
Custom map[string][]string
|
||||
}
|
||||
|
||||
func headersFromHTTP(h http.Header) Headers {
|
||||
custom := make(map[string][]string)
|
||||
for key, values := range h {
|
||||
if strings.EqualFold(key, "x-mcp-header") {
|
||||
custom["x-mcp-header"] = append([]string(nil), values...)
|
||||
}
|
||||
}
|
||||
return Headers{Method: h.Get("Mcp-Method"), Name: h.Get("Mcp-Name"), Custom: custom}
|
||||
}
|
||||
|
||||
type CallToolRequest struct {
|
||||
Headers Headers
|
||||
Params mcptypes.CallToolParams
|
||||
}
|
||||
type ReadResourceRequest struct {
|
||||
Headers Headers
|
||||
Params mcptypes.ReadResourceParams
|
||||
}
|
||||
type ToolHandlerFunc func(context.Context, CallToolRequest) (mcptypes.CallToolResult, error)
|
||||
type ResourceHandlerFunc func(context.Context, ReadResourceRequest) (mcptypes.ReadResourceResult, error)
|
||||
|
||||
// RegisterTypedTool decodes a tool's arguments into T before calling handler.
|
||||
// T is the application Go type for the tool input; no JSON Schema validator is
|
||||
// involved.
|
||||
func RegisterTypedTool[T any](s *MCPServer, definition mcptypes.Tool, handler func(context.Context, Headers, T) (mcptypes.CallToolResult, error)) {
|
||||
s.RegisterTool(definition, func(ctx context.Context, request CallToolRequest) (mcptypes.CallToolResult, error) {
|
||||
args, err := mcptypes.Decode[T](request.Params.Arguments)
|
||||
if err != nil {
|
||||
return mcptypes.CallToolResult{}, fmt.Errorf("decode %s arguments: %w", definition.Name, err)
|
||||
}
|
||||
return handler(ctx, request.Headers, args)
|
||||
})
|
||||
}
|
||||
|
||||
type MCPServer struct {
|
||||
Name string
|
||||
Title string
|
||||
Version string
|
||||
Instructions string
|
||||
Capabilities mcptypes.ServerCapabilities
|
||||
AllowedOrigins []string
|
||||
mu sync.RWMutex
|
||||
tools []registeredTool
|
||||
resources []registeredResource
|
||||
}
|
||||
type registeredTool struct {
|
||||
definition mcptypes.Tool
|
||||
handler ToolHandlerFunc
|
||||
}
|
||||
type registeredResource struct {
|
||||
definition mcptypes.Resource
|
||||
handler ResourceHandlerFunc
|
||||
}
|
||||
|
||||
func (s *MCPServer) RegisterTool(definition mcptypes.Tool, handler ToolHandlerFunc) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, tool := range s.tools {
|
||||
if tool.definition.Name == definition.Name {
|
||||
panic("mcp: duplicate tool: " + definition.Name)
|
||||
}
|
||||
}
|
||||
s.tools = append(s.tools, registeredTool{definition, handler})
|
||||
}
|
||||
func (s *MCPServer) RegisterResource(definition mcptypes.Resource, handler ResourceHandlerFunc) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for _, resource := range s.resources {
|
||||
if resource.definition.URI == definition.URI {
|
||||
panic("mcp: duplicate resource: " + definition.URI)
|
||||
}
|
||||
}
|
||||
s.resources = append(s.resources, registeredResource{definition, 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
|
||||
}
|
||||
if r.Header.Get("Mcp-Method") == "" {
|
||||
s.writeError(w, nil, mcptypes.ErrHeaderMismatch, "missing Mcp-Method header")
|
||||
return
|
||||
}
|
||||
if origin := r.Header.Get("Origin"); origin != "" && !contains(s.AllowedOrigins, origin) {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, 10<<20+1))
|
||||
if err != nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(body) > 10<<20 {
|
||||
http.Error(w, "Request Entity Too Large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
result := s.handle(r.Context(), headersFromHTTP(r.Header), body)
|
||||
if result == nil {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (s *MCPServer) handle(ctx context.Context, headers Headers, body []byte) any {
|
||||
var envelope struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *mcptypes.RequestID `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &envelope); err != nil {
|
||||
return s.error(nil, mcptypes.ErrParse, "Parse error")
|
||||
}
|
||||
if headers.Method != envelope.Method {
|
||||
return s.error(envelope.ID, mcptypes.ErrHeaderMismatch, "Mcp-Method does not match request method")
|
||||
}
|
||||
var request mcptypes.Request
|
||||
if err := json.Unmarshal(body, &request); err != nil || request.JSONRPC != mcptypes.JSONRPCVersion || request.Method == "" || (request.IDPresent && (request.ID == nil || request.ID.IsNull())) {
|
||||
return s.error(envelope.ID, mcptypes.ErrInvalidRequest, "Invalid JSON-RPC request")
|
||||
}
|
||||
if request.IsNotification() {
|
||||
return nil
|
||||
}
|
||||
switch request.Method {
|
||||
case "server/discover":
|
||||
return s.discover(envelope.ID)
|
||||
case "tools/list":
|
||||
return s.toolsList(envelope.ID)
|
||||
case "tools/call":
|
||||
return s.toolsCall(ctx, headers, envelope.ID, envelope.Params)
|
||||
case "resources/list":
|
||||
return s.resourcesList(envelope.ID)
|
||||
case "resources/read":
|
||||
return s.resourceRead(ctx, headers, envelope.ID, envelope.Params)
|
||||
default:
|
||||
return s.error(envelope.ID, mcptypes.ErrMethodNotFound, fmt.Sprintf("Method not found: %s", request.Method))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) discover(id *mcptypes.RequestID) any {
|
||||
value := mcptypes.DiscoverResult{ResultType: "complete", ProtocolVersions: []string{ProtocolVersion}, Capabilities: s.capabilities(), ServerInfo: mcptypes.ServerInfo{Name: s.Name, Title: s.Title, Version: s.Version}, Instructions: s.Instructions}
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Failed to encode result")
|
||||
}
|
||||
return s.result(id, raw)
|
||||
}
|
||||
func (s *MCPServer) capabilities() mcptypes.ServerCapabilities {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
value := s.Capabilities
|
||||
if value.Tools == nil && len(s.tools) != 0 {
|
||||
value.Tools = &mcptypes.ToolsCapability{}
|
||||
}
|
||||
if value.Resources == nil && len(s.resources) != 0 {
|
||||
value.Resources = &mcptypes.ResourcesCapability{}
|
||||
}
|
||||
return value
|
||||
}
|
||||
func (s *MCPServer) toolsList(id *mcptypes.RequestID) any {
|
||||
s.mu.RLock()
|
||||
tools := make([]mcptypes.Tool, len(s.tools))
|
||||
for i := range s.tools {
|
||||
tools[i] = s.tools[i].definition
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
raw, err := json.Marshal(mcptypes.ToolsListResult{ResultType: "complete", Tools: tools, CacheScope: mcptypes.CachePrivate})
|
||||
if err != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Failed to encode result")
|
||||
}
|
||||
return s.result(id, raw)
|
||||
}
|
||||
func (s *MCPServer) toolsCall(ctx context.Context, headers Headers, id *mcptypes.RequestID, raw json.RawMessage) any {
|
||||
params, err := mcptypes.Decode[mcptypes.CallToolParams](raw)
|
||||
if err != nil || params.Name == "" {
|
||||
return s.error(id, mcptypes.ErrInvalidParams, "Invalid params for tools/call")
|
||||
}
|
||||
s.mu.RLock()
|
||||
var handler ToolHandlerFunc
|
||||
for _, tool := range s.tools {
|
||||
if tool.definition.Name == params.Name {
|
||||
handler = tool.handler
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
if handler == nil {
|
||||
return s.error(id, mcptypes.ErrInvalidParams, "Unknown tool")
|
||||
}
|
||||
value, err := handler(ctx, CallToolRequest{headers, params})
|
||||
if err != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Tool execution failed")
|
||||
}
|
||||
encoded, encodeErr := json.Marshal(value)
|
||||
if encodeErr != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Failed to encode result")
|
||||
}
|
||||
return s.result(id, encoded)
|
||||
}
|
||||
func (s *MCPServer) resourcesList(id *mcptypes.RequestID) any {
|
||||
s.mu.RLock()
|
||||
resources := make([]mcptypes.Resource, len(s.resources))
|
||||
for i := range s.resources {
|
||||
resources[i] = s.resources[i].definition
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
raw, err := json.Marshal(mcptypes.ResourcesListResult{ResultType: "complete", Resources: resources, CacheScope: mcptypes.CachePrivate})
|
||||
if err != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Failed to encode result")
|
||||
}
|
||||
return s.result(id, raw)
|
||||
}
|
||||
func (s *MCPServer) resourceRead(ctx context.Context, headers Headers, id *mcptypes.RequestID, raw json.RawMessage) any {
|
||||
params, err := mcptypes.Decode[mcptypes.ReadResourceParams](raw)
|
||||
if err != nil || params.URI == "" {
|
||||
return s.error(id, mcptypes.ErrInvalidParams, "Invalid params for resources/read")
|
||||
}
|
||||
s.mu.RLock()
|
||||
var handler ResourceHandlerFunc
|
||||
for _, resource := range s.resources {
|
||||
if resource.definition.URI == params.URI {
|
||||
handler = resource.handler
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
if handler == nil {
|
||||
return s.error(id, mcptypes.ErrInvalidParams, "Unknown resource")
|
||||
}
|
||||
value, err := handler(ctx, ReadResourceRequest{headers, params})
|
||||
if err != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Resource read failed")
|
||||
}
|
||||
encoded, encodeErr := json.Marshal(value)
|
||||
if encodeErr != nil {
|
||||
return s.error(id, mcptypes.ErrInternal, "Failed to encode result")
|
||||
}
|
||||
return s.result(id, encoded)
|
||||
}
|
||||
|
||||
func (s *MCPServer) result(id *mcptypes.RequestID, value json.RawMessage) mcptypes.Response {
|
||||
return mcptypes.Response{JSONRPC: mcptypes.JSONRPCVersion, ID: id, Result: value}
|
||||
}
|
||||
func (s *MCPServer) error(id *mcptypes.RequestID, code int, message string) mcptypes.ErrorResponse {
|
||||
return mcptypes.ErrorResponse{JSONRPC: mcptypes.JSONRPCVersion, ID: id, Error: mcptypes.ErrorObject{Code: code, Message: message}}
|
||||
}
|
||||
func (s *MCPServer) writeError(w http.ResponseWriter, id *mcptypes.RequestID, code int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(s.error(id, code, message))
|
||||
}
|
||||
func contains(values []string, target string) bool {
|
||||
return slices.Contains(values, target)
|
||||
}
|
||||
60
mcp/server20260728/server_test.go
Normal file
60
mcp/server20260728/server_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mcptypes "github.com/therootcompany/golib/mcp/types20260728"
|
||||
)
|
||||
|
||||
type echoArgs struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func TestServeHTTPUsesHeadersBeforeTypedDecode(t *testing.T) {
|
||||
var got Headers
|
||||
var gotArgs echoArgs
|
||||
srv := &MCPServer{Name: "test", Version: "1"}
|
||||
RegisterTypedTool(srv, mcptypes.Tool{Name: "echo", InputSchema: json.RawMessage(`{"type":"object"}`)}, func(_ context.Context, headers Headers, args echoArgs) (mcptypes.CallToolResult, error) {
|
||||
got = headers
|
||||
gotArgs = args
|
||||
return mcptypes.CallToolResult{ResultType: "complete", Content: []mcptypes.Content{{Type: "text", Text: args.Message}}}, nil
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/mcp", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"message":"hello"}}}`))
|
||||
req.Header.Set("Mcp-Method", "tools/call")
|
||||
req.Header.Set("Mcp-Name", "echo")
|
||||
req.Header.Add("x-mcp-header", "trace-id")
|
||||
res := httptest.NewRecorder()
|
||||
srv.ServeHTTP(res, req)
|
||||
if res.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200: %s", res.Code, res.Body)
|
||||
}
|
||||
if got.Method != "tools/call" || got.Name != "echo" || len(got.Custom["x-mcp-header"]) != 1 {
|
||||
t.Fatalf("headers = %#v", got)
|
||||
}
|
||||
if gotArgs.Message != "hello" {
|
||||
t.Fatalf("args = %#v", gotArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeHTTPRejectsHeaderMethodMismatch(t *testing.T) {
|
||||
srv := &MCPServer{}
|
||||
req := httptest.NewRequest(http.MethodPost, "/mcp", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"tools/list"}`))
|
||||
req.Header.Set("Mcp-Method", "tools/call")
|
||||
res := httptest.NewRecorder()
|
||||
srv.ServeHTTP(res, req)
|
||||
if res.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200 JSON-RPC error", res.Code)
|
||||
}
|
||||
var response mcptypes.ErrorResponse
|
||||
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.Error.Code != mcptypes.ErrHeaderMismatch {
|
||||
t.Fatalf("code = %d, want %d", response.Error.Code, mcptypes.ErrHeaderMismatch)
|
||||
}
|
||||
}
|
||||
254
mcp/types20260728/types.go
Normal file
254
mcp/types20260728/types.go
Normal file
@ -0,0 +1,254 @@
|
||||
// Package mcptypes defines the typed MCP 2026-07-28 wire model.
|
||||
//
|
||||
// Core protocol values use Go structs. json.RawMessage is used only where the
|
||||
// specification deliberately permits arbitrary JSON (extensions, JSON Schema,
|
||||
// and structured tool content).
|
||||
package mcptypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
JSONRPCVersion = "2.0"
|
||||
ProtocolVersion = "2026-07-28"
|
||||
)
|
||||
|
||||
type RequestID struct{ value json.RawMessage }
|
||||
|
||||
func NewStringID(value string) RequestID {
|
||||
return RequestID{value: json.RawMessage(fmt.Sprintf("%q", value))}
|
||||
}
|
||||
func NewNumberID(value json.Number) RequestID {
|
||||
return RequestID{value: json.RawMessage(value.String())}
|
||||
}
|
||||
func (id RequestID) IsNull() bool { return len(id.value) == 0 || bytes.Equal(id.value, []byte("null")) }
|
||||
func (id RequestID) MarshalJSON() ([]byte, error) {
|
||||
if id.IsNull() {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return id.value, nil
|
||||
}
|
||||
func (id *RequestID) UnmarshalJSON(data []byte) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
var value json.RawMessage
|
||||
if err := decoder.Decode(&value); err != nil {
|
||||
return fmt.Errorf("invalid JSON-RPC id: %w", err)
|
||||
}
|
||||
if len(value) == 0 {
|
||||
return fmt.Errorf("invalid JSON-RPC id")
|
||||
}
|
||||
switch value[0] {
|
||||
case '"', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
id.value = append(id.value[:0], value...)
|
||||
return nil
|
||||
case 'n':
|
||||
if bytes.Equal(value, []byte("null")) {
|
||||
id.value = []byte("null")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("JSON-RPC id must be a string, number, or null")
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *RequestID `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params,omitempty"`
|
||||
Meta RequestMeta `json:"_meta"`
|
||||
IDPresent bool `json:"-"`
|
||||
}
|
||||
|
||||
func (r *Request) UnmarshalJSON(data []byte) error {
|
||||
type alias Request
|
||||
var decoded alias
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return err
|
||||
}
|
||||
*r = Request(decoded)
|
||||
_, r.IDPresent = fields["id"]
|
||||
return nil
|
||||
}
|
||||
func (r Request) IsNotification() bool { return !r.IDPresent }
|
||||
|
||||
type RequestMeta struct {
|
||||
ProtocolVersion string `json:"io.modelcontextprotocol/protocolVersion,omitempty"`
|
||||
ClientCapabilities ClientCapabilities `json:"io.modelcontextprotocol/clientCapabilities"`
|
||||
ClientInfo *ClientInfo `json:"io.modelcontextprotocol/clientInfo,omitempty"`
|
||||
LogLevel *LogLevel `json:"io.modelcontextprotocol/logLevel,omitempty"`
|
||||
Extensions map[string]json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
type ResultMeta struct {
|
||||
ServerInfo *ServerInfo `json:"io.modelcontextprotocol/serverInfo,omitempty"`
|
||||
SubscriptionID string `json:"io.modelcontextprotocol/subscriptionId,omitempty"`
|
||||
Extensions map[string]json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *RequestID `json:"id"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
type ErrorResponse struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *RequestID `json:"id"`
|
||||
Error ErrorObject `json:"error"`
|
||||
}
|
||||
type ErrorObject struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
ErrParse = -32700
|
||||
ErrInvalidRequest = -32600
|
||||
ErrMethodNotFound = -32601
|
||||
ErrInvalidParams = -32602
|
||||
ErrInternal = -32603
|
||||
ErrHeaderMismatch = -32020
|
||||
ErrMissingRequiredClientCapability = -32021
|
||||
ErrUnsupportedProtocolVersion = -32022
|
||||
)
|
||||
|
||||
type ClientInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
type ServerInfo struct {
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
type ClientCapabilities struct {
|
||||
Extensions map[string]json.RawMessage `json:"extensions,omitempty"`
|
||||
}
|
||||
type ServerCapabilities struct {
|
||||
Extensions map[string]json.RawMessage `json:"extensions,omitempty"`
|
||||
Prompts *PromptsCapability `json:"prompts,omitempty"`
|
||||
Resources *ResourcesCapability `json:"resources,omitempty"`
|
||||
Tools *ToolsCapability `json:"tools,omitempty"`
|
||||
}
|
||||
type PromptsCapability struct {
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
type ResourcesCapability struct {
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
type ToolsCapability struct {
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
type LogLevel string
|
||||
|
||||
// Method parameter and result types.
|
||||
type DiscoverParams struct{}
|
||||
type DiscoverResult struct {
|
||||
ResultType string `json:"resultType"`
|
||||
ProtocolVersions []string `json:"protocolVersions"`
|
||||
Capabilities ServerCapabilities `json:"capabilities"`
|
||||
ServerInfo ServerInfo `json:"serverInfo"`
|
||||
Instructions string `json:"instructions,omitempty"`
|
||||
Meta ResultMeta `json:"_meta"`
|
||||
}
|
||||
type PaginatedParams struct {
|
||||
Cursor string `json:"cursor,omitempty"`
|
||||
}
|
||||
type ToolsListResult struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Tools []Tool `json:"tools"`
|
||||
NextCursor string `json:"nextCursor,omitempty"`
|
||||
TTLMS int64 `json:"ttlMs"`
|
||||
CacheScope CacheScope `json:"cacheScope"`
|
||||
Meta ResultMeta `json:"_meta"`
|
||||
}
|
||||
type ResourcesListResult struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Resources []Resource `json:"resources"`
|
||||
NextCursor string `json:"nextCursor,omitempty"`
|
||||
TTLMS int64 `json:"ttlMs"`
|
||||
CacheScope CacheScope `json:"cacheScope"`
|
||||
Meta ResultMeta `json:"_meta"`
|
||||
}
|
||||
type ReadResourceParams struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
type ReadResourceResult struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Contents []ResourceContents `json:"contents"`
|
||||
TTLMS int64 `json:"ttlMs"`
|
||||
CacheScope CacheScope `json:"cacheScope"`
|
||||
Meta ResultMeta `json:"_meta"`
|
||||
}
|
||||
type CallToolParams struct {
|
||||
Name string `json:"name"`
|
||||
Arguments json.RawMessage `json:"arguments,omitempty"`
|
||||
}
|
||||
type CallToolResult struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Content []Content `json:"content"`
|
||||
StructuredContent json.RawMessage `json:"structuredContent,omitempty"`
|
||||
IsError bool `json:"isError,omitempty"`
|
||||
Meta ResultMeta `json:"_meta"`
|
||||
}
|
||||
type CacheScope string
|
||||
|
||||
const (
|
||||
CachePublic CacheScope = "public"
|
||||
CachePrivate CacheScope = "private"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
Meta json.RawMessage `json:"_meta,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
|
||||
Annotations *ToolAnnotations `json:"annotations,omitempty"`
|
||||
}
|
||||
type ToolAnnotations struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
|
||||
DestructiveHint *bool `json:"destructiveHint,omitempty"`
|
||||
IdempotentHint *bool `json:"idempotentHint,omitempty"`
|
||||
OpenWorldHint *bool `json:"openWorldHint,omitempty"`
|
||||
}
|
||||
type Resource struct {
|
||||
Meta json.RawMessage `json:"_meta,omitempty"`
|
||||
URI string `json:"uri"`
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
MIMEType string `json:"mimeType,omitempty"`
|
||||
Size *int64 `json:"size,omitempty"`
|
||||
}
|
||||
type ResourceContents struct {
|
||||
Meta json.RawMessage `json:"_meta,omitempty"`
|
||||
URI string `json:"uri"`
|
||||
MIMEType string `json:"mimeType,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Blob string `json:"blob,omitempty"`
|
||||
}
|
||||
type Content struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
MIMEType string `json:"mimeType,omitempty"`
|
||||
}
|
||||
|
||||
func Decode[T any](data json.RawMessage) (T, error) {
|
||||
var value T
|
||||
if len(bytes.TrimSpace(data)) == 0 {
|
||||
data = []byte("{}")
|
||||
}
|
||||
err := json.Unmarshal(data, &value)
|
||||
return value, err
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user