This commit is contained in:
Shen Junzheng
2025-03-12 01:19:35 +08:00
parent 160040f3e1
commit 78cce92ce3
70 changed files with 10134 additions and 1 deletions

160
internal/mcp/sse.go Normal file
View 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