京东自营 618 + 国补 iPhone 历史最低价

解锁MCP开发:AI时代万能接口

在 AI 的快速发展进程中,2024 年 11 月,Anthropic 推出的 Model Context Protocol(MCP),即模型上下文协议,宛如一颗投入湖面的石子,激起层层涟漪 ,引发了广泛关注。这一开放协议致力于解决 LLM 与外部工具集成时面临的标准化问题,为 AI 模型与各类数据源和工具的交互,提供了一种统一的方式,被形象地称作 AI 应用的 “USB-C 端口”。

MCP 的出现,有着其深刻的背景。早期的大型语言模型,就像被禁锢在一座孤岛上的智者,虽然拥有丰富的知识储备,但这些知识局限于训练时的数据,如同被定格在过去的画卷,无法获取实时信息,也难以与外部系统自如交互。随着 AI 应用场景日益复杂,多轮对话系统需要理解用户的历史语境,企业级数据分析渴望模型能调用外部工具执行复杂任务,代码生成工具期望获取最新的代码库信息…… 这些需求如同一声声急切的呼唤,催促着技术的革新。开发者们尝试通过定制化的 API 或插件将模型与外部数据源连接,然而,这一过程却困难重重。每个数据源都如同一个独特的个体,有着自己的 “脾气” 和 “语言”,需要独立开发接口,这不仅导致重复劳动和维护成本激增,还使得系统扩展性受限,开发效率低下,安全性和一致性管理也变得异常艰难。

MCP 的诞生,犹如一道曙光,照亮了这片混沌的领域。它采用客户端 - 服务器架构,巧妙地将模型与外部系统之间的通信抽象化。在这个架构中,主机(Host)是承载 AI 交互环境的应用程序,如 Claude Desktop、Cursor 等,它们是用户与 AI 模型互动的桥梁,负责集成外部工具、访问多样化的数据资源,并运行 MCP 客户端(MCP Client)。MCP 客户端就像是一个勤劳的信使,运行于主机内部,专门负责与 MCP 服务器(MCP Server)建立高效通信。而 MCP 服务器则是一个个轻量级的程序,每个都通过标准化模型上下文协议公开特定功能,它们可以安全访问本地资源,如数据库、文件、服务等,也能连接到互联网资源。

MCP 的核心价值,体现在多个关键方面。它统一了集成标准,就像制定了一套通用的语言,让 AI 模型与各种数据源和工具的对接变得简单高效。以往开发者需要为每个 AI 集成创建定制化的解决方案,而现在,一个 MCP 协议就能对接所有集成,大大降低了开发难度,减少了开发时间,提高了系统可靠性。在实时数据更新方面,MCP 支持动态数据交互,而非静态连接,让 AI 模型摆脱了静态数据的束缚,能够根据最新的数据做出更准确的判断和决策。自动工具发现功能也十分强大,它支持动态工具发现和上下文处理,使 AI 模型能够根据任务的需求,自动寻找并调用最合适的工具,就像一个聪明的助手,总能在关键时刻找到解决问题的最佳方法。数据隐私保护一直是人们关注的焦点,MCP 规定数据和工具不需上传远端,从源头上保护了数据隐私,让用户更加安心地使用 AI 服务。

以数据库查询为例,在 MCP 出现之前,如果 AI 模型需要查询数据库中的信息,开发者可能需要针对不同的数据库类型,如 MySQL、Oracle 等,编写不同的接口代码,过程繁琐且容易出错。而有了 MCP,开发者只需按照 MCP 标准开发一个数据库查询的 MCP 服务器,AI 模型就可以通过 MCP 协议轻松访问各种数据库,获取所需信息。再比如,在智能客服场景中,客服 AI 可以通过 MCP 协议实时访问产品信息库、订单系统、物流系统等,为客户提供准确的订单状态、产品信息和个性化建议,大大提升了客户服务的质量和效率 。

