【MCP】小白详解从0开始构建MCP服务端全流程

简介

构建MCP服务端的目的是用于串通我的项目中各种结构和零件 MCP可以分离代码的llm的工作部分,大模型专注于它最擅长的"思考",而我们的代码专注于我们擅长的"执行"。这使得整个系统更加清晰和模块化。 为此,我将讲一下构建MCP的流程

MCP服务端的构建

然后对于mcp函数的内部 首先导入必要的包

python 复制代码
import os # 获取地址
import logging # 导出日志,可选
from mcp.server.fastmcp import FastMCP # 导入MCP服务器框架

初始化服务器

ini 复制代码
mcp = FastMCP("file-reader")

定义一个工具,这个工具就是llm会调用的工具列表,是整个服务器最重要的部分 @mcp.tool() 是一个"装饰器" 它告诉 FastMCP 框架:下面这个普通的 Python 函数是一个工具 请把它注册并暴露给外部。、 这里的函数内部可以是任何想拥有的内容,这里只展示读取其他文件的效果

python 复制代码
@mcp.tool()
async def read_file(filename: str) -> str:
"""
    简要分析

    Args:(示例)
        filename:参数含义
        username: 参数含义。
    
    Returns:
        一个包含分析结果的JSON字符串。(返回示例。自行修改)
    """

后面必须跟一段函数解释 这一段的解释是为了之后返回给大语言模型的描述 这是对这个函数功能的自然语言描述。这是整个结构中对大模型来说最重要的一部分! 大模型就是通过阅读这段描述来理解"这个工具是干什么用的?"、"我应该在什么时候用它?"。一个清晰、准确的描述,是让大模型能正确使用工具的关键 可以在下列自行添加分析后的效果: 例如 最后写一段MCP持续被监听的函数,这时候MCP就会通过 通过标准输入/输出(stdio)来监听来自客户端的连接和指令。

python 复制代码
if __name__ == "__main__":
    logging.info("--- MCP 文件读取服务器启动 ---")
    mcp.run(transport='stdio')

主代码群的初始化MCP

首先需要启动mcp服务,这里是构建两个关键的参数,一个是session,一个是tool 这里由于是直接启动以py文件构建的服务端,所以我们首先要获取该文件的相对路径,然后配置python解释器

python 复制代码
mcp_server_path = os.path.join(current_dir, "CausalChatMCP", "mcp_server.py")
server_params = StdioServerParameters(command=sys.executable, args=[mcp_server_path])

获取完之后,我们就需要我们就要运行该程序,并建立起主程序和子程序之间的通信 stdio_client(server_params) 启动 mcp_server.py 子进程, 由于mcp需要输入输出流,所以在这里定义两个参数 注意,这里的enter_async_context指的是使用异步上下文管理器管理子进程生命周期 当退出上下文时自动终止子进程,这里的mcp_process_stack一般是异步上下栈的实例

python 复制代码
read_stream, write_stream = await mcp_process_stack.enter_async_context(stdio_client(server_params))

建立完通信之后,并没有载体进行互连,所以建立一个session进行手法MCP的消息

python 复制代码
session = await exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
await session.initialize()

建立完通信之后,就需要获取我们在MCP中定义的工具,工具有哪些呢 获取完工具之后,我们需要转换为LLm所需要的格式信息 其中的MCPtool是严格定义的

  1. "type": "function": 含义: 这是在告诉大模型,我提供给你的这个工具是一个函数(function),你可以像调用函数一样调用它。 是否可变: 不可变。这是API定义好的类型。
  2. "function": 含义: 这是一个JSON对象,用来包裹这个函数的所有具体信息。 是否可变: 不可变。这是固定的结构。
  3. "name": tool.name: 含义: 这是函数的名字。这个名字非常重要,它就是这个工具的唯一ID。当大模型决定要调用这个工具时,它就会通过这个名字来告诉我们。 tool.name 的值来自于哪里?来自于您在 py 中定义的函数名
  4. "description": tool.description: 含义: 这是对这个函数功能的自然语言描述。这是整个结构中对大模型来说最重要的一部分! 大模型就是通过阅读这段描述来理解"这个工具是干什么用的?"、"我应该在什么时候用它?"。一个清晰、准确的描述,是让大模型能正确使用工具的关键。 tool.description 的值来自于哪里?来自于在 py 中为函数编写的文档字符串。
  5. "parameters": tool.inputSchema: 含义: 这里定义了函数需要哪些参数,以及这些参数的类型和格式。它使用的是一种叫做"JSON Schema"的规范。这等于告诉大模型:"如果你要调用read_file,你必须提供一个名为filename的参数,并且它必须是一个字符串。" tool.inputSchema 的值来自于哪里?来自于 mcp 框架对 参数类型注解的自动解析,例如 filename: str。

