1. 为什么你需要一个自己的MCP服务器?
最近在折腾AI应用开发,我发现一个挺有意思的现象:很多开发者都想给大模型“装”上自己的工具,比如查查数据库、发个邮件,或者像咱们今天要做的,搜一下特定领域的新闻。但一提到要自己从头写一个服务,还要能让OpenWebUI这样的Web界面直接调用,不少人就头大了。流程复杂、协议陌生,光是看文档就够喝一壶的。
这时候,MCP(Model Context Protocol) 和 MCPO 这两个东西就派上大用场了。简单来说,MCP是连接大模型和外部工具的一套“普通话”标准,它规定了工具怎么告诉模型“我能干啥”,以及模型怎么调用工具。而MCPO,你可以把它理解为一个“万能翻译官”或者“适配器”。它自己内置了一个标准的OpenAPI接口,然后通过标准输入输出(stdio)去和真正的MCP服务器“对话”。这样一来,任何支持OpenAPI调用的前端(比如OpenWebUI),就能通过MCPO,间接地使用所有遵循MCP协议的工具了。
这个组合的妙处在哪里呢?想象一下,你开发了一个很酷的工具,比如能分析股票数据。按照传统方式,你得为OpenWebUI专门写一套插件,或者自己搞个API服务再让OpenWebUI去对接,非常麻烦。但现在,你只需要按照MCP的规范写好你的工具服务器,然后用MCPO一“包装”,OpenWebUI就能直接识别和调用它了。这相当于你把工具的“驱动”标准化了,一次编写,到处运行。
所以,今天咱们这个实战的目标就很明确了:亲手打造一个能搜索“关税新闻反应”的MCP服务器,然后通过MCPO把它“挂载”到OpenWebUI上,让AI助手能直接使用这个工具来回答相关问题。 这个过程会涉及到环境搭建、代码生成、配置调试和集成测试,我会把我踩过的坑和验证过的稳定方案都分享给你,保证你跟着做就能跑通。
2. 动手之前:搞定你的开发环境
工欲善其事,必先利其器。咱们这个项目会用到Python、Node.js和一些特定的工具链,别担心,我会一步步带你装好。我自己的开发机是Mac,但Windows和Linux的命令也大同小异,关键步骤我都会注明。
### 2.1 核心三件套:Python、Node.js和包管理工具
首先,确保你的电脑上有 Python 3.11 或更高版本。这是OpenWebUI和很多MCP服务器的推荐版本。打开你的终端,输入 python3 --version 或 python --version 检查一下。如果没有,去Python官网下载安装就好。
接下来是 Node.js,主要是为了运行一些基于JavaScript的MCP服务器(比如我们后面会用到的memory服务器)。同样,用 node --version 和 npm --version 检查。安装Node.js最省事的方法是使用nvm(Node Version Manager),可以轻松切换版本。
最后,我强烈推荐一个Python包管理的新秀:uv。它比传统的pip快得多,而且能更好地处理依赖冲突。安装就一行命令(在终端里执行):
curl -LsSf https://astral.sh/uv/install.sh | sh
安装完成后,重新打开终端,输入 uv --version 确认安装成功。之后我们安装Python包都会优先使用 uv pip install 来代替 pip install。
### 2.2 安装OpenWebUI和Ollama(可选但推荐)
OpenWebUI是我们的“操作前台”,一个非常漂亮的Web聊天界面。Ollama则是用来在本地运行开源大模型的,比如Llama 3、Qwen等。虽然我们今天的重点是工具集成,但有一个本地模型配合测试,体验会更完整。
- 安装Ollama:直接去官网 ollama.com 下载安装包,傻瓜式安装。装好后在终端运行
ollama run llama3:8b就可以拉取并运行一个模型试试。 - 安装OpenWebUI:官方推荐用Docker,最简单。确保你安装了Docker Desktop,然后一行命令启动:
启动后,打开浏览器访问docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:mainhttp://localhost:3000,注册一个账号就能用了。
### 2.3 代码生成利器:VS Code + Roo Code + 大模型
咱们要创建的关税新闻MCP服务器,代码量虽然不大,但结构有讲究。为了高效且不出错,我用了“VS Code + Roo Code + 大模型”这个组合拳。VS Code是你的代码编辑器。Roo Code是一个强大的VS Code插件,它内置了连接多种大模型(包括Claude、GPT、Gemini等)的能力,并且原生支持MCP!这意味着你可以在Roo Code里直接测试你的MCP服务器,非常方便。
安装好VS Code后,在扩展市场搜索“Roo Code”安装。然后,你需要在Roo Code的设置里配置一个大模型的API密钥,比如Gemini 2.5 Pro或者GPT-4o。这一步是关键,因为后面我们生成服务器代码就靠它了。准备好这些,你的开发环境就基本就绪了。
3. 第一步:让MCPO先跑起来,连接现成工具
在造新车之前,我们先试试“改装”现有的。MCP社区有很多现成的服务器,比如查时间的time、记事情的memory、抓网页的fetch。我们先让MCPO代理这些工具,感受一下整个工作流。这会让你对MCPO的配置方式有个直观印象。
### 3.1 安装MCPO和示例服务器
首先,创建一个项目文件夹,比如叫 mcpo-playground,然后进入。
mkdir mcpo-playground && cd mcpo-playground
接着,我们创建一个Python虚拟环境来隔离依赖,然后用uv安装MCPO:
python3 -m venv .venv
source .venv/bin/activate # Windows系统用 `.venv\Scripts\activate`
uv pip install mcpo
现在,安装几个社区里有趣的MCP服务器:
# 时间服务器,告诉你现在几点
uv pip install mcp-server-time
# 记忆服务器,像一个临时的记事本,AI可以往里读写内容
npm install -g @modelcontextprotocol/server-memory
# 抓取服务器,可以获取网页内容
uv pip install mcp-server-fetch
### 3.2 编写MCPO的配置文件
MCPO的核心是一个 config.json 文件,它告诉MCPO:我要代理哪些服务器,以及怎么启动它们。我们在项目根目录创建这个文件:
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"time": {
"command": "uvx",
"args": ["mcp-server-time", "--local-timezone=Asia/Shanghai"]
},
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
}
}
}
我来解释一下这个配置:
mcpServers对象里每个键(如memory)是你给这个服务器起的名字,之后在OpenWebUI里会看到。command是启动命令,npx用于运行Node.js包,uvx是uv工具里用来直接运行Python可执行脚本的,非常方便。args是传递给命令的参数。注意time服务器我们设置了时区为上海。
### 3.3 启动MCPO并验证
保存好配置文件后,在终端运行:
uvx mcpo --config config.json --port 8001
如果一切顺利,你会看到类似下面的日志,说明MCPO服务器已经在8001端口启动,并连接上了各个MCP服务器。
Starting MCP OpenAPI Proxy with config file: config.json
INFO: Started server process [1190222]
INFO: Waiting for application startup.
Knowledge Graph MCP Server running on stdio
最酷的是,MCPO会自动为这些工具生成OpenAPI文档。打开你的浏览器,访问 http://localhost:8001/docs。你应该能看到一个Swagger UI页面,里面列出了fetch、time等工具,并且可以尝试调用!比如点开/fetch/相关的POST端点,试试输入一个URL,它能帮你把网页内容抓取回来。这一步成功,就证明你的MCPO代理层工作正常了。
4. 第二步:用AI生成我们自己的关税新闻MCP服务器
现在进入重头戏:创建我们定制化的工具。我们的目标是:一个能搜索近期国际社会对某国关税反应新闻的服务器。手动写虽然可以,但借助AI,我们可以把精力更多放在设计需求和调试上。
### 4.1 向Roo Code下达精准的“开发任务”
打开VS Code,确保Roo Code插件已激活。在Roo Code的聊天窗口里,我们需要输入一份非常清晰、结构化的需求提示(Prompt)。这份Prompt的质量直接决定了生成代码的质量。我是这样写的:
**项目目标:**创建一个基于Python的MCP服务器,提供一个工具,用于搜索有关近期宣布的关税的国际反应的新闻文章,同时支持stdio和SSE传输。
**核心需求:**
1. **工具功能**:提供一个名为 `get_tariff_reaction_news` 的工具。用户(或AI)可以输入一个`country`(国家,可选)和 `additional_keywords`(附加关键词,可选),服务器将使用DuckDuckGo搜索引擎,查找过去一周内关于该国对该关税反应的新闻。
2. **技术栈**:
* 使用官方 `mcp` Python SDK(版本>=1.6.0)。
* 使用 `duckduckgo-search` 库执行搜索。
* 使用 `pydantic` 定义严格的输入输出数据模型。
* 使用 `click` 库处理命令行参数(`--transport` 选择stdio或sse,`--port` 设置SSE模式端口)。
* 支持两种传输方式:stdio(默认,用于MCPO集成)和SSE(可用于直接测试)。
3. **代码结构**:请生成完整的项目文件,包括:
* `pyproject.toml`:定义项目元数据和依赖。
* `src/tariff_news_server/` 包目录,内含:
* `schemas.py`:定义Pydantic模型(输入、输出、搜索结果项)。
* `tool.py`:实现核心的搜索工具函数。
* `server.py`:MCP服务器主入口,注册工具并启动。
* `requirements.txt` 或通过 `pyproject.toml` 管理依赖。
4. **打包与运行**:项目应可通过 `pip install -e .` 以开发模式安装,并提供一个控制台入口点 `tariff-news-server` 来启动服务器。
把这段Prompt发给Roo Code连接的大模型(比如Gemini 2.5 Pro)。稍等片刻,它就会生成一个完整的、结构清晰的Python项目。我实测下来,生成的代码质量很高,基本可以直接运行,只需要我们微调一下搜索逻辑和错误处理。
### 4.2 关键代码解析与微调
AI生成的代码通常骨架很好,但有些细节需要我们关注。我们打开生成的 src/tariff_news_server/tool.py 文件,看看核心的搜索函数:
from duckduckgo_search import DDGS
from ..schemas import GetTariffReactionNewsInput, SearchResultItem
import logging
logger = logging.getLogger(__name__)
async def get_tariff_reaction_news(input_data: GetTariffReactionNewsInput):
"""执行关税新闻搜索的工具函数。"""
logger.info(f"Executing search with query for country: {input_data.country}")
# 构建搜索查询词
base_query = "reactions to new tariffs announcements"
if input_data.country:
query = f"reactions from {input_data.country} to new tariffs"
else:
query = base_query
if input_data.additional_keywords:
query = f"{query} {input_data.additional_keywords}"
try:
with DDGS() as ddgs:
# 使用DuckDuckGo搜索,限制时间为过去一周('w')
results = list(ddgs.news(
keywords=query,
timelimit='w',
max_results=10
))
except Exception as e:
logger.error(f"DuckDuckGo search failed: {e}")
# 这里可以返回一个友好的错误信息,而不是直接抛出异常
return [{"title": "Search Error", "snippet": f"Failed to fetch news: {str(e)}", "url": "", "source": "System", "published_date": None}]
# 将搜索结果转换为我们定义的格式
search_results = []
for r in results:
# 注意:duckduckgo-search返回的字段名可能是 `date` 而不是 `published_date`
item = SearchResultItem(
title=r.get('title', ''),
url=r.get('url', ''),
snippet=r.get('body', '')[:200], # 摘要取前200字符
source=r.get('source', ''),
published_date=r.get('date') # 适配实际返回的字段
)
search_results.append(item)
logger.info(f"Found {len(search_results)} results.")
return search_results
这里有几个我修改过的点:
- 查询词:我将原来固定的“April 2025”改成了更通用的“new tariffs announcements”,这样工具适用性更广。
- 错误处理:在搜索失败时,我让它返回一个结构化的错误结果,而不是直接抛出异常,这样前端展示会更友好。
- 字段映射:
duckduckgo-search库返回的新闻结果字段名可能和AI生成的模型略有不同(比如body对应snippet,date对应published_date),需要根据实际情况调整。
### 4.3 在Roo Code中本地测试
代码生成并调整好后,我们首先在Roo Code内部测试。在Roo Code的侧边栏找到“MCP Servers”选项卡,点击“+”添加服务器。选择“Stdio”模式,然后配置启动命令。因为我们是以开发模式安装的,命令可以这样填:
Command: python
Args: -m, tariff_news_server.server
添加成功后,你会在聊天模型的选择旁看到一个工具图标。点击它,如果能看到 get_tariff_reaction_news 这个工具,就说明服务器加载成功了!你可以直接在聊天框里问:“What's the latest reaction from Germany regarding the new tariffs?”。Roo Code会调用你这个本地服务器,并返回搜索结果。这一步测试能极大节省后续集成调试的时间。
5. 第三步:将自定义服务器集成到MCPO和OpenWebUI
本地测试通过,意味着我们的MCP服务器本身是健康的。现在要把它接入MCPO这个代理,并最终在OpenWebUI的Web界面上使用。
### 5.1 更新MCPO配置文件
编辑我们之前创建的 config.json,在 mcpServers 对象里添加我们的关税新闻服务器:
{
"mcpServers": {
"memory": { ... },
"time": { ... },
"fetch": { ... },
"tariff_news": {
"command": "python",
"args": [
"-m",
"tariff_news_server.server"
]
}
}
}
这里我给这个服务器起名叫 tariff_news。command 是 python,args 指定了以模块方式运行我们的服务器主文件。确保你的终端当前处于项目的虚拟环境中,然后重启MCPO服务器:
uvx mcpo --config config.json --port 8001
重启后,再次访问 http://localhost:8001/docs,你应该能看到多出了一组 tariff_news 相关的API端点。点击试试调用,输入 {"country": "Canada"},看看能否返回加拿大的相关新闻。这一步验证了MCPO已经成功“代理”了我们的新工具。
### 5.2 在OpenWebUI中添加工具
打开你的OpenWebUI(通常是 http://localhost:3000),登录后点击左下角的设置图标(⚙️),进入“设置”页面。在左侧菜单找到“工具”(Tools),点击右上角的“+ Add Tool”按钮。
关键配置如下:
- Tool Name:可以填一个易懂的名字,比如“Tariff News Search”。
- API URL:这里就填MCPO提供的OpenAPI接口地址,注意要精确到我们工具的端点:
http://localhost:8001/tariff_news/get_tariff_reaction_news。(根据你的MCPO配置,端口和路径可能不同,以/docs页面显示的为准)。 - Schema:这里通常选择“OpenAPI”。神奇的是,OpenWebUI通常能根据API URL自动获取并填充参数格式。如果不行,你可能需要手动填写一下输入参数的JSON Schema,这在我们代码的Pydantic模型里可以找到。
保存后,这个工具就添加成功了。你可以打开一个新的聊天会话,在输入框旁边找到那个工具图标(🔧),点开它,应该能看到“Tariff News Search”在可用工具列表里,并且是已启用状态。
### 5.3 最终测试与效果验证
现在,就是见证奇迹的时刻。在OpenWebUI的聊天框里,选择任意一个你连接好的大模型(本地Ollama的Llama 3或者云端的API模型都可以),然后直接提问:“Can you find me some recent news about how Vietnam is reacting to the latest tariff changes?”
如果一切配置正确,你会看到模型在思考过程中,自动调用了“Tariff News Search”工具,并将工具的返回结果整合到它的最终回复中。回复里会包含从网上搜到的、一周内关于越南对关税反应的新闻摘要和链接。
同时,你可以回头查看运行MCPO服务器的终端窗口。里面应该有详细的调用日志,记录着OpenWebUI发来的请求、工具接收到的参数、执行的搜索查询以及返回的结果数量。这串日志是你调试和验证整个调用链路是否通畅的最有力证据。看到工具被成功调用并返回数据,这个从零构建、集成、测试的完整闭环就圆满完成了。
1958




被折叠的 条评论
为什么被折叠?