MCP 的出现,为 LLM 与外部工具的集成带来了新的曙光,它的统一标准、实时数据更新、自动工具发现和隐私保护等价值,使其成为推动 AI 发展的重要力量,为构建更加智能、高效的 AI 应用生态奠定了坚实的基础。

开发前的知识储备

(一)MCP 核心概念深入剖析

在深入探索 MCP 开发之前,我们需要对 MCP 协议规范中的几个核心概念有清晰的理解,这是构建强大 AI 应用的基石。在 MCP 的世界里,服务器提供的资源、提示模板和工具是三个至关重要的对象,它们各自扮演着独特的角色,共同为 AI 交互提供支持 。

资源,是 AI 模型可以访问的数据,如同一个丰富的知识库。这些数据可以是静态的,如文件内容、数据库查询结果或 API 的响应。想象一下,AI 助手要为你制定一份旅行计划,它可以通过 MCP 服务器提供的资源,获取目的地的景点介绍、酒店信息、交通时刻表等,这些资源为 AI 的决策和生成提供了坚实的数据基础。资源就像是建造高楼大厦的砖块,每一块都蕴含着独特的信息,缺了它们,AI 就如同无米之炊,难以给出准确而丰富的回答 。

提示模板,是服务器提供给 AI 的预写消息或模板,它就像是一位经验丰富的导师,引导 AI 如何使用资源和工具。例如,当用户询问如何优化一段代码时,提示模板可以告诉 AI:“你可以先分析代码的功能和逻辑,然后参考代码规范,给出具体的优化建议。” 通过这种方式,提示模板帮助 AI 理解用户的意图,选择合适的工具和资源,以更高效地完成任务,提升用户体验 。

工具,是 AI 可以调用的函数,用于执行特定操作,是 AI 实现复杂任务的得力助手。比如,当 AI 需要查询实时天气信息时,它可以调用天气预报工具;当需要发送邮件时,邮件发送工具就能派上用场。使用工具时,通常需要用户批准,以确保操作的安全性。工具的存在,让 AI 不再局限于文本生成,而是能够与外部世界进行交互,实现从想法到行动的跨越,极大地扩展了 AI 的应用场景 。

这些核心概念相互协作,资源提供数据,提示模板引导方向,工具执行操作,它们共同构成了 MCP 协议的核心,为 AI 与外部系统的交互提供了强大的支持,让 AI 能够在各种复杂的场景中发挥出最大的效能。

(二)开发工具与环境搭建攻略

“工欲善其事,必先利其器”,搭建合适的开发工具与环境是 MCP 开发的关键第一步。在 MCP 开发中,Python 的 uv 和 Node.js 是常用的工具,它们各自有着独特的优势,为开发者提供了便利。

uv 是一个由 Astral 开发的高性能 Python 包管理工具,基于 Rust 编写,旨在替代传统的 pip 和 pip - tools。它就像是 Python 开发的超级加速器,具有极快的速度,比 pip 快 10 - 100 倍,能大大缩短依赖安装的时间,让开发过程更加流畅。uv 的轻量级设计也十分出色,仅几十 MB,不会占用过多的系统资源。它集成了虚拟环境管理、Python 版本控制、依赖解析等功能,让开发者可以一站式完成多项任务,无需在多个工具之间切换。例如,使用 uv 创建虚拟环境只需简单的命令 “uv venv”,安装依赖包时,速度更是令人惊叹,“uv pip install requests” 就能快速完成安装,为开发者节省了大量的时间和精力 。

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,它让 JavaScript 能够在服务器端运行,为 MCP 开发带来了更多的可能性。Node.js 拥有丰富的 npm 包生态系统,开发者可以轻松地找到各种工具和库,快速搭建项目。在 MCP 开发中,我们可以利用 Node.js 来开发 MCP 服务器,通过 npm 安装相关的 SDK,如 “npm install @modelcontextprotocol/server - sdk”,就能快速开始服务器的开发工作 。

