当前位置: 首页 > news >正文

MCP 协议实战:用 50 行代码给本地大模型接上“工具手“,让 Ollama 也能干 Agent 的活

本文面向:对 MCP 协议有基本了解、希望把它真正接到自己本地模型上的开发者。

全文示例已在 Windows 11 + Python 3.11 + Ollama 0.5+ 环境下完整跑通。

0. 写在前面:为什么写这篇

2026 年了,MCP(Model Context Protocol)已经从 Anthropic 自家的协议,变成了几乎所有主流 AI 工具的"事实标准"——Claude Desktop、Cursor、Windsurf、Cline,乃至大部分国产 IDE 插件,都在原生支持。

但当我在 CSDN、掘金、知乎搜了一圈"MCP 教程"以后,发现一个有意思的现象:

90% 的内容停在"MCP 是什么"和"怎么把官方 demo 跑起来"

真正回答"怎么把 MCP 接到本地模型(Ollama / vLLM)上"的资料几乎没有;

而这恰好是企业内网部署、私有化场景下,最刚需的一件事。

所以这篇文章不再重复 MCP 的概念铺垫,而是直接动手——用一个最简单的例子,把"自己写的工具 → 通过 MCP 协议 → 喂给本地 Ollama 模型"这整条链路打通,全部代码不超过 200 行

1. 三分钟看懂 MCP 在干什么

如果你已经熟悉 MCP,可以跳过本节。

简单来说,MCP 把"模型 ↔ 工具"之间的接线,标准化成了三个角色:

Host(宿主):跑模型的那个程序,比如 Claude Desktop、Cursor、或者下面我们自己写的 Python 脚本。

Client(客户端):Host 内部的一个连接器,负责跟下面的 Server 握手、收发消息。

Server(服务端):暴露工具/资源/Prompt 的进程,比如"查数据库""读文件""调内网接口"。

三者之间走的是 JSON-RPC,传输层既可以是stdio(最常用,本地拉起子进程),也可以是SSE / Streamable HTTP(远程部署)。

MCP 架构示意

理解到这里就够了。MCP 的精髓不是"协议有多复杂",而是它把"插一个工具进来"这件事变成了即插即用——你写一个 MCP Server,全世界支持 MCP 的客户端都能直接用它。

但反过来想——既然 MCP Server 是标准的,那么只要我自己写一个最小的 Host,让它能跟 MCP Server 通信、再把工具转给 Ollama 调用,本地模型就同样能享受这套生态。这就是本文的主线。

2. 环境准备

# Python 端

pip install "mcp[cli]" ollama

# Ollama 端(如果还没装)

# Windows: https://ollama.com/download

# 拉一个支持 tool calling 的模型,体积友好的可以选 qwen3:8b

ollama pull qwen3:8b

ollama serve

需要注意两点:

模型必须支持 tool calling。Qwen3、Llama 3.1+、Mistral、DeepSeek-V3 等都已经支持。早期模型(如 Llama 2、Qwen1.5)不支持,硬接会得到一堆乱七八糟的输出。

Ollama 版本 ≥ 0.4.x才有完善的 tool 字段,建议升到最新版。

3. 第一步:写一个最小的 MCP Server(≈ 50 行)

为了让 demo 有"业务感",我们假装自己是某个企业内部场景,需要两个工具:

1.query_orders— 按客户 ID 查最近订单

2.search_docs— 在本地知识库里做一个简单的关键词检索

新建my_mcp_server.py

import asyncio

from mcp.server import Server

from mcp.server.stdio import stdio_server

from mcp.types import Tool, TextContent

# —— 假数据:真实场景这里就是连数据库、调内部 API ——

ORDERS = {

"C001": ["2026-05-12 ¥899 蓝牙耳机", "2026-05-18 ¥2399 显示器"],

"C002": ["2026-05-09 ¥159 USB-C 扩展坞"],

}

