十分钟从零开始开发一个自己的MCP server(一)

1 MCP为什么这么受欢迎

自从chatGPT发布以后,大家探索和应用大语言模型(LLM)的热情不断高涨,不同行业、不同应用领域都在积极尝试使用大语言模型的能力解决各自的问题,取得了惊人的效果。当前,大语言模型最基础的使用方式,就是像chatGPT那样,我们向它提问,然后它给我们一个答案。这个答案,可能是提供一些信息知识,生成sql查询语句,生成一段代码,制定一个行动计划等等。然后我们使用模型提供的sql语句去查询数据库,将代码集成到自己的项目中编译执行。于是,自然而然地,我们就可以想到让大语言模型应用程序自己用大语言模型生成的sql语句去查询数据库,自动编译执行模型生成的的代码,这样就形成一个自动闭环,无需人的参与(或许有的场景需要人的监督和决策,然后人工触发自动执行,但也只需要下发一个执行的指令即可)。初始的时候,大家提供一个函数接口(function call),让大语言模型应用程序在满足条件时自动调用。但是,不同的功能、不同的厂商各自独立定义自己的函数接口,这样函数接口的形式五花八门,不便于应用程序的集成,非常不利于大模型应用生态的发展。anthropic公司为大模型应用生态各组件的交互设计并实践了一个MCPModel Contex Protocol)协议,规定了大模型应用组件间通信的通信机制、消息格式等,现在把它开源出来,获得了大家的一致认同。可以预见到,MCP协议可能会大大地促进大模型应用生态的发展和繁荣。这一现象在历史上屡见不鲜,如硬件领域的USB协议、PCIE协议,软件领域的TCP/IP协议等等。

2 MCP的架构和组件间交互机制

站在通信的角度,MCP将大模型生态各组件抽象为三个主要角色:MCP hostsMCP clientMCP server。MCP官网说明MCP架构时,除了上述三大角色,还列出了Local Data SourceRemote Service两个角色,但这两个角色主要是详细说明MCP server支持的核心功能,不影响对MCP协议本身的理解,本文不做过多说明。同时,MCP官网将大模型与MCP host当做一个整体,没有进行区分。但是,考虑到MCP host与大模型往往是不同的独立实体,host与大模型间需要通过通信进行交互,且一个host可以连接多个大模型,为了描述方便,我将大模型独立出来作为一个单独的角色进行描述。

MCP官网给出的MCP架构图:

我梳理了一下MCP主要角色的交互机制序列图如下:

主要角色说明:

  • Host:就是LLM应用程序,如AI agent、Claude Desktop、Cursor、Trae等。往往通过LLM、MCP server等提供的能力,向最终用户提供具体的产品和服务。Host往往可以使用一个或多个LLM。Host可以持有多个Client。
  • Client:MCP协议是一个client-server架构的协议。Client请求Server执行某个功能,并从Server获取结果。当前定义的MCP架构中,Client和Server是一一对应的。Client往往是Host内部的一个组件,Host通过Client实现对Server的连接和请求Server实现某项功能。
  • Server:相对于LLM来说,Server往往是一个传统的应用或服务,是LLM应用程序为了弥补和增强LLM的功能而采用的一个措施。Server可以进一步使用外部的资源和服务来完成Client的请求。他可以提供各种各种的功能,如处理文件、查询数据库、编译执行代码、检索搜索引擎、订机票等等
  • Local Data Sources/Remote Services:本地或远程的资源和服务,如文件、数据库、搜索引擎等。

