mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
Merge pull request #334 from APIParkLab/feature/liujian-1.8
Optimizing the parameter tiling of MCP Tool to facilitate AI understanding
This commit is contained in:
@@ -190,6 +190,7 @@ func (i *imlMcpController) OnComplete() {
|
||||
i.server["ja-JP"] = server.NewSSEServer(i.generateJPMCPServer(), server.WithBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
|
||||
|
||||
i.openServer = server.NewSSEServer(enSer, server.WithBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
|
||||
|
||||
}
|
||||
|
||||
func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
|
||||
@@ -263,6 +264,28 @@ 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.CheckAPIKeyAuthorization(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")
|
||||
apikey, ok := i.sessionKeys.Load(sessionId)
|
||||
|
||||
@@ -15,6 +15,7 @@ type IMcpController interface {
|
||||
ServiceHandleSSE(ctx *gin.Context)
|
||||
ServiceHandleMessage(ctx *gin.Context)
|
||||
GlobalMCPConfig(ctx *gin.Context) (string, error)
|
||||
ServiceHandleStreamHTTP(ctx *gin.Context)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -9,13 +9,13 @@ require (
|
||||
github.com/eolinker/eosc v0.18.3
|
||||
github.com/eolinker/go-common v1.1.7
|
||||
github.com/gabriel-vasile/mimetype v1.4.4
|
||||
github.com/getkin/kin-openapi v0.127.0
|
||||
github.com/getkin/kin-openapi v0.132.0
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||
github.com/mark3labs/mcp-go v0.17.0
|
||||
github.com/mark3labs/mcp-go v0.33.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nsqio/go-nsq v1.1.0
|
||||
github.com/ollama/ollama v0.5.8
|
||||
@@ -46,7 +46,6 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/invopop/yaml v0.3.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -59,10 +58,13 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/oapi-codegen/runtime v1.0.0 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
|
||||
@@ -34,10 +34,12 @@ github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
|
||||
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
|
||||
github.com/eolinker/go-common v1.1.7 h1:bi7wDmlCYQGjS3k8Bz/o+Mo9aMJAzmPsBLXWurxPfwk=
|
||||
github.com/eolinker/go-common v1.1.7/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
|
||||
github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
@@ -78,8 +80,6 @@ github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjw
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -101,8 +101,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930=
|
||||
github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
|
||||
github.com/mark3labs/mcp-go v0.33.0 h1:naxhjnTIs/tyPZmWUZFuG0lDmdA6sUyYGGf3gsHvTCc=
|
||||
github.com/mark3labs/mcp-go v0.33.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
@@ -118,6 +118,10 @@ github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
|
||||
github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
|
||||
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
|
||||
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/ollama/ollama v0.5.8 h1:b2S6YdZ18/ntCsWzoy/HmB3BHGW4GX0Qp7RARrJtJXU=
|
||||
github.com/ollama/ollama v0.5.8/go.mod h1:ibdmDvb/TjKY1OArBWIazL3pd1DHTk8eG2MMjEkWhiI=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
@@ -134,6 +138,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
|
||||
@@ -33,12 +33,12 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// queries.Set("query", "{cluster=\"apinto\"} | json | request_id = `c9f6b19c-7dfe-496b-9b39-4d049232fe95`")
|
||||
// queries.SetMCPServer("query", "{cluster=\"apinto\"} | json | request_id = `c9f6b19c-7dfe-496b-9b39-4d049232fe95`")
|
||||
// now := time.Now()
|
||||
// start := now.Add(-time.Hour * 24 * 30)
|
||||
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.Set("limit", "100")
|
||||
// queries.SetMCPServer("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.SetMCPServer("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.SetMCPServer("limit", "100")
|
||||
// a := time.Now()
|
||||
// result, err := send[LogInfo](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
|
||||
// if err != nil {
|
||||
@@ -57,8 +57,8 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// //queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h])) by (strategy)")
|
||||
// queries.Set("query", "sum(count_over_time({cluster=\"apinto\"}[24h]))")
|
||||
// //queries.SetMCPServer("query", "sum(count_over_time({cluster=\"apinto\"}[24h])) by (strategy)")
|
||||
// queries.SetMCPServer("query", "sum(count_over_time({cluster=\"apinto\"}[24h]))")
|
||||
// result, err := send[LogCount](http.MethodGet, "http://localhost:3100/loki/api/v1/query", headers, queries, "")
|
||||
// if err != nil {
|
||||
// t.Fatalf("failed to send request: %v", err)
|
||||
@@ -75,12 +75,12 @@ func TestLoki(t *testing.T) {
|
||||
// headers["Content-Type"] = "application/json"
|
||||
// headers["X-Scope-OrgID"] = "tenant1"
|
||||
// queries := url.Values{}
|
||||
// queries.Set("query", "{cluster=\"apinto\"} | json | strategy=\"03899736-5d79-4f26-bd6a-c312a5880780\"")
|
||||
// queries.SetMCPServer("query", "{cluster=\"apinto\"} | json | strategy=\"03899736-5d79-4f26-bd6a-c312a5880780\"")
|
||||
// now := time.Now()
|
||||
// start := now.Add(-time.Hour * 24 * 30)
|
||||
// queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.Set("limit", "1")
|
||||
// queries.SetMCPServer("start", strconv.FormatInt(start.UnixNano(), 10))
|
||||
// queries.SetMCPServer("end", strconv.FormatInt(now.UnixNano(), 10))
|
||||
// queries.SetMCPServer("limit", "1")
|
||||
// now = time.Now()
|
||||
// result, err := send[map[string]interface{}](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
|
||||
// t.LogItem(time.Now().Sub(now))
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package mcp_server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var client = http.Client{}
|
||||
|
||||
type Position string
|
||||
|
||||
const (
|
||||
PositionHeader Position = "header"
|
||||
PositionBody Position = "body"
|
||||
PositionQuery Position = "query"
|
||||
PositionPath Position = "path"
|
||||
)
|
||||
|
||||
type ContentType string
|
||||
|
||||
const (
|
||||
ContentTypeJSON ContentType = "application/json"
|
||||
ContentTypeXML ContentType = "application/xml"
|
||||
ContentTypeHTML ContentType = "text/html"
|
||||
ContentTypeText ContentType = "text/plain"
|
||||
ContentTypeForm ContentType = "application/x-www-form-urlencoded"
|
||||
ContentTypeFile ContentType = "multipart/form-data"
|
||||
)
|
||||
|
||||
func NewParam(position Position, required bool, description string) *Param {
|
||||
return &Param{position: position, required: required, description: description}
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
position Position
|
||||
required bool
|
||||
description string
|
||||
}
|
||||
|
||||
func (p *Param) Description() string {
|
||||
return p.description
|
||||
}
|
||||
|
||||
func (p *Param) Required() bool {
|
||||
return p.required
|
||||
}
|
||||
|
||||
type BodyParam struct {
|
||||
contentType ContentType
|
||||
params map[string]interface{}
|
||||
}
|
||||
|
||||
func NewBodyParam(contentType string) *BodyParam {
|
||||
t := ContentType(contentType)
|
||||
if t == "" {
|
||||
t = ContentTypeJSON
|
||||
}
|
||||
return &BodyParam{contentType: t}
|
||||
}
|
||||
|
||||
func (p *BodyParam) Set(k string, v interface{}) {
|
||||
if p.params == nil {
|
||||
p.params = make(map[string]interface{})
|
||||
}
|
||||
p.params[k] = v
|
||||
}
|
||||
|
||||
func (p *BodyParam) Encode() (string, error) {
|
||||
switch p.contentType {
|
||||
case ContentTypeJSON:
|
||||
data, err := json.Marshal(p.params)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("body param encode error: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
case ContentTypeForm, ContentTypeFile:
|
||||
data := url.Values{}
|
||||
for k, v := range p.params {
|
||||
data.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
return data.Encode(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported content type: %s", p.contentType)
|
||||
}
|
||||
}
|
||||
+213
-34
@@ -4,10 +4,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
|
||||
"github.com/eolinker/eosc"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,55 +20,120 @@ var (
|
||||
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
sseServers: eosc.BuildUntyped[string, *server.SSEServer](),
|
||||
servers: make(map[string]*Handler),
|
||||
}
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sseServers eosc.Untyped[string, *server.SSEServer]
|
||||
servers map[string]*Handler
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *Server) Set(path string, sseServer *server.SSEServer) {
|
||||
s.sseServers.Set(path, sseServer)
|
||||
type Handler struct {
|
||||
*server.MCPServer
|
||||
handlers map[string]http.Handler
|
||||
}
|
||||
|
||||
func (s *Server) Del(path string) {
|
||||
s.sseServers.Del(path)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
sseServer, has := s.sseServers.Get(trimPath(r.URL.Path))
|
||||
if has {
|
||||
sseServer.ServeHTTP(w, r)
|
||||
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 trimPath(path string) string {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
path = strings.TrimSuffix(path, "/message")
|
||||
path = strings.TrimSuffix(path, "/sse")
|
||||
return path
|
||||
}
|
||||
|
||||
func SetSSEServer(sid string, name string, version string, tools ...ITool) {
|
||||
s := server.NewMCPServer(name, version)
|
||||
for _, tool := range tools {
|
||||
tool.RegisterMCP(s)
|
||||
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),
|
||||
}
|
||||
apiPath := fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, sid)
|
||||
openAPIPath := fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, sid)
|
||||
mcpServer.Set(apiPath, server.NewSSEServer(s, server.WithBasePath(apiPath)))
|
||||
mcpServer.Set(openAPIPath, server.NewSSEServer(s, server.WithBasePath(openAPIPath)))
|
||||
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 DelSSEServer(sid string) {
|
||||
apiPath := fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, sid)
|
||||
openAPIPath := fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, sid)
|
||||
mcpServer.Del(apiPath)
|
||||
mcpServer.Del(openAPIPath)
|
||||
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) {
|
||||
@@ -76,3 +143,115 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
+44
-60
@@ -2,7 +2,6 @@ package mcp_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -16,36 +15,38 @@ import (
|
||||
)
|
||||
|
||||
type ITool interface {
|
||||
RegisterMCP(s *server.MCPServer)
|
||||
Tool() server.ServerTool
|
||||
}
|
||||
|
||||
const (
|
||||
MCPBody = "Body"
|
||||
MCPHeader = "Header"
|
||||
MCPQuery = "Query"
|
||||
MCPPath = "Path"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
name string
|
||||
url string
|
||||
method string
|
||||
contentType string
|
||||
params map[string]*Param
|
||||
opts []mcp.ToolOption
|
||||
}
|
||||
|
||||
func NewTool(name string, uri string, method string, contentType string, opts ...mcp.ToolOption) ITool {
|
||||
func (t *Tool) Tool() server.ServerTool {
|
||||
return server.ServerTool{
|
||||
Tool: mcp.NewTool(t.name, t.opts...),
|
||||
Handler: generateInvokeTool(t.url, t.method, t.contentType, t.params),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTool(name string, uri string, method string, contentType string, params map[string]*Param, opts ...mcp.ToolOption) ITool {
|
||||
return &Tool{
|
||||
name: name,
|
||||
url: uri,
|
||||
method: method,
|
||||
contentType: contentType,
|
||||
params: params,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
s.AddTool(mcp.NewTool(t.name, t.opts...), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
func generateInvokeTool(path string, method string, contentType string, params map[string]*Param) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
invokeAddress := utils.GatewayInvoke(ctx)
|
||||
if invokeAddress == "" {
|
||||
return nil, fmt.Errorf("invoke address is empty")
|
||||
@@ -58,69 +59,54 @@ func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
|
||||
path := t.url
|
||||
queries := url.Values{}
|
||||
headers := make(map[string]string)
|
||||
body := ""
|
||||
for k, v := range request.Params.Arguments {
|
||||
if k == "Body" {
|
||||
switch a := v.(type) {
|
||||
case string:
|
||||
body = a
|
||||
case map[string]interface{}:
|
||||
switch t.contentType {
|
||||
case "application/json":
|
||||
tmp, _ := json.Marshal(a)
|
||||
body = string(tmp)
|
||||
case "application/x-www-form-urlencoded":
|
||||
bodyValue := url.Values{}
|
||||
for kk, vv := range a {
|
||||
bodyValue.Set(kk, fmt.Sprintf("%v", vv))
|
||||
}
|
||||
body = bodyValue.Encode()
|
||||
bodyParam := NewBodyParam(contentType)
|
||||
for k, p := range params {
|
||||
vv, ok := request.GetArguments()[k]
|
||||
if !ok && p.required {
|
||||
return nil, fmt.Errorf("param %s is required", k)
|
||||
}
|
||||
if p.position == PositionHeader || p.position == PositionQuery || p.position == PositionPath {
|
||||
v, ok := vv.(string)
|
||||
if !ok || v == "<nil>" {
|
||||
if p.required {
|
||||
return nil, fmt.Errorf("param %s is required", k)
|
||||
}
|
||||
default:
|
||||
tmp, _ := json.Marshal(a)
|
||||
body = string(tmp)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
tmp, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case MCPHeader:
|
||||
for kk, vv := range tmp {
|
||||
headers[kk] = fmt.Sprintf("%v", vv)
|
||||
}
|
||||
|
||||
case MCPQuery:
|
||||
for kk, vv := range tmp {
|
||||
queries.Set(kk, fmt.Sprintf("%v", vv))
|
||||
}
|
||||
case MCPPath:
|
||||
for kk, vv := range tmp {
|
||||
p, ok := vv.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid path %s", v)
|
||||
}
|
||||
path = strings.Replace(path, fmt.Sprintf("{%s}", kk), p, -1)
|
||||
switch p.position {
|
||||
case PositionPath:
|
||||
path = strings.ReplaceAll(path, "{"+k+"}", fmt.Sprintf("%v", vv))
|
||||
case PositionQuery:
|
||||
queries.Set(k, fmt.Sprintf("%v", vv))
|
||||
case PositionHeader:
|
||||
headers[k] = fmt.Sprintf("%v", vv)
|
||||
case PositionBody:
|
||||
if vv == nil {
|
||||
continue
|
||||
}
|
||||
bodyParam.Set(k, vv)
|
||||
}
|
||||
}
|
||||
bodyData, err := bodyParam.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = path
|
||||
u.RawQuery = queries.Encode()
|
||||
|
||||
req, err := http.NewRequest(t.method, u.String(), strings.NewReader(body))
|
||||
req, err := http.NewRequest(method, u.String(), strings.NewReader(bodyData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if t.contentType != "" {
|
||||
req.Header.Set("Content-Type", t.contentType)
|
||||
if contentType != "" {
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
}
|
||||
apikey := utils.Label(ctx, "apikey")
|
||||
if apikey != "" {
|
||||
@@ -141,7 +127,5 @@ func (t *Tool) RegisterMCP(s *server.MCPServer) {
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(string(d)), nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var client = http.Client{}
|
||||
|
||||
+24
-15
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
func genOpenAPI3Template(title string, description string) *openapi3.T {
|
||||
result := new(openapi3.T)
|
||||
result.OpenAPI = "3.1.0"
|
||||
result.OpenAPI = "3.0.1"
|
||||
result.Info = &openapi3.Info{
|
||||
Title: title,
|
||||
Description: description,
|
||||
@@ -37,6 +37,8 @@ func genOperation(summary string, description string, variables []*ai_api_dto.Ai
|
||||
|
||||
func genRequestBody(variables []*ai_api_dto.AiPromptVariable) *openapi3.RequestBodyRef {
|
||||
requestBody := openapi3.NewRequestBody()
|
||||
requestBody.Description = "Request body"
|
||||
requestBody.Required = true
|
||||
requestBody.Content = openapi3.NewContentWithSchema(genRequestBodySchema(variables), []string{"application/json"})
|
||||
return &openapi3.RequestBodyRef{
|
||||
Value: requestBody,
|
||||
@@ -55,10 +57,14 @@ func genResponse() *openapi3.ResponseRef {
|
||||
|
||||
func genRequestBodySchema(variables []*ai_api_dto.AiPromptVariable) *openapi3.Schema {
|
||||
result := openapi3.NewObjectSchema()
|
||||
required := make([]string, 0, 2)
|
||||
required = append(required, "messages")
|
||||
if len(variables) > 0 {
|
||||
result.WithProperty("variables", genVariableSchema(variables))
|
||||
result.WithRequired([]string{"variables", "messages"})
|
||||
required = append(required, "variables")
|
||||
}
|
||||
|
||||
result.WithRequired(required)
|
||||
streamSchema := openapi3.NewBoolSchema()
|
||||
streamSchema.Title = "stream"
|
||||
streamSchema.Description = "Whether to stream the response"
|
||||
@@ -129,6 +135,8 @@ func genMessageSchema() *openapi3.Schema {
|
||||
"role": roleSchema,
|
||||
"content": contentSchema,
|
||||
})
|
||||
|
||||
result.WithRequired([]string{"role", "content"})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -137,20 +145,21 @@ func genMessagesSchema() *openapi3.Schema {
|
||||
result.Title = "Messages"
|
||||
result.Description = "Chat Messages"
|
||||
result.Items = openapi3.NewSchemaRef("#/components/schemas/Message", messageSchema)
|
||||
result.Required = []string{"content", "role"}
|
||||
return result
|
||||
}
|
||||
|
||||
func genResponseSchema() *openapi3.Schema {
|
||||
result := openapi3.NewObjectSchema()
|
||||
result.Description = "Response from the server"
|
||||
|
||||
|
||||
// 创建 choices 数组
|
||||
choicesSchema := openapi3.NewArraySchema()
|
||||
choiceItemSchema := openapi3.NewObjectSchema()
|
||||
|
||||
|
||||
// choice 中的 message 字段
|
||||
choiceItemSchema.WithPropertyRef("message", messageSchemaRef)
|
||||
|
||||
|
||||
// finish_reason 字段
|
||||
finishReasonSchema := openapi3.NewStringSchema().WithEnum(
|
||||
"stop",
|
||||
@@ -160,41 +169,41 @@ func genResponseSchema() *openapi3.Schema {
|
||||
"null",
|
||||
)
|
||||
choiceItemSchema.WithProperty("finish_reason", finishReasonSchema)
|
||||
|
||||
|
||||
// index 字段
|
||||
choiceItemSchema.WithProperty("index", openapi3.NewIntegerSchema())
|
||||
|
||||
|
||||
// logprobs 字段,可以为 null
|
||||
choiceItemSchema.WithProperty("logprobs", openapi3.NewSchema().WithNullable())
|
||||
|
||||
|
||||
choicesSchema.Items = &openapi3.SchemaRef{Value: choiceItemSchema}
|
||||
result.WithProperty("choices", choicesSchema)
|
||||
|
||||
|
||||
// object 字段
|
||||
result.WithProperty("object", openapi3.NewStringSchema().WithEnum("chat.completion"))
|
||||
|
||||
|
||||
// usage 字段
|
||||
usageSchema := openapi3.NewObjectSchema()
|
||||
usageSchema.WithProperty("prompt_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("completion_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("total_tokens", openapi3.NewIntegerSchema())
|
||||
|
||||
|
||||
// prompt_tokens_details 字段
|
||||
promptTokensDetailsSchema := openapi3.NewObjectSchema()
|
||||
promptTokensDetailsSchema.WithProperty("cached_tokens", openapi3.NewIntegerSchema())
|
||||
usageSchema.WithProperty("prompt_tokens_details", promptTokensDetailsSchema)
|
||||
|
||||
|
||||
result.WithProperty("usage", usageSchema)
|
||||
|
||||
|
||||
// 其他字段
|
||||
result.WithProperty("created", openapi3.NewIntegerSchema())
|
||||
result.WithProperty("system_fingerprint", openapi3.NewStringSchema().WithNullable())
|
||||
result.WithProperty("model", openapi3.NewStringSchema())
|
||||
result.WithProperty("id", openapi3.NewStringSchema())
|
||||
|
||||
|
||||
// 保留原有的错误字段
|
||||
result.WithProperty("code", openapi3.NewIntegerSchema())
|
||||
result.WithProperty("error", openapi3.NewStringSchema())
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -78,55 +78,6 @@ type imlCatalogueModule struct {
|
||||
root *Root
|
||||
}
|
||||
|
||||
//func (i *imlCatalogueModule) OnInit() {
|
||||
// register.Handle(func(v server.Server) {
|
||||
// ctx := context.Background()
|
||||
// list, err := i.releaseService.GetRunningList(ctx)
|
||||
// if err != nil {
|
||||
// log.Errorf("onInit: get running list failed:%s", err.Error())
|
||||
// return
|
||||
// }
|
||||
// if len(list) < 1 || list[0].APICount > 0 {
|
||||
// return
|
||||
// }
|
||||
// serviceMap := make(map[string]*release.Release)
|
||||
// serviceIds := make([]string, 0, len(list))
|
||||
// for _, v := range list {
|
||||
// if _, ok := serviceMap[v.Service]; !ok {
|
||||
// serviceMap[v.Service] = v
|
||||
// serviceIds = append(serviceIds, v.Service)
|
||||
// }
|
||||
// }
|
||||
// if len(serviceIds) < 1 {
|
||||
// return
|
||||
// }
|
||||
// commitIds, err := i.releaseService.GetRunningApiDocCommits(ctx, serviceIds...)
|
||||
// if err != nil {
|
||||
// log.Errorf("onInit: get running api doc commits failed:%s", err.Error())
|
||||
// return
|
||||
// }
|
||||
// if len(commitIds) < 1 {
|
||||
// return
|
||||
// }
|
||||
// listCommits, err := i.apiDocService.ListDocCommit(ctx, commitIds...)
|
||||
// if err != nil {
|
||||
// log.Error("onInit: list doc commit failed:", err.Error())
|
||||
// return
|
||||
// }
|
||||
// for _, v := range listCommits {
|
||||
// m, ok := serviceMap[v.Target]
|
||||
// if !ok {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// i.releaseService.UpdateRelease(ctx, m.UUID, &release.Update{
|
||||
// APICount: &v.Data.APICount,
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
//
|
||||
//}
|
||||
|
||||
func (i *imlCatalogueModule) DefaultCatalogue(ctx context.Context) (*catalogue_dto.Catalogue, error) {
|
||||
catalogues, err := i.catalogueService.List(ctx)
|
||||
if err != nil {
|
||||
|
||||
+72
-73
@@ -10,7 +10,6 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/subscribe"
|
||||
|
||||
@@ -48,7 +47,7 @@ type imlMcpModule struct {
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
keyword, _ := req.Params.Arguments["keyword"].(string)
|
||||
keyword, _ := req.GetArguments()["keyword"].(string)
|
||||
list, err := i.serviceService.Search(ctx, keyword, map[string]interface{}{
|
||||
"as_server": true,
|
||||
}, "update_at desc")
|
||||
@@ -116,34 +115,34 @@ func (i *imlMcpModule) Services(ctx context.Context, req mcp.CallToolRequest) (*
|
||||
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
keyword := req.Params.Arguments["keyword"].(string)
|
||||
condition := make(map[string]interface{})
|
||||
condition["as_app"] = true
|
||||
list, err := i.serviceService.Search(ctx, keyword, condition, "update_at desc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search service error: %w", err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
list, err = i.serviceService.Search(ctx, "", condition, "update_at desc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("search service error: %w", err)
|
||||
}
|
||||
}
|
||||
data, _ := json.Marshal(utils.SliceToSlice(list, func(s *service.Service) *mcp_dto.App {
|
||||
return &mcp_dto.App{
|
||||
Id: s.Id,
|
||||
Name: s.Name,
|
||||
Description: s.Name,
|
||||
CreateTime: s.CreateTime,
|
||||
UpdateTime: s.UpdateTime,
|
||||
}
|
||||
}))
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
//func (i *imlMcpModule) Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// keyword := req.GetArguments()["keyword"].(string)
|
||||
// condition := make(map[string]interface{})
|
||||
// condition["as_app"] = true
|
||||
// list, err := i.serviceService.Search(ctx, keyword, condition, "update_at desc")
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("search service error: %w", err)
|
||||
// }
|
||||
// if len(list) == 0 {
|
||||
// list, err = i.serviceService.Search(ctx, "", condition, "update_at desc")
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("search service error: %w", err)
|
||||
// }
|
||||
// }
|
||||
// data, _ := json.Marshal(utils.SliceToSlice(list, func(s *service.Service) *mcp_dto.App {
|
||||
// return &mcp_dto.App{
|
||||
// Id: s.Id,
|
||||
// Name: s.Name,
|
||||
// Description: s.Name,
|
||||
// CreateTime: s.CreateTime,
|
||||
// UpdateTime: s.UpdateTime,
|
||||
// }
|
||||
// }))
|
||||
// return mcp.NewToolResultText(string(data)), nil
|
||||
//}
|
||||
|
||||
func (i *imlMcpModule) APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
serviceId, _ := req.Params.Arguments["service"].(string)
|
||||
serviceId, _ := req.GetArguments()["service"].(string)
|
||||
serviceIds := make([]string, 0, 1)
|
||||
if serviceId == "" {
|
||||
serviceIds = append(serviceIds, serviceId)
|
||||
@@ -190,45 +189,45 @@ func (i *imlMcpModule) APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
|
||||
func (i *imlMcpModule) SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
serviceId, ok := req.Params.Arguments["service"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("service id is required")
|
||||
}
|
||||
subscribes, err := i.subscriberService.Subscribers(ctx, serviceId, subscribe.ApplyStatusSubscribe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get subscriber error: %w,service id is %s", err, serviceId)
|
||||
}
|
||||
appIds := utils.SliceToSlice(subscribes, func(s *subscribe.Subscribe) string {
|
||||
return s.Application
|
||||
})
|
||||
if len(appIds) == 0 {
|
||||
return nil, fmt.Errorf("no subscriber found,service id is %s", serviceId)
|
||||
}
|
||||
list, err := i.appAuthorizationService.ListByApp(ctx, appIds...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get app authorization error: %w,app ids is %s", err, appIds)
|
||||
}
|
||||
result := utils.SliceToSlice(list, func(a *application_authorization.Authorization) *mcp_dto.AppAuthorization {
|
||||
return &mcp_dto.AppAuthorization{
|
||||
Id: a.UUID,
|
||||
Name: a.Name,
|
||||
Position: a.Position,
|
||||
TokenName: a.TokenName,
|
||||
Config: a.Config,
|
||||
}
|
||||
}, func(a *application_authorization.Authorization) bool {
|
||||
if a.Type != "apikey" {
|
||||
return false
|
||||
}
|
||||
if a.ExpireTime != 0 && a.ExpireTime < time.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
data, _ := json.Marshal(result)
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
}
|
||||
//func (i *imlMcpModule) SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// serviceId, ok := req.GetArguments()["service"].(string)
|
||||
// if !ok {
|
||||
// return nil, fmt.Errorf("service id is required")
|
||||
// }
|
||||
// subscribes, err := i.subscriberService.Subscribers(ctx, serviceId, subscribe.ApplyStatusSubscribe)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("get subscriber error: %w,service id is %s", err, serviceId)
|
||||
// }
|
||||
// appIds := utils.SliceToSlice(subscribes, func(s *subscribe.Subscribe) string {
|
||||
// return s.Application
|
||||
// })
|
||||
// if len(appIds) == 0 {
|
||||
// return nil, fmt.Errorf("no subscriber found,service id is %s", serviceId)
|
||||
// }
|
||||
// list, err := i.appAuthorizationService.ListByApp(ctx, appIds...)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("get app authorization error: %w,app ids is %s", err, appIds)
|
||||
// }
|
||||
// result := utils.SliceToSlice(list, func(a *application_authorization.Authorization) *mcp_dto.AppAuthorization {
|
||||
// return &mcp_dto.AppAuthorization{
|
||||
// Id: a.UUID,
|
||||
// Name: a.Name,
|
||||
// position: a.position,
|
||||
// TokenName: a.TokenName,
|
||||
// Config: a.Config,
|
||||
// }
|
||||
// }, func(a *application_authorization.Authorization) bool {
|
||||
// if a.Type != "apikey" {
|
||||
// return false
|
||||
// }
|
||||
// if a.ExpireTime != 0 && a.ExpireTime < time.Now().Unix() {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// })
|
||||
// data, _ := json.Marshal(result)
|
||||
// return mcp.NewToolResultText(string(data)), nil
|
||||
//}
|
||||
|
||||
var (
|
||||
client = &http.Client{}
|
||||
@@ -248,18 +247,18 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
u.Scheme = "http"
|
||||
}
|
||||
|
||||
path, ok := req.Params.Arguments["path"].(string)
|
||||
path, ok := req.GetArguments()["path"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid path")
|
||||
}
|
||||
u.Path = fmt.Sprintf("%s/%s", strings.TrimSuffix(u.Path, "/"), strings.TrimPrefix(path, "/"))
|
||||
|
||||
method, ok := req.Params.Arguments["method"].(string)
|
||||
method, ok := req.GetArguments()["method"].(string)
|
||||
if !ok {
|
||||
method = "GET"
|
||||
}
|
||||
queryParam := url.Values{}
|
||||
query, ok := req.Params.Arguments["query"].(map[string]interface{})
|
||||
query, ok := req.GetArguments()["query"].(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range query {
|
||||
switch v := v.(type) {
|
||||
@@ -278,7 +277,7 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
}
|
||||
u.RawQuery = queryParam.Encode()
|
||||
headerParam := http.Header{}
|
||||
header, ok := req.Params.Arguments["header"].(map[string]interface{})
|
||||
header, ok := req.GetArguments()["header"].(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range header {
|
||||
switch v := v.(type) {
|
||||
@@ -294,12 +293,12 @@ func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mc
|
||||
}
|
||||
}
|
||||
|
||||
body, ok := req.Params.Arguments["body"].(string)
|
||||
body, ok := req.GetArguments()["body"].(string)
|
||||
if !ok {
|
||||
body = ""
|
||||
}
|
||||
|
||||
contentType, ok := req.Params.Arguments["content-type"].(string)
|
||||
contentType, ok := req.GetArguments()["content-type"].(string)
|
||||
if !ok {
|
||||
contentType = "application/json"
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ type IMcpModule interface {
|
||||
// Services 获取服务列表
|
||||
Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
// Apps 获取应用列表
|
||||
Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
//Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
// APIs 获取API列表
|
||||
APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
|
||||
// SubscriberAuthorizations 获取订阅者授权
|
||||
SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
//SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
Invoke(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
}
|
||||
|
||||
|
||||
+1
-64
@@ -15,8 +15,6 @@ import (
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
api_doc "github.com/APIParkLab/APIPark/service/api-doc"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
strategy_driver "github.com/APIParkLab/APIPark/module/strategy/driver"
|
||||
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
|
||||
@@ -657,68 +655,7 @@ func (i *imlPublishModule) updateMCPServer(ctx context.Context, sid string, name
|
||||
if err != nil {
|
||||
return fmt.Errorf("get api doc commit error: %w", err)
|
||||
}
|
||||
mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
|
||||
}
|
||||
tools := make([]mcp_server.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))
|
||||
headers := make(map[string]interface{})
|
||||
queries := make(map[string]interface{})
|
||||
path := make(map[string]interface{})
|
||||
for _, v := range a.Params {
|
||||
p := map[string]interface{}{
|
||||
"type": "string",
|
||||
"required": v.Required,
|
||||
"description": v.Description,
|
||||
}
|
||||
switch v.In {
|
||||
case "header":
|
||||
headers[v.Name] = p
|
||||
case "query":
|
||||
queries[v.Name] = p
|
||||
case "path":
|
||||
path[v.Name] = p
|
||||
}
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPHeader, mcp.Properties(headers), mcp.Description("request headers.")))
|
||||
}
|
||||
if len(queries) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPQuery, mcp.Properties(queries), mcp.Description("request queries.")))
|
||||
}
|
||||
if len(path) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPPath, mcp.Properties(path), mcp.Description("request path params.")))
|
||||
}
|
||||
if a.Body != nil {
|
||||
type Schema struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Properties map[string]interface{} `mapstructure:"properties"`
|
||||
Items interface{} `mapstructure:"items"`
|
||||
}
|
||||
var tmp Schema
|
||||
err = mapstructure.Decode(a.Body, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//switch a.ContentType {
|
||||
//case "application/json":
|
||||
switch tmp.Type {
|
||||
case "object":
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPBody, mcp.Properties(tmp.Properties), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
case "array":
|
||||
toolOptions = append(toolOptions, mcp.WithArray(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
}
|
||||
//case "application/x-www-form-urlencoded":
|
||||
// toolOptions = append(toolOptions, mcp.WithString(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
|
||||
}
|
||||
tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...))
|
||||
}
|
||||
mcp_server.SetSSEServer(sid, name, version, tools...)
|
||||
return nil
|
||||
return mcp_server.SetServerByOpenapi(sid, name, version, commitDoc.Data.Content)
|
||||
}
|
||||
|
||||
func (i *imlPublishModule) Detail(ctx context.Context, serviceId string, id string) (*dto.PublishDetail, error) {
|
||||
|
||||
+2
-62
@@ -14,12 +14,8 @@ import (
|
||||
|
||||
"github.com/APIParkLab/APIPark/common"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/eolinker/go-common/register"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
|
||||
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/release"
|
||||
@@ -395,67 +391,11 @@ func (i *imlServiceModule) updateMCPServer(ctx context.Context, sid string, name
|
||||
if err != nil {
|
||||
return fmt.Errorf("get api doc commit error: %w", err)
|
||||
}
|
||||
mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert mcp from openapi3 data error: %w", err)
|
||||
}
|
||||
tools := make([]mcp_server.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))
|
||||
headers := make(map[string]interface{})
|
||||
queries := make(map[string]interface{})
|
||||
path := make(map[string]interface{})
|
||||
for _, v := range a.Params {
|
||||
p := map[string]interface{}{
|
||||
"type": "string",
|
||||
"required": v.Required,
|
||||
"description": v.Description,
|
||||
}
|
||||
switch v.In {
|
||||
case "header":
|
||||
headers[v.Name] = p
|
||||
case "query":
|
||||
queries[v.Name] = p
|
||||
case "path":
|
||||
path[v.Name] = p
|
||||
}
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPHeader, mcp.Properties(headers), mcp.Description("request headers.")))
|
||||
}
|
||||
if len(queries) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPQuery, mcp.Properties(queries), mcp.Description("request queries.")))
|
||||
}
|
||||
if len(path) > 0 {
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPPath, mcp.Properties(path), mcp.Description("request path params.")))
|
||||
}
|
||||
if a.Body != nil {
|
||||
type Schema struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Properties map[string]interface{} `mapstructure:"properties"`
|
||||
Items interface{} `mapstructure:"items"`
|
||||
}
|
||||
var tmp Schema
|
||||
err = mapstructure.Decode(a.Body, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch tmp.Type {
|
||||
case "object":
|
||||
toolOptions = append(toolOptions, mcp.WithObject(mcp_server.MCPBody, mcp.Properties(tmp.Properties), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
case "array":
|
||||
toolOptions = append(toolOptions, mcp.WithArray(mcp_server.MCPBody, mcp.Items(tmp.Items), mcp.Description("request body,it is avalible when method is POST、PUT、PATCH.")))
|
||||
}
|
||||
}
|
||||
tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...))
|
||||
}
|
||||
mcp_server.SetSSEServer(sid, name, version, tools...)
|
||||
return nil
|
||||
return mcp_server.SetServerByOpenapi(sid, name, version, commitDoc.Data.Content)
|
||||
}
|
||||
|
||||
func (i *imlServiceModule) deleteMCPServer(ctx context.Context, sid string) {
|
||||
mcp_server.DelSSEServer(sid)
|
||||
mcp_server.DelServer(sid)
|
||||
}
|
||||
|
||||
func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.ExportService, error) {
|
||||
|
||||
@@ -15,15 +15,23 @@ func (p *plugin) mcpAPIs() []pm3.Api {
|
||||
globalMessagePath := fmt.Sprintf("/openapi/v1/%s/message", strings.Trim(mcp_server.GlobalBasePath, "/"))
|
||||
serviceMessagePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/message", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
serviceSSEPath := fmt.Sprintf("/openapi/v1/%s/:serviceId/sse", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
serviceStreamablePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/mcp", strings.Trim(mcp_server.ServiceBasePath, "/"))
|
||||
ignore.IgnorePath("openapi", http.MethodPost, globalMessagePath)
|
||||
|
||||
ignore.IgnorePath("openapi", http.MethodGet, serviceSSEPath)
|
||||
ignore.IgnorePath("openapi", http.MethodPost, serviceMessagePath)
|
||||
ignore.IgnorePath("openapi", http.MethodGet, serviceStreamablePath)
|
||||
ignore.IgnorePath("openapi", http.MethodPost, serviceStreamablePath)
|
||||
ignore.IgnorePath("openapi", http.MethodDelete, serviceStreamablePath)
|
||||
|
||||
return []pm3.Api{
|
||||
pm3.CreateApiSimple(http.MethodGet, fmt.Sprintf("/openapi/v1/%s/sse", strings.Trim(mcp_server.GlobalBasePath, "/")), p.mcpController.GlobalHandleSSE),
|
||||
pm3.CreateApiSimple(http.MethodPost, globalMessagePath, p.mcpController.GlobalHandleMessage),
|
||||
pm3.CreateApiSimple(http.MethodGet, serviceSSEPath, p.mcpController.ServiceHandleSSE),
|
||||
pm3.CreateApiSimple(http.MethodPost, serviceMessagePath, p.mcpController.ServiceHandleMessage),
|
||||
|
||||
pm3.CreateApiSimple(http.MethodPost, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
pm3.CreateApiSimple(http.MethodDelete, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
pm3.CreateApiSimple(http.MethodGet, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ type Doc struct {
|
||||
Content string `gorm:"type:longtext;null;column:content;comment:文档内容"`
|
||||
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"`
|
||||
UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
|
||||
APICount int64 `gorm:"type:int(11);not null;column:api_count;comment:接口数量"`
|
||||
APICount int64 `gorm:"type:int(11);not null;column:api_count;default:0;comment:接口数量"`
|
||||
}
|
||||
|
||||
func (i *Doc) TableName() string {
|
||||
|
||||
Reference in New Issue
Block a user