DOCS = {

"退货政策": "签收 7 天内可无理由退货,需保持原包装完整。",

"发票申请": "登录会员中心 > 我的订单 > 申请发票,3 个工作日内开具。",

"保修说明": "标配 1 年保修,主板/电池半年保修,人为损坏不在保修范围。",

}

server = Server("demo-tools")

@server.list_tools()

async def list_tools():

return [

Tool(

name="query_orders",

description="按客户 ID 查询最近订单。输入参数:customer_id(字符串)",

inputSchema={

"type": "object",

"properties": {"customer_id": {"type": "string"}},

"required": ["customer_id"],

},

),

Tool(

name="search_docs",

description="在企业知识库中按关键词检索条目。输入参数:keyword(字符串)",

inputSchema={

"type": "object",

"properties": {"keyword": {"type": "string"}},

"required": ["keyword"],

},

),

]

@server.call_tool()

async def call_tool(name: str, arguments: dict):

if name == "query_orders":

cid = arguments.get("customer_id", "")

rows = ORDERS.get(cid)

text = "未查到该客户订单" if not rows else ";".join(rows)

return [TextContent(type="text", text=text)]

if name == "search_docs":

kw = arguments.get("keyword", "")

hits = [f"【{k}】{v}" for k, v in DOCS.items() if kw in k or kw in v]

text = "未命中条目" if not hits else "\n".join(hits)

return [TextContent(type="text", text=text)]

return [TextContent(type="text", text=f"未知工具: {name}")]

async def main():

async with stdio_server() as (read, write):

await server.run(read, write, server.create_initialization_options())

if __name__ == "__main__":

asyncio.run(main())

这个 Server 现在已经是一个"标准 MCP Server",所有支持 MCP 的客户端都能拿来即用

4. 第二步:先用 Claude Desktop / Cursor 验证一下

在写自己的 Host 之前,强烈建议先用现成客户端测一下,确保 Server 本身没问题。

以 Claude Desktop 为例,编辑配置文件:

Windows:%APPDATA%\Claude\claude_desktop_config.json

macOS:~/Library/Application Support/Claude/claude_desktop_config.json

加入:

{"mcpServers": {

"demo-tools": {

"command": "python",

"args": ["D:/your/path/my_mcp_server.py"]

}

}

}

重启 Claude Desktop,在对话框里你应该能看到demo-tools已经被加载,工具图标里多了query_orderssearch_docs

试着问:"客户 C001 最近买了什么?"

如果你能看到 Claude 主动调用query_orders,并把订单内容回出来——恭喜,Server 已经验证通过。

5. 关键章节:让 Ollama 也能用这套 MCP Server

到这里为止,所有教程都讲过。真正的难点是:怎么让本地跑的 Ollama 模型用上这个 Server?

思路其实只有一句话——

我们自己写一个最小的"Host",它一手连 MCP Server(说协议方言),一手连 Ollama(说 OpenAI 兼容的 tool calling 方言),中间做翻译。

新建local_host.py

import asyncio, json

import ollama

from mcp import ClientSession, StdioServerParameters

from mcp.client.stdio import stdio_client

MODEL = "qwen3:8b" # 任何支持 tool calling 的 Ollama 模型

SERVER_CMD = "python"

SERVER_ARGS = ["my_mcp_server.py"]

def mcp_tool_to_ollama(t):

"""把 MCP 协议里的 Tool 对象,翻成 Ollama / OpenAI 兼容的 tool schema。"""

return {

"type": "function",

"function": {

"name": t.name,

"description": t.description,

"parameters": t.inputSchema,

},

}

async def chat_loop(user_query: str):

params = StdioServerParameters(command=SERVER_CMD, args=SERVER_ARGS)

async with stdio_client(params) as (read, write):

async with ClientSession(read, write) as session:

await session.initialize()

# 1) 拿到 MCP Server 暴露的所有工具

tools_resp = await session.list_tools()

ollama_tools = [mcp_tool_to_ollama(t) for t in tools_resp.tools]