那么对于mcp自动回应的参数就是如下格式

python 复制代码
{
  "type": "function",
  "function": {
    "name": "xx",
    "description": "xx",
    "inputScheme": {
        "username": {
          "type": "string"
        },
        "filename": {
          "type": "string"
        }
      },
      "required": ["username"]
    }
}

我们获取的mcp_tool结构就如下:

python 复制代码
tools_response = await session.list_tools()
        mcp_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }
        } for tool in tools_response.tools]

该部分的完整代码如下

python 复制代码
 current_dir = os.path.dirname(os.path.abspath(__file__))
        mcp_server_path = os.path.join(current_dir, "CausalChatMCP", "mcp_server.py")
        
        server_params = StdioServerParameters(command=sys.executable, args=[mcp_server_path])
        
        read_stream, write_stream = await mcp_process_stack.enter_async_context(stdio_client(server_params))
        session = await mcp_process_stack.enter_async_context(ClientSession(read_stream, write_stream))
        await session.initialize()
        
        tools_response = await session.list_tools()
        mcp_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }
        } for tool in tools_response.tools]

        mcp_session = session
        logging.info(f"MCP服务器连接成功,会话已激活。发现工具: {[tool['function']['name'] for tool in mcp_tools]}")
        

主代码群的调用MCP

当我们在主程序初始化好了MCP所需要的输入输出流,通信工具之后,我们就需要让LLm调用工具了 那么在openai的标准化定义之中,我们将MCPtool传入response之中,又LLM来决定是否调用MCP

python 复制代码
response = client.chat.completions.create(
        model=current_model,
        messages=messages,
        tools=mcp_tools or None,
        tool_choice="auto",
    )
response_message = response.choices[0].message
tool_calls = response_message.tool_calls

这里的tool_calls是市面上的大模型所定义的,所以可以返回我调用了什么工具,或者没有调用 这里返回的结果需要注意 这里的response_message并没有返回一个包含工具的结果,他只返回了一次调用工具的指令 比如

  1. LLM 理解了用户的意图。
  2. 它查看了工具清单,发现 read_file 工具正好能满足这个意图。
  3. 它决定:"好的,我需要调用 read_file,然后就返回给用户了

所以获取到指令之后,需要在主程序中调用他,首先获取工具所调用的名称和参数 首先获取函数名和函数参数,返回给MCP的name和args 用function_response_obj获取返回给MCP返回的响应列表

python 复制代码
 {
      "status": "success",
      "content": [  // 内容可以是一个列表,允许多种类型的返回
        {
          "type": "text",
          "text": "。。。"
        }
      ],
      "metadata": { "execution_time": "0.01s" }
    }

通过function_response_tex来获取我们要的东西

python 复制代码
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
function_response_obj = await mcp_session.call_tool(function_name, function_args)
function_response_text = function_response_obj.content[0].text

调用完我们所需要的东西之后,我们就可以第二次输入给llm总结,加入到历史消息之中

python 复制代码
messages.append({
            "tool_call_id": tool_call.id, # 这里id需要对应第一次调用的id
            "role": "tool",
            "name": function_name,
            "content": function_response_text,
        })
second_response = client.chat.completions.create(
     model=current_model,
     messages=messages,
 )
 summary_text = second_response.choices[0].message.content

这里输出的就是llm所调用的东西了,也就是整个流程需要调用两次。

相关推荐
围巾哥萧尘11 小时前
[「掌握Trae IDE」 Trae+ GodotMCP + Godot,20 分钟用 AI 构建打砖块游戏🧣
mcp
掘金一周12 小时前
Figma Dev Mode MCP:大人,时代变了 | 掘金一周7.10
前端·人工智能·mcp
人生偌只如初见16 小时前
SpringAI学习笔记-MCP客户端简单示例
java·spring·ai·client·mcp
带刺的坐椅16 小时前
Java MCP 鉴权设计与实现指南
java·安全·ai·solon·mcp
开国元帅17 小时前
基于Spring AI的动态注册Tool:构建可扩展的多协议AI工具平台
mcp
rocksun17 小时前
使用MCP Toolbox for Databases访问数据库
数据库·人工智能·mcp
cooldream20091 天前
理解大模型智能体生态:从 Prompt 到 Agent 的完整信息流解析
prompt·ai agent·mcp·fuction calling
围巾哥萧尘1 天前
「掌握Trae IDE」 Git智能体大佬,Trae+ Github + Git,轻松完成代码仓库管理🧣
mcp
xunberg1 天前
AI Agent 实战:将 Node-RED 创建的 MCP 设备服务接入 Dify
人工智能·mcp