Function Calling:Function Calling技術(shù)逐步成熟,成為大模型與外部系統(tǒng)交互的核心方案。

image

Agent框架 Tools: 模型作為代理(Agent),動(dòng)態(tài)選擇工具完成任務(wù),比如langchain的Tool。

image

一個(gè)企業(yè),面對(duì)不同的框架或系統(tǒng),可能都需要參考他們的協(xié)議,去開發(fā)對(duì)應(yīng)Tool,這其實(shí)是一個(gè)非常重復(fù)的工作。面對(duì)這種問題,Anthropic開源了一套MCP協(xié)議(Model Context Protocol),https://www.anthropic.com/news/model-context-protocol https://modelcontextprotocol.io/introduction 它為連接AI系統(tǒng)與數(shù)據(jù)源提供了一個(gè)通用的、開放的標(biāo)準(zhǔn),用單一協(xié)議取代了碎片化的集成方式。其結(jié)果是,能以更簡單、更可靠的方式讓人工智能系統(tǒng)獲取所需數(shù)據(jù)。

二、架構(gòu)

image

MCP Hosts:像 Claude Desktop、Cursor這樣的程序,它們通過MCP訪問數(shù)據(jù)。MCP Clients:與服務(wù)器保持 1:1 連接的協(xié)議客戶端。MCP Servers:輕量級(jí)程序,每個(gè)程序都通過標(biāo)準(zhǔn)化的模型上下文協(xié)議公開特定功能。結(jié)合AI模型,以一個(gè)Java應(yīng)用為例,架構(gòu)是這樣:

image

可以看到傳輸層有兩類:StdioTransportHTTP SSE

image

三、實(shí)現(xiàn)MCP Server

首先看一個(gè)最簡單的MCP Server例子:

`import { McpServer, ResourceTemplate } from “@modelcontextprotocol/sdk/server/mcp.js”;
import { StdioServerTransport } from “@modelcontextprotocol/sdk/server/stdio.js”;
import { z } from “zod”;

// Create an MCP server
const server = new McpServer({
name: “Demo”,
version: “1.0.0”
});

// Add an addition tool
server.tool(
“add”,
“Add two numbers”,
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: “text”, text: String(a + b) }]
})
);

async function main() {
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
}

main();
`

代碼頭部和底部都是一些樣板代碼,主要變化的是在tool這塊,這個(gè)聲明了一個(gè)做加法的工具。這就是一個(gè)最簡單的可運(yùn)行的Server了。同時(shí)也可以使用官方的腳手架,來創(chuàng)建一個(gè)完整復(fù)雜的Server:

npx @modelcontextprotocol/create-server my-server

3.1 使用SDK從上面代碼可以看到很多模塊都是從@modelcontextprotocol/sdk 這個(gè)SDK里導(dǎo)出的。

image

SDK封裝好了協(xié)議內(nèi)部細(xì)節(jié)(JSON-RPC 2.0),包括架構(gòu)分層,開發(fā)者直接寫一些業(yè)務(wù)代碼就可以了。https://github.com/modelcontextprotocol/typescript-sdk MCP服務(wù)器可以提供三種主要功能類型:Resources:可以由客戶端讀取的類似文件的數(shù)據(jù)(例如API響應(yīng)或文件內(nèi)容)Tools:LLM可以調(diào)用的功能(在用戶批準(zhǔn)下)Prompts:可幫助用戶完成特定任務(wù)的預(yù)先編寫的模板Resources和Prompts可以讓客戶端喚起,供用戶選擇,比如用戶所有的筆記,或者最近訂單。

image

重點(diǎn)在Tools,其他很多客戶端都不支持。

image

3.2 調(diào)試如果寫好了代碼,怎么調(diào)試這個(gè)Server呢?官方提供了一個(gè)調(diào)試器:

npx @modelcontextprotocol/inspector

1.連接Server

image

2.獲取工具

image

3.執(zhí)行調(diào)試

image

