This is an automated email from the ASF dual-hosted git repository.

zhangwenfeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu-samples.git


The following commit(s) were added to refs/heads/main by this push:
     new 8aa37e1  feat: add Mcp server with nacos sample (#97)
8aa37e1 is described below

commit 8aa37e12e98249506deae915c65b577c731b0b84
Author: Zerui Yang <[email protected]>
AuthorDate: Thu Oct 30 15:06:02 2025 +0800

    feat: add Mcp server with nacos sample (#97)
    
    * feat: Enhance MCP Samples Documentation and Add Nacos Integration
    
    * fix: add license
    
    * feat: add MCP Nacos integration example in README files
    
    * fix: update configuration path in MCP Nacos test file
---
 README.md                             |   1 +
 README_CN.md                          |   1 +
 mcp/README.md                         |  12 +-
 mcp/README_zh.md                      |   3 +-
 mcp/nacos/README.md                   |  69 ++++++
 mcp/nacos/README_zh.md                |  69 ++++++
 mcp/nacos/mcptools/mcptools.yaml      | 159 ++++++++++++
 mcp/nacos/pixiu/conf.yaml             |  61 +++++
 mcp/nacos/test/mcp_nacos_test.go      | 439 ++++++++++++++++++++++++++++++++++
 mcp/simple/README.md                  |   2 +-
 mcp/simple/README_zh.md               |   2 +-
 mcp/simple/server/{ => app}/server.go |   0
 12 files changed, 810 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 9f5aa7e..2eefb71 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ samples for 
[dubbo-go-pixiu](https://github.com/apache/dubbo-go-pixiu)
 - mcp: demonstrates MCP (Model Context Protocol) filter to expose HTTP APIs as 
LLM tools
   - mcp/simple: basic MCP service integration example showing how to convert 
HTTP APIs to MCP tools
   - mcp/oauth: MCP OAuth authorization example demonstrating how to protect 
MCP endpoints with OAuth2, supporting PKCE authorization code flow
+  - mcp/nacos: MCP Nacos integration example, demonstrating how to use Nacos 
as the MCP Server.
 
 - plugins: this directory contains some plugins for pixiu
   - plugins/ratelimit: rate limit plugin for pixiu
diff --git a/README_CN.md b/README_CN.md
index 98fa666..0968e37 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -39,6 +39,7 @@
 - mcp: 演示 MCP (Model Context Protocol) 过滤器,将 HTTP API 暴露为 LLM 工具
   - mcp/simple: 基础的 MCP 服务集成示例,展示如何将 HTTP API 转换为 MCP 工具
   - mcp/oauth: MCP OAuth 授权示例,演示如何使用 OAuth2 保护 MCP 端点,支持 PKCE 授权码流程
+  - mcp/nacos: MCP Nacos 集成示例,演示如何使用 Nacos 作为 MCP Server 的注册中心和配置中心
 
 - plugins:此目录包含 pixiu 的一些插件
   - plugins/ratelimit:pixiu 的 ratelimit 插件
diff --git a/mcp/README.md b/mcp/README.md
index 6ce0f4f..464402a 100644
--- a/mcp/README.md
+++ b/mcp/README.md
@@ -1,12 +1,14 @@
 # MCP Samples Index
 
-English | [中文](./README_zh.md)
+[中文](./README_zh.md) | English
 
-This directory contains two MCP samples:
+This directory contains several MCP examples:
 
-- `simple/`: MCP without authorization
-- `oauth/`: MCP with authorization enabled (OAuth), protecting only `/mcp`, 
using remote JWKS
+- `simple/`: A basic example demonstrating MCP interaction.
+- `oauth/`: A security example showing how to protect the MCP endpoint with 
OAuth 2.0 and a remote JWKS.
+- `nacos/`: An integration example demonstrating how to use Nacos as the MCP 
Server for dynamic configuration.
+
+Please navigate to each subdirectory and read the corresponding README for 
detailed instructions.
 
-Please navigate into each subfolder and read the README for detailed usage.
 
 
diff --git a/mcp/README_zh.md b/mcp/README_zh.md
index 2c4193f..2d75478 100644
--- a/mcp/README_zh.md
+++ b/mcp/README_zh.md
@@ -4,7 +4,8 @@
 
 本目录下包含两类 MCP 示例:
 
-- `simple/`:无鉴权示例
+- `simple/`:基础示例
 - `oauth/`:启用 MCP 授权(OAuth)示例,仅保护 `/mcp`,远程 JWKS
+- `nacos/`:使用 Nacos 作为 MCP Server 的注册中心和配置中心的示例
 
 请进入各子目录阅读对应的 README 获取详细使用方法。
\ No newline at end of file
diff --git a/mcp/nacos/README.md b/mcp/nacos/README.md
new file mode 100644
index 0000000..a1bfb86
--- /dev/null
+++ b/mcp/nacos/README.md
@@ -0,0 +1,69 @@
+# Dubbo-go-pixiu MCP with Nacos Example
+
+English | [中文](./README_zh.md)
+
+This example demonstrates how to use Nacos 3.0+ as the MCP (Model 
Configuration Protocol) server for the Dubbo-go-pixiu gateway.
+
+## Prerequisites
+
+- Go programming environment
+- Nacos 3.0 or higher installed and running
+
+## Steps
+
+### 1. Configure Nacos MCP Service
+
+First, we need to configure and publish an MCP service in the Nacos console.
+
+1.  **Login to Nacos Console**: Access `http://<nacos-server-ip>:8080/nacos`.
+2.  **Navigate to MCP Management**: Find and click on "MCP Management" in the 
left sidebar.
+3.  **Create MCP Server**:
+    *   Click "MCP List" -> "Create MCP Server".
+    *   **Type**: Select `streamable`.
+    *   **Tools**: Select "Import from OpenAPI", then upload the 
`mcp/nacos/mcptools/mcptools.yaml` file.
+4.  **Correct Backend Address (Important)**:
+    *   After a successful upload, Nacos will automatically parse 
`mcptools.yaml` and generate a list of tools.
+    *   **Note**: There is a known issue in Nacos 3.0 where the imported 
backend address `http://` might be incorrectly changed to `http:/`. Please 
manually check and correct the backend address for all tools to ensure it is 
`http://localhost:8081`.
+5.  **Publish the Service**:
+    *   After confirming all configurations are correct, click "Publish" to 
start the MCP Server.
+    *   *Note: Pixiu currently only supports connecting to a single MCP 
Server.*
+
+### 2. Start the Mock Backend Server
+
+This server provides the API interfaces defined in the OpenAPI file.
+
+```bash
+cd mcp/simple/server/app
+go run .
+```
+
+After starting successfully, you will see output similar to the following:
+
+```
+🚀 Mock Backend Server starting on :8081
+📚 Available endpoints:
+  GET  /api/users/{id}        - Get user by ID
+  GET  /api/users/search      - Search users
+  POST /api/users             - Create user
+  GET  /api/users/{id}/posts  - Get user posts
+  GET  /api/health            - Health check
+  GET  /                      - Root endpoint
+```
+*Please execute the next step in a new terminal window, keeping this server 
running.*
+
+### 3. Start the Pixiu Gateway
+
+Now, start the Pixiu gateway, which will connect to the Nacos MCP service and 
route requests based on the fetched configuration.
+
+```shell
+cd /path/to/dubbo-go-pixiu
+go run cmd/pixiu/*.go gateway start -c 
/path/to/dubbo-go-pixiu-samples/mcp/nacos/pixiu/conf.yaml
+```
+
+### 4. Install and Launch the MCP Inspector Client
+
+```shell
+npx @modelcontextprotocol/inspector
+```
+
+Open the Inspector interface in your browser and connect to 
`http://localhost:8888/mcp` to start testing.
diff --git a/mcp/nacos/README_zh.md b/mcp/nacos/README_zh.md
new file mode 100644
index 0000000..136eb45
--- /dev/null
+++ b/mcp/nacos/README_zh.md
@@ -0,0 +1,69 @@
+# Dubbo-go-pixiu MCP 使用 Nacos 作为注册中心示例
+
+[English](./README.md) | 中文
+
+本示例演示了如何使用 Nacos 3.0+ 作为 Dubbo-go-pixiu 网关的 MCP(模型配置协议)服务器。
+
+## 准备工作
+
+- Go 编程环境
+- 已安装并启动 Nacos 3.0 或更高版本
+
+## 步骤
+
+### 1. 配置 Nacos MCP 服务
+
+首先,我们需要在 Nacos 控制台中配置和发布一个 MCP 服务。
+
+1.  **登录 Nacos 控制台**:访问 `http://<nacos-server-ip>:8080/nacos`。
+2.  **进入 MCP 管理**:在左侧菜单栏找到并点击 “MCP管理”。
+3.  **创建 MCP Server**:
+    *   点击 “MCP列表” -> “创建MCP Server”。
+    *   **类型**:选择 `streamable`。
+    *   **工具(Tools)**:选择 “从OpenAPI导入”,然后上传 `mcp/nacos/mcptools/mcptools.yaml` 
文件。
+4.  **修正后端地址(重要)**:
+    *   上传成功后,Nacos 会自动解析 `mcptools.yaml` 并生成工具列表。
+    *   **注意**:Nacos 3.0 版本存在一个已知问题,导入的后端地址 `http://` 可能会错误地变成 
`http:/`。请手动检查并修正所有工具的后端地址,确保其为 `http://localhost:8081`。
+5.  **发布服务**:
+    *   确认所有配置无误后,点击 “发布”,启动 MCP Server。
+    *   *注意:目前 Pixiu 只支持连接单个 MCP Server。*
+
+### 2. 启动后端模拟服务器
+
+此服务器提供了 OpenAPI 文件中定义的 API 接口。
+
+```bash
+cd mcp/simple/server/app
+go run .
+```
+
+启动成功后,你将看到类似以下的输出:
+
+```
+🚀 Mock Backend Server starting on :8081
+📚 Available endpoints:
+  GET  /api/users/{id}        - Get user by ID
+  GET  /api/users/search      - Search users
+  POST /api/users             - Create user
+  GET  /api/users/{id}/posts  - Get user posts
+  GET  /api/health            - Health check
+  GET  /                      - Root endpoint
+```
+*请在新的终端窗口执行下一步,保持此服务器运行。*
+
+### 3. 启动 Pixiu 网关
+
+现在,启动 Pixiu 网关,它将连接到 Nacos MCP 服务并根据获取的配置进行路由。
+
+```shell
+cd /path/to/dubbo-go-pixiu
+go run cmd/pixiu/*.go gateway start -c 
/path/to/dubbo-go-pixiu-samples/mcp/nacos/pixiu/conf.yaml
+```
+
+### 4. 安装并启动 MCP Inspector Client
+
+```shell
+npx @modelcontextprotocol/inspector
+```
+
+在浏览器中打开 Inspector 界面,连接到 `http://localhost:8888/mcp` 便可以进行测试。
\ No newline at end of file
diff --git a/mcp/nacos/mcptools/mcptools.yaml b/mcp/nacos/mcptools/mcptools.yaml
new file mode 100644
index 0000000..e103951
--- /dev/null
+++ b/mcp/nacos/mcptools/mcptools.yaml
@@ -0,0 +1,159 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+openapi: 3.0.0
+info:
+  title: User API
+  description: 一个用于管理用户和帖子的模拟 API。
+  version: 1.0.0
+servers:
+  - url: http://localhost:8081
+    description: 模拟服务器
+
+paths:
+  /api/users/{id}:
+    get:
+      summary: "get_user"
+      description: "通过 ID 获取用户信息,可选择是否包含个人资料详情"
+      operationId: "getUserById"
+      parameters:
+        - name: id
+          in: path
+          description: "要检索的用户 ID"
+          required: true
+          schema:
+            type: integer
+        - name: include_profile
+          in: query
+          description: "是否包含用户个人资料信息"
+          required: false
+          schema:
+            type: boolean
+            default: false
+      responses:
+        '200':
+          description: "成功获取用户信息"
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  id:
+                    type: integer
+                  name:
+                    type: string
+                  email:
+                    type: string
+
+  /api/users/search:
+    get:
+      summary: "search_users"
+      description: "通过姓名或邮箱搜索用户,支持分页"
+      operationId: "searchUsers"
+      parameters:
+        - name: q
+          in: query
+          description: "搜索查询(姓名或邮箱)"
+          required: true
+          schema:
+            type: string
+        - name: page
+          in: query
+          description: "分页的页码"
+          required: false
+          schema:
+            type: integer
+            default: 1
+        - name: limit
+          in: query
+          description: "每页的结果数量 (1-100)"
+          required: false
+          schema:
+            type: integer
+            default: 10
+      responses:
+        '200':
+          description: "成功的搜索结果"
+
+  /api/users:
+    post:
+      summary: "create_user"
+      description: "创建一个新的用户账户"
+      operationId: "createUser"
+      requestBody:
+        description: "新用户的数据"
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              required:
+                - name
+                - email
+              properties:
+                name:
+                  type: string
+                  description: "用户的全名"
+                email:
+                  type: string
+                  description: "用户的电子邮件地址"
+                age:
+                  type: integer
+                  description: "用户的年龄"
+      responses:
+        '201':
+          description: "用户创建成功"
+
+  /api/users/{user_id}/posts:
+    get:
+      summary: "get_user_posts"
+      description: "获取特定用户的所有帖子,并按状态筛选"
+      operationId: "getUserPosts"
+      parameters:
+        - name: user_id
+          in: path
+          description: "要获取帖子的用户 ID"
+          required: true
+          schema:
+            type: integer
+        - name: status
+          in: query
+          description: "按状态筛选帖子"
+          required: false
+          schema:
+            type: string
+            default: "published"
+            enum: ["published", "draft", "all"]
+      responses:
+        '200':
+          description: "成功获取帖子列表"
+
+  /api/health:
+    get:
+      summary: "health_check"
+      description: "检查服务器服务的健康状况和状态"
+      operationId: "healthCheck"
+      responses:
+        '200':
+          description: "服务健康"
+
+  /:
+    get:
+      summary: "get_server_info"
+      description: "获取基本服务器信息和可用的端点"
+      operationId: "getServerInfo"
+      responses:
+        '200':
+          description: "服务器信息"
\ No newline at end of file
diff --git a/mcp/nacos/pixiu/conf.yaml b/mcp/nacos/pixiu/conf.yaml
new file mode 100644
index 0000000..890d7de
--- /dev/null
+++ b/mcp/nacos/pixiu/conf.yaml
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_resources:
+  listeners:
+    - name: "net/http"
+      protocol_type: "HTTP"
+      address:
+        socket_address:
+          address: "0.0.0.0"
+          port: 8888
+      filter_chains:
+        filters:
+          - name: "dgp.filter.httpconnectionmanager"
+            config:
+              route_config:
+                routes:
+                  # Protected MCP endpoint
+                  - match:
+                      prefix: "/"
+                    route:
+                      cluster: "mcp-protected"
+                      cluster_not_found_response_code: 505
+              http_filters:
+                - name: "dgp.filter.mcp.mcpserver"
+                  config:
+                    server_info:
+                      name: "MCP OAuth Sample Server"
+                      version: "1.0.0"
+                      description: "MCP Server protected by OAuth for tools 
demonstration"
+                      instructions: "Use read/write tokens to interact with 
the mock server API via MCP"
+
+                # Downstream HTTP proxy
+                - name: "dgp.filter.http.httpproxy"
+
+  adapters:
+    - id: test
+      name: dgp.adapter.mcpserver
+      config:
+        registries:
+          nacos:
+            protocol: nacos
+            address: "127.0.0.1:8848"
+            timeout: "5s"
+            username: "nacos"
+            password: "nacos"
+
+
+
diff --git a/mcp/nacos/test/mcp_nacos_test.go b/mcp/nacos/test/mcp_nacos_test.go
new file mode 100644
index 0000000..6b330e4
--- /dev/null
+++ b/mcp/nacos/test/mcp_nacos_test.go
@@ -0,0 +1,439 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Basic tests for the MCP tools (no authorization). These tests exercise the
+// JSON-RPC initialize flow, tools discovery, and typical tool calls against 
the
+// mock backend. They are intended to validate the sample configuration in
+// mcp/nacos/pixiu/conf.yaml.
+package test
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       "io"
+       "net/http"
+       "testing"
+       "time"
+)
+
+const (
+       // Test service addresses
+       pixiuURL    = "http://localhost:8888";
+       mcpEndpoint = "/mcp"
+       backendURL  = "http://localhost:8081";
+)
+
+// JSONRPCRequest represents a minimal JSON-RPC 2.0 request envelope.
+type JSONRPCRequest struct {
+       JSONRPC string `json:"jsonrpc"`
+       ID      any    `json:"id"`
+       Method  string `json:"method"`
+       Params  any    `json:"params,omitempty"`
+}
+
+// JSONRPCResponse represents a minimal JSON-RPC 2.0 response envelope.
+type JSONRPCResponse struct {
+       JSONRPC string `json:"jsonrpc"`
+       ID      any    `json:"id"`
+       Result  any    `json:"result,omitempty"`
+       Error   any    `json:"error,omitempty"`
+}
+
+// ToolCallParams represents parameters for tools/call requests.
+type ToolCallParams struct {
+       Name      string         `json:"name"`
+       Arguments map[string]any `json:"arguments"`
+}
+
+// sendJSONRPCRequest posts a JSON-RPC request to /mcp and returns the decoded
+// response. It fails the test on transport or protocol errors.
+func sendJSONRPCRequest(t *testing.T, method string, params any) 
*JSONRPCResponse {
+       req := JSONRPCRequest{
+               JSONRPC: "2.0",
+               ID:      1,
+               Method:  method,
+               Params:  params,
+       }
+
+       reqBody, err := json.Marshal(req)
+       if err != nil {
+               t.Fatalf("Failed to marshal request: %v", err)
+       }
+
+       resp, err := http.Post(pixiuURL+mcpEndpoint, "application/json", 
bytes.NewBuffer(reqBody))
+       if err != nil {
+               t.Fatalf("Failed to send request: %v", err)
+       }
+       defer resp.Body.Close()
+
+       if resp.StatusCode != http.StatusOK {
+               t.Fatalf("Expected status 200, got %d", resp.StatusCode)
+       }
+
+       body, err := io.ReadAll(resp.Body)
+       if err != nil {
+               t.Fatalf("Failed to read response: %v", err)
+       }
+
+       var jsonResp JSONRPCResponse
+       if err := json.Unmarshal(body, &jsonResp); err != nil {
+               t.Fatalf("Failed to unmarshal response: %v", err)
+       }
+
+       if jsonResp.Error != nil {
+               t.Fatalf("JSON-RPC error: %v", jsonResp.Error)
+       }
+
+       return &jsonResp
+}
+
+// checkServiceAvailable performs a quick HTTP GET and returns true if 200 OK.
+func checkServiceAvailable(url string) bool {
+       client := &http.Client{Timeout: 5 * time.Second}
+       resp, err := client.Get(url)
+       if err != nil {
+               return false
+       }
+       defer resp.Body.Close()
+       return resp.StatusCode == http.StatusOK
+}
+
+// TestServiceAvailability tests service availability
+func TestServiceAvailability(t *testing.T) {
+       t.Log("Checking backend service availability...")
+       if !checkServiceAvailable(backendURL) {
+               t.Skip("Backend service is not available, skipping test. Please 
start backend service first: go run mcp/server/server.go")
+       }
+
+       t.Log("Checking Pixiu gateway availability...")
+       if !checkServiceAvailable(pixiuURL) {
+               t.Skip("Pixiu gateway is not available, skipping test. Please 
start Pixiu gateway first")
+       }
+}
+
+// TestMCPInitialize tests MCP initialization
+func TestMCPInitialize(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) {
+               t.Skip("Pixiu gateway is not available")
+       }
+
+       t.Log("Testing MCP initialization...")
+
+       params := map[string]any{
+               "protocolVersion": "2024-11-05",
+               "capabilities": map[string]any{
+                       "roots": map[string]any{
+                               "listChanged": true,
+                       },
+               },
+               "clientInfo": map[string]any{
+                       "name":    "test-client",
+                       "version": "1.0.0",
+               },
+       }
+
+       resp := sendJSONRPCRequest(t, "initialize", params)
+
+       // Validate response structure
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       // Check server info
+       serverInfo, ok := result["serverInfo"].(map[string]any)
+       if !ok {
+               t.Fatalf("Expected serverInfo in result")
+       }
+
+       if name, ok := serverInfo["name"].(string); !ok || name == "" {
+               t.Fatalf("Expected server name")
+       }
+
+       t.Logf("MCP server initialized successfully: %s", serverInfo["name"])
+}
+
+// TestToolsList tests getting tools list
+func TestToolsList(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) {
+               t.Skip("Pixiu gateway is not available")
+       }
+
+       t.Log("Testing tools list...")
+
+       resp := sendJSONRPCRequest(t, "tools/list", nil)
+
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       tools, ok := result["tools"].([]any)
+       if !ok {
+               t.Fatalf("Expected tools array in result")
+       }
+
+       if len(tools) == 0 {
+               t.Fatalf("Expected at least one tool")
+       }
+
+       t.Logf("Found %d tools", len(tools))
+
+       // Validate tool structure
+       for i, tool := range tools {
+               toolObj, ok := tool.(map[string]any)
+               if !ok {
+                       t.Fatalf("Tool %d is not an object", i)
+               }
+
+               if name, ok := toolObj["name"].(string); !ok || name == "" {
+                       t.Fatalf("Tool %d missing name", i)
+               }
+
+               if desc, ok := toolObj["description"].(string); !ok || desc == 
"" {
+                       t.Fatalf("Tool %d missing description", i)
+               }
+
+               t.Logf("Tool %d: %s - %s", i+1, toolObj["name"], 
toolObj["description"])
+       }
+}
+
+// TestGetUser tests get user tool
+func TestGetUser(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) || 
!checkServiceAvailable(backendURL) {
+               t.Skip("Services are not available")
+       }
+
+       t.Log("Testing get user tool...")
+
+       params := ToolCallParams{
+               Name: "getUserById",
+               Arguments: map[string]any{
+                       "id":              1,
+                       "include_profile": true,
+               },
+       }
+
+       resp := sendJSONRPCRequest(t, "tools/call", params)
+
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       content, ok := result["content"].([]any)
+       if !ok || len(content) == 0 {
+               t.Fatalf("Expected content array in result")
+       }
+
+       contentItem, ok := content[0].(map[string]any)
+       if !ok {
+               t.Fatalf("Expected content item to be an object")
+       }
+
+       text, ok := contentItem["text"].(string)
+       if !ok {
+               t.Fatalf("Expected text in content item")
+       }
+
+       // Parse returned user data
+       var userData map[string]any
+       if err := json.Unmarshal([]byte(text), &userData); err != nil {
+               t.Fatalf("Failed to parse user data: %v", err)
+       }
+
+       // Validate user data
+       if id, ok := userData["id"].(float64); !ok || id != 1 {
+               t.Fatalf("Expected user ID to be 1")
+       }
+
+       if name, ok := userData["name"].(string); !ok || name == "" {
+               t.Fatalf("Expected user name")
+       }
+
+       t.Logf("Successfully got user: %s", userData["name"])
+}
+
+// TestSearchUsers tests search users tool
+func TestSearchUsers(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) || 
!checkServiceAvailable(backendURL) {
+               t.Skip("Services are not available")
+       }
+
+       t.Log("Testing search users tool...")
+
+       params := ToolCallParams{
+               Name: "searchUsers",
+               Arguments: map[string]any{
+                       "q":     "alice",
+                       "page":  1,
+                       "limit": 10,
+               },
+       }
+
+       resp := sendJSONRPCRequest(t, "tools/call", params)
+
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       content, ok := result["content"].([]any)
+       if !ok || len(content) == 0 {
+               t.Fatalf("Expected content array in result")
+       }
+
+       contentItem, ok := content[0].(map[string]any)
+       if !ok {
+               t.Fatalf("Expected content item to be an object")
+       }
+
+       text, ok := contentItem["text"].(string)
+       if !ok {
+               t.Fatalf("Expected text in content item")
+       }
+
+       // Parse search result
+       var searchResult map[string]any
+       if err := json.Unmarshal([]byte(text), &searchResult); err != nil {
+               t.Fatalf("Failed to parse search result: %v", err)
+       }
+
+       // Validate search result
+       users, ok := searchResult["users"].([]any)
+       if !ok {
+               t.Fatalf("Expected users array in search result")
+       }
+
+       total, ok := searchResult["total"].(float64)
+       if !ok {
+               t.Fatalf("Expected total in search result")
+       }
+
+       t.Logf("Found %d users, total %.0f", len(users), total)
+}
+
+// TestCreateUser tests create user tool
+func TestCreateUser(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) || 
!checkServiceAvailable(backendURL) {
+               t.Skip("Services are not available")
+       }
+
+       t.Log("Testing create user tool...")
+
+       // Use timestamp to ensure unique email
+       timestamp := time.Now().Unix()
+       email := fmt.Sprintf("[email protected]", timestamp)
+
+       params := ToolCallParams{
+               Name: "createUser",
+               Arguments: map[string]any{
+                       "name":  "Test User",
+                       "email": email,
+                       "age":   25,
+               },
+       }
+
+       resp := sendJSONRPCRequest(t, "tools/call", params)
+
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       content, ok := result["content"].([]any)
+       if !ok || len(content) == 0 {
+               t.Fatalf("Expected content array in result")
+       }
+
+       contentItem, ok := content[0].(map[string]any)
+       if !ok {
+               t.Fatalf("Expected content item to be an object")
+       }
+
+       text, ok := contentItem["text"].(string)
+       if !ok {
+               t.Fatalf("Expected text in content item")
+       }
+
+       // Parse created user data
+       var userData map[string]any
+       if err := json.Unmarshal([]byte(text), &userData); err != nil {
+               t.Fatalf("Failed to parse user data: %v", err)
+       }
+
+       // Validate created user data
+       if name, ok := userData["name"].(string); !ok || name != "Test User" {
+               t.Fatalf("Expected user name to be 'Test User'")
+       }
+
+       if userEmail, ok := userData["email"].(string); !ok || userEmail != 
email {
+               t.Fatalf("Expected user email to be '%s'", email)
+       }
+
+       t.Logf("Successfully created user: %s (%s)", userData["name"], 
userData["email"])
+}
+
+// TestHealthCheck tests health check tool
+func TestHealthCheck(t *testing.T) {
+       if !checkServiceAvailable(pixiuURL) || 
!checkServiceAvailable(backendURL) {
+               t.Skip("Services are not available")
+       }
+
+       t.Log("Testing health check tool...")
+
+       params := ToolCallParams{
+               Name:      "healthCheck",
+               Arguments: map[string]any{},
+       }
+
+       resp := sendJSONRPCRequest(t, "tools/call", params)
+
+       result, ok := resp.Result.(map[string]any)
+       if !ok {
+               t.Fatalf("Expected result to be an object")
+       }
+
+       content, ok := result["content"].([]any)
+       if !ok || len(content) == 0 {
+               t.Fatalf("Expected content array in result")
+       }
+
+       contentItem, ok := content[0].(map[string]any)
+       if !ok {
+               t.Fatalf("Expected content item to be an object")
+       }
+
+       text, ok := contentItem["text"].(string)
+       if !ok {
+               t.Fatalf("Expected text in content item")
+       }
+
+       // Parse health check result
+       var healthData map[string]any
+       if err := json.Unmarshal([]byte(text), &healthData); err != nil {
+               t.Fatalf("Failed to parse health data: %v", err)
+       }
+
+       // Validate health check result
+       if status, ok := healthData["status"].(string); !ok || status != 
"healthy" {
+               t.Fatalf("Expected status to be 'healthy'")
+       }
+
+       t.Logf("Health check passed: %s", healthData["status"])
+}
diff --git a/mcp/simple/README.md b/mcp/simple/README.md
index a9fcc3b..a7f577a 100644
--- a/mcp/simple/README.md
+++ b/mcp/simple/README.md
@@ -13,7 +13,7 @@ MCP is a protocol designed for LLMs to interact with external 
tools, implemented
 ### 1. Start Backend Service
 
 ```shell
-cd mcp/simple/server
+cd mcp/simple/server/app
 go run server.go
 ```
 
diff --git a/mcp/simple/README_zh.md b/mcp/simple/README_zh.md
index 2255bfe..b492687 100644
--- a/mcp/simple/README_zh.md
+++ b/mcp/simple/README_zh.md
@@ -13,7 +13,7 @@ MCP 是一种专为 LLM 与外部工具交互而设计的协议,基于 JSON-RP
 ### 1. 启动后端服务
 
 ```shell
-cd mcp/simple/server
+cd mcp/simple/server/app
 go run server.go
 ```
 
diff --git a/mcp/simple/server/server.go b/mcp/simple/server/app/server.go
similarity index 100%
rename from mcp/simple/server/server.go
rename to mcp/simple/server/app/server.go

Reply via email to