x
This commit is contained in:
55
internal/mcp/error.go
Normal file
55
internal/mcp/error.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// enum ErrorCode {
|
||||
// // Standard JSON-RPC error codes
|
||||
// ParseError = -32700,
|
||||
// InvalidRequest = -32600,
|
||||
// MethodNotFound = -32601,
|
||||
// InvalidParams = -32602,
|
||||
// InternalError = -32603
|
||||
// }
|
||||
|
||||
// Error
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
ErrParseError = &Error{Code: -32700, Message: "Parse error"}
|
||||
ErrInvalidRequest = &Error{Code: -32600, Message: "Invalid Request"}
|
||||
ErrMethodNotFound = &Error{Code: -32601, Message: "Method not found"}
|
||||
ErrInvalidParams = &Error{Code: -32602, Message: "Invalid params"}
|
||||
ErrInternalError = &Error{Code: -32603, Message: "Internal error"}
|
||||
|
||||
ErrInvalidSessionID = &Error{Code: 400, Message: "Invalid session ID"}
|
||||
ErrSessionNotFound = &Error{Code: 404, Message: "Could not find session"}
|
||||
ErrTooManyRequests = &Error{Code: 429, Message: "Too many requests"}
|
||||
)
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func (e *Error) JsonRPC() Response {
|
||||
return Response{
|
||||
JsonRPC: JsonRPCVersion,
|
||||
Error: e,
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrorResponse(id interface{}, code int, err error) *Response {
|
||||
return &Response{
|
||||
JsonRPC: JsonRPCVersion,
|
||||
ID: id,
|
||||
Error: &Error{
|
||||
Code: code,
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
78
internal/mcp/initialize.go
Normal file
78
internal/mcp/initialize.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package mcp
|
||||
|
||||
const (
|
||||
MethodInitialize = "initialize"
|
||||
MethodPing = "ping"
|
||||
ProtocolVersion = "2024-11-05"
|
||||
)
|
||||
|
||||
// {
|
||||
// "method": "initialize",
|
||||
// "params": {
|
||||
// "protocolVersion": "2024-11-05",
|
||||
// "capabilities": {
|
||||
// "sampling": {},
|
||||
// "roots": {
|
||||
// "listChanged": true
|
||||
// }
|
||||
// },
|
||||
// "clientInfo": {
|
||||
// "name": "mcp-inspector",
|
||||
// "version": "0.0.1"
|
||||
// }
|
||||
// },
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 0
|
||||
// }
|
||||
type InitializeRequest struct {
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
Capabilities M `json:"capabilities"`
|
||||
ClientInfo *ClientInfo `json:"clientInfo"`
|
||||
}
|
||||
|
||||
type ClientInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 0,
|
||||
// "result": {
|
||||
// "protocolVersion": "2024-11-05",
|
||||
// "capabilities": {
|
||||
// "experimental": {},
|
||||
// "prompts": {
|
||||
// "listChanged": false
|
||||
// },
|
||||
// "resources": {
|
||||
// "subscribe": false,
|
||||
// "listChanged": false
|
||||
// },
|
||||
// "tools": {
|
||||
// "listChanged": false
|
||||
// }
|
||||
// },
|
||||
// "serverInfo": {
|
||||
// "name": "weather",
|
||||
// "version": "1.4.1"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
type InitializeResponse struct {
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
Capabilities M `json:"capabilities"`
|
||||
ServerInfo ServerInfo `json:"serverInfo"`
|
||||
}
|
||||
|
||||
type ServerInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
var DefaultCapabilities = M{
|
||||
"experimental": M{},
|
||||
"prompts": M{"listChanged": false},
|
||||
"resources": M{"subscribe": false, "listChanged": false},
|
||||
"tools": M{"listChanged": false},
|
||||
}
|
||||
62
internal/mcp/jsonrpc.go
Normal file
62
internal/mcp/jsonrpc.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package mcp
|
||||
|
||||
const (
|
||||
JsonRPCVersion = "2.0"
|
||||
)
|
||||
|
||||
// Documents: https://modelcontextprotocol.io/docs/concepts/transports
|
||||
|
||||
// Request
|
||||
//
|
||||
// {
|
||||
// jsonrpc: "2.0",
|
||||
// id: number | string,
|
||||
// method: string,
|
||||
// params?: object
|
||||
// }
|
||||
type Request struct {
|
||||
JsonRPC string `json:"jsonrpc"`
|
||||
ID interface{} `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// Response
|
||||
//
|
||||
// {
|
||||
// jsonrpc: "2.0",
|
||||
// id: number | string,
|
||||
// result?: object,
|
||||
// error?: {
|
||||
// code: number,
|
||||
// message: string,
|
||||
// data?: unknown
|
||||
// }
|
||||
// }
|
||||
type Response struct {
|
||||
JsonRPC string `json:"jsonrpc"`
|
||||
ID interface{} `json:"id"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *Error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func NewResponse(id interface{}, result interface{}) *Response {
|
||||
return &Response{
|
||||
JsonRPC: JsonRPCVersion,
|
||||
ID: id,
|
||||
Result: result,
|
||||
}
|
||||
}
|
||||
|
||||
// Notifications
|
||||
//
|
||||
// {
|
||||
// jsonrpc: "2.0",
|
||||
// method: string,
|
||||
// params?: object
|
||||
// }
|
||||
type Notification struct {
|
||||
JsonRPC string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
}
|
||||
107
internal/mcp/mcp.go
Normal file
107
internal/mcp/mcp.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
ProcessChanCap = 1000
|
||||
)
|
||||
|
||||
type MCP struct {
|
||||
sessions map[string]*Session
|
||||
sessionMu sync.Mutex
|
||||
|
||||
ProcessChan chan ProcessCtx
|
||||
}
|
||||
|
||||
func NewMCP() *MCP {
|
||||
return &MCP{
|
||||
sessions: make(map[string]*Session),
|
||||
ProcessChan: make(chan ProcessCtx, ProcessChanCap),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MCP) HandleSSE(c *gin.Context) {
|
||||
id := uuid.New().String()
|
||||
m.sessionMu.Lock()
|
||||
m.sessions[id] = NewSession(c, id)
|
||||
m.sessionMu.Unlock()
|
||||
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
<-c.Request.Context().Done()
|
||||
return false
|
||||
})
|
||||
|
||||
m.sessionMu.Lock()
|
||||
delete(m.sessions, id)
|
||||
m.sessionMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *MCP) GetSession(id string) *Session {
|
||||
m.sessionMu.Lock()
|
||||
defer m.sessionMu.Unlock()
|
||||
return m.sessions[id]
|
||||
}
|
||||
|
||||
func (m *MCP) HandleMessages(c *gin.Context) {
|
||||
|
||||
// panic("xxx")
|
||||
|
||||
// 啊这, 一个 sessionid 有 3 种写法 session_id, sessionId, sessionid
|
||||
// 官方 SDK 是 session_id: https://github.com/modelcontextprotocol/python-sdk/blob/c897868/src/mcp/server/sse.py#L98
|
||||
// 写的是 sessionId: https://github.com/modelcontextprotocol/inspector/blob/aeaf32f/server/src/index.ts#L157
|
||||
|
||||
sessionID := c.Query("session_id")
|
||||
if sessionID == "" {
|
||||
sessionID = c.Query("sessionId")
|
||||
}
|
||||
if sessionID == "" {
|
||||
sessionID = c.Param("sessionid")
|
||||
}
|
||||
if sessionID == "" {
|
||||
c.JSON(http.StatusBadRequest, ErrInvalidSessionID.JsonRPC())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
session := m.GetSession(sessionID)
|
||||
if session == nil {
|
||||
c.JSON(http.StatusNotFound, ErrSessionNotFound.JsonRPC())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var req Request
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrInvalidRequest.JsonRPC())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("收到消息: %v\n", req)
|
||||
select {
|
||||
case m.ProcessChan <- ProcessCtx{Session: session, Request: &req}:
|
||||
default:
|
||||
c.JSON(http.StatusTooManyRequests, ErrTooManyRequests.JsonRPC())
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.String(http.StatusAccepted, "Accepted")
|
||||
}
|
||||
|
||||
func (m *MCP) Close() {
|
||||
close(m.ProcessChan)
|
||||
}
|
||||
|
||||
type ProcessCtx struct {
|
||||
Session *Session
|
||||
Request *Request
|
||||
}
|
||||
137
internal/mcp/prompt.go
Normal file
137
internal/mcp/prompt.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package mcp
|
||||
|
||||
// Document: https://modelcontextprotocol.io/docs/concepts/prompts
|
||||
|
||||
const (
|
||||
// Client => Server
|
||||
MethodPromptsList = "prompts/list"
|
||||
MethodPromptsGet = "prompts/get"
|
||||
)
|
||||
|
||||
// Prompt
|
||||
//
|
||||
// {
|
||||
// name: string; // Unique identifier for the prompt
|
||||
// description?: string; // Human-readable description
|
||||
// arguments?: [ // Optional list of arguments
|
||||
// {
|
||||
// name: string; // Argument identifier
|
||||
// description?: string; // Argument description
|
||||
// required?: boolean; // Whether argument is required
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
type Prompt struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Arguments []PromptArgument `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type PromptArgument struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
// ListPrompts
|
||||
//
|
||||
// {
|
||||
// prompts: [
|
||||
// {
|
||||
// name: "analyze-code",
|
||||
// description: "Analyze code for potential improvements",
|
||||
// arguments: [
|
||||
// {
|
||||
// name: "language",
|
||||
// description: "Programming language",
|
||||
// required: true
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
type PromptsListResponse struct {
|
||||
Prompts []Prompt `json:"prompts"`
|
||||
}
|
||||
|
||||
// Use Prompt
|
||||
// Request
|
||||
//
|
||||
// {
|
||||
// method: "prompts/get",
|
||||
// params: {
|
||||
// name: "analyze-code",
|
||||
// arguments: {
|
||||
// language: "python"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Response
|
||||
//
|
||||
// {
|
||||
// description: "Analyze Python code for potential improvements",
|
||||
// messages: [
|
||||
// {
|
||||
// role: "user",
|
||||
// content: {
|
||||
// type: "text",
|
||||
// text: "Please analyze the following Python code for potential improvements:\n\n```python\ndef calculate_sum(numbers):\n total = 0\n for num in numbers:\n total = total + num\n return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n```"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
type PromptsGetRequest struct {
|
||||
Name string `json:"name"`
|
||||
Arguments M `json:"arguments"`
|
||||
}
|
||||
|
||||
type PromptsGetResponse struct {
|
||||
Description string `json:"description"`
|
||||
Messages []PromptMessage `json:"messages"`
|
||||
}
|
||||
|
||||
type PromptMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content PromptContent `json:"content"`
|
||||
}
|
||||
|
||||
type PromptContent struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Resource interface{} `json:"resource,omitempty"` // Resource or ResourceTemplate
|
||||
}
|
||||
|
||||
// {
|
||||
// "messages": [
|
||||
// {
|
||||
// "role": "user",
|
||||
// "content": {
|
||||
// "type": "text",
|
||||
// "text": "Analyze these system logs and the code file for any issues:"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "role": "user",
|
||||
// "content": {
|
||||
// "type": "resource",
|
||||
// "resource": {
|
||||
// "uri": "logs://recent?timeframe=1h",
|
||||
// "text": "[2024-03-14 15:32:11] ERROR: Connection timeout in network.py:127\n[2024-03-14 15:32:15] WARN: Retrying connection (attempt 2/3)\n[2024-03-14 15:32:20] ERROR: Max retries exceeded",
|
||||
// "mimeType": "text/plain"
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "role": "user",
|
||||
// "content": {
|
||||
// "type": "resource",
|
||||
// "resource": {
|
||||
// "uri": "file:///path/to/code.py",
|
||||
// "text": "def connect_to_service(timeout=30):\n retries = 3\n for attempt in range(retries):\n try:\n return establish_connection(timeout)\n except TimeoutError:\n if attempt == retries - 1:\n raise\n time.sleep(5)\n\ndef establish_connection(timeout):\n # Connection implementation\n pass",
|
||||
// "mimeType": "text/x-python"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
74
internal/mcp/resource.go
Normal file
74
internal/mcp/resource.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package mcp
|
||||
|
||||
// Document: https://modelcontextprotocol.io/docs/concepts/resources
|
||||
|
||||
const (
|
||||
// Client => Server
|
||||
MethodResourcesList = "resources/list"
|
||||
MethodResourcesTemplateList = "resources/templates/list"
|
||||
MethodResourcesRead = "resources/read"
|
||||
MethodResourcesSubscribe = "resources/subscribe"
|
||||
MethodResourcesUnsubscribe = "resources/unsubscribe"
|
||||
|
||||
// Server => Client
|
||||
NotificationResourcesListChanged = "notifications/resources/list_changed"
|
||||
NofiticationResourcesUpdated = "notifications/resources/updated"
|
||||
)
|
||||
|
||||
// Direct resources
|
||||
//
|
||||
// {
|
||||
// uri: string; // Unique identifier for the resource
|
||||
// name: string; // Human-readable name
|
||||
// description?: string; // Optional description
|
||||
// mimeType?: string; // Optional MIME type
|
||||
// }
|
||||
type Resource struct {
|
||||
URI string `json:"uri"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
}
|
||||
|
||||
// Resource templates
|
||||
//
|
||||
// {
|
||||
// uriTemplate: string; // URI template following RFC 6570
|
||||
// name: string; // Human-readable name for this type
|
||||
// description?: string; // Optional description
|
||||
// mimeType?: string; // Optional MIME type for all matching resources
|
||||
// }
|
||||
type ResourceTemplate struct {
|
||||
URITemplate string `json:"uriTemplate"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
}
|
||||
|
||||
// Reading resources
|
||||
// {
|
||||
// contents: [
|
||||
// {
|
||||
// uri: string; // The URI of the resource
|
||||
// mimeType?: string; // Optional MIME type
|
||||
|
||||
// // One of:
|
||||
// text?: string; // For text resources
|
||||
// blob?: string; // For binary resources (base64 encoded)
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
type ReadingResource struct {
|
||||
Contents []ReadingResourceContent `json:"contents"`
|
||||
}
|
||||
|
||||
type ResourcesReadRequest struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
type ReadingResourceContent struct {
|
||||
URI string `json:"uri"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Blob string `json:"blob,omitempty"`
|
||||
}
|
||||
48
internal/mcp/session.go
Normal file
48
internal/mcp/session.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
id string
|
||||
w io.Writer
|
||||
c *ClientInfo
|
||||
}
|
||||
|
||||
func NewSession(c *gin.Context, id string) *Session {
|
||||
return &Session{
|
||||
id: id,
|
||||
w: NewSSEWriter(c, id),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Write(p []byte) (n int, err error) {
|
||||
return s.w.Write(p)
|
||||
}
|
||||
|
||||
func (s *Session) WriteError(req *Request, err error) {
|
||||
resp := NewErrorResponse(req.ID, 500, err)
|
||||
b, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Write(b)
|
||||
}
|
||||
|
||||
func (s *Session) WriteResponse(req *Request, data interface{}) error {
|
||||
resp := NewResponse(req.ID, data)
|
||||
b, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Write(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) SaveClientInfo(c *ClientInfo) {
|
||||
s.c = c
|
||||
}
|
||||
160
internal/mcp/sse.go
Normal file
160
internal/mcp/sse.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
SSEPingIntervalS = 30
|
||||
SSEMessageChanCap = 100
|
||||
SSEContentType = "text/event-stream; charset=utf-8"
|
||||
)
|
||||
|
||||
type SSEWriter struct {
|
||||
id string
|
||||
c *gin.Context
|
||||
}
|
||||
|
||||
func NewSSEWriter(c *gin.Context, id string) *SSEWriter {
|
||||
c.Writer.Header().Set("Content-Type", SSEContentType)
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Flush()
|
||||
|
||||
w := &SSEWriter{
|
||||
id: id,
|
||||
c: c,
|
||||
}
|
||||
w.WriteEndpoing()
|
||||
go w.ping()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *SSEWriter) Write(p []byte) (n int, err error) {
|
||||
w.WriteMessage(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *SSEWriter) WriteMessage(data string) {
|
||||
w.WriteEvent("message", data)
|
||||
}
|
||||
|
||||
func (w *SSEWriter) WriteEvent(event string, data string) {
|
||||
w.c.Writer.WriteString(fmt.Sprintf("event: %s\n", event))
|
||||
w.c.Writer.WriteString(fmt.Sprintf("data: %s\n\n", data))
|
||||
w.c.Writer.Flush()
|
||||
}
|
||||
|
||||
func (w *SSEWriter) ping() {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Second * SSEPingIntervalS):
|
||||
w.writePing()
|
||||
case <-w.c.Request.Context().Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteEndpoing
|
||||
// event: endpoint
|
||||
// data: /message?sessionId=285d67ee-1c17-40d9-ab03-173d5ff48419
|
||||
func (w *SSEWriter) WriteEndpoing() {
|
||||
w.c.Writer.WriteString(fmt.Sprintf("event: endpoint\n"))
|
||||
w.c.Writer.WriteString(fmt.Sprintf("data: /message?sessionId=%s\n\n", w.id))
|
||||
w.c.Writer.Flush()
|
||||
}
|
||||
|
||||
// WritePing
|
||||
// : ping - 2025-03-16 06:41:51.280928+00:00
|
||||
func (w *SSEWriter) writePing() {
|
||||
w.c.Writer.WriteString(fmt.Sprintf(": ping - %s\n\n", time.Now().Format("2006-01-02 15:04:05.999999-07:00")))
|
||||
}
|
||||
|
||||
// SSE Session
|
||||
// 维持一个 SSE 连接的会话
|
||||
// 会话中包含了 SSE 连接的 ID,事件通道,停止通道
|
||||
// 事件通道用于发送事件,停止通道用于停止会话
|
||||
// 需要轮询发送 ping 事件以保持连接
|
||||
type SSESession struct {
|
||||
SessionID string
|
||||
Events map[string]chan string
|
||||
Stop chan bool
|
||||
|
||||
c *gin.Context
|
||||
}
|
||||
|
||||
func NewSSESession(c *gin.Context) *SSESession {
|
||||
return &SSESession{c: c}
|
||||
}
|
||||
func (s *SSESession) SendEvent(event string, data string) {
|
||||
s.c.SSEvent(event, data)
|
||||
}
|
||||
|
||||
func (s *SSESession) Close() {
|
||||
close(s.Stop)
|
||||
}
|
||||
|
||||
// Event
|
||||
// request:
|
||||
// POST /messages?sesessionId=?
|
||||
// '{"method":"prompts/list","params":{},"jsonrpc":"2.0","id":3}'
|
||||
//
|
||||
// response:
|
||||
// GET /sse
|
||||
// event: message
|
||||
// data: {"jsonrpc":"2.0","id":3,"result":{"prompts":[]}}
|
||||
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 1,
|
||||
// "result": {
|
||||
// "tools": [
|
||||
// {
|
||||
// "name": "get_alerts",
|
||||
// "description": "Get weather alerts for a US state.\n\n Args:\n state: Two-letter US state code (e.g. CA, NY)\n ",
|
||||
// "inputSchema": {
|
||||
// "properties": {
|
||||
// "state": {
|
||||
// "title": "State",
|
||||
// "type": "string"
|
||||
// }
|
||||
// },
|
||||
// "required": [
|
||||
// "state"
|
||||
// ],
|
||||
// "title": "get_alertsArguments",
|
||||
// "type": "object"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "name": "get_forecast",
|
||||
// "description": "Get weather forecast for a location.\n\n Args:\n latitude: Latitude of the location\n longitude: Longitude of the location\n ",
|
||||
// "inputSchema": {
|
||||
// "properties": {
|
||||
// "latitude": {
|
||||
// "title": "Latitude",
|
||||
// "type": "number"
|
||||
// },
|
||||
// "longitude": {
|
||||
// "title": "Longitude",
|
||||
// "type": "number"
|
||||
// }
|
||||
// },
|
||||
// "required": [
|
||||
// "latitude",
|
||||
// "longitude"
|
||||
// ],
|
||||
// "title": "get_forecastArguments",
|
||||
// "type": "object"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
// PING
|
||||
1
internal/mcp/stdio.go
Normal file
1
internal/mcp/stdio.go
Normal file
@@ -0,0 +1 @@
|
||||
package mcp
|
||||
142
internal/mcp/tool.go
Normal file
142
internal/mcp/tool.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package mcp
|
||||
|
||||
// Document: https://modelcontextprotocol.io/docs/concepts/tools
|
||||
|
||||
const (
|
||||
// Client => Server
|
||||
MethodToolsList = "tools/list"
|
||||
MethodToolsCall = "tools/call"
|
||||
)
|
||||
|
||||
type M map[string]interface{}
|
||||
|
||||
// Tool
|
||||
//
|
||||
// {
|
||||
// name: string; // Unique identifier for the tool
|
||||
// description?: string; // Human-readable description
|
||||
// inputSchema: { // JSON Schema for the tool's parameters
|
||||
// type: "object",
|
||||
// properties: { ... } // Tool-specific parameters
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// name: "analyze_csv",
|
||||
// description: "Analyze a CSV file",
|
||||
// inputSchema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// filepath: { type: "string" },
|
||||
// operations: {
|
||||
// type: "array",
|
||||
// items: {
|
||||
// enum: ["sum", "average", "count"]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 1,
|
||||
// "result": {
|
||||
// "tools": [
|
||||
// {
|
||||
// "name": "get_alerts",
|
||||
// "description": "Get weather alerts for a US state.\n\n Args:\n state: Two-letter US state code (e.g. CA, NY)\n ",
|
||||
// "inputSchema": {
|
||||
// "properties": {
|
||||
// "state": {
|
||||
// "title": "State",
|
||||
// "type": "string"
|
||||
// }
|
||||
// },
|
||||
// "required": [
|
||||
// "state"
|
||||
// ],
|
||||
// "title": "get_alertsArguments",
|
||||
// "type": "object"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "name": "get_forecast",
|
||||
// "description": "Get weather forecast for a location.\n\n Args:\n latitude: Latitude of the location\n longitude: Longitude of the location\n ",
|
||||
// "inputSchema": {
|
||||
// "properties": {
|
||||
// "latitude": {
|
||||
// "title": "Latitude",
|
||||
// "type": "number"
|
||||
// },
|
||||
// "longitude": {
|
||||
// "title": "Longitude",
|
||||
// "type": "number"
|
||||
// }
|
||||
// },
|
||||
// "required": [
|
||||
// "latitude",
|
||||
// "longitude"
|
||||
// ],
|
||||
// "title": "get_forecastArguments",
|
||||
// "type": "object"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
type Tool struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
InputSchema ToolSchema `json:"inputSchema"`
|
||||
}
|
||||
|
||||
type ToolSchema struct {
|
||||
Type string `json:"type"`
|
||||
Properties M `json:"properties"`
|
||||
}
|
||||
|
||||
// {
|
||||
// "method": "tools/call",
|
||||
// "params": {
|
||||
// "name": "chatlog",
|
||||
// "arguments": {
|
||||
// "start": "2006-11-12",
|
||||
// "end": "2020-11-20",
|
||||
// "limit": "50",
|
||||
// "offset": "6"
|
||||
// },
|
||||
// "_meta": {
|
||||
// "progressToken": 1
|
||||
// }
|
||||
// },
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 3
|
||||
// }
|
||||
type ToolsCallRequest struct {
|
||||
Name string `json:"name"`
|
||||
Arguments M `json:"arguments"`
|
||||
}
|
||||
|
||||
// {
|
||||
// "jsonrpc": "2.0",
|
||||
// "id": 2,
|
||||
// "result": {
|
||||
// "content": [
|
||||
// {
|
||||
// "type": "text",
|
||||
// "text": "\nEvent: Winter Storm Warning\n"
|
||||
// }
|
||||
// ],
|
||||
// "isError": false
|
||||
// }
|
||||
// }
|
||||
type ToolsCallResponse struct {
|
||||
Content []Content `json:"content"`
|
||||
IsError bool `json:"isError"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
Reference in New Issue
Block a user