本文面向:对 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_orders 和 search_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 服务》。感兴趣可以先关注。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