MCP聊天系统

一个集成了模型上下文协议(MCP)与OpenAI函数调用API的聊天系统,提供HTTP传输层实现和动态工具执行功能。

  • by lfbos
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 lfbos/fastmcp-http-example

使用说明

MCP聊天系统

一个将模型上下文协议(MCP)与OpenAI的函数调用API集成的实用示例,包含一个自定义的HTTP服务器实现。

为什么是这个项目? FastMCP主要使用STDIO传输方式,这对于基于HTTP的客户端来说并不理想。本项目展示了如何为MCP构建一个完整的HTTP传输层,以实现RESTful API访问,并更好地与Web应用程序集成。

📋 目录

🏗️ 建筑学

这个项目展示了如何构建一个完整的基于MCP(可能是指某种通信协议或平台,如“消息通信协议”等,具体需根据上下文确定)的聊天系统,包含以下组件:

  • **server.py**用于销售分析的基础MCP服务器,定义工具、资源和提示
  • **http_server.py**基于FastAPI的HTTP服务器,通过REST API暴露MCP功能
  • **http_client.py**用于与MCP服务器通信的HTTP客户端
  • **chat.py**通过HTTP集成OpenAI与MCP的交互式REPL聊天客户端

✨ 主要特点

  • ✅ 适当的工具调用处理为MCP正确格式化OpenAI函数调用
  • ✅ HTTP传输为MCP定制的HTTP实现(FastMCP默认使用STDIO)
  • ✅ 动态工具执行大型语言模型(LLM)根据用户查询自动决定何时使用工具
  • ✅ 丰富的终端用户界面美观的格式化,包含颜色、Markdown 和加载动画
  • ✅ 资源管理静态资源和模板资源
  • ✅ 可选提示模板适用于多客户端场景的可重用模板(单个聊天机器人无需此功能)
  • ✅ 错误处理全面的错误处理和信息丰富的提示消息

🚀 快速入门

先决条件

  • Python 3.12+
  • 紫外线 包管理器
  • OpenAI API密钥

安装

  1. 克隆仓库:
git clone <repository-url>
cd mcptests
  1. 安装依赖项:
uv sync
  1. 创建一个 .env 项目根目录下的文件:
# .env
OPENAI_API_KEY=sk-your-api-key-here
OPENAI_MODEL=gpt-4o-mini
MCP_BASE_URL=http://127.0.0.1:8001/mcp
DOCS_DB=biz.sqlite

# Optional server configuration
MCP_SERVER_NAME=biz-server
DEFAULT_TOP_N=5

注永远不要把你的(事情/秘密/情感等)交给(某人) .env 文件已提交到版本控制系统。其中包含敏感的API密钥。

运行系统

1号航站楼 - 启动MCP服务器:

chmod +x start_server.sh
./start_server.sh

或者手动地:

uv run python http_server.py

2号航站楼 - 启动聊天客户端:

chmod +x start_chat.sh
./start_chat.sh

或者手动地:

uv run python chat.py

📚 使用方法

可用命令

  • /tools - 列出可用工具
  • /resources - 列出可用资源
  • /read <uri> - 读取特定资源
  • /prompt <name> key=value ... - 准备一个提示模板
  • /exit 或者 /quit - 退出聊天

示例会话

you> /tools
- find_products: Return products whose name or id contains the query...
- sales_between: Aggregate sales between [date_start, date_end]...
- top_products: Top-N products by sales amount...

you> Find products containing "laptop"
assistant> [Uses find_products tool and displays results]

you> /prompt summarize_sales date_start=2024-01-01 date_end=2024-01-31
✅ Prompt 'summarize_sales' prepared for the next message.

you> Analyze the sales
assistant> [Generates analysis using the prepared prompt]

🔌 HTTP API 端点

