前言
模型上下文协议MCP与Ollama的整合实现指南
在过去一两个个月里,模型上下文协议(Model Context Protocol,MCP)频繁出现在各种技术微信交流群中。我们已经看到了许多很酷的集成案例,大家似乎相信这个标准会长期存在,因为它为大模型与工具或软件的集成设立了规范。
今天,向大家展示如何实现Ollama与MCP服务器的集成。
实现步骤
整个集成的主要步骤包括:
- 创建测试以及使用MCP服务
- 创建客户端文件来发送请求并启动服务
- 从服务获取工具到客户端
- 将工具转换为pydantic模型
- 通过response format将工具(作为pydantic模型)传递给Ollama
- 通过Ollama发送对话并接收结构化输出
- 如果响应中包含工具,则向服务器发送请求
安装依赖
要运行这个项目,需要安装必要的包。fastmcp库在使用uv运行代码时效果最佳。uv很容易下载和使用,类似于Poetry和pip。
使用以下命令将所需库添加到你的项目中:
uv add fastmcp ollama
这会同时安装MCP服务器和Ollama聊天库,以便你在它们的基础上构建客户端和服务器逻辑。
文件结构
设置时,你的文件夹应该是这样的:
your folder
├── server.py
└── client.py
server.py
文件包含MCP服务器和想要暴露的工具。client.py
文件在后台进程中启动服务器,获取可用工具,并与Ollama连接。
示例MCP服务器
首先,让我们使用fastmcp库创建一个简单的MCP服务器。该服务器暴露了一个名为magicoutput
的工具。这个函数接受两个字符串输入并返回一个固定的字符串作为输出。
@mcp.tool()
装饰器用于将函数注册为MCP服务器中的可用工具。当服务器启动后,任何客户端都可以获取并调用这个工具。
通过在主块中调用mcp.run()
来启动服务器。
# server.py
from fastmcp import FastMCP
# 创建MCP服务器
mcp = FastMCP("TestServer")
# 我的工具:
@mcp.tool()
def magicoutput(obj1: str, obj2: str) -> int:
"""使用此函数获取魔法输出"""
print(f"输入参数:obj1:{obj1},obj2:{obj2}")
return f"输入参数:obj1:{obj1},obj2:{obj2},魔法输出:Hello MCP,MCP Hello"
if __name__ == "__main__":
mcp.run()
我们运行下面命令,进行调试服务端的工具:
fastmcp dev server.py
输入日志如下:
Need to install the following packages:
@modelcontextprotocol/inspector@0.10.2
Ok to proceed? (y) y
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
New SSE connection
Query parameters: [Object:
本地访问页面http://127.0.0.1:6274/#tools
,我们可以看构造的函数,并且可以调试
获取服务器工具
为了连接到MCP服务器并列出可用工具,我们使用来自mcp库的ClientSession
、StdioServerParameters
和stdio_client
。
我们定义一个名为OllamaMCP
的类来处理服务器连接和工具获取。在类内部,_async_run
方法启动异步会话,初始化它,并从服务器获取工具列表。
我们使用threading.Event()
来跟踪会话何时准备就绪,并将工具列表存储在self.tools
中。
在脚本末尾,我们定义服务器参数并在后台线程中运行客户端。这会启动连接并打印服务器返回的工具元数据。
# client.py
import asyncio
import threading
from pathlib import Path
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from typing import Any
class OllamaMCP:
"""
Ollama和FastMCP的简单集成
"""
def __init__(self, server_params: StdioServerParameters):
self.server_params = server_params
self.initialized = threading.Event()
self.tools: list[Any] = []
def _run_background(self):
asyncio.run(self._async_run())
async def _async_run(self):
try:
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
self.session = session
tools_result = await session.list_tools()
self.tools = tools_result.tools
print(tools_result)
except Exception as e:
print(f"启动MCP服务器时出错 {str(e)}")
if __name__ == "__main__":
server_parameters = StdioServerParameters(
command="uv",
args=["run", "python", "server.py"],
cwd=str(Path.cwd())
)
ollamamcp = OllamaMCP(server_params=server_parameters)
ollamamcp._run_background()
运行上面的代码后,你会从服务器得到以下响应,其中可以看到服务器上可用的工具列表。
[04/19/25 12:10:47] INFO Starting server "TestServer"... server.py:261
meta=None nextCursor=None tools=[Tool(name='magicoutput', description='使用此函数获取魔法输出', inputSchema={'properties': {'obj1': {'title': 'Obj1', 'type': 'string'}, 'obj2': {'title': 'Obj2', 'type': 'string'}}, 'required': ['obj1', 'obj2'], 'title': 'magicoutputArguments', 'type': 'object'})]
将工具转换为pydantic模型
现在我们已经从服务器接收到了工具列表,下一步是将它们转换为Pydantic模型。我们使用Pydantic的create_model
来动态定义新的响应模式,基于服务器的工具定义。还有一个辅助函数来将JSON类型映射到有效的Python类型。
Pydantic 是一个用于数据验证和序列化的 Python 模型库。它在 FastAPI 中广泛使用,用于定义请求体、响应体和其他数据模型,提供了强大的类型检查和自动文档生成功能
这帮助我们动态定义模型,使语言模型确切知道在返回工具参数时应使用什么结构。
# client.py
import asyncio
import threading
from pathlib import Path
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from typing import Any, Union, Optional
from pydantic import BaseModel, create_model, Field
class OllamaMCP:
"""Ollama和FastMCP的简单集成"""
def __init__(self, server_params: StdioServerParameters):
self.server_params = server_params
self.initialized = threading.Event()
self.tools: list[Any] = []
def _run_background(self):
asyncio.run(self._async_run())
async def _async_run(self):
try:
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
self.session = session
tools_result = await session.list_tools()
self.tools = tools_result.tools
except Exception as e:
print(f"启动MCP服务器时出错 {str(e)}")
def create_response_model(self):
dynamic_classes = {}
for tool in self.tools:
class_name = tool.name.capitalize()
properties = {}
for prop_name, prop_info in tool.inputSchema.get("properties", {}).items():
json_type = prop_info.get("type", "string")
properties[prop_name] = self.convert_json_type_to_python_type(json_type)
model = create_model(
class_name,
__base__=BaseModel,
__doc__=tool.description,
**properties,
)
dynamic_classes[class_name] = model
if dynamic_classes:
all_tools_type = Union[tuple(dynamic_classes.values())]
Response = create_model(
"Response",
__base__=BaseModel,
__doc__="LLm响应类",
response=(str, Field(..., description= "向用户确认函数将被调用。")),
tool=(all_tools_type, Field(
...,
description="用于运行和获取魔法输出的工具"
)),
)
else:
Response = create_model(
"Response",
__base__=BaseModel,
__doc__="LLm响应类",
response=(str, ...),
tool=(Optional[Any], Field(None, description="如果不返回None则使用的工具")),
)
self.response_model = Response
print(Response.model_fields)
@staticmethod
def convert_json_type_to_python_type(json_type: str):
"""简单地将JSON类型映射到Python(Pydantic)类型。"""
if json_type == "integer":
return (int, ...)
if json_type == "number":
return (float, ...)
if json_type == "string":
return (str, ...)
if json_type == "boolean":
return (bool, ...)
return (str, ...)
if __name__ == "__main__":
server_parameters = StdioServerParameters(
command="uv",
args=["run", "python", "server.py"],
cwd=str(Path.cwd())
)
ollamamcp = OllamaMCP(server_params=server_parameters)
ollamamcp._run_background()
ollamamcp.create_response_model()
运行代码后,print(Response.model_fields)
的输出显示了我们刚刚构建的响应模型的完整结构。这个模型包括两部分:一部分是助手发送给用户的消息,另一部分是可选字段,保存工具参数。
如果模型填充了工具字段,我们将使用它来调用服务器。否则,我们只使用普通的响应字符串。
[04/19/25 12:17:20] INFO Starting server "TestServer"... server.py:261
{'response': FieldInfo(annotation=str, required=True, description='向用户确认函数将被调用。'), 'tool': FieldInfo(annotation=Magicoutput, required=True, description='用于运行和获取魔法输出的工具')}
使用后台线程和队列调用工具
现在工具可以作为pydantic模型使用了,我们可以继续并启用工具调用。为此,我们使用后台线程并设置两个队列。一个用于向服务器发送请求,另一个用于接收响应。
call_tool
方法将请求放入队列,后台线程监听该请求。一旦使用MCP会话调用工具,结果就会放入响应队列。
import asyncio
import threading
import threading
import queue
from pathlib import Path
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from typing import Any, Union, Optional
from pydantic import BaseModel, create_model, Field
class OllamaMCP:
"""Ollama和FastMCP的简单集成"""
def __init__(self, server_params: StdioServerParameters):
self.server_params = server_params
self.initialized = threading.Event()
self.tools: list[Any] = []
self.request_queue = queue.Queue()
self.response_queue = queue.Queue()
# 启动后台线程异步处理请求
self.thread = threading.Thread(target=self._run_background, daemon=True)
self.thread.start()
def _run_background(self):
asyncio.run(self._async_run())
async def _async_run(self):
try:
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
self.session = session
tools_result = await session.list_tools()
self.tools = tools_result.tools
self.initialized.set()
while True:
try:
tool_name, arguments = self.request_queue.get(block=False)
except queue.Empty:
await asyncio.sleep(0.01)
continue
if tool_name is None:
print("收到关闭信号。")
break
try:
result = await session.call_tool(tool_name, arguments)
self.response_queue.put(result)
except Exception as e:
self.response_queue.put(f"错误: {str(e)}")
except Exception as e:
print("MCP会话初始化错误:", str(e))
self.initialized.set() # 即使初始化失败也解除等待线程的阻塞
self.response_queue.put(f"MCP初始化错误: {str(e)}")
def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
"""
发布工具调用请求并等待结果
"""
if not self.initialized.wait(timeout=30):
raise TimeoutError("MCP会话未能及时初始化。")
self.request_queue.put((tool_name, arguments))
result = self.response_queue.get()
return result
def shutdown(self):
"""
干净地关闭持久会话
"""
self.request_queue.put((None, None))
self.thread.join()
print("持久MCP会话已关闭。")
def create_response_model(self):
dynamic_classes = {}
for tool in self.tools:
class_name = tool.name.capitalize()
properties = {}
for prop_name, prop_info in tool.inputSchema.get("properties", {}).items():
json_type = prop_info.get("type", "string")
properties[prop_name] = self.convert_json_type_to_python_type(json_type)
model = create_model(
class_name,
__base__=BaseModel,
__doc__=tool.description,
**properties,
)
dynamic_classes[class_name] = model
if dynamic_classes:
all_tools_type = Union[tuple(dynamic_classes.values())]
Response = create_model(
"Response",
__base__=BaseModel,
response=(str, ...),
tool=(Optional[all_tools_type], Field(None, description="如果不返回None则使用的工具")),
)
else:
Response = create_model(
"Response",
__base__=BaseModel,
response=(str, ...),
tool=(Optional[Any], Field(None, description="如果不返回None则使用的工具")),
)
self.response_model = Response
@staticmethod
def convert_json_type_to_python_type(json_type: str):
"""简单地将JSON类型映射到Python(Pydantic)类型。"""
if json_type == "integer":
return (int, ...)
if json_type == "number":
return (float, ...)
if json_type == "string":
return (str, ...)
if json_type == "boolean":
return (bool, ...)
return (str, ...)
if __name__ == "__main__":
server_parameters = StdioServerParameters(
command="uv",
args=["run", "python", "server.py"],
cwd=str(Path.cwd())
)
ollamamcp = OllamaMCP(server_params=server_parameters)
if ollamamcp.initialized.wait(timeout=30):
print("准备调用工具。")
result = ollamamcp.call_tool(
tool_name="magicoutput",
arguments={"obj1": "dog", "obj2": "cat"}
)
print(result)
else:
print("错误: 初始化超时。")
请注意,在这个阶段,我们正在使用call_tool
方法手动传递函数名称和参数。在下一节中,我们将根据Ollama返回的结构化输出触发这个调用。
运行此代码后,我们可以确认一切正常。工具被服务器正确识别、执行,并返回结果。
[04/19/25 12:18:26] INFO Starting server "TestServer"... server.py:261
准备调用工具。
meta=None content=[TextContent(type='text', text='输入参数:obj1:dog,obj2:cat,魔法输出:Hello MCP,MCP Hello', annotations=None)] isError=False
Ollama + MCP结合
首先我们先通过Ollama部署一个大模型服务,这里我们下gemma3
下面代码中,我是设置的局域网的ip
client = Client(
host='http://192.168.1.5:11434',
headers={'x-some-header': 'some-value'}
)
有了队列和call_tool
函数,现在是时候该集成Ollama了。我们将响应类传递到Ollama的format字段中,告诉我们的语言模型(这里是Gemma)在生成输出时遵循该模式。
我们还定义了一个ollama_chat
方法,用于发送对话,验证模型的响应是否符合模式,并检查是否包含工具。如果是,它提取函数名称和参数,然后使用在后台线程中的持久MCP会话调用它。
在main函数中,我们设置服务器连接,启动后台循环,并等待一切就绪。然后我们准备系统提示和用户消息,将它们发送给Ollama,并等待结构化输出。
最后,我们打印服务器的结果并关闭会话。
import asyncio
import threading
import queue
from pathlib import Path
from typing import Any, Optional, Union
from pydantic import BaseModel, Field, create_model
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from ollama import chat,Client
client = Client(
host='http://192.168.1.5:11434',
headers={'x-some-header': 'some-value'}
)
class OllamaMCP:
def __init__(self, server_params: StdioServerParameters):
self.server_params = server_params
self.request_queue = queue.Queue()
self.response_queue = queue.Queue()
self.initialized = threading.Event()
self.tools: list[Any] = []
self.thread = threading.Thread(target=self._run_background, daemon=True)
self.thread.start()
def _run_background(self):
asyncio.run(self._async_run())
async def _async_run(self):
try:
async with stdio_client(self.server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
self.session = session
tools_result = await session.list_tools()
self.tools = tools_result.tools
self.initialized.set()
while True:
try:
tool_name, arguments = self.request_queue.get(block=False)
except queue.Empty:
await asyncio.sleep(0.01)
continue
if tool_name is None:
break
try:
result = await session.call_tool(tool_name, arguments)
self.response_queue.put(result)
except Exception as e:
self.response_queue.put(f"错误: {str(e)}")
except Exception as e:
print("MCP会话初始化错误:", str(e))
self.initialized.set() # 即使初始化失败也解除等待线程的阻塞
self.response_queue.put(f"MCP初始化错误: {str(e)}")
def call_tool(self, tool_name: str, arguments: dict[str, Any]) -> Any:
"""
发布工具调用请求并等待结果
"""
if not self.initialized.wait(timeout=30):
raise TimeoutError("MCP会话未能及时初始化。")
self.request_queue.put((tool_name, arguments))
result = self.response_queue.get()
return result
def shutdown(self):
"""
干净地关闭持久会话
"""
self.request_queue.put((None, None))
self.thread.join()
print("持久MCP会话已关闭。")
@staticmethod
def convert_json_type_to_python_type(json_type: str):
"""简单地将JSON类型映射到Python(Pydantic)类型。"""
if json_type == "integer":
return (int, ...)
if json_type == "number":
return (float, ...)
if json_type == "string":
return (str, ...)
if json_type == "boolean":
return (bool, ...)
return (str, ...)
def create_response_model(self):
"""
基于获取的工具创建动态Pydantic响应模型
"""
dynamic_classes = {}
for tool in self.tools:
class_name = tool.name.capitalize()
properties: dict[str, Any] = {}
for prop_name, prop_info in tool.inputSchema.get("properties", {}).items():
json_type = prop_info.get("type", "string")
properties[prop_name] = self.convert_json_type_to_python_type(json_type)
model = create_model(
class_name,
__base__=BaseModel,
__doc__=tool.description,
**properties,
)
dynamic_classes[class_name] = model
if dynamic_classes:
all_tools_type = Union[tuple(dynamic_classes.values())]
Response = create_model(
"Response",
__base__=BaseModel,
__doc__="LLm响应类",
response=(str, Field(..., description= "向用户确认函数将被调用。")),
tool=(all_tools_type, Field(
...,
description="用于运行和获取魔法输出的工具"
)),
)
else:
Response = create_model(
"Response",
__base__=BaseModel,
__doc__="LLm响应类",
response=(str, ...),
tool=(Optional[Any], Field(None, description="如果不返回None则使用的工具")),
)
self.response_model = Response
async def ollama_chat(self, messages: list[dict[str, str]]) -> Any:
"""
使用动态响应模型向Ollama发送消息。
如果在响应中检测到工具,则使用持久会话调用它。
"""
conversation = [{"role":"assistant", "content": f"你必须使用工具。你可以使用以下函数:{[ tool.name for tool in self.tools]}"}]
conversation.extend(messages)
if self.response_model is None:
raise ValueError("响应模型尚未创建。请先调用create_response_model()。")
# 获取聊天消息格式的JSON模式
format_schema = self.response_model.model_json_schema()
# 调用Ollama(假定是同步的)并解析响应
response = client.chat(
model="gemma3:latest",
messages=conversation,
format=format_schema
)
print("Ollama响应", response.message.content)
response_obj = self.response_model.model_validate_json(response.message.content)
maybe_tool = response_obj.tool
if maybe_tool:
function_name = maybe_tool.__class__.__name__.lower()
func_args = maybe_tool.model_dump()
# 使用asyncio.to_thread在线程中调用同步的call_tool方法
output = await asyncio.to_thread(self.call_tool, function_name, func_args)
return output
else:
print("响应中未检测到工具。返回纯文本响应。")
return response_obj.response
async def main():
server_parameters = StdioServerParameters(
command="uv",
args=["run", "python", "server.py"],
cwd=str(Path.cwd())
)
# 创建持久会话
persistent_session = OllamaMCP(server_parameters)
# 等待会话完全初始化
if persistent_session.initialized.wait(timeout=30):
print("准备调用工具。")
else:
print("错误: 初始化超时。")
# 从获取的工具创建动态响应模型
persistent_session.create_response_model()
# 准备给Ollama的消息
messages = [
{
"role": "system",
"content": (
"你是一个听话的助手,上下文中有一系列工具。"
"你的任务是使用这个函数获取魔法输出。"
"不要自己生成魔法输出。"
"简洁地回复一条简短消息,提及调用函数,"
"但不提供函数输出本身。"
"将该简短消息放在'response'属性中。"
"例如:'好的,我会运行magicoutput函数并返回输出。'"
"同时用正确的参数填充'tool'属性。"
)
},
{
"role": "user",
"content": "使用函数获取这些参数的魔法输出(obj1 = Ollama和obj2 = Gemma3)"
}
]
# 调用Ollama并处理响应
result = await persistent_session.ollama_chat(messages)
print("最终结果:", result)
# 完成后关闭持久会话
persistent_session.shutdown()
if __name__ == "__main__":
asyncio.run(main())
你可以看到输出工作得非常完美。我们收到了一个带有模型简短消息的响应,然后是一个带有将发送到MCP服务器的参数的工具。最后,我们得到了来自服务器的输出,如下所示:
[04/19/25 12:39:55] INFO Starting server "TestServer"... server.py:261
准备调用工具。
Ollama响应 {
"response": "好的,我将使用magicoutput函数获取obj1和obj2的魔法输出。",
"tool": {"obj1": "Wombat", "obj2": "Dog"}
}
最终结果: meta=None content=[TextContent(type='text', text='输入参数:obj1:Wombat,obj2:Dog,魔法输出:Hello MCP,MCP Hello', annotations=None)] isError=False
持久MCP会话已关闭。
最后的最后
感谢你们的阅读和喜欢,作为一位在一线互联网行业奋斗多年的老兵,我深知在这个瞬息万变的技术领域中,持续学习和进步的重要性。
为了帮助更多热爱技术、渴望成长的朋友,我特别整理了一份涵盖大模型领域的宝贵资料集。
这些资料不仅是我多年积累的心血结晶,也是我在行业一线实战经验的总结。
这些学习资料不仅深入浅出,而且非常实用,让大家系统而高效地掌握AI大模型的各个知识点。如果你愿意花时间沉下心来学习,相信它们一定能为你提供实质性的帮助。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】

大模型知识脑图
为了成为更好的 AI大模型 开发者,这里为大家提供了总的路线图。它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
经典书籍阅读
阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。
实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
面试资料
我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下
640套AI大模型报告合集
这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
】