安装相关的 SDK 是 MCP 开发的重要环节。以 Python 为例,我们可以使用 uv 来安装 MCP 的 SDK。首先,确保已经安装了 uv,如果没有安装,可以通过 pip 进行安装:“pip install uv”。安装完成后,使用 “uv add mcp [cli]” 命令,就能轻松安装 MCP 的 Python SDK,为后续的开发工作做好准备。在安装过程中,可能会遇到一些依赖问题,这时可以参考官方文档或社区论坛,寻找解决方案,确保安装顺利进行 。

在搭建开发环境时,还需要注意一些细节。比如,确保 Python 和 Node.js 的版本符合要求,避免因版本不兼容导致的问题。同时,合理配置环境变量,让工具能够顺利找到所需的文件和库。开发工具与环境的搭建就像是为一场精彩的演出搭建舞台,只有舞台搭建得稳固、完善,才能让开发者在 MCP 开发的舞台上尽情发挥,创造出令人惊叹的 AI 应用。

MCP 开发全流程

(一)创建你的第一个 MCP 项目

现在,我们已经对 MCP 的核心概念和开发工具与环境有了深入的了解,接下来就进入激动人心的实战环节,创建我们的第一个 MCP 项目 。

以 Python 为例,我们可以使用官方脚手架快速创建项目。首先,确保已经安装了 uv 和 MCP 的 Python SDK。打开命令行工具,输入以下命令:

uv init mcp - server - demo

cd mcp - server - demo

uv add "mcp\[cli]"

上述命令首先使用 uv 创建了一个名为mcp - server - demo的项目目录,然后进入该目录,并安装了 MCP 的 CLI 依赖。

创建完成后,项目的初始结构如下:

mcp - server - demo

├── pyproject.toml

├── src

│   └── main.py

└── tests

   └── test\_main.py

pyproject.toml是项目的配置文件,用于管理项目的依赖和元数据 。src目录是项目的源代码目录,main.py是项目的入口文件,我们将在这个文件中编写 MCP 服务器的核心代码 。tests目录用于存放测试代码,test_main.py是对main.py中代码的测试文件,通过编写测试代码,可以确保我们的 MCP 服务器功能的正确性和稳定性 。

如果使用 TypeScript,我们可以使用@modelcontextprotocol/create - server脚手架来创建项目。在命令行中输入:

npx @modelcontextprotocol/create - server my - server

cd my - server

这将创建一个名为my - server的项目,并进入该项目目录。项目的初始结构如下:

my - server

├── README.md

├── package.json

├── src

│   └── index.ts

├── tsconfig.json

└── yarn.lock

README.md是项目的说明文档,用于介绍项目的功能、使用方法和注意事项等 。package.json是 Node.js 项目的配置文件,管理项目的依赖和脚本等信息 。src目录存放 TypeScript 源代码,index.ts是项目的入口文件 。tsconfig.json是 TypeScript 的配置文件,用于指定编译选项 。yarn.lock用于锁定项目依赖的版本,确保在不同环境中安装的依赖版本一致 。

无论是使用 Python 还是 TypeScript,创建项目只是第一步,接下来我们将在项目中开发 MCP 服务器的核心功能 。

(二)核心功能开发秘籍

创建好项目后,就可以开始开发 MCP 服务器的核心功能了,包括工具注册与实现、资源与提示模板设置,这些功能是 MCP 服务器的核心,决定了服务器能够为 AI 模型提供哪些能力 。

工具注册与实现:工具是 MCP 服务器的重要组成部分,它允许 AI 模型执行特定的操作。在 Python 中,我们可以使用装饰器来注册工具。以一个简单的加法工具为例,在main.py中编写如下代码:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("MyServer")

@mcp.tool()

def add\_numbers(a: int, b: int) -> int:

   """

   这个工具用于将两个数字相加。

   :param a: 第一个数字

   :param b: 第二个数字

   :return: 两个数字相加的结果

   """

   return a + b

