
构建高效的模型上下文协议服务器:使用jina.ai和python实现web搜索与事实核查功能
在这篇文章中,我们将讨论模型上下文协议,它的重要性,以及构建一个MCP服务器的过程,以帮助我们与Jina.ai进行交互,并能够在Claude Desktop中使用Python和FastMCP添加网络搜索和事实核查功能。
Anthropic 宣布 这是在去年的感恩节期间。尽管引起了一些关注,但考虑到它可能是开发下一层AI软件堆栈的关键踏脚石,其认可度可能仍然不足。
什么
模型上下文协议 (MCP) 是一个针对大型语言模型 (LLMs) 和其他生成性人工智能工具的标准化通信协议。
可以将其视为“生成性人工智能的 HTTP”——就像 HTTP 标准化了网页浏览器与网页服务器之间的通信,MCP 标准化了 LLM 应用程序与工具和数据源之间的通信。
为什么我们需要MCP?
当前大型语言模型(LLM)开发面临几个障碍:
-
工具集成复杂性:每个LLM服务(如OpenAI、Anthropic等)都有其实现工具调用和函数调用的方式,这使得构建可移植工具变得复杂。
-
上下文管理:LLM需要访问各种数据源和工具,但安全高效地管理这种访问一直是个挑战。
-
标准化:没有标准协议,开发人员必须为他们希望支持的每个LLM平台重建集成层。
MCP通过提供以下解决方案来应对这些挑战:
- 一种标准化的方式来向LLM暴露工具和数据
- 一个安全的客户端-服务器架构
- 一个一致的接口,无论底层LLM如何
MCP 是如何工作的?
MCP 遵循客户端-服务器架构,主要由三个组件组成:
-
MCP 服务器:一个暴露以下内容的服务:
- 工具(LLMs 可以调用的函数)
- 资源(数据源)
- 提示(模板化指令)
- 上下文(动态信息)
-
MCP 客户端:该应用程序连接到 MCP 服务器并管理 LLM 与服务器之间的通信。客户端支持仍处于早期阶段,目前只有少数工具实现了协议规范的任何部分,还有一些功能尚未被任何客户端支持。
当然,还有 LLM…
工作流程
-
一个 MCP 服务器注册其能力(工具、资源等)
-
客户端连接到服务器或将其作为子进程启动
-
然后,LLM 可以通过标准化接口使用这些能力
传输协议
该协议支持多种传输机制:
- SSE (服务器推送事件):通过HTTP双向通信,服务器进程与客户端隔离
- Stdio (标准输入/输出):通过标准输入/输出管道进行通信;在这里,服务器进程基本上是客户端的子进程。
安全性
安全形势更加复杂。虽然使用 stdio
传输的服务器通常与客户端共同放置,因此 API 密钥不一定暴露在互联网上,但在我看来,它们似乎被相对随意地传递。
这些密钥需要在服务器启动时加载到客户端,以便可以传递给子进程,它们甚至出现在桌面应用程序的日志中,这让人感到担忧。
API 密钥的广泛使用是一个更广泛的问题,影响着生成性人工智能服务、平台和工具。像 Okta 和 Auth0 这样的公司正在研究 一个解决方案 来管理和授权生成性人工智能,而不完全依赖于密钥。
SDKs
Anthropic officially supports low-level SDKs for TypeScript, Python, and Kotlin. Some of the higher-level wrappers that have recently been created已经覆盖了一些现有的样板代码,并具有其他良好的功能,例如用于调试、检查和在客户端上安装服务器的打包CLI,以便更轻松地开发模型上下文协议服务器。
开始使用 Python SDK
我们现在将探讨如何创建一个几乎实用的工具,用于读取网站、通过网络回答搜索查询以及进行事实核查。我们将使用 Jina.ai。
这是一项非常出色的服务,提供一个“搜索基础平台”,结合“嵌入、重排序器和小型语言模型”,帮助企业构建生成性人工智能和多模态搜索体验。
先决条件
您需要安装 uv
。这是创建和管理 Python 项目的推荐方式。它是一个相对较新但令人兴奋的 Python 工具链的一部分,名为 astral.sh。
uv
旨在成为一个管理项目、依赖项、虚拟环境、版本、代码检查以及执行 Python 脚本和模块的一站式商店。它是用 Rust 编写的。请随意处理这些信息。
您还需要安装 Claude Desktop 应用程序。对于我们的目的,Claude Desktop 应用将作为 MCP 客户端,并且是 Anthropic 的一个关键目标客户端。
项目设置
使用 uv
你可以初始化一个项目:
uv init mcp-jinaai-reader — python 3.11
这将创建一个名为 mcp-jinaai-reader
的文件夹,一个 .python-version
,以及一个 pyproject.toml
。
cd mcp-jinaai-reader
uv venv
这将创建一个与我们选择的 Python 版本相对应的虚拟环境。
创建环境后,它将提供有关如何在会话中激活它的说明。
source .venv/bin/activate
添加一个 src
目录并安装我们需要的一个依赖项。
在项目根目录创建一个 .env
文件,并将你的 JINAAI_API_KEY
添加到该文件中。通常,服务器运行所需的任何 API 密钥或其他环境变量都将放在此文件中。
JINAAI_API_KEY=jina_************
在 src
目录中,创建一个 server.py
文件……你应该准备好编码了。
服务器代码
from mcp.server.fastmcp import FastMCP
import httpx
from urllib.parse import urlparse
import os
从导入开始:httpx
将是我们在这里用来发起 HTTP 请求的库。urlparse
方法帮助我们判断一个字符串是否可能是有效的 URL。
mcp = FastMCP("search", dependencies=["mcp[cli]"])
这初始化了服务器;第一个参数是工具的名称。当前一个 bug 需要将 mcp[cli]
添加为依赖项,这在理想情况下是不必要的,但可能很快会被修复。
如果您有其他依赖项,您必须在这里添加它们,以便客户端知道您需要在运行客户端之前安装它们;我们稍后会看到这如何运作。
JINAAI_SEARCH_URL = "https://s.jina.ai/"
JINAAI_READER_URL = "https://r.jina.ai/"
JINAAI_GROUNDING_URL = "https://g.jina.ai/"
JINAAI_API_KEY = os.getenv("JINAAI_API_KEY")
您可能能推测出这里的模式,但 Jina 使用不同的子域来路由特定的请求。搜索端点期望一个查询,读取端点期望一个 URL,而基础端点可以为大型语言模型提供特定的响应或答案。
基础是一个更大的主题,并与其他技术结合使用,例如 RAG 和微调,以帮助大型语言模型减少幻觉并改善决策。
我们的第一个工具
@mcp.tool()
async def read(query_or_url: str) -> str:
"""
从 URL 读取内容或执行搜索查询。
"""
try:
if not JINAAI_API_KEY:
return "JINAAI_API_KEY 环境变量未设置"
headers = {
"Authorization": f"Bearer {JINAAI_API_KEY}",
"X-Retain-Images": "none",
"X-Timeout": "20",
"X-Locale": "en-US",
}
async with httpx.AsyncClient() as client:
if is_valid_url(query_or_url):
headers["X-With-Links-Summary"] = "true"
url = f"{JINAAI_READER_URL}{query_or_url}"
else:
url = f"{JINAAI_SEARCH_URL}{query_or_url}"
response = await client.get(url, headers=headers)
return response.text
except Exception as e:
return str(e)
注解 @mcp
.tool
执行了大量的工作。库中还存在用于资源和提示的类似注解。该注解提取函数签名和返回类型的详细信息,以创建输入和输出模式,以便大型语言模型调用该工具。它配置工具,使客户端理解服务器的能力。它还将函数调用注册为配置工具的处理程序。
接下来,您会注意到该函数是 async
的。无需运行时配置,也不需要 asyncio.run
的内容。如果您出于某种原因需要将服务器作为独立服务运行,您确实需要自己处理其中的一些内容。FastMCP 仓库中有一个示例说明如何做到这一点。
函数主体相对简单;它验证是否接收到一个 URL,设置适当的头部,调用 Jina 端点,并返回文本。
def is_valid_url(url: str) -> bool:
"""
验证给定字符串是否是一个有效的 URL。
"""
try:
result = urlparse(url)
return all([result.scheme in ("http", "https"), result.netloc])
except:
return False
这是一个相当简单的函数,用于确定可能的 URL 字符串的有效性。
第二个工具
@mcp.tool()
async def fact_check(query: str) -> str:
"""
Perform a fact-checking query.
"""
try:
if not JINAAI_API_KEY:
return "JINAAI_API_KEY environment variable is not set"
headers = {
"Authorization": f"Bearer {JINAAI_API_KEY}",
"Accept": "application/json",
}
async with httpx.AsyncClient() as client:
url = f"{JINAAI_GROUNDING_URL}{query}"
response = await client.get(url, headers=headers)
res = response.json()
if res["code"] != 200:
return "Failed to fetch fact-check result"
return res["data"]["reason"]
except Exception as e:
return str(e)
测试和调试
mcp dev src/server.py — with-editable .
运行上述命令将启动 mcp
检查器。这是 sdk 提供的一个工具,用于测试和调试服务器响应。— with-editable
标志允许您在不重新启动检查器的情况下更改服务器(强烈推荐)。
您应该看到:
🔍 MCP 检查器正在 http://localhost:5173 上运行 🚀
默认情况下,检查器在 5173 端口运行,而服务器(您刚刚编写的代码)将在 3000 端口运行;您可以通过在调用之前设置 SERVER_PORT
和 CLIENT_PORT
来更改此设置。
SERVER_PORT=3000 mcp dev src/server.py
检查器
如果一切顺利,你应该会看到类似于上面的内容,在左侧你可以添加所需的环境变量,这里 JINAAI_API_KEY
是唯一的一个。
如果你点击顶部菜单栏中的 Tools
,然后选择 List Tools
,你应该会看到我们创建的工具,请注意文档字符串作为工具的描述。
点击特定工具将弹出文本框,供你输入调用该工具所需的参数。
安装服务器
在您确认一切正常后,您现在可以在 Claude Desktop App 客户端上安装服务器。
mcp install src/server.py -f .env
我相信未来会支持其他客户端,但目前您只需执行此操作。-f .env
将环境变量传递给应用客户端。
其背后的原理是更新 claude_desktop_config.json
并提供运行服务器所需的命令和参数。默认情况下,它使用 uv
,该工具必须在您的 PATH
中可用。
如果您现在打开 Claude Desktop App,进入菜单栏并点击 Claude > 设置
,然后点击 开发者
,您应该会看到在初始化服务器时设置的工具名称。
点击它应该会弹出其配置。您不仅会知道它是如何执行的,而且在 高级选项
中,您还会看到已设置的环境变量 😬。
您也可以直接编辑此配置,但我并不一定推荐这样做。
利润
如果一切顺利,当你进入桌面应用程序时,你应该不会看到任何错误(如果有错误,进入 设置
应该会给你一个按钮来查看日志并从那里进行调查)。
此外,你应该看到一个锤子符号,显示你可以使用的工具数量(注意:除非你安装了其他 MCP 服务器,否则你的数量应该是 2)。
你不需要直接调用工具,而是像往常一样与应用程序聊天,当它遇到认为工具有帮助的情况时,它会询问你是否想使用它。这里不需要额外的代码或配置。
我认为它依赖于工具名称和描述来决定是否合适,因此值得精心编写一个清晰简单的工具描述。
你会收到如下提示:
你可以直接与它“聊天”,诚然,写得不够好的工具有时会遇到问题。偶尔它会决定无法访问互联网,有时它无法检索结果,但有时你会得到这个:
这个过程有一种相对自然的流畅感,它读取页面,提供摘要,然后你要求它去特定文章并阅读。
最终备注
希望这能让你对 MCP 服务器有一些了解。还有很多内容可以阅读和观看,但我再推荐一个网站:glama.ai。他们维护了一份相当全面的可下载和试用的 MCP 服务器列表,包括其他比我们的玩具示例更可靠的网络搜索工具。感谢你的关注。