HTTP服务器公开了以下端点:

  • GET /mcp/tools - 列出所有可用的工具
  • GET /mcp/prompts - 列出所有可用的提示
  • GET /mcp/resources - 列出所有可用资源
  • POST /mcp/tools/call - 执行一个工具
    {"name": "tool_name", "arguments": {...}}
    
  • POST /mcp/prompts/get - 获取一个带有参数的提示
    {"name": "prompt_name", "arguments": {...}}
    
  • GET /mcp/resources/read/{uri} - 阅读资源
  • GET /health - 健康检查

🛠️ 技术细节

它是如何工作的

┌─────────────────────────────────────────────────────────────────────────┐
│                          INTERACTION FLOW                               │
└─────────────────────────────────────────────────────────────────────────┘

┌──────────┐
│  USER    │  "What products do we have?"
└────┬─────┘
     │
     ▼
┌────────────────────────────────────────────────────────────────┐
│  CHAT CLIENT (chat.py)                                         │
│                                                                │
│  1️⃣  On Startup:                                               │
│     • Connect to MCP server (http_client.py)                   │
│     • Fetch available tools → self.cached_tools                │
│     • Convert to OpenAI format → self.oai_tools_spec           │
│                                                                │
│  2️⃣  On User Message:                                          │
│     messages = [                                               │
│         {"role": "system", "content": "You are..."},           │
│         {"role": "user", "content": "What products..."}        │
│     ]                                                          │
│                                                                │
│     ⚡ TOOLS INJECTION HAPPENS HERE:                            │
│     ┌──────────────────────────────────────────────────┐       │
│     │ openai.chat.completions.create(                  │       │
│     │     messages=messages,                           │       │
│     │     tools=self.oai_tools_spec  ← 🔧 MCP TOOLS!   │       │
│     │ )                                                │       │
│     └──────────────────────────────────────────────────┘       │
└────────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
                    ┌─────────────────┐
                    │  OPENAI API     │
                    │  (GPT-4o-mini)  │
                    │                 │
                    │  Analyzes:      │
                    │  • User message │
                    │  • Available    │
                    │    tools 🔧     │
                    │                 │
                    │  Decides:       │
                    │  "I need to use │
                    │   find_products"│
                    └────────┬────────┘
                             │
                             │ Returns tool_calls
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  CHAT CLIENT (chat.py)                                          │
│                                                                 │
│  3️⃣  Receives tool_calls from OpenAI:                           │
│     {                                                           │
│       "tool_calls": [{                                          │
│         "function": {                                           │
│           "name": "find_products",                              │
│           "arguments": '{"query": ""}'                          │
│         }                                                       │
│       }]                                                        │
│     }                                                           │
│                                                                 │
│  4️⃣  Execute tools via HTTP:                                    │
│     result = await mcp.call_tool("find_products", {"query":""}) │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         │ POST /mcp/tools/call
                         ▼
┌────────────────────────────────────────────────────────────────┐
│  HTTP SERVER (http_server.py)                                  │
│                                                                │
│  Receives: {"name": "find_products", "arguments": {...}}       │
│                                                                │
│  Calls: tool.fn(**args)  ────────────────┐                     │
└──────────────────────────────────────────┼─────────────────────┘
                                           │
                                           ▼
                         ┌─────────────────────────────┐
                         │  MCP SERVER (server.py)     │
                         │                             │
                         │  @mcp.tool                  │
                         │  def find_products(...):    │
                         │      # Execute SQL          │
                         │      # Return products      │
                         └──────────────┬──────────────┘
                                        │
                   Returns: [{"id": "p-100", ...}, ...]
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────┐
│  CHAT CLIENT (chat.py)                                          │
│                                                                 │
│  5️⃣  Receives tool results                                      │
│                                                                 │
│  6️⃣  Sends back to OpenAI:                                      │
│     messages = [                                                │
│         {"role": "system", ...},                                │
│         {"role": "user", "content": "What products..."},        │
│         {"role": "assistant", "tool_calls": [...]},             │
│         {"role": "tool", "content": "[{products...}]"} ← 📊     │
│     ]                                                           │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
                    ┌─────────────────┐
                    │  OPENAI API     │
                    │  (GPT-4o-mini)  │
                    │                 │
                    │  Synthesizes:   │
                    │  "Here are the  │
                    │   products: ..."│
                    └────────┬────────┘
                             │
                             │ Final response
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  CHAT CLIENT (chat.py)                                          │
│                                                                 │
│  7️⃣  Displays formatted response with Rich                      │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
                    ┌──────────┐
                    │  USER    │  Sees beautiful formatted response
                    └──────────┘

