模型上下文协议 (MCP) 的出现源于不兼容性问题。在我们的日常生活中,不兼容以各种方式给我们带来不便。
您是否曾在国外旅行时携带美发工具或电子设备,却发现插头与插座不兼容?从开发者的角度来看,这个问题就像为您的 AI 智能体构建 API 和开发工具,却发现它们似乎无法正确连接或调用一样令人沮丧。这正是 MCP 解决这种不兼容问题的场景。
Anthropic 于 2024 年推出的模型上下文协议 (MCP) 可用作 AI 应用程序与工具、数据源/数据集和预定义模板等外部服务进行有效通信的标准化层。MCP 本质上是 AI 与工具交互的开放标准和连接器。MCP 架构可分为三个关键架构组件:
首先, IBM Bob 是什么?IBM Bob 是一款由生成式人工智能驱动的集成开发环境 (IDE)和现代化助手。将 Bob 视为您的 AI 优先集成开发环境(IDE)与结对开发伙伴——一款能够理解您的意图、代码库与组织标准的工具。
从头开始构建自己的 MCP 服务器可能非常耗时,尤其是对于初级开发人员而言。相反,在本教程中,我们将深入了解如何依靠 IBM Bob,使用一些简单的自然语言提示快速启动工作流。
MCP 整合通过连接外部工具和服务扩展了 Bob 的功能,可在全局(适用于所有工作区)或项目(可通过 .Bob/mcp.json 文件进行团队共享)级别配置。MCP 支持两种传输类型:标准输入/输出(STDIO),用于本地、低延迟服务器;服务器发送事件(SSE),通过 HTTP/HTTPS 可访问的远程 MCP 服务器。用户可以在 Bob 设置菜单中使用 JSON 文件配置服务器,定义命令、环境变量和自动批准的工具。这两种传输类型都允许您启用或禁用服务器,自动批准特定工具,并通过 Bob 的 MCP 设置面板管理它们的运营。
我们开始吧。如果您更喜欢在 GitHub 上跟随本教程进行操作,请查看我们的存储库。
1. 打开作为先决条件安装的 IBM Bob IDE,熟悉新的 AI Coding 伴侣的布局。
2. 要进入 MCP 设置面板,点击聊天窗口右上角齿轮图标旁的 3 个点。然后,选择 MCP 服务器 从下拉菜单中选择。
在这里,我们可以全局或每台服务器启用或禁用 MCP 并管理工具(例如,自动批准、删除或重启)。我们还可以通过 Bob 市场深入了解社区服务器,或使用 MCP SDK 构建自己的服务器。
如果选择从 Bob 市场安装服务器,您会注意到配置可以在两个级别进行设置:全局(存储在
项目级设置覆盖全局设置,使您能够针对不同用例灵活定制 MCP 行为。要管理这些配置,您可以直接通过 Bob 的设置菜单编辑 JSON 文件,从而定义服务器详细信息,如命令、环境变量和自动批准的工具。在本教程中,我们将使用 SDK。
3. 确保启用 MCP 服务器创建 设置,以便使用 Bob 创建自定义 MCP 服务器。
4. 如果您有偏好的项目目录,可以在 IDE 里自己打开,或者让 Bob 在聊天窗口帮您设置。
3. 创建虚拟 Python 环境以隔离项目的依赖关系,避免不同项目相互冲突,这是常见做法。为此,请输入提示:“在此目录中,激活 Python 虚拟环境”。
4. 您应该看到 Bob 运行一系列终端命令。这些命令可能类似于:
我们建议您保持禁用自动批准开关,这样您就可以在 Bob 实时运行每条命令之前批准或拒绝该命令。
太棒了。Bob 在
虚拟环境设置完成后,我们现在可以开始创建 MCP 服务器了。在提示 Bob 生成代码,或提示任何 AI 模型或 AI 助手生成代码时,最好包含重要细节。为了看看 Bob 的实际应用,让我们提交提示:
“创建一个 MCP 服务器,提供对 arXiv 的只读访问权限。服务器应该:
仅凭这一个简单的提示,Bob 就获得了足够的信息,既能理解任务内容,又能制定完成任务的步骤“待办事项清单”。请注意,该列表不仅包括 MCP 服务器的创建,还包括其配置和测试。整洁。
请注意,我们将在本教程中使用 arXiv API 作为简单示例,不需要任何 API 密钥或身份验证即可开始使用。但这只是一个示例,Bob 也有能力处理具有所需权限的更复杂服务器。
在执行每个命令之前,我们可以批准或拒绝该操作。若您发现相关命令已自动执行,可能表明您已启用自动审批切换开关。
2. 接下来,Bob 创建一个
{
"name": "arxiv-server",
"version": "0.1.0",
"description": "MCP server for read-only access to arXiv papers",
"type": "module",
"bin": {
"arxiv-server": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"keywords": [
"mcp",
"arxiv",
"research",
"papers"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "^22.10.5",
"typescript": "^5.7.3"
}
}
arxiv-server/tsconfig.json{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
4. 然后,具有 arXiv 搜索功能的主服务器实现将存储在
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import axios from 'axios'; // 定义 arXiv API 响应的类型 接口 ArxivEntry {
id: string;
title: string;
summary: string;
authors: Array<{ name: string }>;已发布:字符串;已更新:字符串; Categories: string[]; main_category: string; links: Array<{ href: string; rel: string; type?: string }>; }接口 ArxivSearchResult {
entries: ArxivEntry[];
totalResults: number;
startIndex: number;
itemsPerPage: number;
} // 创建 MCP 服务器 const server = new McpServer({
name: "arxiv-server",
version: "0.1.0"
}); // 为 arXiv API 创建 axios 实例 const arxivApi = axios.create({
baseURL: 'http://export.arxiv.org/api',
timeout: 30000,
});/** * Parse arXiv API XML response to JSON */ functionparseArxivXML(xmlData: string): ArxivSearchResult{
const entries: ArxivEntry[] = [];
// Extract total results
const totalResultsMatch = xmlData.match(/<opensearch:totalResults[^>]*>(\d+)<\/opensearch:totalResults>/);
const totalResults = totalResultsMatch ? parseInt(totalResultsMatch[1]) : 0;
const startIndexMatch = xmlData.match(/<opensearch:startIndex[^>]*>(\d+)<\/opensearch:startIndex>/);
const startIndex = startIndexMatch ? parseInt(startIndexMatch[1]) : 0;
const itemsPerPageMatch = xmlData.match(/<opensearch:itemsPerPage[^>]*>(\d+)<\/opensearch:itemsPerPage>/);
const itemsPerPage = itemsPerPageMatch ? parseInt(itemsPerPageMatch[1]) : 0;
// Extract entries
const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
let entryMatch;
while ((entryMatch = entryRegex.exec(xmlData)) !== null) {
const entryXml = entryMatch[1];
// Extract ID
const idMatch = entryXml.match(/<id>([^<]+)<\/id>/);
const id = idMatch ? idMatch[1] : '';
// Extract title
const titleMatch = entryXml.match(/<title>([^<]+)<\/title>/);
const title = titleMatch ? titleMatch[1].trim().replace(/\s+/g, ' ') : '';
// Extract summary
const summaryMatch = entryXml.match(/<summary>([^<]+)<\/summary>/);
const summary = summaryMatch ? summaryMatch[1].trim().replace(/\s+/g, ' ') : '';
// Extract authors
const authors: Array<{ name: string } > = []; const authorRegex = /<author> <name>\s*([^<]+)<\/名称>/g;让作者匹配; while ((AuthorMatch = AuthorRegex.exec(entryXml))!== null) {
authors.push({ name: authorMatch[1].trim() }); } // 提取发布日期 constpublishedMatch =entryXml.match(/<published>([^<]+)<\/published>/);const已发布=已发布的匹配?已发布的匹配[1] : ''; // 提取更新日期 constupdatedMatch =entryXml.match(/<updated>([^<]+)<\/updated>/);const updated = updatedMatch ?updatedMatch[1] : ''; // Extract Categories cons Categories: string[] = []; const categoryRegex = /<category[^>]+term="([^"]+)"/g; 让 categoryMatch; while ((categoryMatch = categoryRegex.exec(entryXml))!== 空) {
categories.push(categoryMatch[1]);
}
const primary_category = 类别 [0] || “;
//提取链接
常量链接:数组 < { href: string; rel: string; type?: string } > = [];
const linkRegeX =/< link [^ >] +href= " ([^ "] +) " [^ >] +rel= " ([^ "] +) " (?: [^ >] +type= " ([^ "] +) ")?/g;
让 linkMatch;
while ((linkMatch = linkRegex.exec (entryXML))!== null) {
links.push({
href: linkMatch[1],
rel: linkMatch[2],
type: linkMatch[3]
} );
}
entries.push( {
id,
title,
summary,
authors,
published,
updated,
categories,
primary_category,
links
} );} return {
entries,
totalResults,
startIndex,
itemsPerPage
}; } /** *格式化搜索结果显示*/ function formatSearchResults(result: ArxivSearchResult): string {
let output = `Found ${result.totalResults} 结果 (显示 ${result.entries.length})\n\n`; result.entries.forEach((entry,index) => {
output += `${index + 1}.${entry.title}\n';
输出 += ' ID: ${entry.id}\n';
输出 += ' 作者:${entry.authors.map(a => a.name).join(', ')}\n';
输出 += ' 发布:${entry.published}\n';
输出 += ' Categories:${entry.categories.join(', ')}\n';
输出 += ' 摘要:${entry.summary.substring(0, 300)}${entry.summary.length > 300 ? '...' : ''}\n';
// 查找摘要链接(非PDF)
const abstractLink = entry.links.find(l => l.rel === 'alternate'); if (abstractLink) {
output += ` URL: ${abstractLink.href}\n`; } output += '\n'; }); return 输出; } // 添加 arXiv 论文搜索工具 server.tool("search_arxiv", {
query: z.string().describe("Search query (supports arXiv query syntax)"),
max_results: z.number().min(1).max(50).optional().describe("Maximum number of results to return (1-50, default: 10)"),
start: z.number().min(0).optional().describe("Starting index for pagination (default: 0)"),
sort_by: z.enum(["relevance", "lastUpdatedDate", "submittedDate"]).optional().describe("Sort order (default: relevance)"),
sort_order: z.enum(["ascending", "descending"]).optional().describe("Sort direction (default: descending)")
}, async ({ query, max_results = 10, start = 0, sort_by = "relevance", sort_order = "descending" }) => {
try {
// Enforce reasonable limits
const limitedMaxResults = Math.min(max_results, 50);
const limitedStart = Math.max(start, 0);
// Build query parameters
const params: Record<string, string | number> = {
search_query: query,
start: limitedStart,
max_results: limitedMaxResults,
}; if (sort_by) {
params.sortBy = sort_by;
} if (sort_order) {
params.sortOrder = sort_order;
} const response = wait arxivApi.get('/query',{ params }); // 解析 XML 响应 const 结果 = ParseArxivXML(response.data);// 设置结果 const formattedResults = formatSearchResults(result); return {
content: [
{
type: "text",
text: formattedResults,
}, ], }; }catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: "text",
text: `arXiv API error: ${error.response?.data?.message ?? error.message}`, }, ], isError: true, }; } 引发错误; } } ); // 开始通过标准输入接收消息,通过标准输出发送消息 const Transport = new StdioServerTransport(); wait server.connect(transport);console.error('arXivMCP 服务器运行在 stdio');
// 由 Bob 制作
在这个阶段,如果您的服务器需要任何 API 密钥,Bob 会向您索要。
5. 后续步骤,Bob 通过运行以下命令在 arxiv-server 目录中安装依赖项。
mcp_settings.json.Bob/mcp.json{
"mcpServers": {}
"mcpServers": {
"arxiv": {
"command": "node",
"args": [ "/here/is/the/path/to/arxiv-server/build/index.js"
],
"disabled": false,
"alwaysAllow": [],
"disabledTools": []
}
}
}
这些信息完善了 Bob 为设置 MCP 服务器所采取的一系列步骤。
为了完成一系列“待办事项”任务,Bob 测试了 MCP 服务器。如前面的屏幕截图所示,Bob 查询 MCP 服务器中的
点击向下箭头展开窗口即可看到,测试成功后,鲍勃又进行了一次测试。此次查询的内容是 2 篇机器学习论文,按提交日期降序排列。即使使用了不同的参数,此次 MCP 工具调用也成功执行了。
鉴于测试成功,Bob 现在已完成所有子任务。
Bob 已经为我们测试了 MCP 服务器。不过,我们不妨在聊天窗口里自己做一些查询,看看 Bob 是否能推断出正确的搜索词和必要的参数。
询问 Bob: “关于 LLM 智能体人追踪的最新论文有哪些?”
Bob 自行从我们的提示中提取出正确的查询,并添加了一些额外的关键词来改进搜索。Bob 还将结果的最大数量设置为 10,并按相关性对论文进行排序。太棒了。
让我们再尝试一个查询。
有趣的是,Bob 在发现不需要的结果后,自动调整了搜索查询。Bob 并没有盲目执行工具调用,而是以热心智能体的身份与用户开展协作。经过两次迭代后,Bob 返回了几篇与我们最初的任务非常吻合的论文。
许多开源 MCP 服务器还包含文档,方便其他人轻松入门。我们将此提示粘贴到聊天窗口中:
“在此目录中,创建一个 README.md 文件来记录此 MCP 服务器。附上设置和使用说明。
通过一个简单的提示,Bob 创建了一份全面的
您或许有兴趣使用 watsonx Orchestrate 构建一个能够访问此 MCP 服务器的研究专用 AI 智能体?Bob 只需一个简单的提示就能创建智能体。首先,找到我们在第一步中探索过的 MCP 设置面板中的市场,安装预构建的 watsonx Orchestrate ADK 服务器。然后,发送以下命令:
“创建使用此 arxiv MCP 服务器的 watsonx Orchestrate 智能体。”
如果您在本地运行了 watsonx Orchestrate 服务,Bob 将为您运行必要的命令。如果您希望稍后自行运行命令,Bob 将为您编写脚本并记录所需的任何命令。
让我们逐个查看每个文件,以便更好地理解 Bob 生成的内容。这是 toolkit-config.yaml
类型:mcp
名称:arxiv-toolkit
描述:用于搜索 arXiv 研究论文的工具包。提供对 arXiv 数据库中论文元数据、摘要和出版信息的访问。package_root:/local/path/to/arxiv-server
语言:node
命令:node /local/path/to/arxiv-server/arxiv-server/build/index.js
工具:
- "*"
# Bob 制作
接下来,我们打开 research-assistant-agent.yaml
名称:研究助手
kind:native
描述:AI 研究助手,可帮助在 arXiv 中查找和总结学术论文。专注于检索科学文献、解释研究成果及提供引用。标题:研究助手
说明:|您是一位专家研究助理,专门从事 arXiv 学术文献检索。
## 您的能力
-在 arXiv 中搜索有关任何科学主题的论文
-用通俗的语言总结摘要中的研究结果
-确定主要作者和研究趋势
-建议相关的论文和主题
-使用 arXiv ID 和 URL 提供适当的引文
## 回复用户时
1。务必提供 arXiv ID 和论文的直接网址
2。用通俗易懂的语言总结摘要
3.重点介绍主要贡献和发现
4。相关时建议后续搜索
5。简洁但内容丰富
## 使用 search_arxiv 工具
可用参数:
-查询(必填):使用 arXiv 语法搜索查询
-max_results(可选):1-50 篇论文,默认 10
-开始(可选):分页的起始索引,默认为 0
-排序依据(可选):" 相关性 "、" 提交日期 " 或 " LastupdatedDate "
-sort_order(可选):" 升序 " 或 " 降序 "
## 高级搜索语法
您可以在查询中使用这些运算符:
-ti: keyword-在标题中搜索
-au: author-按作者姓名搜索
-abs: keyword-摘要搜索
-cat: category-按类别筛选(例如 cs.AI、quant-ph、Math.co)
-AND、OR、ANDNOT-布尔运算符
示例:
-" ti: transformer AND Cat: cs.lg "-机器学习中的变形金刚论文
-" au: Hinton "-杰弗里·欣顿的论文
-" 量子计算和 cat: quant-ph "-量子物理学中的量子计算
## 常见类别
-cs.AI-人工智能
-cs.lg-机器学习
-cs.cl-计算和语言
-cs.cv-计算机视觉
-quant-ph-量子物理学
-Math.co-组合学
-stat.ml-机器学习(统计)
## 响应格式
在提交论文时,请使用以下格式:
** " 论文标题 " **
-作者:[作者姓名]
-发布时间:[日期]
-类别:[类别]
-摘要:[摘要的简要摘要]
-[查看论文] (arXiv 网址)
## 处理边缘案例
-如果未找到结果:建议替代搜索词或更广泛的查询
-如果结果太多:建议使用类别或日期过滤器缩小搜索范围
-如果用户要求提供 PDF:请说明您仅提供元数据,但要包括他们可以访问论文的 URL
-如果问题不明确:询问有关研究领域或特定兴趣的澄清问题
工具:
-search_arxiv
配置:
隐藏:假
enable_cot: true
标签:
-研究
-学术
-论文
-arxiv
-文献搜索
# 由 Bob 制作
此 YAML 文件配置我们的研究助理,这是一种专门的 AI 智能体,可通过搜索 arXiv、总结论文并以结构化格式提供引文来简化学术研究。其中包括指导智能体行为的详细说明,从查询语法和工具使用到响应格式和边缘情况处理。所有这些信息都被详细展示,同时利用工具 search_arxivsetup-instructions.md
在本教程中,我们使用 IBM Bob(一种 AI 优先的 IDE 和结对开发工具)来简化 MCP 的集成,以用于学术研究。您高效地构建了一个可扩展的 MCP 服务器,能够从 arXiv.org 存储库中搜索和检索论文。现在,无论您选择为研究社区开放源代码,还是将其部署在企业应用程序中以增强知识发现,您的解决方案都已生产就绪。
IBM Bob 的真正神奇之处在于能够将复杂的开发任务转化为直观的、由提示驱动的工作流。只需少量战略输入,Bob 就可以加速原型设计,减少样板代码,甚至提出优化建议,使您能够专注于创新而不是实施。
在继续深入了解其功能时,请考虑 Bob 如何进一步完善您的 MCP 服务器。我们可以列举出的一些优点包括:添加高级筛选功能、实时更新功能,以及与其他学术数据库(如 PubMed 或 IEEE Xplore)的整合。所有这些可能性,以及更多其他可能性,只需使用 IBM Bob 进行一些简单的提示即可实现。
是否已准备好迈出下一步?您可以尝试自定义 MCP 服务器,或者深入了解 Bob 的功能,以解锁更多自动化潜力。快乐编码!
构建、部署和管理强大的 AI 助手和智能体,运用生成式 AI 实现工作流和流程自动化。
借助值得信赖的 AI 解决方案,您可以勾勒未来业务发展蓝图。
IBM Consulting AI 服务有助于重塑企业利用 AI 实现转型的方式。