引言

大模型的能力边界往往受限于训练数据,想要让模型获取实时数据、操作外部系统,就需要让它具备“调用工具”的能力。传统做法是使用 function calling,但各模型实现方式不统一,维护成本高。而 MCP(Model Context Protocol) 提供了一套标准化的协议,让 AI 应用可以统一地接入各种外部工具和数据源。

本文将展示一个完整的本地闭环:

  • 用 Ollama 运行轻量级模型 qwen2.5:0.5b-instruct
  • 开发一个简单的 Python API(模拟你自己的软件接口)
  • 编写 MCP Server 将 API 包装成标准工具
  • 让模型通过 MCP 调用该工具,实现自然语言查询

环境准备

1. 安装 Ollama

Ollama 是一个开源的本地大模型运行框架,支持 macOS、Linux 和 Windows。

# macOS/Linux
curl -fsSL  | sh

# 或直接下载安装包 

安装完成后,验证:

ollama --version

2. 下载 qwen2.5:0.5b-instruct

这是阿里巴巴通义千问系列中非常轻量的指令微调模型,仅有 0.5B 参数,在普通 CPU 上也能流畅运行。

ollama pull qwen2.5:0.5b-instruct

测试模型是否可用:

ollama run qwen2.5:0.5b-instruct "你好,请介绍一下自己"

如果看到回复,说明模型部署成功。

开发自定义 API

为了演示,我们开发一个简单的 Flask API,提供用户数据查询功能(你可以替换成自己的业务 API)。

创建文件 my_api.py

from flask import Flask, request, jsonify

app = Flask(__name__)

# 模拟数据库
users = {
    1: {"name": "张三", "age": 28, "city": "北京"},
    2: {"name": "李四", "age": 35, "city": "上海"},
    3: {"name": "王五", "age": 22, "city": "深圳"},
}

@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = users.get(user_id)
    if user:
        return jsonify(user)
    else:
        return jsonify({"error": "用户不存在"}), 404

@app.route('/api/search', methods=['GET'])
def search_users():
    name = request.args.get('name')
    result = [u for u in users.values() if name in u['name']] if name else []
    return jsonify(result)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

启动 API:

python my_api.py

测试接口:

curl 
# 返回 {"age":28,"city":"北京","name":"张三"}

curl "http://localhost:5000/api/search?name=李"
# 返回 [{"age":35,"city":"上海","name":"李四"}]

MCP 协议简介

MCP(Model Context Protocol)是 Anthropic 推出的开源协议,旨在让 AI 模型能够安全、标准地访问外部工具和数据源。其核心概念:

  • MCP Server:暴露工具(Tools)和资源(Resources)的服务端
  • MCP Client:与 Server 通信,并将结果提供给 AI 模型
  • 工具(Tool):可被模型调用的函数,有明确的输入输出 schema

MCP 使用 JSON-RPC 2.0 进行通信,支持多种传输方式(stdio, SSE 等)。

实现 MCP Server 包装 API

我们将使用 Python 的 mcp 官方 SDK 来编写 Server。首先安装依赖:

pip install mcp flask requests

创建 mcp_server.py

import asyncio
import json
import requests
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

# 创建 MCP Server 实例
server = Server("my-api-server")

# 定义工具列表
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_user_by_id",
            description="根据用户ID查询用户信息",
            inputSchema={
                "type": "object",
                "properties": {
                    "user_id": {
                        "type": "integer",
                        "description": "用户ID,例如 1,2,3"
                    }
                },
                "required": ["user_id"]
            }
        ),
        types.Tool(
            name="search_users_by_name",
            description="根据姓名关键词搜索用户",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "姓名关键词"
                    }
                },
                "required": ["name"]
            }
        )
    ]