if \_\_name\_\_ == "\_\_main\_\_":

   mcp.run(transport='stdio')

在上述代码中,首先创建了一个FastMCP实例,命名为mcp,并指定服务器名称为MyServer 。然后,使用@mcp.tool()装饰器将add_numbers函数注册为 MCP 工具 。add_numbers函数接受两个整数参数ab,返回它们的和 。函数的文档字符串清晰地描述了工具的功能和参数,这对于 AI 模型理解工具的用途非常重要 。最后,在if __name__ == "__main__":代码块中,调用mcp.run(transport='stdio')启动服务器,并使用标准输入输出(stdio)进行通信 。

在 TypeScript 中,使用 SDK 注册工具的方式如下:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { z } from "zod";

// 创建一个MCP服务器实例

const server = new McpServer({

   name: "MyServer",

   version: "1.0.0"

});

// 注册一个加法工具

server.tool("add\_numbers", "将两个数字相加", {

       a: z.number().describe("第一个数字"),

       b: z.number().describe("第二个数字")

   },

   async ({ a, b }) => {

       return {

           content: \[{ type: "text", text: String(a + b) }]

       };

   }

);

// 连接到传输层,这里可以使用不同的传输方式,如stdio或SSE

const transport = new StdioServerTransport();

server.connect(transport).then(() => {

   console.log("Server started");

}).catch((err) => {

   console.error("Error starting server:", err);

});

这段代码首先从@modelcontextprotocol/sdk/server/mcp.js中导入McpServer类,从zod库中导入z用于定义输入参数的模式 。然后创建一个McpServer实例,设置服务器名称和版本 。使用server.tool方法注册一个名为add_numbers的工具,工具的描述为 “将两个数字相加”,输入参数模式使用zod定义,确保输入参数的类型和格式正确 。工具的处理函数接受包含ab的参数对象,返回一个包含相加结果的响应 。最后,创建一个StdioServerTransport实例,用于与客户端进行通信,并将服务器连接到该传输层 。

资源与提示模板设置:资源是 AI 模型可以访问的数据,提示模板是引导 AI 模型使用资源和工具的预写消息。在 Python 中,定义资源和提示模板的示例如下:

@mcp.resource("example\_resource")

def get\_example\_resource() -> str:

   """

   获取示例资源,这里返回一个固定的字符串。

   :return: 示例资源字符串

   """

   return "This is an example resource"

mcp.prompt\_template("example\_prompt", "请根据以下资源回答问题:{resource}", {

   "resource": get\_example\_resource

})

上述代码中,使用@mcp.resource装饰器将get_example_resource函数注册为资源,该函数返回一个固定的字符串作为示例资源 。然后,使用mcp.prompt_template方法定义一个提示模板,名称为example_prompt,模板内容为 “请根据以下资源回答问题:{resource}”,其中{resource}是一个占位符,会被实际的资源内容替换 。模板的参数中,resource指向get_example_resource函数,这样在使用提示模板时,会自动获取该资源的内容 。

在 TypeScript 中,定义资源和提示模板的代码如下:

server.resource("example\_resource", async () => {

   return {

       content: \[{ type: "text", text: "This is an example resource" }]

   };

});

server.promptTemplate("example\_prompt", "请根据以下资源回答问题:{resource}", {

   resource: {

       name: "example\_resource",

       description: "示例资源"

   }

});

这段 TypeScript 代码中,使用server.resource方法定义一个名为example_resource的资源,资源的处理函数返回一个包含文本内容的响应 。然后,使用server.promptTemplate方法定义一个提示模板,模板名称为example_prompt,内容与 Python 示例类似,参数中指定resource为前面定义的example_resource,并提供了资源的描述 。通过这样的设置,AI 模型可以根据提示模板和资源进行交互,获取更准确和有用的信息 。

(三)传输方式选择与实现