MCP典型的工作过程如下:

  1. MCP Host配置或发现了一个MCP server后,则创建一个对应的MCP Client,来负责与这个Server的通信

  2. Client向Server发送一个Initialize Request,请求中会包含Client的基本信息和支持的能力

  3. Server向Client回复一个Initialize Response,告诉Client自己支持的功能描述和每个功能的参数描述。我们需要仔细描述Server的功能和参数,LLM就是根据Server提供的功能和参数描述来决定如何调用Server的功能并提供具体的参数值的。当前MCP定义了多个类型的功能,如Resource、Prompt、Tool、Root、Sampling,他们的具体功能可以参考MCP官网资料

  4. Client再向Server发送一个Initialized Notification,通知Server Initialize完成了。Initialize阶段定义了Client与Server的协商和握手过程。这个过程与TCP的三次握手是不是很像?其实很多通信协议都有一个类似的握手协商的过程。当前的实现中,有些Client可能并没有Initialized Notification这一步,Server可能也没有实现处理Initialized Notification消息。

  5. Host向LLM发起问答请求时,会把它知道的所有Server的功能和参数描述作为Prompt的一部分,连同问题一起发送给LLM。Host向LLM发起的Prompt模板可能长下面这样:

    python 复制代码
    system_message = (
             "You are a helpful assistant with access to these tools:\n\n"
             f"{tools_description}\n"
             "Choose the appropriate tool based on the user's question. "
             "If no tool is needed, reply directly.\n\n"
             "IMPORTANT: When you need to use a tool, you must ONLY respond with "
             "the exact JSON object format below, nothing else:\n"
             "{\n"
             '    "tool": "tool-name",\n'
             '    "arguments": {\n'
             '        "argument-name": "value"\n'
             "    }\n"
             "}\n\n"
             "After receiving a tool's response:\n"
             "1. Transform the raw data into a natural, conversational response\n"
             "2. Keep responses concise but informative\n"
             "3. Focus on the most relevant information\n"
             "4. Use appropriate context from the user's question\n"
             "5. Avoid simply repeating the raw data\n\n"
             "Please use only the tools that are explicitly defined above."
         )
         messages = [{"role": "system", "content": system_message}]

    上面的system_messagef"{tools_description}\n"部分会被渲染为Server在Initialize阶段提供的功能描述内容

  6. LLM响应问答时,决定是否需要调用某个Server工具。如果需要,则会根据调用工具的参数要求提供具体的参数值

  7. Host通知Client向Server发起具体的调用请求

  8. Client向Server真正发起调用请求

  9. Server在完成Client请求时,有可能会继续请求外部系统,如搜索引擎、RAG系统等

  10. Server向Client回复请求的处理结果

  11. Client将处理结果最终返回给Host

  12. Host可能会使用Client的返回结果继续请求LLM,循环上面的步骤直到最终解决用户的需求,或者让LLM整理一下Client的返回结果,转换为自然语言的表达方式返回给用户。

3 MCP协议的消息格式

MCP Client和Server之间的通信消息使用JSON-RPC 2.0编码,这样我们知道:1)MCP消息都是JSON格式的;2)当Client或Server需要向对方描述功能和参数时(例如Initialize阶段),使用JSON-RPC 2.0语法来描述参数的名称和类型。

MCP协议包括三者类型的消息:

  1. Request

    请求消息的格式如下:

    json 复制代码
    {
      jsonrpc: "2.0",
      id: number | string,
      method: string,
      params?: object
    }

    jsonrpc字段是每个MCP消息都有的一个公共字段,表示消息遵从的JSON-RPC版本。id字段用于标识一个Request,可以是一个数字或字符串。在Response中,这个id值会原封不动地透传回来,请求方通过id值确定是哪个Request的响应。method表示某项功能,例如:tools/list表示请求Server返回它支持的所有tools(功能),tools/call表示请求Server执行某个具体的动作。当前比较常用的method包括:initializeresources/listresources/templates/listresources/readprompts/listprompts/gettools/listtools/call。当然,我们也可以定义自己的method。params字段表示method需要的参数,根据method不同提供不同的具体值,所以它是一个json对象。params后面的?表示参数字段可以不提供,例如tools/list不需要提供参数

  2. Response

    响应的消息格式如下:

    json 复制代码
    {
      jsonrpc: "2.0",
      id: number | string,
      result?: object,
      error?: {
        code: number,
        message: string,
        data?: unknown
      }
    }

    id字段就是对应请求的id字段值。result字段表示响应的内容。如果出现错误,由error字段提供错误信息。MCP已经贴心的定义了error信息的具体格式。

  3. Notification

    通知用于Server主动向Client通知一个事件,当然也可以由Client向Server端发送。它的格式如下:

    json 复制代码
    {
      jsonrpc: "2.0",
      method: string,
      params?: object
    }

    可见Notification与Request的格式差不多,只是Notification由于不需要对方响应,所有没有id字段

4 MCP的消息传递方式

