Anthropic开源了一套MCP协议,它为连接AI系统与数据源提供了一个通用的、开放的标准,用单一协议取代了碎片化的集成方式。本文教你从零打造一个MCP客户端。

一、背景

如何让大语言模型与外部系统交互,一直是AI系统需要解决的问题:

  • Plugins:OpenAI推出ChatGPT Plugins,首次允许模型通过插件与外部应用交互。插件功能包括实时信息检索(如浏览器访问)、代码解释器(Code Interpreter)执行计算、第三方服务调用(如酒店预订、外卖服务等)

在这里插入图片描述

  • Function Calling:Function Calling技术逐步成熟,成为大模型与外部系统交互的核心方案。

在这里插入图片描述

  • Agent框架 Tools: 模型作为代理(Agent),动态选择工具完成任务,比如langchain的Tool。

在这里插入图片描述

一个企业,面对不同的框架或系统,可能都需要参考他们的协议,去开发对应Tool,这其实是一个非常重复的工作。

面对这种问题,Anthropic开源了一套MCP协议(Model Context Protocol),

https://www.anthropic.com/news/model-context-protocol

https://modelcontextprotocol.io/introduction

它为连接AI系统与数据源提供了一个通用的、开放的标准,用单一协议取代了碎片化的集成方式。其结果是,能以更简单、更可靠的方式让人工智能系统获取所需数据。

二、架构

在这里插入图片描述

  • MCP Hosts: 像 Claude Desktop、Cursor这样的程序,它们通过MCP访问数据。
  • MCP Clients: 与服务器保持 1:1 连接的协议客户端。
  • MCP Servers: 轻量级程序,每个程序都通过标准化的模型上下文协议公开特定功能。

结合AI模型,以一个Java应用为例,架构是这样:

在这里插入图片描述

可以看到传输层有两类:

  • StdioTransport
  • HTTP SSE

img
在这里插入图片描述

三、实现MCP Server

首先看一个最简单的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 serverconst server = new McpServer({  name: "Demo",  version: "1.0.0"});
// Add an addition toolserver.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这块,这个声明了一个做加法的工具。这就是一个最简单的可运行的Server了。

同时也可以使用官方的脚手架,来创建一个完整复杂的Server:

npx @modelcontextprotocol/create-server my-server

3.1 使用SDK

从上面代码可以看到很多模块都是从@modelcontextprotocol/sdk 这个SDK里导出的。

img

SDK封装好了协议内部细节(JSON-RPC 2.0),包括架构分层,开发者直接写一些业务代码就可以了。

https://github.com/modelcontextprotocol/typescript-sdk

MCP服务器可以提供三种主要功能类型:

  • Resources: 可以由客户端读取的类似文件的数据(例如API响应或文件内容)
  • Tools: LLM可以调用的功能(在用户批准下)
  • Prompts: 可帮助用户完成特定任务的预先编写的模板

ResourcesPrompts 可以让客户端唤起,供用户选择,比如用户所有的笔记,或者最近订单。

img

重点在Tools,其他很多客户端都不支持。

img

3.2 调试

如果写好了代码,怎么调试这个Server呢?官方提供了一个调试器:

npx @modelcontextprotocol/inspector

1.连接Server

img

2.获取工具

img

3.执行调试

img

3.3 在客户端使用

如果运行结果没错,就可以上架到支持MCP协议的客户端使用了,比如Claude、Cursor,这里以Cursor为例:

img

在Cursor Composer中对话,会自动识别这个Tool,并寻求用户是否调用

img

点击运行,就可以调用执行:

img

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 }) => {    // 使用固定汇率进行演示,实际应该调用汇率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<string, { transport: SSEServerTransport; response: express.Response }> = {}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);

核心的差别在于需要提供一个sse服务,对于Tool基本一样,但是sse类型就可以部署在服务端了。上架也和command类型相似:

img

img

3.5 一个复杂一点的例子

操作浏览器执行自动化流程。

,时长00:56

可以操作浏览器,Cursor秒变Devin。想象一下,写完代码,编辑器自动打开浏览器预览效果,然后截图给视觉模型,发现样式不对,自动修改。

如果对接好内部系统,贴一个需求地址,自动连接浏览器,打开网页,分析需求,分析视觉稿,然后自己写代码,对比视觉稿,你就喝杯咖啡,静静的看着它工作。

3.6 MCP Server资源

有很多写好的Server,可以直接复用。

  • https://github.com/modelcontextprotocol/servers
  • https://github.com/punkpeye/awesome-mcp-servers/blob/main/README-zh.md

四、实现MCP Client

一般MCP Host以一个Chat box为入口,对话形式去调用。

img

那我们怎么在自己的应用里支持MCP协议呢?这里需要实现MCP Client。

4.1 配置文件

使用配置文件来标明有哪些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 确认交互形态

MCP Client主要还是基于LLM,识别到需要调用外部系统,调用MCP Server提供的Tool,所以还是以对话为入口,可以方便一点,直接在terminal里对话,使用readline来读取用户输入。大模型可以直接使用openai,Tool的路由直接使用function calling。

4.3 编写Client

大致的逻辑:

1.读取配置文件,运行所有Server,获取可用的Tools

2.用户与LLM对话(附带所有Tools名称描述,参数定义)

3.LLM识别到要执行某个Tool,返回名称和参数

4.找到对应Server的Tool,调用执行,返回结果

5.把工具执行结果提交给LLM

6.LLM返回分析结果给用户

使用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";// 初始化环境变量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<string, Client> = new Map();    private transports: Map<string, StdioClientTransport | SSEClientTransport> = new Map();    private openai: OpenAI;    constructor() {        this.openai = new OpenAI({            apiKey: OPENAI_API_KEY        });    }    async connectToServer(serverName: string): Promise<void> {        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(`\nConnected to server '${serverName}' with tools:`, response.tools.map((tool: Tool) => tool.name));    }    private async createCommandTransport(shell: string): Promise<StdioClientTransport> {        const [command, ...shellArgs] = shell.split(' ');        if (!command) {            throw new Error("Invalid shell command");        }        // 处理参数中的波浪号路径        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<string, string>        };        return new StdioClientTransport(serverParams);    }    private async createSSETransport(url: string): Promise<SSEClientTransport> {        return new SSEClientTransport(new URL(url));    }    async processQuery(query: string): Promise<string> {        if (this.sessions.size === 0) {            throw new Error("Not connected to any server");        }        const messages: ChatCompletionMessageParam[] = [            {                role: "user",                content: query            }        ];        // 获取所有服务器的工具列表        const availableTools: any[] = [];        for (const [serverName, session] of this.sessions) {            const response = await session.listTools();            const tools = response.tools.map((tool: Tool) => ({                type: "function" as const,                function: {                    name: `${serverName}__${tool.name}`,                    description: `[${serverName}] ${tool.description}`,                    parameters: tool.inputSchema                }            }));            availableTools.push(...tools);        }        // 调用OpenAI API        const completion = await this.openai.chat.completions.create({            model: "gpt-4-turbo-preview",            messages,            tools: availableTools,            tool_choice: "auto"        });        const finalText: string[] = [];                // 处理OpenAI的响应        for (const choice of completion.choices) {            const message = choice.message;                        if (message.content) {                finalText.push(message.content);            }            if (message.tool_calls) {                for (const toolCall of message.tool_calls) {                    const [serverName, toolName] = toolCall.function.name.split('__');                    const session = this.sessions.get(serverName);                                        if (!session) {                        finalText.push(`[Error: Server ${serverName} not found]`);                        continue;                    }                    const toolArgs = JSON.parse(toolCall.function.arguments);                    // 执行工具调用                    const result = await session.callTool({                        name: toolName,                        arguments: toolArgs                    });                    const toolResult = result as unknown as MCPToolResult;                    finalText.push(`[Calling tool ${toolName} on server ${serverName} with args ${JSON.stringify(toolArgs)}]`);                    console.log(toolResult.content);                    finalText.push(toolResult.content);                    // 继续与工具结果的对话                    messages.push({                        role: "assistant",                        content: "",                        tool_calls: [toolCall]                    });                    messages.push({                        role: "tool",                        tool_call_id: toolCall.id,                        content: toolResult.content                    });                    // 获取下一个响应                    const nextCompletion = await this.openai.chat.completions.create({                        model: "gpt-4-turbo-preview",                        messages,                        tools: availableTools,                        tool_choice: "auto"                    });                    if (nextCompletion.choices[0].message.content) {                        finalText.push(nextCompletion.choices[0].message.content);                    }                }            }        }        return finalText.join("\n");    }    async chatLoop(): Promise<void> {        console.log("\nMCP Client Started!");        console.log("Type your queries or 'quit' to exit.");        const readline = createInterface({            input: process.stdin,            output: process.stdout        });        const askQuestion = () => {            return new Promise<string>((resolve) => {                readline.question("\nQuery: ", resolve);            });        };        try {            while (true) {                const query = (await askQuestion()).trim();                if (query.toLowerCase() === 'quit') {                    break;                }                try {                    const response = await this.processQuery(query);                    console.log("\n" + response);                } catch (error) {                    console.error("\nError:", error);                }            }        } finally {            readline.close();        }    }    async cleanup(): Promise<void> {        for (const transport of this.transports.values()) {            await transport.close();        }        this.transports.clear();        this.sessions.clear();    }    hasActiveSessions(): boolean {        return this.sessions.size > 0;    }}// 主函数async function main() {    const openServers = MCPClient.getOpenServers();    console.log("Connecting to servers:", openServers.join(", "));    const client = new MCPClient();        try {        // 连接所有开启的服务器        for (const serverName of openServers) {            try {                await client.connectToServer(serverName);            } catch (error) {                console.error(`Failed to connect to server '${serverName}':`, error);            }        }        if (!client.hasActiveSessions()) {            throw new Error("Failed to connect to any server");        }        await client.chatLoop();    } finally {        await client.cleanup();    }}// 运行主函数main().catch(console.error);

4.4 运行效果

NODE_TLS_REJECT_UNAUTHORIZED=0 node build/client.js

NODE_TLS_REJECT_UNAUTHORIZED=0 可以忽略(不校验证书)

,时长00:55

4.5 时序图

img

五、总结

总体来说解决了Client和Server数据交互的问题,但是没有解决LLM到Tool的对接:不同模型实现function call支持度不一样,比如DeepSeek R1不支持,那么如何路由到工具就成了问题。

不足:

1.开源时间不长,目前还不是很完善,语言支持度不够,示例代码不多。

2.Server质量良莠不齐,缺乏一个统一的质量保障体系和包管理工具,很多Server运行不起来,或者经常崩。

3.本地的Server还是依赖Node.js或者Python环境,远程Server支持的很少。

如果未来都开始接入MCP协议,生态起来了,能力就会非常丰富了,使用的人多了,就会有更多的系统愿意来对接,写一套代码就可以真正所有地方运行了。

个人认为MCP还是有前途的,未来可期!

普通人如何抓住AI大模型的风口?

领取方式在文末

为什么要学习大模型?

目前AI大模型的技术岗位与能力培养随着人工智能技术的迅速发展和应用 , 大模型作为其中的重要组成部分 , 正逐渐成为推动人工智能发展的重要引擎 。大模型以其强大的数据处理和模式识别能力, 广泛应用于自然语言处理 、计算机视觉 、 智能推荐等领域 ,为各行各业带来了革命性的改变和机遇 。

目前,开源人工智能大模型已应用于医疗、政务、法律、汽车、娱乐、金融、互联网、教育、制造业、企业服务等多个场景,其中,应用于金融、企业服务、制造业和法律领域的大模型在本次调研中占比超过 30%。
在这里插入图片描述

随着AI大模型技术的迅速发展,相关岗位的需求也日益增加。大模型产业链催生了一批高薪新职业:

在这里插入图片描述

人工智能大潮已来,不加入就可能被淘汰。如果你是技术人,尤其是互联网从业者,现在就开始学习AI大模型技术,真的是给你的人生一个重要建议!

最后

如果你真的想学习大模型,请不要去网上找那些零零碎碎的教程,真的很难学懂!你可以根据我这个学习路线和系统资料,制定一套学习计划,只要你肯花时间沉下心去学习,它们一定能帮到你!

大模型全套学习资料领取

这里我整理了一份AI大模型入门到进阶全套学习包,包含学习路线+实战案例+视频+书籍PDF+面试题+DeepSeek部署包和技巧,需要的小伙伴文在下方免费领取哦,真诚无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发

在这里插入图片描述

部分资料展示

一、 AI大模型学习路线图

整个学习分为7个阶段
在这里插入图片描述
在这里插入图片描述

二、AI大模型实战案例

涵盖AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,皆可用。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、视频和书籍PDF合集

从入门到进阶这里都有,跟着老师学习事半功倍。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

四、LLM面试题

在这里插入图片描述
在这里插入图片描述

五、AI产品经理面试题

在这里插入图片描述

六、deepseek部署包+技巧大全

在这里插入图片描述

😝朋友们如果有需要的话,可以V扫描下方二维码联系领取~
在这里插入图片描述

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