package mcp_server import ( "fmt" "net/http" "strings" "sync" "github.com/mark3labs/mcp-go/mcp" "github.com/mitchellh/mapstructure" "github.com/mark3labs/mcp-go/server" ) var ( mcpServer = NewServer() ServiceBasePath = "mcp/service" GlobalBasePath = "mcp/global" AppBasePath = "mcp/app" ) func NewServer() *Server { return &Server{ servers: make(map[string]*Handler), } } type Server struct { servers map[string]*Handler locker sync.RWMutex } type Handler struct { *server.MCPServer handlers map[string]http.Handler } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.URL.Path = strings.TrimSuffix(r.URL.Path, "/") if strings.HasSuffix(r.URL.Path, "/mcp") { h.handlers["openapi-stream"].ServeHTTP(w, r) return } if strings.HasPrefix(r.URL.Path, "/api") { h.handlers["api-sse"].ServeHTTP(w, r) return } else if strings.HasPrefix(r.URL.Path, "/openapi") { h.handlers["openapi-sse"].ServeHTTP(w, r) return } http.NotFound(w, r) return } func (s *Server) Set(id string, ser *server.MCPServer) { s.locker.Lock() defer s.locker.Unlock() tmp := &Handler{ MCPServer: ser, handlers: make(map[string]http.Handler), } tmp.handlers["api-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, id))) tmp.handlers["openapi-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, id))) tmp.handlers["openapi-stream"] = server.NewStreamableHTTPServer(ser, server.WithEndpointPath(fmt.Sprintf("/openapi/v1/%s/%s/mcp", ServiceBasePath, id))) s.servers[id] = tmp } func (s *Server) Del(id string) { s.locker.Lock() defer s.locker.Unlock() delete(s.servers, id) } func (s *Server) Get(id string) (*Handler, bool) { s.locker.RLock() defer s.locker.RUnlock() ser, has := s.servers[id] if !has { return nil, false } m := &Handler{ MCPServer: ser.MCPServer, handlers: make(map[string]http.Handler), } for k, v := range ser.handlers { m.handlers[k] = v } return m, true } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { sid, err := genPath(r.URL.Path) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } ser, has := s.Get(sid) if has { ser.ServeHTTP(w, r) return } http.NotFound(w, r) return } func genPath(path string) (sid string, err error) { path = strings.TrimSuffix(path, "/") ps := strings.Split(path, "/") if len(ps) < 2 { err = fmt.Errorf("invalid path: %s", path) return } sid = ps[len(ps)-2] return } func SetServer(sid string, name string, version string, tools ...ITool) { ser, has := mcpServer.Get(sid) if !has { mcpServer.Set(sid, server.NewMCPServer(name, version, server.WithToolCapabilities(true))) ser, has = mcpServer.Get(sid) if !has { return } } ts := make([]server.ServerTool, 0, len(tools)) for _, tool := range tools { ts = append(ts, tool.Tool()) } ser.SetTools(ts...) } func DelServer(sid string) { mcpServer.Del(sid) } func ServeHTTP(w http.ResponseWriter, r *http.Request) { mcpServer.ServeHTTP(w, r) } func DefaultMCPServer() *Server { return mcpServer } func SetServerByOpenapi(sid, name, version, content string) error { mcpInfo, err := ConvertMCPFromOpenAPI3Data([]byte(content)) if err != nil { return fmt.Errorf("convert mcp from openapi3 data error: %w", err) } tools := make([]ITool, 0, len(mcpInfo.Apis)) for _, a := range mcpInfo.Apis { toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2) toolOptions = append(toolOptions, mcp.WithDescription(a.Description)) params := make(map[string]*Param) for _, v := range a.Params { if v.In == "header" && v.Name == "Authorization" { continue } params[v.Name] = NewParam(Position(v.In), v.Required, v.Description) options := make([]mcp.PropertyOption, 0, 2) if v.Required { options = append(options, mcp.Required()) } options = append(options, mcp.Description(v.Description)) toolOptions = append(toolOptions, mcp.WithString(v.Name, options...)) } if a.Body != nil { type Schema struct { Type string `mapstructure:"type"` Properties map[string]interface{} `mapstructure:"properties"` Items interface{} `mapstructure:"items"` Required interface{} `mapstructure:"required"` } var tmp Schema err = mapstructure.Decode(a.Body, &tmp) if err != nil { return err } required := map[string]struct{}{} switch t := tmp.Required.(type) { case []interface{}: for _, v := range t { i, ok := v.(string) if !ok { continue } required[i] = struct{}{} } } for k, v := range tmp.Properties { description := "" typ := "string" isRequired := false if _, ok := required[k]; ok { isRequired = true } var props map[string]interface{} var items interface{} switch t := v.(type) { case map[string]interface{}: if m, ok := t["type"]; ok { n, ok := m.(string) if ok { typ = n } } if m, ok := t["description"]; ok { n, ok := m.(string) if ok { description = n } } switch typ { case "array": if m, ok := t["items"]; ok { items = m } case "object": if m, ok := t["properties"]; ok { n, ok := m.(map[string]interface{}) if ok { props = n } } } } params[k] = NewParam(PositionBody, isRequired, description) options := make([]mcp.PropertyOption, 0, 3) options = append(options, mcp.Description(description)) if props != nil { options = append(options, mcp.Properties(props)) } if items != nil { options = append(options, mcp.Items(items)) } switch typ { case "string": toolOptions = append(toolOptions, mcp.WithString(k, options...)) case "integer", "number", "float": toolOptions = append(toolOptions, mcp.WithNumber(k, options...)) case "boolean": toolOptions = append(toolOptions, mcp.WithBoolean(k, options...)) case "array": toolOptions = append(toolOptions, mcp.WithArray(k, options...)) case "object": toolOptions = append(toolOptions, mcp.WithObject(k, options...)) default: return fmt.Errorf("unsupported type: %s", typ) } } } tools = append(tools, NewTool(a.Summary, a.Path, a.Method, a.ContentType, params, toolOptions...)) } SetServer(sid, name, version, tools...) return nil }