本文面向对 MCP 协议有基本了解、希望把它真正接到自己本地模型上的开发者。全文示例已在 Windows 11 Python 3.11 Ollama 0.5 环境下完整跑通。0. 写在前面为什么写这篇2026 年了MCPModel 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:8bollama pull qwen3:8bollama 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.pyimport asynciofrom mcp.server import Serverfrom mcp.server.stdio import stdio_serverfrom 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(namequery_orders,description按客户 ID 查询最近订单。输入参数customer_id字符串,inputSchema{type: object,properties: {customer_id: {type: string}},required: [customer_id],},),Tool(namesearch_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(typetext, texttext)]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(typetext, texttext)]return [TextContent(typetext, textf未知工具: {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.jsonmacOS:~/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_orders和search_docs。试着问客户 C001 最近买了什么如果你能看到 Claude 主动调用query_orders并把订单内容回出来——恭喜Server 已经验证通过。5. 关键章节让 Ollama 也能用这套 MCP Server到这里为止所有教程都讲过。真正的难点是怎么让本地跑的 Ollama 模型用上这个 Server思路其实只有一句话——我们自己写一个最小的Host它一手连 MCP Server说协议方言一手连 Ollama说 OpenAI 兼容的 tool calling 方言中间做翻译。新建local_host.pyimport asyncio, jsonimport ollamafrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientMODEL qwen3:8b # 任何支持 tool calling 的 Ollama 模型SERVER_CMD pythonSERVER_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(commandSERVER_CMD, argsSERVER_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(modelMODEL, messagesmessages, toolsollama_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# 终端 2python local_host.py预期输出大概是这样[模型调用工具] query_orders({customer_id: C001})[工具返回] 2026-05-12 ¥899 蓝牙耳机2026-05-18 ¥2399 显示器[模型调用工具] search_docs({keyword: 退货})[工具返回] 【退货政策】签收 7 天内可无理由退货需保持原包装完整。[最终回答]客户 C001 最近购买记录12026-05-12蓝牙耳机¥89922026-05-18显示器¥2399退货政策自签收之日起 7 天内可无理由退货需保持原包装完整。注意三点细节模型自己判断了这个问题里有两件事要分别调两个工具两次 tool 调用都真实走了 MCP 协议而不是模型自己幻觉编造数据整套流程完全跑在本地没有任何一次外部 API 调用——这正是企业内网场景最需要的形态。7. 踩坑清单建议收藏下面这些坑是我把这套链路跑通过程中真实踩到的按出现频率排序1模型不调用工具直接瞎回答。99% 的情况是模型没选对。Qwen3、Llama 3.1、DeepSeek-V3 这类才行Llama 2 / 早期 Qwen 不支持。另一个原因是 system prompt 太软把需要时调用工具改成必须先调用工具再回答会立竿见影。2stdio_client 启动报 Failed to spawn。SERVER_ARGS里的脚本路径必须 Host 那边能解析到。直接写绝对路径最稳。Windows 下 Python 不在 PATH 时把SERVER_CMD python改成 Python 绝对路径。3tool_calls 字段一直为空。检查 Ollama 版本0.3.x 之前的版本对 tool 字段支持不全。也可以打印resp看完整结构——有些模型会把工具调用塞到content里的 JSON 字符串需要兼容解析。4中文输入参数被截断或乱码。MCP 走 JSON-RPC本身是 UTF-8。问题通常出在 Windows 终端编码。进入脚本前执行chcp 65001或在 Python 启动前设置PYTHONIOENCODINGutf-8。5多工具并行调用顺序乱。上面的 demo 是顺序执行生产环境如果模型一次返回多个 tool call记得用asyncio.gather并发跑但要小心写库类工具的并发安全。8. 接下来还可以怎么扩展这套最小骨架跑通以后往下走的路非常宽把 stdio 换成 SSE / Streamable HTTPMCP Server 部署到内网服务器多台机器共用一个工具集。把 Ollama 换成 vLLM / SGLang吞吐量和并发上一个台阶对外提供企业级 Agent 服务。接入更多 ServerMCP 生态目前已经有几百个开源 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 服务》。感兴趣可以先关注。
MCP 协议实战:用 50 行代码给本地大模型接上“工具手“,让 Ollama 也能干 Agent 的活
本文面向对 MCP 协议有基本了解、希望把它真正接到自己本地模型上的开发者。全文示例已在 Windows 11 Python 3.11 Ollama 0.5 环境下完整跑通。0. 写在前面为什么写这篇2026 年了MCPModel 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:8bollama pull qwen3:8bollama 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.pyimport asynciofrom mcp.server import Serverfrom mcp.server.stdio import stdio_serverfrom 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(namequery_orders,description按客户 ID 查询最近订单。输入参数customer_id字符串,inputSchema{type: object,properties: {customer_id: {type: string}},required: [customer_id],},),Tool(namesearch_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(typetext, texttext)]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(typetext, texttext)]return [TextContent(typetext, textf未知工具: {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.jsonmacOS:~/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_orders和search_docs。试着问客户 C001 最近买了什么如果你能看到 Claude 主动调用query_orders并把订单内容回出来——恭喜Server 已经验证通过。5. 关键章节让 Ollama 也能用这套 MCP Server到这里为止所有教程都讲过。真正的难点是怎么让本地跑的 Ollama 模型用上这个 Server思路其实只有一句话——我们自己写一个最小的Host它一手连 MCP Server说协议方言一手连 Ollama说 OpenAI 兼容的 tool calling 方言中间做翻译。新建local_host.pyimport asyncio, jsonimport ollamafrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientMODEL qwen3:8b # 任何支持 tool calling 的 Ollama 模型SERVER_CMD pythonSERVER_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(commandSERVER_CMD, argsSERVER_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(modelMODEL, messagesmessages, toolsollama_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# 终端 2python local_host.py预期输出大概是这样[模型调用工具] query_orders({customer_id: C001})[工具返回] 2026-05-12 ¥899 蓝牙耳机2026-05-18 ¥2399 显示器[模型调用工具] search_docs({keyword: 退货})[工具返回] 【退货政策】签收 7 天内可无理由退货需保持原包装完整。[最终回答]客户 C001 最近购买记录12026-05-12蓝牙耳机¥89922026-05-18显示器¥2399退货政策自签收之日起 7 天内可无理由退货需保持原包装完整。注意三点细节模型自己判断了这个问题里有两件事要分别调两个工具两次 tool 调用都真实走了 MCP 协议而不是模型自己幻觉编造数据整套流程完全跑在本地没有任何一次外部 API 调用——这正是企业内网场景最需要的形态。7. 踩坑清单建议收藏下面这些坑是我把这套链路跑通过程中真实踩到的按出现频率排序1模型不调用工具直接瞎回答。99% 的情况是模型没选对。Qwen3、Llama 3.1、DeepSeek-V3 这类才行Llama 2 / 早期 Qwen 不支持。另一个原因是 system prompt 太软把需要时调用工具改成必须先调用工具再回答会立竿见影。2stdio_client 启动报 Failed to spawn。SERVER_ARGS里的脚本路径必须 Host 那边能解析到。直接写绝对路径最稳。Windows 下 Python 不在 PATH 时把SERVER_CMD python改成 Python 绝对路径。3tool_calls 字段一直为空。检查 Ollama 版本0.3.x 之前的版本对 tool 字段支持不全。也可以打印resp看完整结构——有些模型会把工具调用塞到content里的 JSON 字符串需要兼容解析。4中文输入参数被截断或乱码。MCP 走 JSON-RPC本身是 UTF-8。问题通常出在 Windows 终端编码。进入脚本前执行chcp 65001或在 Python 启动前设置PYTHONIOENCODINGutf-8。5多工具并行调用顺序乱。上面的 demo 是顺序执行生产环境如果模型一次返回多个 tool call记得用asyncio.gather并发跑但要小心写库类工具的并发安全。8. 接下来还可以怎么扩展这套最小骨架跑通以后往下走的路非常宽把 stdio 换成 SSE / Streamable HTTPMCP Server 部署到内网服务器多台机器共用一个工具集。把 Ollama 换成 vLLM / SGLang吞吐量和并发上一个台阶对外提供企业级 Agent 服务。接入更多 ServerMCP 生态目前已经有几百个开源 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 服务》。感兴趣可以先关注。