messages = [

{"role": "system",

"content": "你是企业客服助手,需要时调用工具查询订单和知识库。"},

{"role": "user", "content": user_query},

]

# 2) 让 Ollama 决定要不要调用工具(最多 5 轮,防止死循环)

for _ in range(5):

resp = ollama.chat(model=MODEL, messages=messages, tools=ollama_tools)

msg = resp["message"]

messages.append(msg)

tool_calls = msg.get("tool_calls") or []

if not tool_calls:

print("\n[最终回答]\n", msg["content"])

return

# 3) 模型决定调用工具 → 走 MCP 真实执行

for call in tool_calls:

fname = call["function"]["name"]

fargs = call["function"]["arguments"]

if isinstance(fargs, str):

fargs = json.loads(fargs)

print(f"[模型调用工具] {fname}({fargs})")

result = await session.call_tool(fname, fargs)

text = result.content[0].text if result.content else ""

print(f"[工具返回] {text}\n")

messages.append({

"role": "tool",

"content": text,

})

if __name__ == "__main__":

asyncio.run(chat_loop("客户 C001 最近买了什么?另外帮我查下退货政策。"))

这就是全部"翻译层"代码。不到 50 行,就把一个标准 MCP Server 接到了本地 Ollama 模型上。

6. 跑通效果

# 终端 1:确保 Ollama 在运行

ollama serve

# 终端 2

python local_host.py

预期输出大概是这样:

[模型调用工具] query_orders({'customer_id': 'C001'})

[工具返回] 2026-05-12 ¥899 蓝牙耳机;2026-05-18 ¥2399 显示器

[模型调用工具] search_docs({'keyword': '退货'})

[工具返回] 【退货政策】签收 7 天内可无理由退货,需保持原包装完整。

[最终回答]

客户 C001 最近购买记录:

1)2026-05-12,蓝牙耳机,¥899

2)2026-05-18,显示器,¥2399

退货政策:自签收之日起 7 天内可无理由退货,需保持原包装完整。

注意三点细节:

模型自己判断了"这个问题里有两件事,要分别调两个工具";

两次 tool 调用都真实走了 MCP 协议,而不是模型自己幻觉编造数据;

整套流程完全跑在本地,没有任何一次外部 API 调用——这正是企业内网场景最需要的形态。

7. 踩坑清单(建议收藏)

下面这些坑,是我把这套链路跑通过程中真实踩到的,按出现频率排序:

1)模型不调用工具,直接瞎回答。

99% 的情况是模型没选对。Qwen3、Llama 3.1+、DeepSeek-V3 这类才行;Llama 2 / 早期 Qwen 不支持。

另一个原因是 system prompt 太软,把"需要时调用工具"改成"必须先调用工具再回答"会立竿见影。

2)`stdio_client` 启动报 "Failed to spawn"。

SERVER_ARGS里的脚本路径必须 Host 那边能解析到。直接写绝对路径最稳。

Windows 下 Python 不在 PATH 时,把SERVER_CMD = "python"改成 Python 绝对路径。

3)`tool_calls` 字段一直为空。

检查 Ollama 版本,0.3.x 之前的版本对 tool 字段支持不全。

也可以打印resp看完整结构——有些模型会把工具调用塞到content里的 JSON 字符串,需要兼容解析。

4)中文输入参数被截断或乱码。

MCP 走 JSON-RPC,本身是 UTF-8。问题通常出在 Windows 终端编码。

进入脚本前执行chcp 65001,或在 Python 启动前设置PYTHONIOENCODING=utf-8

5)多工具并行调用顺序乱。

上面的 demo 是顺序执行,生产环境如果模型一次返回多个 tool call,记得用asyncio.gather并发跑,但要小心写库类工具的并发安全。

8. 接下来还可以怎么扩展

这套最小骨架跑通以后,往下走的路非常宽:

把 stdio 换成 SSE / Streamable HTTP:MCP Server 部署到内网服务器,多台机器共用一个工具集。