在 MCP 开发中,传输方式的选择至关重要,它决定了 MCP 客户端与服务器之间的通信方式。MCP 协议目前定义了多种传输机制,其中 stdio 传输和 SSE 传输是较为常用的两种方式,它们各有特点,适用于不同的场景 。

stdio 传输即标准输入 / 输出,主要用于本地进程通信 。在这种传输方式中,客户端以子进程的形式启动 MCP 服务器,服务器从其标准输入(stdin)读取 JSON - RPC 消息,并将消息发送到其标准输出(stdout) 。消息由换行符分隔,且不得包含嵌套的换行符 。服务器可以将其 UTF - 8 字符串写入标准错误(stderr)以进行日志记录,客户端可以捕获、转发或忽略此日志 。stdio 传输的主要优势在于无外部依赖,实现简单,无网络传输,通信速度快,本地通信,安全性高 。然而,它也存在一些局限性,例如单进程通信,无法并行处理多个客户端请求,进程通信的资源开销大,很难在本地运行非常多的服务 。stdio 传输适用于要操作的数据资源位于本地计算机,且不希望暴露外部访问的场景 。

以 Python 为例,使用 MCP 的 Python SDK 实现 stdio 传输的代码如下:

import asyncio

from mcp.server import create\_server, stdio\_server

async def main():

   async with stdio\_server() as (read\_stream, write\_stream):

       server = await create\_server()

       await server.run(read\_stream, write\_stream)

if \_\_name\_\_ == "\_\_main\_\_":

   asyncio.run(main())

在这段代码中,首先导入asyncio库用于异步编程,以及create_serverstdio_server函数 。在main函数中,使用async with语句创建一个 stdio 服务器,stdio_server函数返回两个流,read_stream用于从标准输入读取消息,write_stream用于向标准输出写入消息 。然后创建 MCP 服务器实例server,并使用await server.run(read_stream, write_stream)方法运行服务器,使其通过标准输入输出与客户端进行通信 。最后,在if __name__ == "__main__":代码块中,使用asyncio.run(main())运行异步函数main

SSE 传输即 Server - Sent Events,是一种基于 HTTP 的通信机制,主要用于实现服务器到客户端的流式传输 。在 SSE 传输中,客户端通过 HTTP GET 请求建立与服务器的连接,服务器以流式方式持续向客户端发送数据,客户端通过解析流数据来获取实时信息 。SSE 传输适用于客户端需要接收服务器推送的场景,通常用于实时数据更新,如实时聊天、天气预报、新闻更新等 。

以下是使用 Python 的uvicornstarlette库实现 SSE 传输的示例代码:

from mcp.server.sse import SseServerTransport

from starlette.applications import Starlette

from starlette.routing import Route, Mount

from starlette.responses import StreamingResponse

import uvicorn

async def handle\_sse(request):

   async with SseServerTransport("/messages/").connect\_sse(request.scope, request.receive, request.send) as streams:

       \# 这里可以处理服务器逻辑

       await streams\[1].send({"type": "text", "text": "Hello, SSE!"})

app = Starlette(routes=\[

   Route("/sse", endpoint=handle\_sse),

   Mount("/messages/", app=SseServerTransport("/messages/").handle\_post\_message)

])

if \_\_name\_\_ == "\_\_main\_\_":

   uvicorn.run(app, host="0.0.0.0", port=8000)

在这段代码中,首先从mcp.server.sse导入SseServerTransport类,从starlette.applications导入Starlette类,从starlette.routing导入RouteMount用于定义路由,从starlette.responses导入StreamingResponse用于流式响应 。然后定义一个handle_sse异步函数,在函数中使用SseServerTransportconnect_sse方法建立 SSE 连接,通过streams对象可以进行数据的发送和接收 。这里简单地发送了一条 “Hello, SSE!” 的消息 。接着创建一个Starlette应用实例app,定义了两个路由,/sse路由用于处理 SSE 连接,/messages/路由用于处理客户端发送的 POST 请求 。最后,在if __name__ == "__main__":代码块中,使用uvicorn.run运行应用,指定主机为0.0.0.0,端口为8000