# 实现工具的调用逻辑
@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
    if not arguments:
        arguments = {}

    if name == "get_user_by_id":
        user_id = arguments.get("user_id")
        if not user_id:
            return [types.TextContent(type="text", text="错误:缺少 user_id")]
        try:
            # 调用自定义 API
            resp = requests.get(f"http://localhost:5000/api/user/{user_id}")
            if resp.status_code == 200:
                data = resp.json()
                text = f"用户信息:{json.dumps(data, ensure_ascii=False)}"
            else:
                text = f"查询失败:{resp.status_code} - {resp.text}"
            return [types.TextContent(type="text", text=text)]
        except Exception as e:
            return [types.TextContent(type="text", text=f"调用API出错:{str(e)}")]

    elif name == "search_users_by_name":
        name_keyword = arguments.get("name")
        if not name_keyword:
            return [types.TextContent(type="text", text="错误:缺少 name")]
        try:
            resp = requests.get(f"http://localhost:5000/api/search", params={"name": name_keyword})
            if resp.status_code == 200:
                data = resp.json()
                if data:
                    text = f"搜索结果:{json.dumps(data, ensure_ascii=False)}"
                else:
                    text = "未找到匹配的用户"
            else:
                text = f"搜索失败:{resp.status_code} - {resp.text}"
            return [types.TextContent(type="text", text=text)]
        except Exception as e:
            return [types.TextContent(type="text", text=f"调用API出错:{str(e)}")]

    else:
        raise ValueError(f"未知工具: {name}")

async def main():
    # 使用 stdio 传输运行 server
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="my-api-server",
                server_version="1.0.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )

if __name__ == "__main__":
    asyncio.run(main())

这个 MCP Server 暴露了两个工具,分别对应我们 API 的两个端点。当模型需要查询用户时,会调用相应的工具,Server 会转发请求到 Flask API 并返回结果。

集成模型与 MCP

现在我们要让模型能够识别用户意图并调用这些工具。有多种方式实现:

  1. 使用 MCP Client 并配合模型:例如使用 mcp-client 库,将工具描述嵌入到系统提示中,让模型决定调用。
  2. 利用 Ollama 的 function calling 支持:qwen2.5:0.5b-instruct 不支持原生 function calling,但我们可以手动构造 prompt。

由于 qwen2.5:0.5b 没有内置 function calling,我们采用一种简单的“意图识别 + 工具调用”流程:

  • 用户输入 → 模型判断是否需要调用工具 → 如果需要,让模型输出结构化的调用指令 → 解析指令,调用 MCP Server → 将结果返回给模型 → 模型生成最终回答。

我们用一个 Python 脚本整合所有环节。安装额外依赖:

pip install ollama

创建 main.py

import json
import subprocess
import sys
import ollama
import requests

# MCP Server 通信(这里简化:直接通过 HTTP 调用 MCP Server?不,我们上面 MCP Server 是用 stdio 的)
# 为了简化演示,我们不使用 MCP Client 库,而是直接让模型输出结构化指令,然后手动调用 API。
# 这样虽然不符合 MCP 标准流程,但能让你看到核心思想。你也可以使用 MCP 的 Python 客户端库来完整实现。

# 定义工具描述(给模型看的)
TOOLS_DESCRIPTION = """
你可以使用以下工具来帮助用户:
1. get_user_by_id: 根据用户ID查询用户信息。参数 user_id (整数)
2. search_users_by_name: 根据姓名关键词搜索用户。参数 name (字符串)

当用户询问关于用户信息时,你需要决定是否使用工具。如果需要使用,请以 JSON 格式输出你的调用,格式为:
{"tool": "工具名称", "arguments": {"参数名": 值}}
然后我会执行工具并返回结果。如果不需要工具,直接回答即可。
"""

def call_mcp_tool(tool_name, arguments):
    """模拟 MCP 调用:这里直接调用我们的 Flask API"""
    if tool_name == "get_user_by_id":
        user_id = arguments.get("user_id")
        resp = requests.get(f"http://localhost:5000/api/user/{user_id}")
        if resp.status_code == 200:
            return resp.json()
        else:
            return {"error": resp.text}
    elif tool_name == "search_users_by_name":
        name = arguments.get("name")
        resp = requests.get(f"http://localhost:5000/api/search", params={"name": name})
        if resp.status_code == 200:
            return resp.json()
        else:
            return {"error": resp.text}
    else:
        return {"error": f"未知工具: {tool_name}"}

