http.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package agentmcp
  2. import (
  3. "context"
  4. "crypto/subtle"
  5. "errors"
  6. "net/http"
  7. "strings"
  8. "time"
  9. "github.com/mark3labs/mcp-go/server"
  10. "go.uber.org/zap"
  11. "fwh-toolbox-server/agentmcp/kit"
  12. )
  13. // Endpoint 一个工具集的 MCP 端点。
  14. type Endpoint struct {
  15. Set string // 工具集,决定路径 EndpointPath(Set)
  16. Server *server.MCPServer // 注册了该工具集的 MCPServer
  17. Tasks []kit.TaskTool // 其中的持久化长任务工具
  18. Token string // 该端点的 Bearer 令牌(同时用于签名任务 id)
  19. }
  20. // Handler 返回 HTTP 处理器:每个工具集挂在 /mcp/<工具集>(Streamable HTTP,无状态,需 Bearer 令牌),只读工具集另挂 /mcp 兼容旧地址;另有 /healthz。
  21. // 各端点的 MCPServer、并发名额、令牌相互独立,只共用监听端口。
  22. // 任务协议请求由 kit.TaskRouter 在 mcp-go 之前处理(任务 id 以端点令牌签名)。
  23. func Handler(endpoints []Endpoint, log *zap.Logger) http.Handler {
  24. mux := http.NewServeMux()
  25. for _, ep := range endpoints {
  26. path := EndpointPath(ep.Set)
  27. mcpHandler := server.NewStreamableHTTPServer(ep.Server,
  28. server.WithStateLess(true),
  29. server.WithEndpointPath(path),
  30. )
  31. h := bearerAuth(ep.Token, kit.NewTaskRouter(mcpHandler, ep.Tasks, ep.Token, log))
  32. mux.Handle(path, h)
  33. if ep.Set == SetReadOnly {
  34. mux.Handle("/mcp", h)
  35. }
  36. }
  37. mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
  38. w.WriteHeader(http.StatusOK)
  39. _, _ = w.Write([]byte("ok"))
  40. })
  41. return mux
  42. }
  43. // bearerAuth 校验 Authorization: Bearer <token>,常量时间比较。
  44. func bearerAuth(token string, next http.Handler) http.Handler {
  45. want := []byte("Bearer " + token)
  46. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  47. got := []byte(strings.TrimSpace(r.Header.Get("Authorization")))
  48. if subtle.ConstantTimeCompare(got, want) != 1 {
  49. w.Header().Set("Content-Type", "application/json")
  50. w.WriteHeader(http.StatusUnauthorized)
  51. _, _ = w.Write([]byte(`{"jsonrpc":"2.0","error":{"code":-32001,"message":"unauthorized"},"id":null}`))
  52. return
  53. }
  54. next.ServeHTTP(w, r)
  55. })
  56. }
  57. // Serve 启动 HTTP 服务,ctx 取消时优雅关闭。
  58. func Serve(ctx context.Context, addr string, h http.Handler, log *zap.Logger) error {
  59. srv := &http.Server{
  60. Addr: addr,
  61. Handler: h,
  62. ReadHeaderTimeout: 10 * time.Second,
  63. }
  64. errCh := make(chan error, 1)
  65. go func() { errCh <- srv.ListenAndServe() }()
  66. log.Info("[agent-mcp] HTTP 服务已启动", zap.String("addr", addr))
  67. select {
  68. case <-ctx.Done():
  69. shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  70. defer cancel()
  71. return srv.Shutdown(shutdownCtx)
  72. case err := <-errCh:
  73. if errors.Is(err, http.ErrServerClosed) {
  74. return nil
  75. }
  76. return err
  77. }
  78. }