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

从零开始构建 Ollama + MCP 服务器

Model Context Protocol(模型上下文协议)在过去几个月里已经霸占了大家的视野,出现了许多酷炫的集成示例。我坚信它会成为一种标准,因为它正在定义工具与代理或软件与 AI 模型之间如何集成的新方式。

我决定尝试将 Ollama 中的一个小型 LLM 连接到一个 MCP 服务器上,感受一下这个新标准的魅力。今天,我想向大家展示一种将 Ollama 与 MCP 服务器集成的可能实现方式。
在这里插入图片描述

魔法配方

集成的主要步骤如下:

  1. 创建一个测试用的 MCP 服务器。
  2. 创建一个客户端文件,用于发送请求并启动服务器。
  3. 将服务器的工具获取到客户端。
  4. 将工具转换为 Pydantic 模型。
  5. 通过响应的 format 字段将工具(作为 Pydantic 模型)传递给 Ollama。
  6. 通过 Ollama 发送对话并接收结构化输出。
  7. 如果响应中包含工具,则向服务器发起请求。

安装依赖

要运行这个项目,需要安装所需的包。fastmcp 库在使用 uv 运行代码时表现最佳。它像 Poetry 和 pip 一样,下载方便,使用简单。

使用以下命令将所需的库添加到项目中:

uv add fastmcp ollama

这将安装 MCP 服务器和 Ollama 聊天库,你可以在此基础上构建客户端和服务器逻辑。

文件结构

设置完成后,你的文件夹结构应该如下所示:

your folder
├── server.py
└── client.py

server.py 文件包含 MCP 服务器和你想要暴露的工具。client.py 文件在后台进程启动服务器,获取可用工具,并与 Ollama 连接。

示例 MCP 服务器

让我们从创建一个简单的 MCP 服务器开始,使用 fastmcp 库。服务器暴露了一个名为 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:"""使用这个函数来获取神奇的输出"""return "WomboWombat"
if __name__ == "__main__":mcp.run()

获取服务器工具

为了连接到 MCP 服务器并列出可用工具,我们使用 ClientSessionStdioServerParametersstdio_client,它们都来自 mcp 库。

我们定义了一个名为 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 Anyclass OllamaMCP:"""Ollama 和 FastMCP 之间的简单集成"""def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsprint(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/14/25 22:29:08] INFO     正在启动服务器 "TestServer"...    server.py:171INFO     正在处理请求类型         server.py:534ListToolsRequest
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 类型。

这可以帮助我们动态定义模型,以便 LLM 精确地知道在返回工具参数时应使用什么结构。

# 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, Fieldclass OllamaMCP:"""Ollama 和 FastMCP 之间的简单集成"""def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsexcept 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] = modelif 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 = Responseprint(Response.model_fields)@staticmethoddef 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, ...)# 从零开始构建 Ollama + MCP 服务器Model Context Protocol(模型上下文协议)在过去几个月里已经霸占了大家的视野,出现了许多酷炫的集成示例。我坚信它会成为一种标准,因为它正在定义工具与代理或软件与 AI 模型之间如何集成的新方式。既然我最喜欢写的是小型语言模型(LLMs),我决定尝试将 Ollama 中的一个小型 LLM 连接到一个 MCP 服务器上,感受一下这个新标准的魅力。今天,我想向大家展示一种将 Ollama 与 MCP 服务器集成的可能实现方式。## 魔法配方集成的主要步骤如下:1. 创建一个测试用的 MCP 服务器。
2. 创建一个客户端文件,用于发送请求并启动服务器。
3. 将服务器的工具获取到客户端。
4. 将工具转换为 Pydantic 模型。
5. 通过响应的 `format` 字段将工具(作为 Pydantic 模型)传递给 Ollama。
6. 通过 Ollama 发送对话并接收结构化输出。
7. 如果响应中包含工具,则向服务器发起请求。## 安装依赖要运行这个项目,需要安装所需的包。`fastmcp` 库在使用 `uv` 运行代码时表现最佳。它像 Poetry 和 pip 一样,下载方便,使用简单。使用以下命令将所需的库添加到项目中:```csharp
uv add fastmcp ollama

这将安装 MCP 服务器和 Ollama 聊天库,你可以在此基础上构建客户端和服务器逻辑。

文件结构

设置完成后,你的文件夹结构应该如下所示:

your folder
├── server.py
└── client.py

server.py 文件包含 MCP 服务器和你想要暴露的工具。client.py 文件在后台进程启动服务器,获取可用工具,并与 Ollama 连接。

示例 MCP 服务器

让我们从创建一个简单的 MCP 服务器开始,使用 fastmcp 库。服务器暴露了一个名为 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:"""使用这个函数来获取神奇的输出"""return "WomboWombat"
if __name__ == "__main__":mcp.run()

获取服务器工具

为了连接到 MCP 服务器并列出可用工具,我们使用 ClientSessionStdioServerParametersstdio_client,它们都来自 mcp 库。

我们定义了一个名为 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 Anyclass OllamaMCP:"""Ollama 和 FastMCP 之间的简单集成"""def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsprint(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/14/25 22:29:08] INFO     正在启动服务器 "TestServer"...    server.py:171INFO     正在处理请求类型         server.py:534ListToolsRequest
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 类型。

这可以帮助我们动态定义模型,以便 LLM 精确地知道在返回工具参数时应使用什么结构。

# 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, Fieldclass OllamaMCP:"""Ollama 和 FastMCP 之间的简单集成"""def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsexcept 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] = modelif 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 = Responseprint(Response.model_fields)@staticmethoddef 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, ...)

运行代码后,print(Response.model_fields) 的输出将显示我们刚刚构建的响应模型的完整结构。该模型包括两部分:一部分是助手发送回用户的消息,另一部分是可选字段,用于保存工具参数。

如果模型填写了 tool 字段,我们将使用它来调用服务器。否则,我们只使用纯响应字符串。

uv run python -m convert_tools
[04/15/25 10:15:32] INFO     正在启动服务器 "TestServer"...    server.py:171INFO     正在处理请求类型         server.py:534ListToolsRequest
{'response': FieldInfo(annotation=str, required=True, description='确认将调用该函数的消息'), 'tool': FieldInfo(annotation=Magicoutput, required=True, description='用于运行并获取神奇输出的工具')}

使用后台线程和队列调用工具

现在工具已经作为 Pydantic 模型可用,我们可以继续启用工具调用。为此,我们使用一个后台线程并设置两个队列。一个用于向服务器发送请求,另一个用于接收响应。

call_tool 方法将请求放入队列中,后台线程监听该请求。一旦使用 MCP 会话调用工具,结果将放入响应队列中。

import asyncio
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, Fieldclass OllamaMCP:"""Ollama 和 FastMCP 之间的简单集成"""def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsself.initialized.set()while True:try:tool_name, arguments = self.request_queue.get(block=False)except queue.Empty:await asyncio.sleep(0.01)continueif tool_name is None:print("收到关闭信号。")breaktry: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 resultdef 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] = modelif 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@staticmethoddef 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/15/25 11:37:47] INFO     正在启动服务器 "TestServer"...    server.py:171INFO     正在处理请求类型         server.py:534ListToolsRequest
已准备好调用工具。INFO     正在处理请求类型         server.py:534CallToolRequest
meta=None content=[TextContent(type='text', text='WomboWombat', annotations=None)] isError=False

Ollama + MCP

随着队列和 call_tool 函数准备就绪,现在是时候集成 Ollama 了。我们将响应类传递给 Ollama 的 format 字段,告诉我们的 LLM(这里使用 Gemma)在生成输出时遵循该模式。

我们还定义了一个 ollama_chat 方法,用于发送对话,验证模型的响应是否符合模式,并检查是否包含工具。如果是,则提取函数名和参数,然后使用持久的 MCP 会话在后台线程中调用它。

main 函数中,我们设置服务器连接,启动后台循环,并等待一切准备就绪。然后我们准备一个系统提示和用户消息,将它们发送到 Ollama,并等待结构化输出。

最后,我们打印服务器的结果并关闭会话。

