MCP服务器开发指南 (4)
这是关于构建MCP服务器的四部分教程的最后一部分。在第一部分中,我们创建了一个具有基本资源的MCP服务器。在第二部分中,我们添加了资源模板并改进了代码组织,在第三部分中,我们添加了提示并进一步优化了结构。现在我们将通过添加工具来完成我们的服务器。
1、什么是MCP工具?
工具是可以调用以执行操作或检索动态信息的可执行函数。与只读的资源和结构化LLM交互的提示不同,工具允许LLM主动执行一些事情,如计算值、进行API调用或修改数据。
2、为什么要使用工具?
工具使LLM能够与系统交互并执行操作。以下是一些实际示例:
文件操作:
name: "write-file"
arguments: {
path: "/logs/report.txt",
content: "每日总结..."
}
用户:
将此报告保存到文件中
AI:
我将使用write-file工具……文件已成功保存。
API交互:
name: "fetch-weather"
arguments: {
location: "旧金山",
units: "摄氏度"
}
用户:
旧金山的天气如何?
AI:
让我查一下……根据天气API,气温为18°C,天气晴朗。
数据处理:
name: "analyze-data"
arguments: {
dataset: "sales_2024_q1",
operation: "summary_stats"
}
用户:
计算第一季度销售的汇总统计
AI:
正在运行分析……平均销售额为342,中位数为342,……
3、添加工具
为我们的新工具创建一个新文件,并添加一个“create-message”工具:
// src/tools.ts
// 允许的值
const messageTypes = ['greeting', 'farewell', 'thank-you'] as const;
const tones = ['formal', 'casual', 'playful'] as const;
// 工具定义
export const tools = {
'create-message': {
name: 'create-message',
description: '生成带有各种选项的自定义消息',
inputSchema: {
type: 'object',
properties: {
messageType: {
type: 'string',
enum: messageTypes,
description: '要生成的消息类型',
},
recipient: {
type: 'string',
description: '消息的接收者姓名',
},
tone: {
type: 'string',
enum: tones,
description: '消息的语气',
},
},
required: ['messageType', 'recipient'],
},
},
};
到目前为止,我们添加的只是一个描述,它将使使用它的模型理解其功能以及期望的信息类型。
现在让我们添加实际的处理器:
// src/tools.ts
// 允许的值
const messageTypes = ['greeting', 'farewell', 'thank-you'] as const;
const tones = ['formal', 'casual', 'playful'] as const;
// 工具定义
export const tools = {
// ... 现有的定义
};
type CreateMessageArgs = {
messageType: typeof messageTypes[number];
recipient: string;
tone?: typeof tones[number];
};
// 各种消息组合的简单模板
const messageFns = {
greeting: {
formal: (recipient: string) =>
`亲爱的${recipient},希望这封信能让你一切安好`,
playful: (recipient: string) => `嘿嘿 ${recipient}!🎉 有什么新鲜事吗?`,
casual: (recipient: string) => `嗨 ${recipient}!你好吗?`,
},
farewell: {
formal: (recipient: string) =>
`祝好,${recipient}。期待再次相见。`,
playful: (recipient: string) =>
`再见啦,${recipient}!👋 保持精彩!`,
casual: (recipient: string) => `再见 ${recipient},保重!`,
},
"thank-you": {
formal: (recipient: string) =>
`亲爱的${recipient},我真诚地感谢你的帮助。`,
playful: (recipient: string) =>
`你是最棒的,${recipient}!🌟 谢谢你一百万次!`,
casual: (recipient: string) =>
`非常感谢你,${recipient}!真的很感激!`,
},
};
const createMessage = (args: CreateMessageArgs) => {
if (!args.messageType) throw new Error("必须提供消息类型。");
if (!args.recipient) throw new Error("必须提供接收者。");
const { messageType, recipient } = args;
const tone = args.tone || "casual";
if (!messageTypes.includes(messageType)) {
throw new Error(
`消息类型必须是以下之一:${messageTypes.join(", ")}`,
);
}
if (!tones.includes(tone)) {
throw new Error(
`如果提供了语气,则必须是以下之一:${
tones.join(", ")
}`,
);
}
const message = messageFns[messageType][tone](recipient);
return {
content: [
{
type: "text",
text: message,
},
],
};
};
export const toolHandlers = {
"create-message": createMessage,
};
现在让我们更新处理器:
// src/handlers.ts
import {
CallToolRequestSchema, // <-- 添加这个
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema, // <-- 并添加这个
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { resourceHandlers, resources } from "./resources.js";
import {
getResourceTemplate,
resourceTemplates,
} from "./resource-templates.js";
import { promptHandlers, prompts } from "./prompts.js";
import { toolHandlers, tools } from "./tools.js"; // <-- 导入我们的工具
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
export const setupHandlers = (server: Server): void => {
// 当客户端请求时列出可用资源
// ... 之前创建的处理器在这里
// 工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: Object.values(tools),
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
type ToolHandlerKey = keyof typeof toolHandlers;
const { name, arguments: params } = request.params ?? {};
const handler = toolHandlers[name as ToolHandlerKey];
if (!handler) throw new Error("未找到工具");
type HandlerParams = Parameters<typeof handler>;
return handler(...[params] as HandlerParams);
});
};
最后,更新服务器初始化:
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { setupHandlers } from "./handlers.js";
const server = new Server(
{
name: "hello-mcp",
version: "1.0.0",
},
{
capabilities: {
resources: {},
prompts: {},
tools: {}, // <-- 添加工具能力
},
},
);
setupHandlers(server);
// 使用stdio传输启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);
console.info(
'{"jsonrpc": "2.0", "method": "log", "params": { "message": "服务器运行中..." }}',
);
工具结构:
- 工具通过inputSchema定义其接口
- 处理器实现实际功能
- 返回格式符合MCP规范
错误处理:
- 必需参数验证
- 特定错误消息
- 类型安全的处理器访问
4、使用Inspector测试
记得先构建输出:
npm run build
然后启动Inspector:
npx @modelcontextprotocol/inspector node build/index.js
测试工具:
- 点击“工具”标签
- 查找“create-message”
- 尝试不同的组合:
{
"messageType": "thank-you",
"recipient": "Alice",
"tone": "playful"
}
5、使用Claude桌面测试
尝试以下示例
注意:您可能会收到授权工具使用的请求:
基本消息:
用户:
为Bob创建一条问候消息
Claude:
我将使用消息工具……‘嗨Bob!你好吗?’
样式化消息:
用户:
给Alice发送一条有趣的感谢
Claude:
使用消息工具……‘你是最棒的,Alice!🌟 谢谢你一百万次!’
不同消息类型:
用户:
你能创建哪些类型的消息?
Claude:
我可以帮助你使用create-message函数创建不同类型的消息。你可以生成:
1. 问候
2. 再见
3. 感谢消息
对于每种类型,你可以指定接收者,并且可以设置语气为正式、随意或有趣。你想让我演示创建特定类型的消息吗?
您的结果可能与此不同,特别是如果您使用的是不同于Claude的不同工具或不同于Sonnet 3.5的不同模型
6、结束语
恭喜!你现在构建了一个完整的MCP服务器,包括:
- 静态和基于模板的资源
- 可定制的提示
- 动态工具
- 结构良好、类型安全的代码
你已经学会了如何:
- 构建MCP服务器
- 实现不同的MCP功能
- 有效组织代码
- 优雅地处理错误
- 使用Inspector进行测试
- 与Claude桌面集成
从这里开始,你可以:
- 添加更复杂的工具
- 集成外部API
- 实现文件操作
- 添加数据库连接
- 创建自己的自定义功能
请记住,MCP是一个不断发展的协议,因此请密切关注官方文档中的新功能和最佳实践。
原文链接:Building MCP Servers: Part 4 — Creating Tools
汇智网翻译整理,转载请标明出处