上面描述了MCP消息的格式,但是Client需要一种具体通信手段将消息传递给Server。目前MCP官方定义了两种具体的方法:

  1. stdio

    这时Client和Server在同一台服务器上,Client通过向Server提供的stdin(标准输入)写入消息,从而将消息传递给Server。同样地,Client通过从Server提供的stdout(标准输出)、stderr(标准错误)读取消息,从而获取Server的响应。

  2. http SSEServer Side Event

    这时Client和Server可以不在同一台服务器上。他们通过http post进行消息传递

  3. 其他

    其实其它的通信协议也都可以使用,如http、websocket,只要MCP生态中大家都支持,就可以方便的使用

5 MCP消息内容举例

上面描述了MCP消息的格式定义,可能还是有些抽象,下面举几个常用消息的例子,感受一下

5.1 initialize请求消息

下面是Claude Desktop MCP Client发送的Initialize请求消息例子

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "initialize",
	"params": {
		"protocolVersion": "2024-11-05",
		"clientInfo": {
			"name": "claude-ai",
			"version": "0.1.0"
		},
		"capabilities": {}
	}	
}

5.2 Initialize响应消息

Server在Initialize响应消息的result字段中提供了Server的基本信息和支持的能力

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": {
		"protocolVersion": "2024-11-05",
		"capabilities": {
			"resources": {
				"subscribe": true,
				"listChanged": true
			},
			"tools": {
				"listChanged": true
			}
		},
		"serverInfo": {
			"name": "mcp-fs-server",
			"version": "0.0.1"
		}
	}
}

5.3 initialize notification消息

下面的initialize notification消息也是Claude Desktop在Initialize阶段发送给Server端的

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "notifications/initialized"
}

5.4 tools/list请求消息

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 2,
	"method": "tools/list"
}

5.5 tools/list响应消息

这个消息表明这个Server提供了一个名字叫做write_file的tool功能,它需要提供两个参数:文件的路径和写入文件的内容,这两个参数的类型都是字符串。tool的功能和参数的含义都通过description字段进行了准确描述

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 2,
	"result": {
		"tools": [
			{
				"name": "write_file",
				"description": "Create a new file or overwrite an existing file with new content.",
				"inputSchema": {
					"type": "object",
					"properties": {
						"path": {
							"description": "Content to write to the file",
							"type": "string"
						},
						"content": {
							"description": "Content to write to the file",
							"type": "string"
						}
					},
					"required": [
						"path"
					]
				}
			}
		]
	}
}

5.6 tools/call请求消息

通过tools/list获取了Server提供的tools,LLM就可以根据tool的功能决定是否需要调用,根据参数定义提供具体的参数。下面是Claude模型提供的调用参数,并由Claude Desktop的Client发送给mcp-fs-server Server端的tools/call请求调用消息

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 3,
	"method": "tools/call",
	"params": {
		"name": "write_file",
		"arguments": {
			"path": "/var/mcp-fs-server/test.txt",
			"content": "hello mcp"
		}
	}
}

5.7 tools/call响应消息

mcp-fs-server Server处理请求后的响应消息

json 复制代码
{
	"jsonrpc": "2.0",
	"id": 3,
	"result": {
		"content": [
			{
				"type": "text",
				"text": "Successfully wrote 9 bytes to /var/mcp-fs-server/test.txt"
			}
		]
	}
}
相关推荐
Asthenia0412几秒前
JavaSE Stream 是否线程安全?并行流又是什么?
后端
半部论语11 分钟前
SpringMVC 中的DispatcherServlet生命周期是否受Spring IOC 容器管理
java·后端·spring
Asthenia041230 分钟前
JavaSE-常见排序:Arrays/Collections/List/StreamAPI
后端
Asthenia041238 分钟前
深入浅出分析JDK动态代理与CGLIB动态代理的区别
后端
追逐时光者1 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 32 期(2025年3.24-3.31)
后端·.net
uhakadotcom1 小时前
轻松掌握XXL-JOB:分布式任务调度的利器
后端·面试·github
小杨4041 小时前
springboot框架项目实践应用十三(springcloud alibaba整合sentinel)
spring boot·后端·spring cloud
程序员一诺2 小时前
【Python使用】嘿马python数据分析教程第1篇:Excel的使用,一. Excel的基本使用,二. 会员分析【附代码文档】
后端·python
神奇侠20242 小时前
快速入手-基于Django-rest-framework的serializers序列化器(二)
后端·python·django
Asthenia04122 小时前
基于Segment-Mybatis的:分布式系统中主键自增拦截器的逻辑分析与实现
后端