具體實現 MCP 服務

具體如何實現一個 MCP 服務上面簡單提了一下 MCP 服務的大致實現原理,接下來我們根據官方文檔[1]中介紹,從零開始實現一個 MCP 服務。

首先我們需要有一套現有的后端服務 API,可以是我們曾經開發過的小項目,或者是一些開源的帶有開放 API 的庫,這里我選擇了開源的 APISIX[2] 網關,因為它有一套現成的 Admin API[3],并且還有 API 文檔,這可以提供給 cursor 作為參考幫助我們快速將所有 API 轉換成 MCP 服務中的操作。

設置環境

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
# 創建一個新的項目目錄
mkdir apisix-mcp
cd apisix-mcp
# 初始化一個新的 npm 項目
npm init -y
# 安裝依賴
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
# 創建項目文件
mkdir src
touch src/index.ts

更新

package.json

文件,添加

type: "module"

和一個構建腳本:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
    "type": "module",
    "bin": {
        "apisix-mcp": "./build/index.js"
    },
    "scripts": {
        "build": "tsc && chmod 755 build/index.js"
    },
    "files": [
        "build"
    ]
}

在項目根目錄下創建一個

tsconfig.json

文件:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
    "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "outDir": "./build",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
}

構建服務

src/index.ts

文件的頂部添加以下內容:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Configuration for APISIX MCP server
const APISIX_API_BASE = "http://127.0.0.1:9180/apisix/admin";
const APISIX_API_KEY = "edd1c9f034335f136f87ad84b625c8f1";// Create server instance
const server = new McpServer({
    name: "apisix-mcp",
    version: "1.0.0",
    config: {
        apiKey: {
            type: "string",
            description: "API key for APISIX Admin API authentication",
            default: APISIX_API_KEY
        },
        apiBase: {
            type: "string",
            description: "Base URL for APISIX Admin API",
            default: APISIX_API_BASE
        }
    }
});

這段代碼用于創建一個 APISIX MCP 服務實例:「導入依賴」:

McpServer

StdioServerTransport

是用于創建 MCP 服務的工具。

z

是 Zod 庫,用于數據驗證。「配置 APISIX MCP 服務」:

APISIX_API_BASE

是 APISIX Admin API 的基礎 URL。

APISIX_API_KEY

是用于身份驗證的 API 密鑰。「創建服務實例」:使用

McpServer

創建一個名為

apisix-mcp

的服務實例,版本為

1.0.0

。配置了

apiKey

apiBase

兩個參數,分別用于指定 API 密鑰和基礎 URL,并提供了默認值。這段代碼用于初始化一個 MCP 服務,為后續與 APISIX Admin API 的交互做準備,大家可以根據自己的業務進行簡單的調整。

創建請求文件

然后我們添加一個單獨的文件用于發送請求,官方的文檔中使用的是

fetch

,但我覺得用

axios

在執行不同的請求方法時會更加方便一點,所以就改用

axios

發送請求 :

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
/**
 * Define the common data structure returned by APISIX Admin API
 */
import axios from 'axios';

interface ApiResponse {
    value?: any;
    modifiedIndex?: number;
    createdIndex?: number;
    error?: string;
    message?: string;
}/**
 * 使用axios發送請求到APISIX管理API
 * @param url 請求的完整URL
 * @param method HTTP方法,默認為'GET'
 * @param data 請求數據,用于PUT/POST/PATCH請求
 * @param apiKey API密鑰,用于認證,默認使用環境變量或硬編碼值
 * @returns 返回API響應或null(如果發生錯誤)
 */
