zijiren 7 месяцев назад
Родитель
Сommit
8592dc99c3

+ 9 - 3
core/controller/mcp/embedmcp.go

@@ -54,7 +54,7 @@ type EmbedMCP struct {
 	ConfigTemplates EmbedMCPConfigTemplates `json:"config_templates"`
 }
 
-func newEmbedMCP(mcp *mcpservers.EmbedMcp, enabled bool) *EmbedMCP {
+func newEmbedMCP(mcp *mcpservers.McpServer, enabled bool) *EmbedMCP {
 	emcp := &EmbedMCP{
 		ID:              mcp.ID,
 		Enabled:         enabled,
@@ -140,7 +140,7 @@ func GetEmbedConfig(
 }
 
 func ToPublicMCP(
-	e mcpservers.EmbedMcp,
+	e mcpservers.McpServer,
 	initConfig map[string]string,
 	enabled bool,
 ) (*model.PublicMCP, error) {
@@ -150,8 +150,8 @@ func ToPublicMCP(
 	}
 	pmcp := &model.PublicMCP{
 		ID:          e.ID,
-		Type:        model.PublicMCPTypeEmbed,
 		Name:        e.Name,
+		LogoURL:     e.LogoURL,
 		Readme:      e.Readme,
 		Tags:        e.Tags,
 		EmbedConfig: embedConfig,
@@ -161,6 +161,12 @@ func ToPublicMCP(
 	} else {
 		pmcp.Status = model.PublicMCPStatusDisabled
 	}
+	switch e.Type {
+	case mcpservers.McpTypeEmbed:
+		pmcp.Type = model.PublicMCPTypeEmbed
+	case mcpservers.McpTypeDocs:
+		pmcp.Type = model.PublicMCPTypeDocs
+	}
 	return pmcp, nil
 }
 

+ 1 - 1
core/controller/mcp/publicmcp.go

@@ -55,7 +55,7 @@ func NewPublicMCPResponse(host string, mcp model.PublicMCP) PublicMCPResponse {
 			ep.SSE = "/sse"
 			ep.StreamableHTTP = "/mcp"
 		}
-	case model.PublicMCPTypeGitRepo:
+	case model.PublicMCPTypeDocs:
 	}
 	return PublicMCPResponse{
 		PublicMCP: mcp,

+ 57 - 3
core/docs/docs.go

@@ -6921,6 +6921,35 @@ const docTemplate = `{
                 }
             }
         },
+        "/mcp": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            },
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            },
+            "delete": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            }
+        },
         "/mcp/group/message": {
             "post": {
                 "security": [
@@ -7023,6 +7052,28 @@ const docTemplate = `{
                 "responses": {}
             }
         },
+        "/message": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Public MCP SSE Server",
+                "responses": {}
+            }
+        },
+        "/sse": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Public MCP SSE Server",
+                "responses": {}
+            }
+        },
         "/v1/audio/speech": {
             "post": {
                 "security": [
@@ -8502,6 +8553,9 @@ const docTemplate = `{
         "controller.MCPEndpoint": {
             "type": "object",
             "properties": {
+                "host": {
+                    "type": "string"
+                },
                 "sse": {
                     "type": "string"
                 },
@@ -10512,17 +10566,17 @@ const docTemplate = `{
             "enum": [
                 "mcp_proxy_sse",
                 "mcp_proxy_streamable",
-                "mcp_git_repo",
+                "mcp_docs",
                 "mcp_openapi",
                 "mcp_embed"
             ],
             "x-enum-comments": {
-                "PublicMCPTypeGitRepo": "read only"
+                "PublicMCPTypeDocs": "read only"
             },
             "x-enum-varnames": [
                 "PublicMCPTypeProxySSE",
                 "PublicMCPTypeProxyStreamable",
-                "PublicMCPTypeGitRepo",
+                "PublicMCPTypeDocs",
                 "PublicMCPTypeOpenAPI",
                 "PublicMCPTypeEmbed"
             ]

+ 57 - 3
core/docs/swagger.json

@@ -6912,6 +6912,35 @@
                 }
             }
         },
+        "/mcp": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            },
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            },
+            "delete": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Host MCP Streamable Server",
+                "responses": {}
+            }
+        },
         "/mcp/group/message": {
             "post": {
                 "security": [
@@ -7014,6 +7043,28 @@
                 "responses": {}
             }
         },
+        "/message": {
+            "post": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Public MCP SSE Server",
+                "responses": {}
+            }
+        },
+        "/sse": {
+            "get": {
+                "security": [
+                    {
+                        "ApiKeyAuth": []
+                    }
+                ],
+                "summary": "Public MCP SSE Server",
+                "responses": {}
+            }
+        },
         "/v1/audio/speech": {
             "post": {
                 "security": [
@@ -8493,6 +8544,9 @@
         "controller.MCPEndpoint": {
             "type": "object",
             "properties": {
+                "host": {
+                    "type": "string"
+                },
                 "sse": {
                     "type": "string"
                 },
@@ -10503,17 +10557,17 @@
             "enum": [
                 "mcp_proxy_sse",
                 "mcp_proxy_streamable",
-                "mcp_git_repo",
+                "mcp_docs",
                 "mcp_openapi",
                 "mcp_embed"
             ],
             "x-enum-comments": {
-                "PublicMCPTypeGitRepo": "read only"
+                "PublicMCPTypeDocs": "read only"
             },
             "x-enum-varnames": [
                 "PublicMCPTypeProxySSE",
                 "PublicMCPTypeProxyStreamable",
-                "PublicMCPTypeGitRepo",
+                "PublicMCPTypeDocs",
                 "PublicMCPTypeOpenAPI",
                 "PublicMCPTypeEmbed"
             ]

+ 33 - 3
core/docs/swagger.yaml

@@ -256,6 +256,8 @@ definitions:
     type: object
   controller.MCPEndpoint:
     properties:
+      host:
+        type: string
       sse:
         type: string
       streamable_http:
@@ -1648,16 +1650,16 @@ definitions:
     enum:
     - mcp_proxy_sse
     - mcp_proxy_streamable
-    - mcp_git_repo
+    - mcp_docs
     - mcp_openapi
     - mcp_embed
     type: string
     x-enum-comments:
-      PublicMCPTypeGitRepo: read only
+      PublicMCPTypeDocs: read only
     x-enum-varnames:
     - PublicMCPTypeProxySSE
     - PublicMCPTypeProxyStreamable
-    - PublicMCPTypeGitRepo
+    - PublicMCPTypeDocs
     - PublicMCPTypeOpenAPI
     - PublicMCPTypeEmbed
   model.RequestDetail:
@@ -6083,6 +6085,22 @@ paths:
       summary: Search tokens
       tags:
       - tokens
+  /mcp:
+    delete:
+      responses: {}
+      security:
+      - ApiKeyAuth: []
+      summary: Host MCP Streamable Server
+    get:
+      responses: {}
+      security:
+      - ApiKeyAuth: []
+      summary: Host MCP Streamable Server
+    post:
+      responses: {}
+      security:
+      - ApiKeyAuth: []
+      summary: Host MCP Streamable Server
   /mcp/group/{id}:
     delete:
       responses: {}
@@ -6139,6 +6157,18 @@ paths:
       security:
       - ApiKeyAuth: []
       summary: Public MCP SSE Server
+  /message:
+    post:
+      responses: {}
+      security:
+      - ApiKeyAuth: []
+      summary: Public MCP SSE Server
+  /sse:
+    get:
+      responses: {}
+      security:
+      - ApiKeyAuth: []
+      summary: Public MCP SSE Server
   /v1/audio/speech:
     post:
       description: AudioSpeech

+ 1 - 1
core/model/publicmcp.go

@@ -29,7 +29,7 @@ type PublicMCPType string
 const (
 	PublicMCPTypeProxySSE        PublicMCPType = "mcp_proxy_sse"
 	PublicMCPTypeProxyStreamable PublicMCPType = "mcp_proxy_streamable"
-	PublicMCPTypeGitRepo         PublicMCPType = "mcp_git_repo" // read only
+	PublicMCPTypeDocs            PublicMCPType = "mcp_docs" // read only
 	PublicMCPTypeOpenAPI         PublicMCPType = "mcp_openapi"
 	PublicMCPTypeEmbed           PublicMCPType = "mcp_embed"
 )

+ 16 - 0
mcp-servers/aiproxy-openapi/init.go

@@ -0,0 +1,16 @@
+package aiproxyopenapi
+
+import mcpservers "github.com/labring/aiproxy/mcp-servers"
+
+// need import in mcpregister/init.go
+func init() {
+	mcpservers.Register(
+		mcpservers.NewMcp(
+			"aiproxy-openapi",
+			"AI Proxy OpenAPI",
+			mcpservers.McpTypeEmbed,
+			mcpservers.WithNewServerFunc(NewServer),
+			mcpservers.WithConfigTemplates(configTemplates),
+		),
+	)
+}

+ 0 - 12
mcp-servers/aiproxy-openapi/openapi.go

@@ -59,15 +59,3 @@ func NewServer(config, reusingConfig map[string]string) (mcpservers.Server, erro
 	})
 	return converter.Convert()
 }
-
-// need import in mcpregister/init.go
-func init() {
-	mcpservers.Register(
-		mcpservers.NewEmbedMcp(
-			"aiproxy-openapi",
-			"AI Proxy OpenAPI",
-			NewServer,
-			mcpservers.WithConfigTemplates(configTemplates),
-		),
-	)
-}

+ 149 - 0
mcp-servers/alipay/README.md

@@ -0,0 +1,149 @@
+## 1. 简介
+
+`@alipay/mcp-server-alipay` 是支付宝开放平台提供的 MCP Server,让你可以轻松将支付宝开放平台提供的交易创建、查询、退款等能力集成到你的 LLM 应用中,并进一步创建具备支付能力的智能工具。
+
+以下是一个虚构的简化使用场景,用于方便理解工具能力:
+
+> 一位插画师希望通过提供定制的原创插画服务谋取收入。传统方式下,他/她需要和每位客户反复沟通需求、确定价格,并发送支付链接,然后再人工确认支付情况,这个过程繁琐且费时。
+>
+> 现在,插画师利用支付宝 MCP Server 与智能 Agent 工具,通过 Agent 搭建平台,开发了一个智能聊天应用(网页或小程序)。客户只需在应用中描述自己的绘画需求(如风格偏好、插画用途、交付时间等),AI 就会自动分析需求,快速生成准确且合理的定制报价,并通过工具即时创建出专用的支付宝支付链接。
+>
+> 客户点击并支付后,创作者立即收到通知,进入创作环节。无需人工往返对话确认交易状态或支付情况,整个流程不仅便捷顺畅,还能显著提高交易效率和客户满意度,让插画师更专注于自己的创作本身,实现更轻松的个性化服务商业模式。
+
+```
+     最终用户设备                     Agent 运行环境
++---------------------+        +--------------------------+      +-------------------+
+|                     |  交流  |   支付宝 MCP Server +    |      |                   |
+|    小程序/WebApp    |<------>|   其他 MCP Server +      |<---->|     支付服务      |
+|                     |  支付  |   Agent 开发工具         |      |   交易/退款/查询  |
++---------------------+        +--------------------------+      +-------------------+
+     创作服务买家                     智能工具开发者                   支付宝开放平台
+      (最终用户)                         (创作者)
+```
+
+关于本工具的更多介绍和使用指南,包括准备收款商户身份等前置流程,请参考支付宝开放平台上的 [支付 MCP 服务文档](https://opendocs.alipay.com/open/0go80l) 。
+
+## 2. 使用和配置
+
+要使用工具的大部分支付能力,你需要先成为支付宝开放平台的收款商户,获取商户私钥。
+之后,你可以直接在主流的 MCP Client 上使用支付宝 MCP Server:
+
+### 在 Cursor 中使用
+
+在 Cursor 项目中的 `.cursor/mcp.json` 加入如下配置:
+
+```json
+{
+  "mcpServers": {
+    "mcp-server-alipay": {
+      "command": "npx",
+      "args": ["-y", "@alipay/mcp-server-alipay"],
+      "env": {
+        "AP_APP_ID": "2014...222",
+        "AP_APP_KEY": "MIIE...DZdM=",
+        "AP_PUB_KEY": "MIIB...DAQAB",
+        "AP_RETURN_URL": "https://success-page",
+        "AP_NOTIFY_URL": "https://your-own-server",
+        "...其他参数": "...其他值"
+      }
+    },
+    "其他工具": { 
+      "...": "..."
+    }
+  }
+}
+```
+
+### 在 Cline 中使用
+
+在你的 Cline 设置中找到 `cline_mcp_settings.json` 配置文件,并加入如下配置:
+
+```json
+{
+  "mcpServers": {
+    "mcp-server-alipay": {
+      "command": "npx",
+      "args": ["-y", "@alipay/mcp-server-alipay"],
+      "env": {
+        "AP_APP_ID": "2014...222",
+        "AP_APP_KEY": "MIIE...DZdM=",
+        "AP_PUB_KEY": "MIIB...DAQAB",
+        "AP_RETURN_URL": "https://success-page",
+        "AP_NOTIFY_URL": "https://your-own-server",
+        "...其他参数": "...其他值"
+      },
+      "disable": false,
+      "autoApprove": []
+    },
+    "其他工具": { 
+      "...": "..."
+    }
+  }
+}
+```
+
+### 在其他 MCP Client 中使用
+
+你也可以在任何其它 MCP Client 中使用,合理配置 Server 进程启动方式 `npx -y @alipay/mcp-server-alipay`,并按下文介绍设置环境参数即可。
+
+### 所有参数
+
+支付宝 MCP Server 通过环境变量接收参数。参数和默认值包括:
+
+```shell
+# 支付宝开放平台配置
+
+AP_APP_ID=2014...222                    # 商户在开放平台申请的应用 ID(APPID)。必需。
+AP_APP_KEY=MIIE...DZdM=                 # 商户在开放平台申请的应用私钥。必需。
+AP_PUB_KEY=MIIB...DAQAB                 # 用于验证支付宝服务端数据签名的支付宝公钥,在开放平台获取。必需。
+AP_RETURN_URL=https://success-page      # 网页支付完成后对付款用户展示的「同步结果返回地址」。
+AP_NOTIFY_URL=https://your-own-server   # 支付完成后,用于告知开发者支付结果的「异步结果通知地址」。
+AP_ENCRYPTION_ALGO=RSA2                 # 商户在开放平台配置的参数签名方式。可选值为 "RSA2" 或 "RSA"。缺省值为 "RSA2"。
+AP_CURRENT_ENV=prod                     # 连接的支付宝开放平台环境。可选值为 "prod"(线上环境)或 "sandbox"(沙箱环境)。缺省值为 "prod"。
+
+# MCP Server 配置
+
+AP_SELECT_TOOLS=all                      # 允许使用的工具。可选值为 "all" 或逗号分隔的工具名称列表。工具名称包括 `mobilePay`, `webPagePay`, `queryPay`, `refundPay`, `refundQuery`。缺省值为 "all"。
+AP_LOG_ENABLED=true                      # 是否在 $HOME/mcp-server-alipay.log 中记录日志。默认值为 true。
+```
+
+## 3. 使用 MCP Inspector 调试
+
+你可以使用 MCP Inspector 来调试和了解支付宝 MCP Server 的功能:
+
+1. 通过 `export` 设置各环境变量;
+2. 执行 `npx -y @modelcontextprotocol/inspector npx -y @alipay/mcp-server-alipay` 启动 MCP Inspector;
+3. 在 MCP Inspector WebUI 中调试即可。
+
+## 4. 支持的能力
+
+以下表格列出了所有可用的支付工具能力:
+
+| 名称 | `AP_SELECT_TOOLS` 中的工具名称 | 描述 | 参数 | 输出 |
+|-------|--------------------------|------|------|------|
+| `create-mobile-alipay-payment` | `mobilePay` | 创建一笔支付宝订单,返回带有支付链接的 Markdown 文本,该链接在手机浏览器中打开后可跳转到支付宝或直接在浏览器中支付。本工具适用于移动网站或移动 App。 | - outTradeNo: 商户订单号,最长 64 个字符<br>- totalAmount: 支付金额,单位:元,最小 0.01<br>- orderTitle: 订单标题,最长 256 个字符 | - url: 支付链接的 markdown 文本 |
+| `create-web-page-alipay-payment` | `webPagePay` | 创建一笔支付宝订单,返回带有支付链接的 Markdown 文本,该链接在电脑浏览器中打开后会展示支付二维码,用户可扫码支付。本工具适用于桌面网站或电脑客户端。 | - outTradeNo: 商户订单号,最长 64 个字符<br>- totalAmount: 支付金额,单位:元,最小 0.01<br>- orderTitle: 订单标题,最长 256 个字符 | - url: 支付链接的 markdown 文本 |
+| `query-alipay-payment` | `queryPay` | 查询一笔支付宝订单,并返回带有订单信息的文本 | - outTradeNo: 商户订单号,最长 64 个字符 | - tradeStatus: 订单的交易状态<br>- totalAmount: 订单的交易金额<br>- tradeNo: 支付宝交易号 |
+| `refund-alipay-payment` | `refundPay` | 对交易发起退款,并返回退款状态和退款金额 | - outTradeNo: 商户订单号,最长 64 个字符<br>- refundAmount: 退款金额,单位:元,最小 0.01<br>- outRequestNo: 退款请求号,最长 64 个字符<br>- refundReason: 退款原因,最长 256 个字符(可选) | - tradeNo: 支付宝交易号<br>- refundResult: 退款结果 |
+| `query-alipay-refund` | `refundQuery` | 查询一笔支付宝退款,并返回退款状态和退款金额 | - outRequestNo: 退款请求号,最长 64 个字符<br>- outTradeNo: 商户订单号,最长 64 个字符 | - tradeNo: 支付宝交易号<br>- refundAmount: 退款金额<br>- refundStatus: 退款状态 |
+
+## 5. 如何选择合适的支付方式
+
+在开发过程中,为了让 LLM 能更准确地选择合适的支付方式,建议在 Prompt 中清晰说明你的产品使用场景:
+
+- **扫码支付(`webPagePay`)**:适用于用户在电脑屏幕上看到支付界面的场景。如果您的应用或网站主要运行在桌面端(PC),你可以在 Prompt 中说明:"我的应用是桌面软件/PC网站,需要在电脑上展示支付二维码"。
+
+- **手机支付(`mobilePay`)**:适用于用户在手机浏览器内发起支付的场景。如果您的应用是手机H5页面或移动端网站,你可以在 Prompt 中说明:"我的页面是手机网页,需要直接在手机上唤起支付宝支付"。
+
+我们会在未来提供更多适合 AI 应用的支付方式,敬请期待。
+
+## 6. 注意事项
+
+- 支付宝 MCP 服务目前处于发布早期阶段,相关能力和配套设施正在持续完善中。如有问题反馈、使用体验或建议,欢迎在 [支付宝开发者社区](https://open.alipay.com/portal/forum) 参与讨论。
+- 部署和使用智能体服务时,请务必妥善保管自己的商户私钥,防止泄露。如需要,可参考 [支付宝开放平台-如何修改密钥](https://opendocs.alipay.com/support/01rav9) 的说明让已有密钥失效。
+- 在开发任何使用 MCP Server 的智能体服务,并提供给用户使用时,请了解必要的安全知识,防范 AI 应用特有的 Prompt 攻击、MCP Server 任意命令执行等安全风险。
+- 更多注意事项和最佳实践,请参考支付宝开放平台上 [关于支付 MCP 服务](https://opendocs.alipay.com/open/0go80l) 的说明。
+
+## 7. 使用协议
+
+本工具是支付宝开放平台能力的组成部分。使用期间,请遵守 [支付宝开放平台开发者服务协议](https://ds.alipay.com/fd-ifz2dlhv/index.html) 及相关商业行为法规。

+ 23 - 0
mcp-servers/alipay/init.go

@@ -0,0 +1,23 @@
+package alipay
+
+import (
+	_ "embed"
+
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
+
+//go:embed README.md
+var readme string
+
+// need import in mcpregister/init.go
+func init() {
+	mcpservers.Register(
+		mcpservers.NewMcp(
+			"alipay",
+			"Alipay",
+			mcpservers.McpTypeDocs,
+			mcpservers.WithTags([]string{"pay"}),
+			mcpservers.WithReadme(readme),
+		),
+	)
+}

+ 22 - 0
mcp-servers/amap/init.go

@@ -0,0 +1,22 @@
+package amap
+
+import mcpservers "github.com/labring/aiproxy/mcp-servers"
+
+// need import in mcpregister/init.go
+func init() {
+	mcpservers.Register(
+		mcpservers.NewMcp(
+			"amap",
+			"AMAP",
+			mcpservers.McpTypeEmbed,
+			mcpservers.WithNewServerFunc(NewServer),
+			mcpservers.WithConfigTemplates(configTemplates),
+			mcpservers.WithTags([]string{"map"}),
+			mcpservers.WithReadme(
+				`# AMAP MCP Server
+
+https://lbs.amap.com/api/mcp-server/gettingstarted
+`),
+		),
+	)
+}

+ 0 - 18
mcp-servers/amap/openapi.go

@@ -56,21 +56,3 @@ func NewServer(config, _ map[string]string) (mcpservers.Server, error) {
 
 	return mcpservers.WrapMCPClient2ServerWithCleanup(client), nil
 }
-
-// need import in mcpregister/init.go
-func init() {
-	mcpservers.Register(
-		mcpservers.NewEmbedMcp(
-			"amap",
-			"AMAP",
-			NewServer,
-			mcpservers.WithConfigTemplates(configTemplates),
-			mcpservers.WithTags([]string{"map"}),
-			mcpservers.WithReadme(
-				`# AMAP MCP Server
-
-https://lbs.amap.com/api/mcp-server/gettingstarted
-`),
-		),
-	)
-}

+ 41 - 14
mcp-servers/embedmcp.go → mcp-servers/mcp.go

@@ -100,40 +100,67 @@ func CheckConfigTemplatesValidate(ct ConfigTemplates) error {
 
 type NewServerFunc func(config, reusingConfig map[string]string) (Server, error)
 
-type EmbedMcp struct {
+type McpType string
+
+const (
+	McpTypeEmbed McpType = "embed"
+	McpTypeDocs  McpType = "docs"
+)
+
+type McpServer struct {
 	ID              string
 	Name            string
+	Type            McpType
 	Readme          string
+	LogoURL         string
 	Tags            []string
 	ConfigTemplates ConfigTemplates
 	newServer       NewServerFunc
 }
 
-type EmbedMcpConfig func(*EmbedMcp)
+type McpConfig func(*McpServer)
 
-func WithReadme(readme string) EmbedMcpConfig {
-	return func(e *EmbedMcp) {
+func WithReadme(readme string) McpConfig {
+	return func(e *McpServer) {
 		e.Readme = readme
 	}
 }
 
-func WithTags(tags []string) EmbedMcpConfig {
-	return func(e *EmbedMcp) {
+func WithType(t McpType) McpConfig {
+	return func(e *McpServer) {
+		e.Type = t
+	}
+}
+
+func WithLogoURL(logoURL string) McpConfig {
+	return func(e *McpServer) {
+		e.LogoURL = logoURL
+	}
+}
+
+func WithTags(tags []string) McpConfig {
+	return func(e *McpServer) {
 		e.Tags = tags
 	}
 }
 
-func WithConfigTemplates(configTemplates ConfigTemplates) EmbedMcpConfig {
-	return func(e *EmbedMcp) {
+func WithConfigTemplates(configTemplates ConfigTemplates) McpConfig {
+	return func(e *McpServer) {
 		e.ConfigTemplates = configTemplates
 	}
 }
 
-func NewEmbedMcp(id, name string, newServer NewServerFunc, opts ...EmbedMcpConfig) EmbedMcp {
-	e := EmbedMcp{
-		ID:        id,
-		Name:      name,
-		newServer: newServer,
+func WithNewServerFunc(newServer NewServerFunc) McpConfig {
+	return func(e *McpServer) {
+		e.newServer = newServer
+	}
+}
+
+func NewMcp(id, name string, mcpType McpType, opts ...McpConfig) McpServer {
+	e := McpServer{
+		ID:   id,
+		Name: name,
+		Type: mcpType,
 	}
 	for _, opt := range opts {
 		opt(&e)
@@ -141,7 +168,7 @@ func NewEmbedMcp(id, name string, newServer NewServerFunc, opts ...EmbedMcpConfi
 	return e
 }
 
-func (e *EmbedMcp) NewServer(config, reusingConfig map[string]string) (Server, error) {
+func (e *McpServer) NewServer(config, reusingConfig map[string]string) (Server, error) {
 	if err := ValidateConfigTemplatesConfig(e.ConfigTemplates, config, reusingConfig); err != nil {
 		return nil, fmt.Errorf("mcp %s config is invalid: %w", e.ID, err)
 	}

+ 1 - 0
mcp-servers/mcpregister/init.go

@@ -3,6 +3,7 @@ package mcpregister
 import (
 	// register embed mcp
 	_ "github.com/labring/aiproxy/mcp-servers/aiproxy-openapi"
+	_ "github.com/labring/aiproxy/mcp-servers/alipay"
 	_ "github.com/labring/aiproxy/mcp-servers/amap"
 	_ "github.com/labring/aiproxy/mcp-servers/web-search"
 )

+ 17 - 7
mcp-servers/register.go

@@ -15,7 +15,7 @@ type mcpServerCacheItem struct {
 }
 
 var (
-	servers             = make(map[string]EmbedMcp)
+	servers             = make(map[string]McpServer)
 	mcpServerCache      = make(map[string]*mcpServerCacheItem)
 	mcpServerCacheLock  = sync.RWMutex{}
 	cacheExpirationTime = 3 * time.Minute
@@ -50,16 +50,26 @@ func init() {
 	startCacheCleaner(time.Minute)
 }
 
-func Register(mcp EmbedMcp) {
+func Register(mcp McpServer) {
 	if mcp.ID == "" {
 		panic("mcp id is required")
 	}
 	if mcp.Name == "" {
 		panic("mcp name is required")
 	}
-	if mcp.newServer == nil {
-		panic(fmt.Sprintf("mcp %s new server is required", mcp.ID))
+	switch mcp.Type {
+	case McpTypeEmbed:
+		if mcp.newServer == nil {
+			panic(fmt.Sprintf("mcp %s new server is required", mcp.ID))
+		}
+	case McpTypeDocs:
+		if mcp.Readme == "" {
+			panic(fmt.Sprintf("mcp %s readme is required", mcp.ID))
+		}
+	default:
+		panic(fmt.Sprintf("mcp %s type is invalid", mcp.ID))
 	}
+
 	if mcp.ConfigTemplates != nil {
 		if err := CheckConfigTemplatesValidate(mcp.ConfigTemplates); err != nil {
 			panic(fmt.Sprintf("mcp %s config templates example is invalid: %v", mcp.ID, err))
@@ -105,7 +115,7 @@ func buildNoReusingConfigCacheKey(config map[string]string) string {
 	return strings.Join(keys, ":")
 }
 
-func loadCacheServer(embedServer EmbedMcp, config map[string]string) (Server, error) {
+func loadCacheServer(embedServer McpServer, config map[string]string) (Server, error) {
 	cacheKey := embedServer.ID
 	if len(config) > 0 {
 		cacheKey = fmt.Sprintf("%s:%s", embedServer.ID, buildNoReusingConfigCacheKey(config))
@@ -139,11 +149,11 @@ func loadCacheServer(embedServer EmbedMcp, config map[string]string) (Server, er
 	return mcpServer, nil
 }
 
-func Servers() map[string]EmbedMcp {
+func Servers() map[string]McpServer {
 	return servers
 }
 
-func GetEmbedMCP(id string) (EmbedMcp, bool) {
+func GetEmbedMCP(id string) (McpServer, bool) {
 	mcp, ok := servers[id]
 	return mcp, ok
 }

+ 25 - 0
mcp-servers/web-search/init.go

@@ -0,0 +1,25 @@
+package websearch
+
+import (
+	_ "embed"
+
+	mcpservers "github.com/labring/aiproxy/mcp-servers"
+)
+
+//go:embed features.md
+var readme string
+
+// Register the server
+func init() {
+	mcpservers.Register(
+		mcpservers.NewMcp(
+			"web-search",
+			"Web Search",
+			mcpservers.McpTypeEmbed,
+			mcpservers.WithNewServerFunc(NewServer),
+			mcpservers.WithConfigTemplates(configTemplates),
+			mcpservers.WithTags([]string{"search", "web", "google", "bing", "arxiv", "searchxng"}),
+			mcpservers.WithReadme(readme),
+		),
+	)
+}

+ 0 - 17
mcp-servers/web-search/server.go

@@ -594,20 +594,3 @@ func removeDuplicates(results []engine.SearchResult) []engine.SearchResult {
 
 	return unique
 }
-
-//go:embed features.md
-var readme string
-
-// Register the server
-func init() {
-	mcpservers.Register(
-		mcpservers.NewEmbedMcp(
-			"web-search",
-			"Web Search",
-			NewServer,
-			mcpservers.WithConfigTemplates(configTemplates),
-			mcpservers.WithTags([]string{"search", "web", "google", "bing", "arxiv", "searchxng"}),
-			mcpservers.WithReadme(readme),
-		),
-	)
-}