def chat_with_model(user_input):
    messages = [
        {"role": "system", "content": TOOLS_DESCRIPTION},
        {"role": "user", "content": user_input}
    ]
    # 调用 Ollama 生成
    response = ollama.chat(model='qwen2.5:0.5b-instruct', messages=messages)
    assistant_reply = response['message']['content']
    print("模型原始回复:", assistant_reply)

    # 尝试解析 JSON 调用
    try:
        # 查找 JSON 块
        import re
        json_match = re.search(r'{.*?}', assistant_reply, re.DOTALL)
        if json_match:
            tool_call = json.loads(json_match.group())
            if "tool" in tool_call and "arguments" in tool_call:
                tool_name = tool_call["tool"]
                args = tool_call["arguments"]
                print(f"调用工具 {tool_name},参数 {args}")
                tool_result = call_mcp_tool(tool_name, args)
                # 将工具结果返回给模型生成最终答案
                messages.append({"role": "assistant", "content": assistant_reply})
                messages.append({"role": "user", "content": f"工具返回结果:{json.dumps(tool_result, ensure_ascii=False)},请根据这个结果回答用户问题。"})
                final_response = ollama.chat(model='qwen2.5:0.5b-instruct', messages=messages)
                return final_response['message']['content']
    except Exception as e:
        print("解析工具调用失败:", e)

    return assistant_reply

if __name__ == "__main__":
    print("AI 助手已启动,输入 quit 退出")
    while True:
        user_input = input("你: ")
        if user_input.lower() == 'quit':
            break
        response = chat_with_model(user_input)
        print("AI:", response)

这个脚本做了:

  • 将工具描述写入系统提示
  • 让模型输出结构化 JSON 调用(如果它认为需要)
  • 解析 JSON,调用对应 API
  • 将结果再次发给模型,让模型生成自然语言回答

注意:这只是一个演示级实现,生产环境建议使用官方 MCP Client 库来规范通信。

运行与测试

  1. 启动 Flask API:

    python my_api.py
    
  2. 启动 MCP Server(可选,我们上面直接调用了 API,但为了演示 MCP 概念,你也可以尝试使用 MCP Client 来连接 Server,这里简化了):

    python mcp_server.py
    

    但我们的 main.py 并未与 MCP Server 通信,而是直接调 API。如果你想体验完整的 MCP 流程,可以使用 mcp 命令行工具或官方 Python 客户端,这里不再展开。

  3. 运行主程序:

    python main.py
    
  4. 测试对话:

    你: 帮我查一下用户ID为2的信息
    AI: 模型原始回复:{"tool": "get_user_by_id", "arguments": {"user_id": 2}}
    调用工具 get_user_by_id,参数 {'user_id': 2}
    AI: 用户ID为2的用户是李四,年龄35岁,来自上海。
    
    你: 搜索名字中包含“王”的用户
    AI: 模型原始回复:{"tool": "search_users_by_name", "arguments": {"name": "王"}}
    调用工具 search_users_by_name,参数 {'name': '王'}
    AI: 找到了用户王五,年龄22岁,在深圳。
    
    你: 你好,请介绍一下自己
    AI: 模型原始回复:你好!我是阿里云研发的超大规模语言模型,我叫通义千问。
    AI: 你好!我是阿里云研发的超大规模语言模型,我叫通义千问。
    

可以看到,模型在需要查询数据时,正确地输出了工具调用指令,我们通过解析并调用真实 API 后,得到了最终回答。

总结

本文实现了一个本地大模型调用自定义 API 的完整流程:

  • 使用 Ollama 快速部署 qwen2.5:0.5b-instruct 轻量模型
  • 开发了一个简单的 Flask API 作为业务接口
  • 介绍了 MCP 协议的基本概念,并编写了一个 MCP Server 来包装 API(虽然最终示例中未强制使用 MCP Client,但展示了 MCP Server 的写法)
  • 通过 prompt engineering 让模型输出结构化指令,完成工具调用

这种架构可以轻松扩展到更多工具和更复杂的业务场景。你可以将 MCP Server 部署为独立服务,让任意支持 MCP 的客户端(如 Claude Desktop)也能调用你的私有 API,实现跨应用的智能交互。

源码仓库:你可以在 GitHub 示例 中找到本文全部代码。


参考资料

  • Ollama 官网
  • Qwen2.5 模型介绍
  • MCP 官方文档
  • MCP Python SDK

希望这篇文章对你有所帮助,欢迎留言交流!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com