export async function makeAdminAPIRequest(
    url: string,
    method: string = 'GET',
    data?: any,    apiKey: string = process.env.APISIX_API_KEY || "edd1c9f034335f136f87ad84b625c8f1"): Promise {
    try {
        // 配置axios請求
        const response = await axios({
            method,
            url,
            data,
            headers: {
                'X-API-KEY': apiKey,
                'Content-Type': 'application/json'
            }
        });
        console.log(Axios請求成功: ${method} ${url});
        return response.data;
    } catch (error) {
        if (axios.isAxiosError(error)) {
            console.error(請求失敗: ${method} ${url});
            console.error(狀態碼: ${error.response?.status}, 錯誤信息: ${error.message});
            console.error(響應數據:, error.response?.data);
        } else {
            console.error('發送API請求時出錯:', error);
        }
        return null;
    }
}export default makeAdminAPIRequest;

實現執行工具

創建好了服務實例,并且實現了一個請求方法后,接下來到最核心的一步,也就是實現執行工具:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
const idSchema = z.object({
    id: z.string().describe("資源 id")
});

server.tool('get-routes',
    '列出所有路由',
    {},
    async (args, extra) => {
        const response = await makeAdminAPIRequest(${APISIX_API_BASE}/routes);
        return {
            content: [
                {
                    type: "text",
                    text: JSON.stringify(response, null, 2),
                },
            ],
        };
    }
);server.tool('create-route',
    '創建一個新的路由',
    routeSchema.shape,
    async (args, extra) => {
        try {            const routeId = args.id || Date.now().toString();            console.log(Starting route creation, ID: ${routeId}, path: ${args.route.uri});
            const response = await makeAdminAPIRequest(
                ${APISIX_API_BASE}/routes/${routeId},
                'PUT',
                args.route
            );
            if (!response) {
                console.error(Failed to create route, ID: ${routeId});
                return {
                    content: [
                        {
                            type: "text",
                            text: JSON.stringify({ error: "Failed to create route, please check the APISIX connection and logs" }, null, 2),
                        },
                    ],
                };
            }
            console.log(Route created successfully, ID: ${routeId});
            return {
                content: [
                    {
                        type: "text",
                        text: JSON.stringify(response, null, 2),
                    },
                ],
            };
        } catch (error: unknown) {
            console.error(Exception occurred:, error);
            const err = error instanceof Error ? error : new Error(String(error));
            return {
                content: [
                    {
                        type: "text",
                        text: JSON.stringify({ error: Failed to create route: ${err.message} }, null, 2),
                    },
                ],
            };
        }
    }
);

server.tool

函數定義了兩個個工具,分別用于獲取路由數據和新增路由,我們在第二個參數中用中文描述了我們這個工具的作用,這個描述就是用來給大模型調用時進行語意分析后進行觸發的,所以描述我們可以寫的更加詳盡一些,讓沒那么聰明的模型也號理解一點,這里只是一個簡單示例,兩個工具都通過

makeAdminAPIRequest

函數與 APISIX Admin API 交互。

接下來我們執行

npm build

將項目構建一下,可以看到

build

目錄也就是構建成功了

image

調試

在我們的 MCP 服務基礎框架完成后,就到了調試環節。 MCP 服務的接入需要用到一些 AI 客戶端,例如 cursor,vscode 中的 cline 插件,或者是 Claude 的官方應用,考慮到我們開發用的就是 curosr,所以這里我們直接使用 cursor 作為客戶端進行調試。

第一步我們需要在

.cursor

目錄中創建

mcp.json

文件用于聲明 MCP 服務,在我的 windows 電腦上路徑如下圖:

image

創建完成后,我們在文件中寫入配置:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
    "mcpServers": {
        "apisix-mcp": {
            "command": "node",
            "args": [
                "E:projectsapisix-mcpbuildindex.js"
            ]
        }
    }
}

mcpServers字段代表我們聲明的 MCP 服務,值的類型是對象,其中鍵名為服務名稱,這里我們叫

apisix-mcp

,可以根據自己的業務修改,

apisix-mcp

的值也是一個對象,其中包含了

command

args

兩個字段。

command

代表啟動 MCP 服務要執行的命令,因為我們的服務是用 node.js 寫的,那么這里我們的

command

就是

node

args是要傳入的參數,也就是拼接在

command

后面的內容,這里我們寫的是上一步構建出來的產物的路徑