3.3 在客戶端使用如果運(yùn)行結(jié)果沒錯(cuò),就可以上架到支持MCP協(xié)議的客戶端使用了,比如Claude、Cursor,這里以Cursor為例:

image

在Cursor Composer中對(duì)話,會(huì)自動(dòng)識(shí)別這個(gè)Tool,并尋求用戶是否調(diào)用

image

點(diǎn)擊運(yùn)行,就可以調(diào)用執(zhí)行:

image

3.4 HTTP SSE類型Server

`import express from “express”;
import { McpServer } from “@modelcontextprotocol/sdk/server/mcp.js”;
import { SSEServerTransport } from “@modelcontextprotocol/sdk/server/sse.js”;
import { z } from “zod”;

const server = new McpServer({
name: “demo-sse”,
version: “1.0.0”
});

server.tool(
“exchange”,
“人民幣匯率換算”,
{ rmb: z.number() },
async ({ rmb }) => {
// 使用固定匯率進(jìn)行演示,實(shí)際應(yīng)該調(diào)用匯率API
const usdRate = 0.14; // 1人民幣約等于0.14美元
const hkdRate = 1.09; // 1人民幣約等于1.09港幣

const usd = (rmb * usdRate).toFixed(2);
const hkd = (rmb * hkdRate).toFixed(2);

return {
content: [{
type: “text”,
text: `${rmb}人民幣等于:\n${usd}美元\n${hkd}港幣`
}]
}
},
);

const app = express();
const sessions: Record = {};

app.get(“/sse”, async (req, res) => {
console.log(`New SSE connection from ${req.ip}`);

const sseTransport = new SSEServerTransport(“/messages”, res);
const sessionId = sseTransport.sessionId;

if (sessionId) {
sessions[sessionId] = { transport: sseTransport, response: res }
}

await server.connect(sseTransport);
});

app.post(“/messages”, async (req, res) => {
const sessionId = req.query.sessionId as string;
const session = sessions[sessionId];

if (!session) {
res.status(404).send(“Session not found”);
return;
}

await session.transport.handlePostMessage(req, res);
});

app.listen(3001);
`

核心的差別在于需要提供一個(gè)sse服務(wù),對(duì)于Tool基本一樣,但是sse類型就可以部署在服務(wù)端了。上架也和command類型相似:

image

image

3.5 一個(gè)復(fù)雜一點(diǎn)的例子操作瀏覽器執(zhí)行自動(dòng)化流程??梢圆僮鳛g覽器,Cursor秒變Devin。想象一下,寫完代碼,編輯器自動(dòng)打開瀏覽器預(yù)覽效果,然后截圖給視覺模型,發(fā)現(xiàn)樣式不對(duì),自動(dòng)修改。如果對(duì)接好內(nèi)部系統(tǒng),貼一個(gè)需求地址,自動(dòng)連接瀏覽器,打開網(wǎng)頁,分析需求,分析視覺稿,然后自己寫代碼,對(duì)比視覺稿,你就喝杯咖啡,靜靜的看著它工作。3.6 MCP Server資源有很多寫好的Server,可以直接復(fù)用。https://github.com/modelcontextprotocol/servers https://github.com/punkpeye/awesome-mcp-servers/blob/main/README-zh.md

四、實(shí)現(xiàn)MCP Client

一般MCP Host以一個(gè)Chat box為入口,對(duì)話形式去調(diào)用。

image

那我們?cè)趺丛谧约旱膽?yīng)用里支持MCP協(xié)議呢?這里需要實(shí)現(xiàn)MCP Client。

4.1 配置文件

使用配置文件來標(biāo)明有哪些MCP Server,以及類型。

const config = [
{
name: 'demo-stdio',
type: 'command',
command: 'node ~/code-open/cursor-toolkits/mcp/build/demo-stdio.js',

isOpen: true

},
{
name: 'weather-stdio',
type: 'command',
command: 'node ~/code-open/cursor-toolkits/mcp/build/weather-stdio.js',

isOpen: true

},
{
name: 'demo-sse',

type: 'sse',

url: 'http://localhost:3001/sse',

isOpen: false

}
];
export default config;

