返回市场
基岩-MCP连接器

基岩-MCP连接器

作者:juspay2 星标更新:2025-08-25

项目介绍

Bedrock MCP Connector

与AWS Bedrock和MCP(模型上下文协议)服务器交互的TypeScript客户端。

功能

  • 无缝集成到AWS Bedrock的Converse API
  • 支持Claude和其他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进行持久存储

该包支持会话历史记录的内存和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" }
});

Redis存储

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
);

工具设计的最佳实践

  1. 单一职责:每个工具应该做好一件事
  2. 清晰命名:使用描述性的、面向动作的名称(例如,getCurrentTimesearchDatabase
  3. 详尽的描述:提供详细的描述,帮助LLM理解何时使用该工具
  4. 彻底的输入验证:始终验证输入以防止错误
  5. 明确的错误消息:当出现问题时返回清晰的错误消息
  6. 一致的返回格式:始终以相同的格式返回结果
  7. 适当的日志记录:包括日志记录以帮助调试

详细的工具注册示例

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"],
  }
);

不同类型的工具示例

1. 数据检索工具

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"],
  }
);

2. 计算工具

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"],
  }
);

3. 外部API工具

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"],
  }
);

有效使用工具的技巧

  1. 在系统提示中提供清晰的指令:指导LLM何时以及如何使用工具
  2. 使用描述性工具名称:名称应清楚表明工具的目的
  3. 包含详细的参数描述:帮助LLM理解每个参数的作用
  4. 在参数描述中添加示例:展示参数的有效值
  5. 实施彻底的验证:在处理之前检查所有输入
  6. 返回结构化数据:一致地格式化工具结果
  7. 优雅地处理错误:提供清晰的错误消息
  8. 彻底测试工具:确保它们能与各种输入一起工作

高级工具注册

对于更复杂的工具,您可以组织代码使其更易于维护:

// 在单独的文件中定义工具处理程序