當配置完成后,我們打開 cursor 設置中 MCP 菜單項,如果沒有看到這個菜單需要升級一下 cursor 的版本,然后我們會看到一個 apisix-mcp 的服務,而且還亮著綠燈,說明已經連接成功了,如果沒有連接成功我們可以點一下刷新按鈕。MCP 服務中還會展示我們的代碼里定義了哪些工具和資源,(這里截圖中的工具比較多是因為我后續實現了比較多個工具了,正常應該只展示我們實現的那兩個工具):

image

要注意的是 cursor 在執行腳本時會彈出一個黑色的終端,代表我們 MCP 服務的進程是執行在這個終端的,在后臺掛著就好了,不要關掉它。

image

接下來,我們在 cursor 的輸入框中,選擇

agent

模式,然后選擇一個比較聰明的模型,例如

claude 3.5/3.7

或者

gpt4oo3-mini

,不要選擇

cursor-small

gpt-4o-mini

這種,因為我自己實測這兩個模型似乎不具備

agent

能力,可能是上下文太小了,所以不會調用 MCP 服務。這里我們選擇

claude-3.7-sonnet

,然后輸入如下圖的操作:

image

然后我們會看到 cursor 執行了

called MCP tool

的操作,一共執行了四次,對應調用的

tool

我們都能直接看到:

image

雖然大模型說創建成功了,但是我們還是自己檢查一下,看看是不是真的成功了,這里我在終端輸入:

ounter(lineounter(lineounter(line
curl http://127.0.0.1:9180/apisix/admin/routes -H "X-API-KEY: $admin_key" | jq

可以看到控制臺打印出了我們創建的路由,并且插件也按照我們的要求配置了,說明真的創建成功了。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
    "key": "/apisix/routes/1742721614502",
    "modifiedIndex": 98,
    "createdIndex": 98,
    "value": {
        "id": "1742721614502",
        "uri": "/api/httpbin/*",
        "upstream": {
            "pass_host": "pass",
            "nodes": {
                "httpbin.org:80": 1
            },
            "type": "roundrobin",
            "scheme": "http",
            "hash_on": "vars"
        },
        "status": 1,
        "create_time": 1742721614,
        "update_time": 1742721614,
        "priority": 0,
        "plugins": {
            "limit-req": {
                "key_type": "var",
                "allow_degradation": false,
                "rate": 2,
                "nodelay": false,
                "key": "remote_addr",
                "burst": 4,
                "rejected_code": 429,
                "policy": "local"
            },
            "cors": {
                "allow_headers": "*",
                "allow_origins": "*",
                "max_age": 3600,
                "expose_headers": "Content-Length,Content-Type",
                "allow_methods": "GET,POST,PUT,DELETE,PATCH,OPTIONS",
                "allow_credential": false
            },
            "limit-count": {
                "show_limit_quota_header": true,
                "count": 5,
                "key_type": "var",
                "time_window": 60,
                "key": "remote_addr",
                "allow_degradation": false,
                "rejected_code": 429,
                "policy": "local"
            }
        }
    }
}

至此為止,我們花了不用兩個小時就能把一個現有服務接入大模型,通過自然語言進行交互。

后續我們要做的就是將所有的 API 轉換為 MCP 中的工具,讓大模型按需調用即可,這一步也很簡單,直接把我們的 OpenAPI 文檔提供給 cursor 就能批量生成了,我用一輪對話就全部生成好了,也就是上面截圖中展示的所有 MCP 工具:

image

而且這里我非常推薦大家直接是用 curosr 去調試 MCP 服務,因為在我們實現 MCP 服務的項目中,如果發現 MCP 服務沒法正常調用,我們可以直接讓 cursor 自己調試,下面貼一個我調試過程中 cursor 自己的執行記錄:

image

cursor 會一邊調用 MCP 服務,根據服務的響應結果去調整我們的源碼,然后自己重新構建新的產物再重新嘗試調用 MCP 服務,直到服務正常工作,從開發到測試的流程自己閉環,真的很強!

總結

MCP 中的概念不僅僅有

tool

這一種,但是目前我實現的功能只用到了這個工具,后續我將繼續完善這個 MCP 服務的項目,等其他功能都摸清楚了再給大家完整的介紹 MCP 中的各種功能的實現。

我花了幾個小時就把這個服務構建出來,而且最終的效果讓我感覺蠻驚訝的,用自然語言去和系統交互的感覺很奇妙,如果再開發一個 MCP 的客戶端,那都不需要寫一個功能健全的控制臺了,可玩性很強,上手又很簡單,非常推薦大家去嘗試寫一下。

目前唯一的問題就是依賴能力比較強的模型,因此調用成本會比較高,但是未來隨著大模型的能力越來越強,調用成本越來越低,后續 MCP 服務可能替代現有傳統前端控制臺成為主流。

如果文章對你有幫助,歡迎點個贊,respect~點擊關注公眾號,“技術干貨” 及時達!

原文轉載自:https://mp.weixin.qq.com/s/YaQuGQeWhwBWU-aMTle6-A

熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業工程師共享工作效率翻倍的秘密
熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
返回頂部
上一篇
SpringAI-MCP技術初探
下一篇
騰訊云DeepSeek API對接微信小程序完全指南
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
最新欧美精品一区二区三区| 欧美二区三区91| 综合在线观看色| 亚洲丶国产丶欧美一区二区三区| 亚洲狠狠丁香婷婷综合久久久| 日本一区二区三区国色天香| 亚洲欧美激情小说另类| av电影在线观看完整版一区二区| 欧美性色综合网| 中文字幕在线不卡一区| av资源站一区| 国产精品午夜在线观看| 成人性生交大片免费看视频在线| 日本道免费精品一区二区三区| 欧美吻胸吃奶大尺度电影| 欧美在线制服丝袜| 777a∨成人精品桃花网| 2021国产精品久久精品| 五月婷婷久久丁香| 成人一道本在线| 91蜜桃在线观看| 亚洲国产精品精华液网站| 成人午夜视频福利| 亚洲欧美一区二区不卡| 在线观看中文字幕不卡| 亚洲大尺度视频在线观看| 欧美一区在线视频| 91在线无精精品入口| 中文字幕一区在线观看| 成人午夜大片免费观看| 免费成人在线播放| 亚洲午夜视频在线观看| 91久久精品一区二区三| 精品一区二区三区在线视频| 色婷婷国产精品综合在线观看| 一区二区三区影院| 欧美伦理影视网| 香蕉影视欧美成人| 国产精品欧美一区喷水| 欧美日韩精品欧美日韩精品一| 处破女av一区二区| 成人综合婷婷国产精品久久蜜臀| 首页综合国产亚洲丝袜| 天天操天天干天天综合网| 一道本成人在线| 欧美午夜片在线看| 欧美情侣在线播放| 国产精品一区二区在线看| 欧美激情一区二区三区在线| 欧美tickling网站挠脚心| 久久久久久久综合| 国产精品女同一区二区三区| 天堂成人免费av电影一区| 美女视频免费一区| 亚洲国产人成综合网站| 激情综合网av| 91精品91久久久中77777| 欧美电视剧在线观看完整版| 91小视频在线| 久久蜜桃一区二区| 欧美日韩在线亚洲一区蜜芽| 久久久高清一区二区三区| 91精品国产色综合久久不卡电影| 国产欧美日韩三区| 人人精品人人爱| 日本不卡一区二区| 亚洲狠狠爱一区二区三区| 亚洲综合图片区| 国产精品伦一区| 国产米奇在线777精品观看| 欧美日韩精品免费观看视频| 一区二区高清免费观看影视大全| 日韩不卡一区二区三区| 9191久久久久久久久久久| 一区二区三区视频在线看| 色综合天天综合给合国产| 亚洲精品少妇30p| av激情综合网| 日韩电影免费在线观看网站| 在线观看免费视频综合| 亚洲视频一区在线观看| 欧美久久一二区| 成人毛片在线观看| 亚洲伦理在线免费看| 久久女同精品一区二区| 91色视频在线| 国产精品一品视频| 国产精品久久久久久久岛一牛影视 | 国产精品对白交换视频| 国产夜色精品一区二区av| 天堂成人国产精品一区| 亚洲欧美福利一区二区| 欧美激情一区二区三区全黄 | 久久精品视频在线免费观看| 91美女在线视频| 99riav久久精品riav| 国产剧情在线观看一区二区| 偷拍与自拍一区| 三级久久三级久久久| 日本aⅴ亚洲精品中文乱码| 日本aⅴ亚洲精品中文乱码| 三级在线观看一区二区| 奇米影视一区二区三区小说| 久久久国产综合精品女国产盗摄| 亚洲一区二区三区不卡国产欧美| 欧美精品视频www在线观看| 欧美日韩国产一级片| 国产蜜臀av在线一区二区三区| 一区二区视频在线| 久久国产尿小便嘘嘘尿| 欧美日韩国产成人在线91 | 色悠悠亚洲一区二区| 国产三区在线成人av| 午夜国产精品一区| 91影院在线观看| 国产性做久久久久久| 久久99久国产精品黄毛片色诱| 成人精品电影在线观看| 国产亚洲女人久久久久毛片| 国产精品亚洲成人| 亚洲视频电影在线| 国产精品色一区二区三区| 成人免费av资源| av高清不卡在线| 日韩精品乱码免费| 久久综合资源网| 91麻豆国产福利精品| 日本欧美久久久久免费播放网| 综合欧美一区二区三区| 久久精品一区蜜桃臀影院| 综合自拍亚洲综合图不卡区| 亚洲精品国产一区二区三区四区在线| 国产午夜亚洲精品不卡| www日韩大片| 色婷婷激情综合| 精品视频在线免费| 国产成人精品免费| 成人国产一区二区三区精品| 日韩高清一区二区| 国产aⅴ综合色| 在线日韩av片| 日韩精品一区二区三区老鸭窝| 日韩电影免费一区| 337p粉嫩大胆色噜噜噜噜亚洲| 99久久精品免费观看| 久久免费偷拍视频| 懂色av噜噜一区二区三区av| 亚洲另类中文字| 久久久91精品国产一区二区三区| 日韩女优毛片在线| 99视频精品全部免费在线| 亚洲一区二区精品久久av| 久久精品欧美一区二区三区麻豆| 日本高清不卡在线观看| 欧美精品一区二区久久婷婷| 国产精品一区二区不卡| 久久机这里只有精品| 激情综合网av| 欧美videossexotv100| 色婷婷激情久久| 欧美亚男人的天堂| 97超碰欧美中文字幕| 欧美日韩精品一区二区天天拍小说 | 午夜a成v人精品| 国产精品色一区二区三区| 亚洲欧美在线视频| 欧洲色大大久久| av一二三不卡影片| 99精品视频在线观看| av资源网一区| 国产精品成人午夜| 国产欧美精品日韩区二区麻豆天美| 国产精品美女一区二区在线观看| 亚洲欧美中日韩| 激情综合色丁香一区二区| 国产成人午夜视频| 成人福利视频在线看| 欧美精品高清视频| 26uuu亚洲| 国产成人精品综合在线观看 | 香蕉av福利精品导航| 国产揄拍国内精品对白| 欧美三级视频在线| 成人欧美一区二区三区| 日韩一级完整毛片| 最新中文字幕一区二区三区| 午夜精品久久久久| 成人国产精品视频| 亚洲电影一级片| 欧美日本一区二区在线观看| 亚洲国产成人91porn| 欧美久久一二三四区| 91美女在线看| 亚洲一二三区不卡| 91色乱码一区二区三区| 国产精品久久久久永久免费观看 | 99久久精品久久久久久清纯| 欧美高清在线精品一区| 国产91露脸合集magnet| 日韩欧美激情在线|