import asyncio
import threading
import queuefrom 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 chatclass OllamaMCP:def __init__(self, server_params: StdioServerParameters):self.server_params = server_paramsself.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 = sessiontools_result = await session.list_tools()self.tools = tools_result.toolsself.initialized.set()while True:try:tool_name, arguments = self.request_queue.get(block=False)except queue.Empty:await asyncio.sleep(0.01)continueif tool_name is None:breaktry: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 resultdef shutdown(self):"""清理并关闭持久会话。"""self.request_queue.put((None, None))self.thread.join()print("持久 MCP 会话已关闭。")@staticmethoddef 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] = modelif 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 = Responseasync 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 = 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.toolif 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 outputelse:print("响应中未检测到工具。返回纯文本响应。")return response_obj.responseasync 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 = Wombat 和 obj2 = Dog)"}]# 调用 Ollama 并处理响应。result = await persistent_session.ollama_chat(messages)print("最终结果:", result)# 关闭持久会话。persistent_session.shutdown()if __name__ == "__main__":asyncio.run(main())

你可以看到输出完美无缺。我们收到了一个包含简短消息的 response,以及将发送到 MCP 服务器的 tool 和参数。最后,我们得到了服务器的输出,如下所示:

[04/15/25 09:52:49] INFO     正在启动服务器 "TestServer"...    server.py:171INFO     正在处理请求类型         server.py:534ListToolsRequest
已准备好调用工具。
Ollama 响应 {
"response": "好的,我将运行 magicoutput,参数为 obj1 = Wombat 和 obj2 = Dog。",
"tool": {"obj1": "Wombat", "obj2": "Dog"}
}[04/15/25 09:52:52] INFO     正在处理请求类型         server.py:534CallToolRequest
最终结果: meta=None content=[TextContent(type='text', text='WomboWombat', annotations=None)] isError=False
持久 MCP 会话已关闭。

更多精彩内容

如果你真的喜欢这些博客,我敢肯定你也会喜欢接下来的内容。一定要为它们点赞,支持每篇博客所需的时间和努力!

总结

就这样,我们刚刚完成了一种将 Ollama 中的小型模型与本地 MCP 服务器连接的方法。

我们从创建一个简单的 MCP 服务器开始,它包含一个工具。然后我们构建了一个客户端,用于连接到服务器,获取工具定义,并将它们转换为 Pydantic 模型。在此基础上,我们通过 format 字段将响应模型传递给 Ollama。模型返回了一个结构化响应,我们使用后台线程和队列在客户端处理工具调用。

点赞并关注我,获取更多类似内容。后期将扩展这个功能,构建一个完整的代理循环,并开始使用真实的 MCP 服务器创建有用的工作流。

相关文章:

从零开始构建 Ollama + MCP 服务器

Model Context Protocol(模型上下文协议)在过去几个月里已经霸占了大家的视野,出现了许多酷炫的集成示例。我坚信它会成为一种标准,因为它正在定义工具与代理或软件与 AI 模型之间如何集成的新方式。 我决定尝试将 Ollama 中的一…...

Oracle--了解Oracle

前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除 一、Oracle简介 Oracle是甲骨文公司开发的一款关系型数据库,是一款可移植性好、使用简单、功能强大的关系型数据库。它为各行业在各类环境…...

Hadoop集群部署教程-END

Hadoop集群部署教程-END 第二十九章:总结与展望 29.1 核心内容回顾 技术体系总结: 分布式存储架构:基于HDFS的多副本机制[^6]计算框架演进:从MapReduce到Spark/Flink的生态演进资源调度优化:YARN的多租户资源隔离方案…...

面试题:谈谈你对覆盖索引的理解

