mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
247 lines
8.0 KiB
Go
247 lines
8.0 KiB
Go
package mcp
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/APIParkLab/APIPark/module/service"
|
|
|
|
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
|
|
|
|
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
|
"github.com/APIParkLab/APIPark/module/mcp"
|
|
"github.com/APIParkLab/APIPark/module/system"
|
|
"github.com/eolinker/go-common/utils"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
var _ IMcpController = (*imlMcpController)(nil)
|
|
|
|
type imlMcpController struct {
|
|
settingModule system.ISettingModule `autowired:""`
|
|
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
|
|
appModule service.IAppModule `autowired:""`
|
|
mcpModule mcp.IMcpModule `autowired:""`
|
|
sessionKeys sync.Map
|
|
server map[string]http.Handler
|
|
openServer http.Handler
|
|
}
|
|
|
|
func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
|
|
appId := ctx.Param("app")
|
|
if appId == "" {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
|
return
|
|
}
|
|
cfg := i.settingModule.Get(ctx)
|
|
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
|
req = req.WithContext(utils.SetLabel(req.Context(), "app", appId))
|
|
paths := strings.Split(req.URL.Path, "/")
|
|
req.URL.Path = fmt.Sprintf("/api/v1/%s/%s", mcp_server.GlobalBasePath, paths[len(paths)-1])
|
|
locale := utils.I18n(ctx)
|
|
if v, ok := i.server[locale]; ok {
|
|
v.ServeHTTP(ctx.Writer, req)
|
|
return
|
|
}
|
|
|
|
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
|
|
}
|
|
|
|
func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
|
|
apikey := ctx.Request.URL.Query().Get("apikey")
|
|
appId := ctx.Param("app")
|
|
if appId == "" {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
|
return
|
|
}
|
|
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByApp(ctx, appId, apikey)
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
|
return
|
|
}
|
|
if !ok {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
|
return
|
|
}
|
|
|
|
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/sse", mcp_server.GlobalBasePath)
|
|
i.handleSSE(ctx, i.openServer, SessionInfo{
|
|
Apikey: apikey,
|
|
App: appId,
|
|
})
|
|
}
|
|
|
|
func (i *imlMcpController) AppHandleMessage(ctx *gin.Context) {
|
|
appId := ctx.Param("app")
|
|
if appId == "" {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
|
|
return
|
|
}
|
|
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/message", mcp_server.GlobalBasePath)
|
|
ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
|
|
i.handleMessage(ctx, i.openServer)
|
|
}
|
|
|
|
func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string, error) {
|
|
cfg := i.settingModule.Get(ctx)
|
|
if cfg.SitePrefix == "" {
|
|
return "", fmt.Errorf("site prefix is empty")
|
|
}
|
|
appInfo, err := i.appModule.GetApp(ctx, appId)
|
|
if err != nil {
|
|
return "", fmt.Errorf("get app info error: %v", err)
|
|
}
|
|
return fmt.Sprintf(mcpDefaultConfig, appInfo.Name, fmt.Sprintf("%s/openapi/v1/mcp/app/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), appId)), nil
|
|
}
|
|
|
|
var mcpDefaultConfig = `{
|
|
"mcpServers": {
|
|
"%s": {
|
|
"url": "%s"
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
|
|
cfg := i.settingModule.Get(ctx)
|
|
if cfg.SitePrefix == "" {
|
|
return "", fmt.Errorf("site prefix is empty")
|
|
}
|
|
return fmt.Sprintf(mcpDefaultConfig, "APIPark-MCP-Server", fmt.Sprintf("%s/openapi/v1/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.GlobalBasePath)), nil
|
|
}
|
|
|
|
func (i *imlMcpController) OnComplete() {
|
|
i.server = make(map[string]http.Handler)
|
|
for language, tools := range mcpToolsByLanguage {
|
|
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
|
|
s.AddTool(tools[ToolServiceList], i.mcpModule.Services)
|
|
s.AddTool(tools[ToolOpenAPIDocument], i.mcpModule.APIs)
|
|
s.AddTool(tools[ToolInvokeAPI], i.mcpModule.Invoke)
|
|
i.server[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
|
if language == languageEnUs {
|
|
i.openServer = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
|
|
cfg := i.settingModule.Get(ctx)
|
|
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
|
locale := utils.I18n(ctx)
|
|
if v, ok := i.server[locale]; ok {
|
|
v.ServeHTTP(ctx.Writer, req)
|
|
return
|
|
}
|
|
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
|
|
}
|
|
|
|
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
|
|
apikey := ctx.Request.URL.Query().Get("apikey")
|
|
i.handleSSE(ctx, i.openServer, SessionInfo{
|
|
Apikey: apikey,
|
|
})
|
|
}
|
|
|
|
func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, sIn SessionInfo) {
|
|
|
|
writer := &ResponseWriter{
|
|
Writer: ctx.Writer,
|
|
sessionId: make(chan string),
|
|
}
|
|
defer close(writer.sessionId)
|
|
sessionId := ""
|
|
go func() {
|
|
var ok bool
|
|
sessionId, ok = <-writer.sessionId
|
|
if !ok {
|
|
return
|
|
}
|
|
i.sessionKeys.Store(sessionId, sIn)
|
|
}()
|
|
server.ServeHTTP(writer, ctx.Request)
|
|
i.sessionKeys.Delete(sessionId)
|
|
}
|
|
|
|
func (i *imlMcpController) GlobalHandleMessage(ctx *gin.Context) {
|
|
i.handleMessage(ctx, i.openServer)
|
|
}
|
|
|
|
func (i *imlMcpController) MCPHandle(ctx *gin.Context) {
|
|
cfg := i.settingModule.Get(ctx)
|
|
|
|
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
|
mcp_server.ServeHTTP(ctx.Writer, req)
|
|
}
|
|
|
|
func (i *imlMcpController) ServiceHandleSSE(ctx *gin.Context) {
|
|
apikey := ctx.Request.URL.Query().Get("apikey")
|
|
serviceId := ctx.Param("serviceId")
|
|
if serviceId == "" {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
|
|
return
|
|
}
|
|
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
|
return
|
|
}
|
|
if !ok {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
|
return
|
|
}
|
|
|
|
i.handleSSE(ctx, mcp_server.DefaultMCPServer(), SessionInfo{
|
|
Apikey: apikey,
|
|
})
|
|
}
|
|
|
|
func (i *imlMcpController) ServiceHandleMessage(ctx *gin.Context) {
|
|
i.handleMessage(ctx, mcp_server.DefaultMCPServer())
|
|
}
|
|
|
|
func (i *imlMcpController) ServiceHandleStreamHTTP(ctx *gin.Context) {
|
|
apikey := ctx.Request.URL.Query().Get("apikey")
|
|
serviceId := ctx.Param("serviceId")
|
|
if serviceId == "" {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
|
|
return
|
|
}
|
|
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
|
|
if err != nil {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
|
|
return
|
|
}
|
|
if !ok {
|
|
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
|
|
return
|
|
}
|
|
cfg := i.settingModule.Get(ctx)
|
|
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
|
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
|
|
mcp_server.DefaultMCPServer().ServeHTTP(ctx.Writer, req)
|
|
}
|
|
|
|
func (i *imlMcpController) handleMessage(ctx *gin.Context, server http.Handler) {
|
|
sessionId := ctx.Request.URL.Query().Get("sessionId")
|
|
params, ok := i.sessionKeys.Load(sessionId)
|
|
if !ok {
|
|
ctx.String(403, "sessionId not found")
|
|
return
|
|
}
|
|
ps, ok := params.(SessionInfo)
|
|
cfg := i.settingModule.Get(ctx)
|
|
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
|
|
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", ps.Apikey))
|
|
req = req.WithContext(utils.SetLabel(req.Context(), "app", ps.App))
|
|
server.ServeHTTP(ctx.Writer, req)
|
|
}
|
|
|
|
type SessionInfo struct {
|
|
Apikey string
|
|
App string
|
|
}
|