embedmcp.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. package controller
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "maps"
  7. "net/http"
  8. "slices"
  9. "strings"
  10. "github.com/gin-gonic/gin"
  11. "github.com/labring/aiproxy/core/mcpproxy"
  12. "github.com/labring/aiproxy/core/middleware"
  13. "github.com/labring/aiproxy/core/model"
  14. mcpservers "github.com/labring/aiproxy/mcp-servers"
  15. // init embed mcp
  16. _ "github.com/labring/aiproxy/mcp-servers/mcpregister"
  17. "github.com/mark3labs/mcp-go/mcp"
  18. )
  19. type EmbedMCPConfigTemplate struct {
  20. Name string `json:"name"`
  21. Required bool `json:"required"`
  22. Example string `json:"example,omitempty"`
  23. Description string `json:"description,omitempty"`
  24. }
  25. func newEmbedMCPConfigTemplate(template mcpservers.ConfigTemplate) EmbedMCPConfigTemplate {
  26. return EmbedMCPConfigTemplate{
  27. Name: template.Name,
  28. Required: template.Required == mcpservers.ConfigRequiredTypeInitOnly,
  29. Example: template.Example,
  30. Description: template.Description,
  31. }
  32. }
  33. type EmbedMCPConfigTemplates = map[string]EmbedMCPConfigTemplate
  34. func newEmbedMCPConfigTemplates(templates mcpservers.ConfigTemplates) EmbedMCPConfigTemplates {
  35. emcpTemplates := make(EmbedMCPConfigTemplates, len(templates))
  36. for key, template := range templates {
  37. emcpTemplates[key] = newEmbedMCPConfigTemplate(template)
  38. }
  39. return emcpTemplates
  40. }
  41. func newEmbedMCPProxyConfigTemplates(
  42. templates mcpservers.ProxyConfigTemplates,
  43. ) EmbedMCPConfigTemplates {
  44. emcpTemplates := make(EmbedMCPConfigTemplates, len(templates))
  45. for key, template := range templates {
  46. emcpTemplates[key] = newEmbedMCPConfigTemplate(template.ConfigTemplate)
  47. }
  48. return emcpTemplates
  49. }
  50. type EmbedMCP struct {
  51. ID string `json:"id"`
  52. Enabled bool `json:"enabled"`
  53. Name string `json:"name"`
  54. NameCN string `json:"name_cn"`
  55. Readme string `json:"readme"`
  56. ReadmeURL string `json:"readme_url"`
  57. ReadmeCN string `json:"readme_cn"`
  58. ReadmeCNURL string `json:"readme_cn_url"`
  59. GitHubURL string `json:"github_url"`
  60. Tags []string `json:"tags"`
  61. ConfigTemplates EmbedMCPConfigTemplates `json:"config_templates"`
  62. EmbedConfig *model.MCPEmbeddingConfig `json:"embed_config"`
  63. }
  64. func newEmbedMCP(
  65. mcp *mcpservers.McpServer,
  66. enabled bool,
  67. embedConfig *model.MCPEmbeddingConfig,
  68. ) *EmbedMCP {
  69. emcp := &EmbedMCP{
  70. ID: mcp.ID,
  71. Enabled: enabled,
  72. Name: mcp.Name,
  73. NameCN: mcp.NameCN,
  74. Readme: mcp.Readme,
  75. ReadmeURL: mcp.ReadmeURL,
  76. ReadmeCN: mcp.ReadmeCN,
  77. ReadmeCNURL: mcp.ReadmeCNURL,
  78. GitHubURL: mcp.GitHubURL,
  79. Tags: mcp.Tags,
  80. EmbedConfig: embedConfig,
  81. }
  82. if len(mcp.ConfigTemplates) != 0 {
  83. emcp.ConfigTemplates = newEmbedMCPConfigTemplates(mcp.ConfigTemplates)
  84. }
  85. if len(mcp.ProxyConfigTemplates) != 0 {
  86. emcp.ConfigTemplates = newEmbedMCPProxyConfigTemplates(mcp.ProxyConfigTemplates)
  87. }
  88. if emcp.ConfigTemplates == nil {
  89. emcp.ConfigTemplates = make(EmbedMCPConfigTemplates)
  90. }
  91. return emcp
  92. }
  93. // GetEmbedMCPs godoc
  94. //
  95. // @Summary Get embed mcp
  96. // @Description Get embed mcp
  97. // @Tags embedmcp
  98. // @Accept json
  99. // @Produce json
  100. // @Security ApiKeyAuth
  101. // @Success 200 {array} EmbedMCP
  102. // @Router /api/embedmcp/ [get]
  103. func GetEmbedMCPs(c *gin.Context) {
  104. embeds := mcpservers.Servers()
  105. embedIDs := slices.Collect(maps.Keys(embeds))
  106. enabledMCPs, err := model.GetPublicMCPsEnabled(embedIDs)
  107. if err != nil {
  108. middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
  109. return
  110. }
  111. embedConfigs, err := model.GetPublicMCPsEmbedConfig(embedIDs)
  112. if err != nil {
  113. middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
  114. return
  115. }
  116. emcps := make([]*EmbedMCP, 0, len(embeds))
  117. for _, mcp := range embeds {
  118. enabled := slices.Contains(enabledMCPs, mcp.ID)
  119. var embedConfig *model.MCPEmbeddingConfig
  120. if c, ok := embedConfigs[mcp.ID]; ok {
  121. embedConfig = &c
  122. }
  123. emcps = append(
  124. emcps,
  125. newEmbedMCP(
  126. &mcp,
  127. enabled,
  128. embedConfig,
  129. ),
  130. )
  131. }
  132. slices.SortFunc(emcps, func(a, b *EmbedMCP) int {
  133. if a.Name != b.Name {
  134. return strings.Compare(a.Name, b.Name)
  135. }
  136. if a.Enabled != b.Enabled {
  137. if a.Enabled {
  138. return -1
  139. }
  140. return 1
  141. }
  142. return strings.Compare(a.ID, b.ID)
  143. })
  144. middleware.SuccessResponse(c, emcps)
  145. }
  146. type SaveEmbedMCPRequest struct {
  147. ID string `json:"id"`
  148. Enabled bool `json:"enabled"`
  149. InitConfig map[string]string `json:"init_config"`
  150. }
  151. func GetEmbedConfig(
  152. ct mcpservers.ConfigTemplates,
  153. initConfig map[string]string,
  154. ) (*model.MCPEmbeddingConfig, error) {
  155. reusingConfig := make(map[string]model.ReusingParam)
  156. embedConfig := &model.MCPEmbeddingConfig{
  157. Init: initConfig,
  158. }
  159. for key, value := range ct {
  160. switch value.Required {
  161. case mcpservers.ConfigRequiredTypeInitOnly:
  162. if v, ok := initConfig[key]; !ok || v == "" {
  163. return nil, fmt.Errorf("config %s is required", key)
  164. }
  165. case mcpservers.ConfigRequiredTypeReusingOnly:
  166. if _, ok := initConfig[key]; ok {
  167. return nil, fmt.Errorf("config %s is provided, but it is not allowed", key)
  168. }
  169. reusingConfig[key] = model.ReusingParam{
  170. Name: value.Name,
  171. Description: value.Description,
  172. Required: true,
  173. }
  174. case mcpservers.ConfigRequiredTypeInitOrReusingOnly:
  175. if v, ok := initConfig[key]; ok && v != "" {
  176. continue
  177. }
  178. reusingConfig[key] = model.ReusingParam{
  179. Name: value.Name,
  180. Description: value.Description,
  181. Required: true,
  182. }
  183. }
  184. }
  185. embedConfig.Reusing = reusingConfig
  186. return embedConfig, nil
  187. }
  188. func GetProxyConfig(
  189. proxyConfigType mcpservers.ProxyConfigTemplates,
  190. initConfig map[string]string,
  191. ) (*model.PublicMCPProxyConfig, error) {
  192. if len(proxyConfigType) == 0 {
  193. return nil, errors.New("proxy config type is empty")
  194. }
  195. config := &model.PublicMCPProxyConfig{
  196. Querys: make(map[string]string),
  197. Headers: make(map[string]string),
  198. Reusing: make(map[string]model.PublicMCPProxyReusingParam),
  199. }
  200. for key, param := range proxyConfigType {
  201. value := initConfig[key]
  202. if value == "" {
  203. value = param.Default
  204. }
  205. switch param.Required {
  206. case mcpservers.ConfigRequiredTypeInitOnly:
  207. // 必须在初始化时提供
  208. if value == "" {
  209. return nil, fmt.Errorf("parameter %s is required", key)
  210. }
  211. applyParamToConfig(config, key, value, param.Type)
  212. case mcpservers.ConfigRequiredTypeReusingOnly:
  213. // 只能通过 reusing 提供,不能在初始化时提供
  214. if value != "" {
  215. return nil, fmt.Errorf(
  216. "parameter %s should not be provided in init config, it should be provided via reusing",
  217. key,
  218. )
  219. }
  220. config.Reusing[key] = model.PublicMCPProxyReusingParam{
  221. ReusingParam: model.ReusingParam{
  222. Name: param.Name,
  223. Description: param.Description,
  224. Required: true,
  225. },
  226. Type: param.Type,
  227. }
  228. case mcpservers.ConfigRequiredTypeInitOrReusingOnly:
  229. // 可以在初始化时提供,也可以通过 reusing 提供
  230. if value != "" {
  231. applyParamToConfig(config, key, value, param.Type)
  232. } else {
  233. config.Reusing[key] = model.PublicMCPProxyReusingParam{
  234. ReusingParam: model.ReusingParam{
  235. Name: param.Name,
  236. Description: param.Description,
  237. Required: true,
  238. },
  239. Type: param.Type,
  240. }
  241. }
  242. default:
  243. // 可选参数
  244. if value != "" {
  245. applyParamToConfig(config, key, value, param.Type)
  246. }
  247. }
  248. }
  249. if config.URL == "" {
  250. return nil, errors.New("url is required in proxy config")
  251. }
  252. return config, nil
  253. }
  254. // 辅助函数:将参数应用到配置中
  255. func applyParamToConfig(
  256. config *model.PublicMCPProxyConfig,
  257. key, value string,
  258. paramType model.ProxyParamType,
  259. ) {
  260. switch paramType {
  261. case model.ParamTypeURL:
  262. config.URL = value
  263. case model.ParamTypeHeader:
  264. config.Headers[key] = value
  265. case model.ParamTypeQuery:
  266. config.Querys[key] = value
  267. }
  268. }
  269. func ToPublicMCP(
  270. e mcpservers.McpServer,
  271. initConfig map[string]string,
  272. enabled bool,
  273. ) (*model.PublicMCP, error) {
  274. pmcp := e.PublicMCP
  275. switch e.Type {
  276. case model.PublicMCPTypeEmbed:
  277. embedConfig, err := GetEmbedConfig(e.ConfigTemplates, initConfig)
  278. if err != nil {
  279. return nil, err
  280. }
  281. pmcp.EmbedConfig = embedConfig
  282. case model.PublicMCPTypeProxySSE, model.PublicMCPTypeProxyStreamable:
  283. proxyConfig, err := GetProxyConfig(e.ProxyConfigTemplates, initConfig)
  284. if err != nil {
  285. return nil, err
  286. }
  287. pmcp.ProxyConfig = proxyConfig
  288. default:
  289. }
  290. if enabled {
  291. pmcp.Status = model.PublicMCPStatusEnabled
  292. } else {
  293. pmcp.Status = model.PublicMCPStatusDisabled
  294. }
  295. return &pmcp, nil
  296. }
  297. // SaveEmbedMCP godoc
  298. //
  299. // @Summary Save embed mcp
  300. // @Description Save embed mcp
  301. // @Tags embedmcp
  302. // @Accept json
  303. // @Produce json
  304. // @Security ApiKeyAuth
  305. // @Param body body SaveEmbedMCPRequest true "Save embed mcp request"
  306. // @Success 200 {object} nil
  307. // @Router /api/embedmcp/ [post]
  308. func SaveEmbedMCP(c *gin.Context) {
  309. var req SaveEmbedMCPRequest
  310. if err := c.ShouldBindJSON(&req); err != nil {
  311. middleware.ErrorResponse(c, http.StatusBadRequest, err.Error())
  312. return
  313. }
  314. emcp, ok := mcpservers.GetEmbedMCP(req.ID)
  315. if !ok {
  316. middleware.ErrorResponse(c, http.StatusNotFound, "embed mcp not found")
  317. return
  318. }
  319. pmcp, err := ToPublicMCP(emcp, req.InitConfig, req.Enabled)
  320. if err != nil {
  321. middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
  322. return
  323. }
  324. if err := model.SavePublicMCP(pmcp); err != nil {
  325. middleware.ErrorResponse(c, http.StatusInternalServerError, err.Error())
  326. return
  327. }
  328. middleware.SuccessResponse(c, nil)
  329. }
  330. // query like:
  331. // /api/test-embedmcp/aiproxy-openapi/sse?key=adminkey&config[key1]=value1&config[key2]=value2&reusing[key3]=value3
  332. func getConfigFromQuery(c *gin.Context) (map[string]string, map[string]string) {
  333. initConfig := make(map[string]string)
  334. reusingConfig := make(map[string]string)
  335. queryParams := c.Request.URL.Query()
  336. for paramName, paramValues := range queryParams {
  337. if len(paramValues) == 0 {
  338. continue
  339. }
  340. paramValue := paramValues[0]
  341. if strings.HasPrefix(paramName, "config[") && strings.HasSuffix(paramName, "]") {
  342. key := paramName[7 : len(paramName)-1]
  343. if key != "" {
  344. initConfig[key] = paramValue
  345. }
  346. }
  347. if strings.HasPrefix(paramName, "reusing[") && strings.HasSuffix(paramName, "]") {
  348. key := paramName[8 : len(paramName)-1]
  349. if key != "" {
  350. reusingConfig[key] = paramValue
  351. }
  352. }
  353. }
  354. return initConfig, reusingConfig
  355. }
  356. // TestEmbedMCPSseServer godoc
  357. //
  358. // @Summary Test Embed MCP SSE Server
  359. // @Description Test Embed MCP SSE Server
  360. // @Tags embedmcp
  361. // @Security ApiKeyAuth
  362. // @Param id path string true "MCP ID"
  363. // @Param config[key] query string false "Initial configuration parameters (e.g. config[host]=http://localhost:3000)"
  364. // @Param reusing[key] query string false "Reusing configuration parameters (e.g. reusing[authorization]=apikey)"
  365. // @Success 200 {object} nil
  366. // @Failure 400 {object} nil
  367. // @Router /api/test-embedmcp/{id}/sse [get]
  368. func TestEmbedMCPSseServer(c *gin.Context) {
  369. id := c.Param("id")
  370. if id == "" {
  371. http.Error(c.Writer, "mcp id is required", http.StatusBadRequest)
  372. return
  373. }
  374. initConfig, reusingConfig := getConfigFromQuery(c)
  375. emcp, err := mcpservers.GetMCPServer(id, initConfig, reusingConfig)
  376. if err != nil {
  377. http.Error(c.Writer, err.Error(), http.StatusBadRequest)
  378. return
  379. }
  380. handleTestEmbedMCPServer(c, emcp)
  381. }
  382. const (
  383. testEmbedMcpType = "test-embedmcp"
  384. )
  385. func handleTestEmbedMCPServer(c *gin.Context, s mcpservers.Server) {
  386. // Store the session
  387. store := getStore()
  388. newSession := store.New()
  389. newEndpoint := sseEndpoint.NewEndpoint(newSession)
  390. server := mcpproxy.NewSSEServer(
  391. s,
  392. mcpproxy.WithMessageEndpoint(newEndpoint),
  393. )
  394. store.Set(newSession, testEmbedMcpType)
  395. defer func() {
  396. store.Delete(newSession)
  397. }()
  398. ctx, cancel := context.WithCancel(c.Request.Context())
  399. defer cancel()
  400. // Start message processing goroutine
  401. go processMCPSSEMpscMessages(ctx, newSession, server)
  402. // Handle SSE connection
  403. server.ServeHTTP(c.Writer, c.Request)
  404. }
  405. // TestEmbedMCPStreamable godoc
  406. //
  407. // @Summary Test Embed MCP Streamable Server
  408. // @Description Test Embed MCP Streamable Server with various HTTP methods
  409. // @Tags embedmcp
  410. // @Security ApiKeyAuth
  411. // @Param id path string true "MCP ID"
  412. // @Param config[key] query string false "Initial configuration parameters (e.g. config[host]=http://localhost:3000)"
  413. // @Param reusing[key] query string false "Reusing configuration parameters (e.g., reusing[authorization]=apikey)"
  414. // @Accept json
  415. // @Produce json
  416. // @Success 200 {object} nil
  417. // @Failure 400 {object} nil
  418. // @Router /api/test-embedmcp/{id} [get]
  419. // @Router /api/test-embedmcp/{id} [post]
  420. // @Router /api/test-embedmcp/{id} [delete]
  421. func TestEmbedMCPStreamable(c *gin.Context) {
  422. id := c.Param("id")
  423. if id == "" {
  424. c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
  425. mcp.NewRequestId(nil),
  426. mcp.INVALID_REQUEST,
  427. "mcp id is required",
  428. ))
  429. return
  430. }
  431. initConfig, reusingConfig := getConfigFromQuery(c)
  432. server, err := mcpservers.GetMCPServer(id, initConfig, reusingConfig)
  433. if err != nil {
  434. c.JSON(http.StatusBadRequest, mcpservers.CreateMCPErrorResponse(
  435. mcp.NewRequestId(nil),
  436. mcp.INVALID_REQUEST,
  437. err.Error(),
  438. ))
  439. return
  440. }
  441. handleStreamableMCPServer(c, server)
  442. }