要点

  1. 工具在启动时一次性注入 - 从MCP服务器获取并缓存
  2. 每条信息都附带工具 作为OpenAI的可用选项
  3. OpenAI决定 根据用户查询调用哪些工具
  4. 聊天执行 通过HTTP将工具传输到MCP服务器
  5. 结果反馈 通过相同的路径生成最终响应

理解工具与提示的区别

🔧(扳手或修理工具的符号,无直接对应中文含义,常用于表示维修、工具等相关概念) 工具 (核心功能)

  • 它们是什么大型语言模型(LLM)可以自动调用的功能
  • 何时使用动态数据和操作时始终需要
  • 谁来决定大型语言模型(GPT)根据用户查询决定何时调用它们
  • 示例流程:
    User: "Show me the products"
    → LLM decides to call find_products()
    → Tool executes and returns data
    → LLM synthesizes natural response
    

💬(表示对话或评论的符号,无具体含义,可理解为“对话”或“评论”) 提示 (可选模板)

  • 它们是什么预配置的消息模板,附带具体说明
  • 何时使用:
    • 多个应用程序消费同一个MCP服务器(Web应用、Slack机器人、API)
    • ✅ 统一不同团队/工具的响应标准
    • ✅ 没有自有大型语言模型(LLM)的客户(使用MCP提示作为指令)
    • ❌(表示错误或否定) 不需要 对于一个使用GPT的单一聊天机器人(如这个示例)
  • 如何使用手动激活,使用 /prompt 命令
  • 示例用例:
    # Useful for internal tools with multiple consumers:
    Web Dashboard ─┐
    Slack Bot ─────┼─→ MCP Server (consistent prompts)
    Mobile App ────┘
    

💡(这个符号本身在中文中没有直接对应的翻译,它通常表示“灯泡”或“灵感”的意思,常用于表示有想法或创意的时刻。) 构建一个类似ChatGPT的助手

如果你正在构建一个对话式助手(比如这个项目),那么你 只需工具:

  • ✅ 工具提供数据和操作
  • ✅ GPT处理对话并决定何时使用工具
  • ❌ 提示词是可选的(主要用于演示/多客户端场景)

本项目中的提示起着……的作用 例子 这些是MCP(可能指某软件或系统)的功能特性,但聊天功能并不依赖于它们。

代码级别的实现

工具注入的位置

步骤1:启动 - 从MCP服务器获取工具

# chat.py - ChatHost.start()
async def start(self):
    self.mcp = MCPHttpClient(MCP_BASE_URL)
    
    # Fetch tools from MCP server via HTTP
    self.cached_tools = await self.mcp.list_tools()
    # Returns: [{"name": "find_products", "description": "...", "inputSchema": {...}}, ...]
    
    # Convert to OpenAI function calling format
    self.oai_tools_spec = as_openai_tools(self.cached_tools)
    # Converts to: [{"type": "function", "function": {"name": "...", "parameters": {...}}}, ...]

步骤2:每条消息 - 将工具发送到OpenAI