在实际开发中,我们需要根据具体的需求和场景来选择合适的传输方式,以确保 MCP 客户端与服务器之间的通信高效、稳定 。

调试与优化

(一)使用 MCP Inspector 进行高效调试

在 MCP 开发过程中,调试是确保服务器功能正常的关键环节。MCP Inspector 作为一款专门为 MCP 服务器设计的交互式调试工具,为开发者提供了便捷的调试方式,大大提高了调试效率 。

MCP Inspector 的安装非常简单,无需安装即可通过 npx 直接运行。在命令行中输入以下命令:

npx @modelcontextprotocol/inspector \<command>

例如,要运行一个 Python 的 MCP 服务器并进行调试,可以使用以下命令:

npx @modelcontextprotocol/inspector uvx mcp - server - mypackage --arg1 value1 --arg2 value2

其中,uvx是使用的包管理器,mcp - server - mypackage是 MCP 服务器的包名,--arg1 value1 --arg2 value2是传递给服务器的参数 。

对于本地开发或下载仓库的服务器,常见的启动方式如下:

在 TypeScript 中,可以使用以下命令:

npx @modelcontextprotocol/inspector node path/to/server/index.js args...

在 Python 中,可以使用以下命令:

npx @modelcontextprotocol/inspector \\

uv \\

\--directory path/to/server \\

run \\

package - name \\

args...

MCP Inspector 的功能十分全面,涵盖了与 MCP 服务器交互的多个方面 。在服务器连接面板,开发者可以选择服务器连接传输方式,无论是 stdio 传输还是 SSE 传输,都能在这里进行配置。同时,还支持为本地服务器自定义命令行参数和环境变量,方便根据不同的需求进行调试 。

资源标签页列出了所有可用资源,不仅显示资源元数据,如 MIME 类型、描述等,还支持资源内容检查,让开发者可以深入了解资源的细节。此外,还提供订阅测试功能,帮助开发者验证资源的订阅机制是否正常 。

提示词标签页展示了可用提示模板,清晰地显示提示参数和描述。开发者可以使用自定义参数测试提示,预览生成的消息内容,确保提示模板能够正确引导 AI 模型 。

工具标签页列出了所有可用工具,展示工具模式和描述。使用自定义输入测试工具时,工具执行结果会直观地展示出来,方便开发者判断工具的功能是否符合预期 。

通知面板呈现了服务器记录的所有日志,以及从服务器接收的通知,让开发者可以及时了解服务器的运行状态和异常信息 。

在实际使用中,假设我们开发了一个文件查询的 MCP 服务器。使用 MCP Inspector 的资源标签页,我们可以检查文件资源的元数据,确认文件的格式、大小等信息是否正确。在工具标签页,输入查询条件,测试文件查询工具是否能够准确地返回所需文件的内容。通过这些操作,能够快速发现并解决服务器开发过程中的问题,提高开发效率 。

(二)性能优化与问题解决策略

在 MCP 服务器开发完成后,性能优化和问题解决是确保服务器稳定运行的重要环节。通过采取一系列有效的策略,可以提升服务器的性能,解决常见的问题,为用户提供更好的服务 。

在性能优化方面,异步操作是提升服务器性能的关键策略之一。MCP 服务器通常会处理大量的并发请求,使用异步操作可以避免线程阻塞,提高服务器的响应速度。在 Python 中,可以使用asyncio库来实现异步编程。例如,在处理资源请求时,可以将文件读取等 I/O 操作设置为异步操作:

import asyncio

async def read\_file\_async(file\_path):

   with open(file\_path, 'r') as f:

       content = await asyncio.get\_running\_loop().run\_in\_executor(None, f.read)

   return content

在上述代码中,read_file_async函数使用asyncio.get_running_loop().run_in_executor将文件读取操作放到线程池中执行,实现了异步读取,避免了主线程的阻塞 。

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java