一、背景

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

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

img

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

img

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

img

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

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

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

https://modelcontextprotocol.io/introduction

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

二、架构

img

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

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

img

可以看到传输层有两类:

  • 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 一个复杂一点的例子

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

可以操作浏览器,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 可以忽略(不校验证书)

4.5 时序图

img

五、总结

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

不足:

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

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

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

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

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

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

大模型时代,企业对人才的需求变了,AIGC相关岗位人才难求,薪资持续走高,AI运营薪资平均值约18457元,AI工程师薪资平均值约37336元,大模型算法薪资平均值约39607元。
在这里插入图片描述

掌握大模型技术你还能拥有更多可能性

• 成为一名全栈大模型工程师,包括Prompt,LangChain,LoRA等技术开发、运营、产品等方向全栈工程;

• 能够拥有模型二次训练和微调能力,带领大家完成智能对话、文生图等热门应用;

• 薪资上浮10%-20%,覆盖更多高薪岗位,这是一个高需求、高待遇的热门方向和领域;

• 更优质的项目可以为未来创新创业提供基石。

可能大家都想学习AI大模型技术,也想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把全套AI技术和大模型入门资料、操作变现玩法都打包整理好,希望能够真正帮助到大家。

读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用

零基础入门AI大模型

今天贴心为大家准备好了一系列AI大模型资源,包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

有需要的小伙伴,可以点击下方链接免费领取【保证100%免费

点击领取 《AI大模型&人工智能&入门进阶学习资源包》*

部分资料展示

一、 AI大模型学习路线图

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

如果大家想领取完整的学习路线及大模型学习资料包,可以扫下方二维码获取
在这里插入图片描述
👉2.大模型配套视频👈

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,每个章节都是当前板块的精华浓缩。(篇幅有限,仅展示部分)

img

👉3.大模型经典学习电子书👈

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。(篇幅有限,仅展示部分,公众号内领取)

img

👉4.大模型面试题&答案👈

截至目前大模型已经超过200个,在大模型纵横的时代,不仅大模型技术越来越卷,就连大模型相关的岗位和面试也开始越来越卷了。为了让大家更容易上车大模型算法赛道,我总结了大模型常考的面试题。(篇幅有限,仅展示部分,公众号内领取)

img

大模型面试

**因篇幅有限,仅展示部分资料,**有需要的小伙伴,可以点击下方链接免费领取【保证100%免费

点击领取 《AI大模型&人工智能&入门进阶学习资源包》

**或扫描下方二维码领取 **

在这里插入图片描述

Logo

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

更多推荐