覆盖索引详解 一、定义 覆盖索引(Covering Index)是指‌索引本身包含查询所需的所有字段数据‌,使得数据库引擎无需访问实际数据行即可完成查询。这种技术通过减少磁盘I/O和避免回表操作来提升查询性能‌:ml-citation{ref“1,5” data“cit…...

「Java EE开发指南」用MyEclipse开发EJB 3无状态会话Bean(二)

本教程介绍在MyEclipse中开发EJB 3无状态会话bean,由于JPA实体和EJB 3实体非常相似,因此本教程不涉及EJB 3实体Bean的开发。在本教程中,您将学习如何: 创建EJB 3项目创建无状态会话bean部署并测试bean 在上文中(点击…...

QML ListView:实现可拖拽排序的组件

目录 引言相关阅读工程结构示例代码详解主窗口(Main.qml)可拖拽列表视图(DraggableListView.qml)基础结构列表项委托拖拽功能实现动画效果 运行效果总结下载链接 引言 在现代UI设计中,可拖拽排序的列表视图是一种常见…...

面向Java程序员的Python AI开发教程

一、Java与Python核心语法对比&#xff08;快速迁移指南&#xff09; 代码结构与类型系统 代码块定义 Java使用{}定义作用域&#xff0c;Python依赖缩进&#xff08;4空格或制表符&#xff09;。例如循环结构&#xff1a; // Java for (int i0; i<10; i) {System.out.print…...

【开篇:机器学习:新能源汽车研发的“数据炼金术“】

机器学习&#xff1a;新能源汽车研发的"数据炼金术" 一、为什么工程师需要机器学习&#xff1f; 在新能源车试验场&#xff0c;每一次碰撞测试要消耗70万元&#xff0c;电池包循环测试耗时超过1000小时&#xff0c;ADAS场景库建立需要数万个corner cases。这不是简…...

【李宏毅深度学习——分类模型的PyTorch架构】Homework 2:Phoneme Classification

目录 一、数据集介绍 1、数据来源 2、数据预处理与格式 二、数据处理方面代码分析 1、下载数据集 2、为全项目过程设置随机数种子 3、预处理相关函数定义 &#xff08;1&#xff09;第一个函数&#xff1a;加载特征数据 load_feat &#xff08;2&#xff09;第二个函数…...

如何实现一个“纯净”的空对象(无原型链属性)?

在 JavaScript 中&#xff0c;要创建一个完全“纯净”的空对象&#xff08;无原型链属性&#xff0c;即对象的原型链指向 null&#xff09;&#xff0c;可以通过以下方法实现&#xff1a; 核心方法&#xff1a;Object.create(null) const pureObject Object.create(null);特性…...

STM32江科大----------PID算法

声明&#xff1a;本人跟随b站江科大学习&#xff0c;本文章是观看完视频后的一些个人总结和经验分享&#xff0c;也同时为了方便日后的复习&#xff0c;如果有错误请各位大佬指出&#xff0c;如果对你有帮助可以点个赞小小鼓励一下&#xff0c;本文章建议配合原视频使用❤️ 如…...

计算机视觉相机模型与标定:如何让计算机“看懂”三维世界?

计算机视觉相机模型与标定:如何让计算机“看懂”三维世界? 一、前言二、相机模型基础​2.1 针孔相机模型​2.1.1 模型原理​2.1.2 代码示例​2.2 透视变换与相机内参​2.2.1 透视变换矩阵​2.2.2 内参矩阵的作用​2.3 相机外参​2.3.1 世界坐标系与相机坐标系的转换​2.3.2 外…...

ETL数据集成平台在制造业有哪些应用场景

在制造业的数字化转型中&#xff0c;数据如同散落的拼图——生产线的实时参数、供应链的物流轨迹、质量检测的海量报告……每一块都承载着关键信息&#xff0c;却因系统割裂、格式混乱而难以拼出完整价值图谱。如何让数据从“成本负担”变为“战略资产”&#xff1f;ETL&#x…...

Redis-07-常见Redis使用场景

文章目录 01.缓存数据&#xff08;Cache&#xff09;02.布式锁&#xff08;Distributed Lock&#xff09;03.计数器&#xff08;Counter&#xff09;04.排行榜&#xff08;Leaderboard&#xff09;05.消息队列&#xff08;Message Queue&#xff09;06.限流&#xff08;Rate Li…...

开源模型应用落地-Podcastfy-从文本到声音的智能跃迁-Gradio(一)

一、前言 在当今信息呈现方式越来越多样化的背景下&#xff0c;如何将文字、图片甚至视频高效转化为可听的音频体验&#xff0c;已经成为内容创作者、教育者和研究者们共同关注的重要话题。Podcastfy是一款基于Python的开源工具&#xff0c;它专注于将多种形式的内容智能转换成…...

【AI News | 20250416】每日AI进展

AI Repos 1、Tutorial-Codebase-Knowledge 自动分析 GitHub 仓库并生成适合初学者的通俗易懂教程&#xff0c;清晰解释代码如何运行&#xff0c;还能生成可视化内容来展示核心功能。爬取 GitHub 仓库并从代码中构建知识库&#xff1b;分析整个代码库以识别核心抽象概念及其交互…...

iOS内存管理中的强引用问题

iOS内存管理 关于强引用循环 强引用循环是 ARC 无法自动处理的常见问题。如果两个对象互相强引用对方&#xff0c;就会造成引用计数不为零&#xff0c;导致对象无法释放。典型的情况是在闭包中引用 self 时&#xff0c;self 和闭包之间可能会互相持有&#xff0c;形成强引用循…...

电脑一直不关机会怎么样?电脑长时间不关机的影响

现代生活中&#xff0c;许多人会让自己的电脑24小时不间断运行&#xff0c;无论是为了持续的工作、娱乐&#xff0c;还是出于忘记关机的习惯。然而&#xff0c;电脑长时间不关机&#xff0c;除了提供便利之外&#xff0c;也可能对设备的健康产生一系列影响。本文将为大家介绍电…...

openGauss DataVec + Dify,快速搭建你的智能助手平台

在当今数字化和智能化的时代&#xff0c;大语言模型&#xff08;LLM&#xff09;的应用正以前所未有的速度改变着各个领域的工作方式和用户体验。Dify 作为一个开源的大语言模型应用开发平台&#xff0c;为开发者们提供了便捷且强大的工具&#xff0c;助力构建从基础智能体到复…...

React 入门教程:构建第一个 React 应用

本教程将带你从零开始构建你的第一个 React 应用。我们将创建一个简单的计数器应用&#xff0c;涵盖 React 的基本概念和开发流程。 准备工作 在开始之前&#xff0c;请确保你的开发环境满足以下要求&#xff1a; Node.js (建议使用最新的 LTS 版本) npm 或 yarn (Node.js 安…...

【数字图像处理】数字图像空间域增强(3)

图像锐化 图像细节增强 图像轮廓&#xff1a;灰度值陡然变化的部分 空间变化&#xff1a;计算灰度变化程度 图像微分法&#xff1a;微分计算灰度梯度突变的速率 一阶微分&#xff1a;单向差值 二阶微分&#xff1a;双向插值 一阶微分滤波 1&#xff1a;梯度法 梯度&#xff1…...

mapstruct使用详解

一、背景&#xff1a;为什么需要 mapstruct 在 Java 开发中&#xff0c;对象之间的映射&#xff08;如 DTO 转实体类、实体类转 VO&#xff09;是一项高频且繁琐的任务。手动编写映射代码存在以下问题&#xff1a; 冗余代码多&#xff1a;每个类都需要重复编写 setter 和 get…...

Token与axios拦截器

目录 一、Token 详解 1. Token 的定义与作用 2. Token 的工作流程 3. Token 的优势 4. Token 的安全实践 5. JWT 结构示例 二、Axios 拦截器详解 1. 拦截器的作用 2. 请求拦截器 3. 响应拦截器 4. 拦截器常见场景 5. 移除拦截器 三、完整代码示例 四、总结 五、…...

windows上安装Jenkins

1. 下载windows版 jenkins安装包 2. 配置本地安全策略 在 Windows 11/10 上打开本地安全策略。 Secpol.msc 或本地安全策略编辑器是一个 Windows 管理工具&#xff0c;允许您在本地计算机上配置和管理与安全相关的策略。 安全设置-》本地策略-》用户权限分配-》作为服务登录…...

鸿蒙NEXT开发文件预览工具类(ArkTs)

import { uniformTypeDescriptor } from kit.ArkData; import { filePreview } from kit.PreviewKit; import { FileUtil } from ./FileUtil; import { AppUtil } from ./AppUtil; import { WantUtil } from ./WantUtil;/*** 文件预览工具类* 提供文件预览、加载、判断等功能。…...

【WPF-VisionMaster源代码】应用OpenCVSharp仿Vision Master页面开发的软件源代码

一、目的&#xff1a;开放WPF-VisionMaster源代码 二、简介 WPF-Vision Master 视觉处理软件源码 WPF-Vision Master是基于WPF-Control的UI框架与OpenCVSharp计算机视觉库联合&#xff0c;并参考Vision Master界面开发的视觉处理软件。该平台深度融合WPF强大的界面控制能力和Op…...

软件研发过程中的技术债

引言&#xff1a;数字时代的“技术利息” 在金融领域&#xff0c;债务是推动发展的杠杆&#xff1b;而在软件开发中&#xff0c;技术债&#xff08;Technical Debt&#xff09;却是一把双刃剑。据行业调查显示&#xff0c;70%的软件项目存在技术债&#xff0c;其中超过半数团队…...

鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析

一、项目初始化与环境准备 1. 创建鸿蒙工程 src/main/ets/ ├── api/ │ ├── api.ets # 接口聚合入口 │ ├── login.ets # 登录模块接口 │ └── request.ets # 网络请求核心封装 └── pages/ └── login.ets # 登录页面逻辑…...

重新定义“边缘”:边缘计算如何重塑人类与数据的关系

在数字化浪潮中&#xff0c;云计算曾是科技界的宠儿&#xff0c;但如今&#xff0c;边缘计算正在悄然改变游戏规则。它不仅是一种技术进步&#xff0c;更是对人类与数据关系的一次深刻反思。本文将探讨边缘计算如何从“中心化”走向“分布式”&#xff0c;以及它如何在效率、隐…...

Flink CDC 出现错误码 1236 和 SQL 状态 HY000 的原因及解决方法

Flink CDC 出现错误码 1236 和 SQL 状态 HY000 的原因及解决方法 常见原因 server-id 冲突:当多个 Flink CDC 任务连接同一个 MySQL 实例,且使用了相同的 server-id 时,会导致该冲突。因为 MySQL 服务器通过 server-id 来区分不同的从服务器,如果多个 Flink CDC 任务使用相…...

如何解除Excel只读状态?4种方法全解析

在日常办公中&#xff0c;我们经常会遇到Excel文件被设置为只读模式的情况。只读模式可以防止文件被意外修改&#xff0c;但在需要编辑时&#xff0c;解除只读模式就显得尤为重要。下面小编分享退出Excel只读方式的4种方法&#xff0c;让你能够轻松编辑工作表。 方法1&#xf…...

深度学习与 Flask 应用常见问题解析

在深度学习和 Flask 应用开发过程中&#xff0c;我们常常会遇到一些关键的知识点和容易混淆的问题。下面我们就来对这些问题进行详细的解析。 一、卷积神经网络&#xff08;CNN&#xff09;常用层 在定义卷积神经网络时&#xff0c;有一些常用的层&#xff1a; Conv2D&#x…...

零浪费,最高效率:通往0%废品率的道路

在工业自动化领域&#xff0c;努力实现废品率为 0% 是最大的挑战之一。这意味着不生产任何有缺陷的产品 —— 从而减少浪费、降低成本&#xff0c;并提高客户满意度。尽管实现这一目标颇具雄心壮志&#xff0c;但企业可以采取几个步骤来改进自身流程&#xff0c;以达成这一目标…...

【c++深入系列】:new和delete运算符详解

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; “生活不会向你许诺什么&#xff0c;尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去&#xff0c;终有一天&…...

基础(测试用例:介绍,测试用例格式,案例)

目录 测试用例介绍 测试用例编写格式 案例 测试用例介绍 用例&#xff1a;用户使用软件的案例场景 测试用例&#xff1a;是为测试项目而设计的测试执行文档 测试用例的作用&#xff1a; 防止漏测是实施测试的标准可以作为测试工作量的评估 测试用例编写格式 用例编号 用例…...

MCP协议,.Net 使用示例

服务器端示例 基础服务器 以下是一个基础的 MCP 服务器示例&#xff0c;它使用标准输入输出&#xff08;stdio&#xff09;作为传输方式&#xff0c;并实现了一个简单的回显工具&#xff1a; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.H…...

Node.js 数据库 事务 项目示例

1、参考&#xff1a;JavaScript语言的事务管理_js 函数 事务性-CSDN博客 或者百度搜索&#xff1a;Nodejs控制事务&#xff0c; 2、实践 2.1、对于MySQL或MariaDB&#xff0c;你可以使用mysql或mysql2库&#xff0c;并结合Promise或async/await语法来控制事务。 使用 mysql2…...

【AI插件开发】Notepad++ AI插件开发实践:支持多平台多模型

引言 上篇文章我们的Notepad插件介绍到Dock窗口集成&#xff0c;本篇将继续完善插件功能&#xff0c;主要包括两个部分&#xff1a; 支持多平台、多模型支持多种授权验证、接口类型 一、多平台 原先的配置项很简单&#xff1a; // PluginConf.h class PlatformConf { publ…...

微信小程序数字滚动效果

效果图 .wxml <view class"container"><view class"container-num" wx:for"{{number}}" wx:key"index"><view class"num-container" style"--h:{{h}}px;--y:{{-item * h }}px;"><view wx:f…...

wx219基于ssm+vue+uniapp的教师管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…...

Python 注释进阶之Google风格

文章目录 1. Google 风格 Docstring 的核心特点2. Google 风格的基本结构3. 编写规则和注意事项4. 最常用的 Google 风格 Docstring 示例示例 1&#xff1a;普通函数 示例 2&#xff1a;带默认参数和可变参数的函数示例 3&#xff1a;类示例 4&#xff1a;生成器函数示例 5&…...

写测试文档时,需要的环境配置怎么查看

操作系统 cat /etc/os-releaseCPU信息 lscpu 内存 sudo dmidecode --type memory | grep -E "Size:|Type:|Speed:"硬盘 列出当前系统中 所有块设备&#xff08;Block Devices&#xff09; 的信息&#xff0c;并显示指定列&#xff08;-o 参数&#xff09; lsblk…...

强化学习的数学原理(十)actor-critic 方法

由于全文太长&#xff0c;只好分开发了。(已完结&#xff01;在专栏查看本系列其他文章&#xff09; 个人博客可以直接看全文~ 本系列为在学习赵世钰老师的“强化学习的数学原理” 课程后所作笔记。 课堂视频链接https://www.bilibili.com/video/BV1sd4y167NS/ 第十章 acto…...

多个定时器同时工作时,会出现哪些常见的bug ,如何解决??(定时任务未实时更新但刷新后正常的问题分析)

1. 定时器冲突与覆盖 问题&#xff1a;后设置的定时器可能覆盖先前的定时器&#xff0c;导致前一个定时器失效 原因&#xff1a;未正确管理定时器ID或未清除前一个定时器 2. 性能问题 内存泄漏&#xff1a;未清除不再需要的定时器会导致内存占用不断增加 CPU过载&#xff1a…...

代码随想录算法训练营day5(哈希表)

华子目录 有效的字母异位词思路 有效的字母异位词 https://leetcode.cn/problems/valid-anagram/description/ 思路 使用哈希表&#xff0c;这里哈希表使用数组先申请一个26空间的大小的数组遍历第一个字符串&#xff0c;记录每个字符出现的次数1遍历第二个字符串&#xff0c…...

Python(17)Python字符编码完全指南:从存储原理到乱码终结实战

目录 背景介绍一、字符编码核心原理1. 计算机存储本质2. Python3的编码革命3. 主流编码格式对比 二、编码转换核心方法1. 编码&#xff08;Encode&#xff09;过程2. 解码&#xff08;Decode&#xff09;过程3. 错误处理策略 三、文件操作编码实战1. 文本文件读写2. 二进制模式…...

Node.js 文件读取与复制相关内容

Node.js 文件读取与复制相关内容的系统总结&#xff0c;包括 同步读取、异步读取、流式读取、复制操作、两者对比及内存测试。 &#x1f9e9; 一、Node.js 文件读取方式总结 Node.js 使用 fs&#xff08;文件系统&#xff09;模块进行文件操作&#xff1a; 1. 同步读取&#…...

大数据面试问答-HBase/ClickHouse

1. HBase 1.1 概念 HBase是构建在Hadoop HDFS之上的分布式NoSQL数据库&#xff0c;采用列式存储模型&#xff0c;支持海量数据的实时读写和随机访问。适用于高吞吐、低延迟的场景&#xff0c;如实时日志处理、在线交易等。 RowKey&#xff08;行键&#xff09; 定义&#xf…...

jupyter 文件浏览器,加强版,超好用,免费exe

第一步&#xff1a;github搜索 lukairui的 jupyter-viewer-plus 仓库 第二步&#xff1a; git clone 到本地。 解压zip包 第三步&#xff1a; 进入压缩包&#xff0c;第一次双击打开jupyter-viewer-plus.exe运行&#xff0c;第一次运行后&#xff0c;界面上有一个“设为…...

【AI工具】用大模型生成脑图初试

刚试用了一下通过大模型生成脑图&#xff0c;非常简单&#xff0c;记录一下 一、用大模型生成脑图文件 关键&#xff1a;存在markdown文件 举例&#xff1a;使用Deepseek&#xff0c;输入问题&#xff1a;“针对大模型的后训练&#xff0c;生成一个开发计划&#xff0c;用ma…...