与AWS Bedrock和MCP(模型上下文协议)服务器交互的TypeScript客户端。
npm install @juspay/bedrock-mcp-connector
import { BedrockMCPClient, LogLevel } from "@juspay/bedrock-mcp-connector";
// 创建一个客户端
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
systemPrompt: "您是一个乐于助人的助手。",
mcpServerUrl: "http://localhost:5713/sse", // 可选
});
// 设置日志级别(可选)
// LogLevel.INFO - 默认,显示重要信息
// LogLevel.DEBUG - 显示详细的调试信息
// LogLevel.WARN - 只显示警告和错误
// LogLevel.ERROR - 只显示错误
// LogLevel.NONE - 抑制所有日志
client.setLogLevel(LogLevel.INFO);
// 连接到MCP服务器(如果提供了URL)
if (client.mcpServerUrl) {
await client.connect();
}
// 发送提示
const response = await client.sendPrompt("法国的首都是什么?");
console.log("响应:", response);
// 完成后断开连接
if (client.isConnectedToMCP()) {
await client.disconnect();
}
该包支持会话历史记录的内存和Redis存储:
import { BedrockMCPClient } from "@juspay/bedrock-mcp-connector";
// 使用Redis存储持久会话
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
sessionId: "user-session-123", // 唯一会话标识符
userId: "user-456", // 可选用户标识符
storage: {
type: "redis",
config: {
host: "localhost",
port: 16379,
password: "your-redis-password", // 可选
db: 0, // Redis数据库编号
keyPrefix: "bedrock-mcp:", // Redis键前缀
ttl: 86400, // TTL(秒数,24小时)
connectionOptions: {
connectTimeout: 5000,
lazyConnect: true
}
}
}
});
// 会话现在在客户端重启之间是持久的
const response = await client.sendPrompt("记住这个:我最喜欢的颜色是蓝色");
console.log("响应:", response);
// 后来,在具有相同sessionId的新客户端实例中...
const newClient = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
sessionId: "user-session-123", // 相同的会话ID
storage: { type: "redis", config: { /* 相同的配置 */ } }
});
const response2 = await newClient.sendPrompt("我最喜欢的颜色是什么?");
// 模型将记得之前的对话!
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
// 没有存储配置 = 内存存储
// 或者显式指定:
storage: { type: "memory" }
});
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
storage: {
type: "redis",
config: {
host: "localhost", // Redis主机(默认:'localhost')
port: 6379, // Redis端口(默认:6379)
password: "password", // Redis密码(可选)
db: 0, // Redis数据库(默认:0)
keyPrefix: "myapp:", // 键前缀(默认:'bedrock-mcp:conversation:')
ttl: 3600, // TTL(秒数,默认:86400 - 24小时)
connectionOptions: { // 额外的Redis连接选项
connectTimeout: 5000,
lazyConnect: true,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3
}
}
}
});
import { BedrockMCPClient, LogLevel } from "@juspay/bedrock-mcp-connector";
// 创建一个客户端
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
});
// 设置日志级别(可选)
client.setLogLevel(LogLevel.INFO);
// 设置事件监听器
const emitter = client.getEmitter();
emitter.on("message", (message) => {
console.log(`消息: ${message}`);
});
emitter.on("error", (error) => {
console.error(`错误: ${error.message}`);
});
emitter.on("tool:start", (toolName, input) => {
console.log(`工具开始: ${toolName}`);
});
emitter.on("tool:end", (toolName, result) => {
console.log(`工具完成: ${toolName}`);
});
emitter.on("response:start", () => {
console.log("响应开始");
});
emitter.on("response:chunk", (chunk) => {
console.log(`响应片段: ${chunk.substring(0, 50)}...`);
});
emitter.on("response:end", (fullResponse) => {
console.log("响应完成");
});
// 发送提示
const response = await client.sendPrompt("法国的首都是什么?");
工具是一个强大的功能,允许LLM执行操作并访问外部数据。本节提供详细的指导,如何有效地创建和注册工具。
registerTool方法接受四个参数:
client.registerTool(
name, // 字符串:工具的唯一标识符
handler, // 函数:实现工具的异步函数
description, // 字符串:人类可读的描述,说明工具的作用
inputSchema // 对象:定义工具参数的JSON Schema
);
getCurrentTime,searchDatabase)import { BedrockMCPClient, LogLevel } from "@juspay/bedrock-mcp-connector";
// 创建一个客户端
const client = new BedrockMCPClient({
modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
});
// 设置日志级别(可选)
client.setLogLevel(LogLevel.INFO);
// 注册一个自定义工具
client.registerTool(
// 名称:使用清晰、描述性的名称
"getCurrentTime",
// 处理程序:实现工具的功能
async (name, input) => {
// 输入验证及默认值
const timezone = input.timezone || "UTC";
try {
// 核心功能
const date = new Date().toLocaleString("en-US", { timeZone: timezone });
// 返回成功的结果
return {
content: [{ text: `当前时间是 ${date} 在 ${timezone}` }],
};
} catch (error) {
// 错误处理
return {
content: [{ text: `获取时间出错: ${error.message}` }],
isError: true,
};
}
},
// 描述:清楚地解释工具的作用
"获取指定时区的当前时间。此工具返回根据美国地区惯例格式化的日期和时间。",
// 输入模式:使用JSON Schema定义参数
{
type: "object",
properties: {
timezone: {
type: "string",
description:
"要获取时间的时区(例如,UTC,America/New_York,Europe/London)",
examples: ["UTC", "America/New_York", "Europe/Paris", "Asia/Tokyo"],
},
},
required: [], // 空数组表示没有需要的参数
}
);
// 发送可能使用工具的提示
const response = await client.sendPrompt("东京现在的时刻是多少?");
输入模式使用JSON Schema格式定义工具接受的参数:
{
type: "object",
properties: {
// 定义每个参数
paramName: {
type: "string" | "number" | "boolean" | "array" | "object",
description: "参数的清晰描述",
examples: ["example1", "example2"], // 可选但有用
enum: ["option1", "option2"], // 固定选项的参数
minimum: 1, // 数字验证
maximum: 100, // 数字验证
pattern: "^[a-z]+$", // 正则表达式的字符串验证
// 根据需要添加额外的JSON Schema属性
},
// 更多参数...
},
required: ["paramName1", "paramName2"], // 列出必需的参数
additionalProperties: false // 防止额外参数(可选)
}
正确的错误处理对于工具至关重要:
client.registerTool(
"divideNumbers",
async (name, input) => {
// 参数验证
if (typeof input.dividend !== "number") {
return {
content: [{ text: "错误:被除数必须是数字" }],
isError: true,
};
}
if (typeof input.divisor !== "number") {
return {
content: [{ text: "错误:除数必须是数字" }],
isError: true,
};
}
// 业务逻辑验证
if (input.divisor === 0) {
return {
content: [{ text: "错误:不能除以零" }],
isError: true,
};
}
try {
// 执行操作
const result = input.dividend / input.divisor;
return {
content: [
{
text: `${input.dividend} 除以 ${input.divisor} 等于 ${result}`,
},
],
};
} catch (error) {
return {
content: [{ text: `计算错误:${error.message}` }],
isError: true,
};
}
},
"两个数字相除",
{
type: "object",
properties: {
dividend: {
type: "number",
description: "要被除的数字",
},
divisor: {
type: "number",
description: "除以的数字(不能为零)",
},
},
required: ["dividend", "divisor"],
}
);
client.registerTool(
"getWeatherForecast",
async (name, input) => {
const { city, days = 3 } = input;
if (!city) {
return {
content: [{ text: "错误:城市参数是必需的" }],
isError: true,
};
}
try {
// 在实际实现中,这会调用天气API
const forecast = await weatherService.getForecast(city, days);
return {
content: [
{
text: `接下来 ${days} 天 ${city} 的天气预报:\n\n${forecast}`,
},
],
};
} catch (error) {
return {
content: [{ text: `获取天气预报出错:${error.message}` }],
isError: true,
};
}
},
"获取城市的天气预报",
{
type: "object",
properties: {
city: {
type: "string",
description: "要获取天气预报的城市",
},
days: {
type: "number",
description: "预报天数(默认:3)",
minimum: 1,
maximum: 10,
},
},
required: ["city"],
}
);
client.registerTool(
"calculateStatistics",
async (name, input) => {
const { numbers } = input;
if (!Array.isArray(numbers) || numbers.length === 0) {
return {
content: [
{ text: "错误:numbers 必须是非空数字数组" },
],
isError: true,
};
}
if (!numbers.every((n) => typeof n === "number")) {
return {
content: [{ text: "错误:numbers 中的所有元素都必须是数字" }],
isError: true,
};
}
try {
const sum = numbers.reduce((a, b) => a + b, 0);
const mean = sum / numbers.length;
const sortedNumbers = [...numbers].sort((a, b) => a - b);
const median =
sortedNumbers.length % 2 === 0
? (sortedNumbers[sortedNumbers.length / 2 - 1] +
sortedNumbers[sortedNumbers.length / 2]) /
2
: sortedNumbers[Math.floor(sortedNumbers.length / 2)];
return {
content: [
{
text: `数字数组 [${numbers.join(
", "
)}] 的统计信息:\n- 总和:${sum}\n- 平均值:${mean}\n- 中位数:${median}\n- 最小值:${Math.min(
...numbers
)}\n- 最大值:${Math.max(...numbers)}`,
},
],
};
} catch (error) {
return {
content: [{ text: `计算统计信息出错:${error.message}` }],
isError: true,
};
}
},
"计算一组数字的基本统计信息",
{
type: "object",
properties: {
numbers: {
type: "array",
items: {
type: "number",
},
description: "要计算统计信息的数字数组",
},
},
required: ["numbers"],
}
);
client.registerTool(
"searchWikipedia",
async (name, input) => {
const { query, limit = 3 } = input;
if (!query || typeof query !== "string") {
return {
content: [
{ text: "错误:查询参数是必需的且必须是字符串" },
],
isError: true,
};
}
try {
// 在实际实现中,这会调用维基百科API
const searchUrl = `https://en.wikipedia.org/w/api.php?action=opensearch&search=${encodeURIComponent(
query
)}&limit=${limit}&namespace=0&format=json`;
const response = await fetch(searchUrl);
const [searchTerm, titles, descriptions, urls] = await response.json();
let resultText = `维基百科搜索结果“${query}”:\n\n`;
for (let i = 0; i < titles.length; i++) {
resultText += `${i + 1}. ${titles[i]}\n`;
resultText += ` ${descriptions[i]}\n`;
resultText += ` ${urls[i]}\n\n`;
}
return {
content: [{ text: resultText }],
};
} catch (error) {
return {
content: [{ text: `搜索维基百科出错:${error.message}` }],
isError: true,
};
}
},
"搜索维基百科上的主题信息",
{
type: "object",
properties: {
query: {
type: "string",
description: "搜索查询",
},
limit: {
type: "number",
description: "返回的最大结果数(默认:3)",
minimum: 1,
maximum: 10,
},
},
required: ["query"],
}
);
对于更复杂的工具,您可以组织代码使其更易于维护:
// 在单独的文件中定义工具处理程序