把 Ollama 换成 vLLM / SGLang:吞吐量和并发上一个台阶,对外提供企业级 Agent 服务。

接入更多 Server:MCP 生态目前已经有几百个开源 Server——文件系统、Git、PostgreSQL、Slack、Notion……都是开箱即用的,你只需要在 Host 里同时连接多个ClientSession即可。

加上权限控制:MCP 协议本身没规定鉴权,企业场景需要自己在 Host 层加一层"哪个用户能调哪个工具"的策略。

结合本地 RAG:让 MCP 工具暴露知识库检索能力,再叠加长上下文,本地模型就能搞定 80% 的客服/内部助手类需求。

9. 结语

回到开头那句话——

MCP 的真正价值,不是给 Claude 用的,是给"所有模型"用的。

在 2026 年这个节点,谁能率先把"本地大模型 + MCP + 自有工具"这条链路在自己业务里跑通,谁就站在了私有化 Agent 的入场口上。

而入场的代价,看完这篇你应该知道了——

不到 200 行 Python。

文中所有代码均原创编写,已在本地环境跑通。如果你按文章操作时遇到坑,欢迎评论区留版本号和报错信息,我会在置顶补充。

后续会写:《MCP Server 走 SSE 远程部署 + 鉴权方案》《把 MCP 接入 vLLM 提供企业级 Agent 服务》。感兴趣可以先关注。

http://www.jsqmd.com/news/863075/

相关文章:

  • “爱能克服远距离......”
  • 桐乡汽车贴膜哪家好?口碑专业靠谱贴膜门店推荐(2026 本地实用指南) - GrowthUME
  • 3步解锁百度网盘全速下载:告别限速困扰的实用指南
  • GitHub中文界面本地化解决方案:技术架构与部署指南
  • 2026年赤峰市育婴师企业推荐排行-育婴师企业口碑排行-育婴师机构口碑排行 - 品牌推广大师
  • Wireshark深度追踪HTTP敏感数据实战方法论
  • 思科:速修复满分 Secure Workload 未授权 API 访问漏洞
  • 告别臃肿!G-Helper:华硕笔记本用户的终极轻量级控制神器
  • 2026行业内靠谱的屏幕贴合机设备厂家口碑排行 - 品牌排行榜
  • Unity UGUI Text性能优化:打字、阴影、渐变的底层原理与实战方案
  • Unity背包系统从零手戳:数据层逻辑层表现层分离实践
  • UE5 BaseInstallBundle.ini深度解析:安装包构建的元数据契约
  • Appium环境搭建实战手册:解决JDK、Android SDK与Node.js兼容性问题
  • 2026年诸暨市汽车贴膜门店合规资质深度测评:4家正规授权店实测对比,新国标下资质核验避坑指南与选型推荐 - GrowthUME
  • Markdown图文教程转PPT实战指南
  • Unity URP下高性能尾气与扬尘粒子系统实现
  • Wireshark实战:HTTP明文敏感数据追踪与识别
  • Selenium动作链原理与Go实战:模拟人类交互的底层机制
  • Unity粒子特效优化:GPU/CPU/内存三重性能攻坚指南
  • G-Helper终极指南:免费轻量级华硕笔记本控制中心完全解决方案
  • Unity翻书效果深度解析:从物理建模到工程落地
  • Unity载具特效实战:尾气与扬尘的物理建模与性能优化
  • 安卓App签名机制逆向:Unidbg与Frida协同分析x-sign
  • 如何用Seraphine英雄联盟辅助工具在5分钟内提升你的排位赛胜率
  • GeoServer SLD环境变量漏洞CVE-2025-58360深度解析与防护
  • GitHub中文界面转换指南:3步打造专属中文GitHub环境
  • UE5场景漫游跳转避坑指南:从UI交互到资源预热
  • Unity翻书效果实现原理:顶点着色器级纸张物理建模
  • Unity沙漠场景模块化开发:参数化装配与空间语法构建
  • UE5 BaseInput.ini深度解析:输入配置的底层原理与跨平台实践