4.2 確認(rèn)交互形態(tài)

MCP Client主要還是基于LLM,識(shí)別到需要調(diào)用外部系統(tǒng),調(diào)用MCP Server提供的Tool,所以還是以對(duì)話為入口,可以方便一點(diǎn),直接在terminal里對(duì)話,使用readline來讀取用戶輸入。大模型可以直接使用openai,Tool的路由直接使用function calling。

4.3 編寫Client

大致的邏輯:1.讀取配置文件,運(yùn)行所有Server,獲取可用的Tools2.用戶與LLM對(duì)話(附帶所有Tools名稱描述,參數(shù)定義)3.LLM識(shí)別到要執(zhí)行某個(gè)Tool,返回名稱和參數(shù)4.找到對(duì)應(yīng)Server的Tool,調(diào)用執(zhí)行,返回結(jié)果5.把工具執(zhí)行結(jié)果提交給LLM6.LLM返回分析結(jié)果給用戶使用SDK編寫Client代碼

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport, StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import OpenAI from "openai";
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { ChatCompletionMessageParam } from "openai/resources/chat/completions.js";
import { createInterface } from "readline";
import { homedir } from 'os';
import config from "./mcp-server-config.js";

// 初始化環(huán)境變量

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (!OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY environment variable is required");
}
interface MCPToolResult {
content: string;
}
interface ServerConfig {
name: string;
type: 'command' | 'sse';
command?: string;
url?: string;
isOpen?: boolean;
}
class MCPClient {
static getOpenServers(): string[] {
return config.filter(cfg => cfg.isOpen).map(cfg => cfg.name);
}
private sessions: Map = new Map();
private transports: Map = new Map();
private openai: OpenAI;
constructor() {
this.openai = new OpenAI({
apiKey: OPENAI_API_KEY
});
}
async connectToServer(serverName: string): Promise {
const serverConfig = config.find(cfg => cfg.name === serverName) as ServerConfig;
if (!serverConfig) {
throw new Error(Server configuration not found for: ${serverName});
}
let transport: StdioClientTransport | SSEClientTransport;
if (serverConfig.type === 'command' && serverConfig.command) {
transport = await this.createCommandTransport(serverConfig.command);
} else if (serverConfig.type === 'sse' && serverConfig.url) {
transport = await this.createSSETransport(serverConfig.url);
} else {
throw new Error(Invalid server configuration for: ${serverName});
}
const client = new Client(
{
name: "mcp-client",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
await client.connect(transport);
this.sessions.set(serverName, client);
this.transports.set(serverName, transport);

// 列出可用工具

const response = await client.listTools();
console.log(`
Connected to server '${serverName}' with tools:`, response.tools.map((tool: Tool) => tool.name));
}
private async createCommandTransport(shell: string): Promise {
const [command, ...shellArgs] = shell.split(' ');
if (!command) {
throw new Error("Invalid shell command");
}

// 處理參數(shù)中的波浪號(hào)路徑

const args = shellArgs.map(arg => {
if (arg.startsWith('~/')) {
return arg.replace('~', homedir());
}
return arg;
});
const serverParams: StdioServerParameters = {

command,
args,

env: Object.fromEntries(
Object.entries(process.env).filter(([_, v]) => v !== undefined)
) as Record
};
return new StdioClientTransport(serverParams);
}
private async createSSETransport(url: string): Promise {
return new SSEClientTransport(new URL(url));
}
async processQuery(query: string): Promise {
if (this.sessions.size === 0) {
throw new Error("Not connected to any server");
}
const messages: ChatCompletionMessageParam[] = [
{

`role:

原文轉(zhuǎn)載自:https://mp.weixin.qq.com/s/zYgQEpdUC5C6WSpMXY8cxw

上一篇:

一文講透 AI Agent 與 AI Workflow 的區(qū)別和深度解析:從自動(dòng)化到智能化的演進(jìn)

下一篇:

DeepSeek發(fā)布開源數(shù)學(xué)定理證明模型
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)