# chat.py - ChatHost.chat_round()
async def chat_round(self, user_text: str):
    messages = [
        {"role": "system", "content": "You are..."},
        {"role": "user", "content": user_text}
    ]
    
    # 🔧 TOOLS INJECTED HERE - sent to OpenAI with every message
    first = self.oai.chat.completions.create(
        model=OPENAI_MODEL,
        messages=messages,
        tools=self.oai_tools_spec,      # ← MCP tools in OpenAI format
        tool_choice="auto"               # ← Let OpenAI decide when to use them
    )
    
    # OpenAI returns either:
    # - Regular response, OR
    # - Response with tool_calls

步骤3:在OpenAI请求时执行工具

# chat.py - ChatHost.chat_round()
if first_msg.tool_calls:
    for tc in first_msg.tool_calls:
        fn = tc.function.name        # e.g., "find_products"
        args = json.loads(tc.function.arguments)  # e.g., {"query": ""}
        
        # Execute tool on MCP server via HTTP
        result = await self.mcp.call_tool(fn, args)
        # ↓
        # POST http://127.0.0.1:8001/mcp/tools/call
        # {"name": "find_products", "arguments": {"query": ""}}

正确的工具调用处理

该系统通过包含(相关机制)来妥善处理OpenAI的功能调用 tool_calls 在助手消息中:

# Build assistant message with tool_calls (REQUIRED by OpenAI)
asst_msg = {"role": "assistant"}
if first_msg.content:
    asst_msg["content"] = first_msg.content

if first_msg.tool_calls:
    # Include tool_calls in assistant message (required by OpenAI API)
    asst_msg["tool_calls"] = [
        {
            "id": tc.id,
            "type": "function", 
            "function": {"name": tc.function.name, "arguments": tc.function.arguments}
        } for tc in first_msg.tool_calls
    ]
    convo.append(asst_msg)
    
    # Then append tool results
    for tc in first_msg.tool_calls:
        result = await mcp.call_tool(...)
        convo.append({
            "role": "tool", 
            "tool_call_id": tc.id,  # Must match the tool_call id
            "content": json.dumps(result)
        })

MCP的HTTP传输

由于FastMCP 2.x主要使用STDIO传输方式,因此该项目实现了一个自定义的HTTP层:

  • FastAPI 服务器 封装了MCP工具/提示/资源
  • HTTP客户端 为服务器提供异步接口
  • 聊天客户端 使用HTTP客户端而不是FastMCP的STDIO客户端

🧪 示例工具

该系统包含示例商业分析工具:

  • **find_products**按名称或类别搜索产品
  • **sales_between**指定日期范围内的总销售额数据
  • **top_products**获取销量最高的N款产品
  • **sales_report**包含关键绩效指标(KPIs)的全面销售报告

📝 可用提示(可选)

注这些提示是 可选示例对于一个使用GPT的单一聊天机器人(如本项目),你只需要 工具 上述内容中,当多个应用程序共享同一个MCP服务器时,提示非常有用。

  • **summarize_sales**生成一段时间内的销售总结
  • **sales_overview_json**获取JSON格式的销售概览
  • **compare_periods_json**比较两个时间段
  • **category_insights_json**特定类别分析
  • **product_deepdive_markdown**详细的产品分析
  • **merchandising_actions_json**可操作的商品陈列建议
  • **natural_language_sales_summary**人性化摘要(支持多种语言)

如何使用: /prompt <name> key=value ... 然后提出你的问题

🐞 故障排除

“无法连接到MCP服务器”

  • 确保服务器在终端1中运行
  • 检查端口8001是否可用: lsof -i :8001
  • 验证URL中的内容 .env: MCP_BASE_URL=http://127.0.0.1:8001/mcp

缺少OPENAI_API_KEY

  • 创建一个 .env 请将原文内容翻译成中文:使用包含您OpenAI API密钥的文件
  • 格式: OPENAI_API_KEY=sk-...

📄 许可证

这个项目采用MIT许可证授权——详见 许可证 详情请查阅文件。

🙏 致谢

🔗 相关资源

📧 支持


以MCP集成的实际应用为例,倾注心血打造