ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MCP Client 的 Converse 请求报错?TaoToken 这边的 Base URL 先别多写 /v1

MCP Client 的 Converse 请求报错?TaoToken 这边的 Base URL 先别多写 /v1 npm run converse 跑起来后终端没有等到response.output.message.content反而抛了 Converse 请求失败或鉴权错误——如果你正在用modelcontextprotocol/sdk的Client配合StdioClientTransport搭 MCP Client再把listTools得到的工具塞进BedrockRuntimeClient的ConverseCommand先别急着翻模型 ID。TaoToken 这边的 Base URL 只填https://taotoken.net/api末尾不要多写/v1Key 和可用模型从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建和查看。很多报错不是 MCP Server 连不上也不是工具描述写错而是BedrockRuntimeClient的 endpoint 被手滑写成了带/v1的地址Converse 请求直接打到错误路径返回 404 或签名校验失败看起来像“通道鉴权有问题”。这篇按原文那条 MCP Client 主路径走一遍初始化项目、改构造函数、连接 MCP Server、发送ConverseCommand、处理tool_use、最后用 Inquirer.js 的聊天循环验证返回内容。1. npm run converse 报错时先看 Base URL 尾部1.1 Converse 请求到底发到了哪个地址原文的ConverseMcpClient里BedrockRuntimeClient是在构造函数里创建的默认只给了region: us-east-1。如果你直接把官方示例里的 AWS 凭据和 region 搬过来再把 endpoint 改成 TaoToken 的兼容通道最容易出问题的地方就是 endpoint 字符串。正确的接口地址是https://taotoken.net/api不要写成https://taotoken.net/api/v1 https://taotoken.net/api/ https://taotoken.net/v1ConverseCommand发出去以后AWS SDK 会按 endpoint 和模型 ID 拼请求路径。多一个/v1服务端收到的路径就不是 Converse 期望的路径于是表现为 404、UnknownOperationException或者某些兼容层直接返回鉴权失败。你以为是 Key 错了其实是地址多了尾巴。1.2 多写 /v1 时终端里常看到什么跑npm run converse后如果bedrock.send(new ConverseCommand(input))抛错先在 catch 里把 endpoint 和 modelId 打出来。常见现象有三类第一类HTTP 404消息里带Could not find operation或Not Found。这时优先检查 endpoint 是否多了/v1以及末尾是否多了/。第二类403 或SignatureDoesNotMatch。如果 Key 是从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的并且复制时没有多余空格那大概率不是 Key 本身而是 endpoint 路径不对导致签名计算的目标不一致。第三类MCP 工具已经listTools成功但 Converse 一调用就失败。说明connectToMcpServer的 stdio 连接没问题问题集中在bedrock.send这一层。原文里能定位问题的两处代码就是connectToMcpServer的 try/catch 和bedrock.send(new ConverseCommand(input))先把这两处日志补全。2. 项目初始化mcp-bedrock-converse 的依赖与脚本2.1 建目录、装依赖、补 package.json原文从零建了一个mcp-bedrock-converse项目。这一步不用改工具链照常初始化即可。区别在于后面配置 Key 和 Base URL 时不要写死 AWS 凭据而是走 TaoToken 创建的 Key。mkdir mcp-bedrock-converse cd mcp-bedrock-converse npm init -y npm install aws-sdk/client-bedrock-runtime modelcontextprotocol/sdk inquirer/prompts npm i --save-dev types/node mkdir src touch src/index.tspackage.json里的 scripts 保持原文节奏一个 build一个 converse。注意type要设为module否则后面import和顶层 await 会出问题。{ scripts: { build: tsc chmod 755 build/index.js, converse: node build/index.js }, type: module }2.2 tsconfig.json 保持 Node16 模块解析原文的tsconfig.json用了Node16和Node16模块解析这对modelcontextprotocol/sdk的 ESM 导入方式比较友好。直接沿用不要随手改成commonjs否则StdioClientTransport的导入路径可能报错。{ compilerOptions: { target: ES2022, module: Node16, moduleResolution: Node16, outDir: ./build, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }2.3 准备 TaoToken 的 Key 与模型 ID原文在构造函数里直接写 AWS 凭据和region: us-east-1。改写后这一步变成打开 TaoToken 注册并创建 API KeyKey 和 Base URL 都从这里拿。Base URL 只填https://taotoken.net/api不要加/v1。模型 ID 不要凭记忆写去模型广场复制当前可用的 ID填到环境变量或代码占位符里。建议用环境变量export TAOTOKEN_API_KEYYOUR_API_KEY export TAOTOKEN_MODEL_IDYOUR_MODEL_IDYOUR_API_KEY从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建YOUR_MODEL_ID以该页面模型广场当时列表为准。不要把真实 Key 提交到 Git。3. 改构造函数BedrockRuntimeClient 指向 TaoToken 接口3.1 不要直接写 AWS 凭据用 TaoToken 的 Key原文的构造函数只创建了BedrockRuntimeClient和 MCPClient。现在要把BedrockRuntimeClient的 endpoint 指到 TaoToken 的接口地址并把凭据替换成 TaoToken 创建的 Key。注意官网落地页和接口 Base URL 不要混用。落地页用于注册、创建 Key、看模型广场、看用量填进 SDK 的 Base URL 是https://taotoken.net/api末尾不带/v1。import { BedrockRuntimeClient, ConverseCommand, ConverseCommandInput, Message, Tool, ToolInputSchema, } from aws-sdk/client-bedrock-runtime; import { input } from inquirer/prompts; import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; const TAOTOKEN_BASE_URL https://taotoken.net/api; const TAOTOKEN_API_KEY process.env.TAOTOKEN_API_KEY ?? YOUR_API_KEY; const TAOTOKEN_MODEL_ID process.env.TAOTOKEN_MODEL_ID ?? YOUR_MODEL_ID; class ConverseMcpClient { private mcp: Client; private bedrock: BedrockRuntimeClient; private transport: StdioClientTransport | null null; private tools: Tool[] []; private modelId: string; constructor(modelId: string) { this.modelId modelId; this.bedrock new BedrockRuntimeClient({ region: us-east-1, endpoint: TAOTOKEN_BASE_URL, credentials: { accessKeyId: TAOTOKEN_API_KEY, secretAccessKey: TAOTOKEN_API_KEY, }, }); this.mcp new Client({ name: mcp-client-cli, version: 1.0.0 }); } }3.2 endpoint 末尾不要带 /v1TAOTOKEN_BASE_URL写死成https://taotoken.net/api之后就不要在别处再拼/v1。有些人会在环境变量里写https://taotoken.net/api/v1然后在代码里又拼一次最后变成/api/v1/v1/...。排障时直接console.log(TAOTOKEN_BASE_URL)确认输出里没有/v1。另外region可以保留us-east-1因为 AWS SDK 需要一个 region 来初始化签名。真正决定请求发到哪里的是endpoint。如果你用的是其他 AWS SDK 版本也可以通过AWS_ENDPOINT_URL_BEDROCK_RUNTIME环境变量覆盖但建议优先在代码里显式写清楚方便排查。4. connectToMcpServer 的 try/catch 里重点看什么4.1 StdioClientTransport 的脚本类型判断原文的connectToMcpServer先判断 MCP Server 脚本是.js还是.py再决定用process.execPath还是python/python3。这段逻辑本身没问题但一旦抛错try/catch 只打印Failed to connect to MCP server信息不够。建议把脚本路径、命令、参数都打出来。async connectToMcpServer(serverScriptPath: string) { try { const isJs serverScriptPath.endsWith(.js); const isPy serverScriptPath.endsWith(.py); if (!isJs !isPy) { throw new Error(Server script must be a .js or .py file); } const command isPy ? process.platform win32 ? python : python3 : process.execPath; console.log([mcp] command , command); console.log([mcp] script , serverScriptPath); this.transport new StdioClientTransport({ command, args: [serverScriptPath], }); await this.mcp.connect(this.transport); const toolsResult await this.mcp.listTools(); this.tools toolsResult.tools.map((tool) { const toolInputSchema: ToolInputSchema { json: JSON.parse(JSON.stringify(tool.inputSchema)), }; const bedrockTool: Tool { toolSpec: { inputSchema: toolInputSchema, name: tool.name, description: tool.description, }, }; return bedrockTool; }); console.log([mcp] connected, tools${this.tools.length}); } catch (e) { console.log(Failed to connect to MCP server: , e); throw e; } }4.2 listTools 映射到 Bedrock Tool 的格式MCP 的listTools()返回的inputSchema不能直接塞给 Bedrock原文用JSON.parse(JSON.stringify(...))转了一层再包成toolSpec.inputSchema.json。这个结构别改错。如果你发现 Converse 返回ValidationException先检查this.tools里的toolSpec是否有name、description、inputSchema.json三个字段。还有一个容易忽略的点MCP Server 的 stdio 连接和callTool始终由 MCP Client 自己完成TaoToken 在这个流程里只提供 Key 和 Base URL不负责替你连接 MCP Server。所以connectToMcpServer报错时先看本地脚本路径和 Python/Node 命令不要第一反应去控制台换 Key。5. ConverseCommand 与 tool_use 循环的排障点5.1 bedrock.send 外层加错误捕获原文真正烧 Token 的请求是this.bedrock.send(new ConverseCommand(input))。这里一定要加 try/catch把 endpoint、modelId、错误名和$metadata打出来。很多 401/404 在这一层就能确认。async converse(conversation: Message[]) { const input: ConverseCommandInput { modelId: this.modelId, messages: conversation, }; if (this.tools.length 0) { input.toolConfig { tools: this.tools }; } let response; try { response await this.bedrock.send(new ConverseCommand(input)); } catch (err) { console.error([bedrock] Converse 请求失败); console.error(endpoint , TAOTOKEN_BASE_URL); console.error(modelId , this.modelId); console.error(err); throw err; } if (response.stopReason tool_use) { if (response.output?.message?.content) { const message response.output.message; conversation.push(message); const content response.output.message.content; for (const contentBlock of content) { if (contentBlock.toolUse?.name) { const toolName contentBlock.toolUse.name; const toolArguments JSON.parse( JSON.stringify(contentBlock.toolUse.input), ); const toolResponse await this.mcp.callTool({ name: toolName, arguments: toolArguments, }); const toolResultMessage: Message { role: user, content: [ { toolResult: { toolUseId: contentBlock.toolUse.toolUseId, content: [{ text: JSON.stringify(toolResponse) }], }, }, ], }; conversation.push(toolResultMessage); await this.converse(conversation); } } } } else if (response.output?.message) { const message response.output.message; console.log(message.content?.[0]?.text ?? ); conversation.push(message); } }5.2 stopReason 为 tool_use 时回填 toolResult当模型决定调用工具response.stopReason会是tool_use。这时不能直接把toolResponse当成普通文本塞回去必须构造toolResult并且带上模型给的toolUseId。原文这里用了client.mcp.callTool在类里应该改成this.mcp.callTool否则会引用到外部不存在的client变量。如果工具调用成功但模型下一轮仍然报错检查conversation.push(toolResultMessage)是否在await this.converse(conversation)之前执行。顺序反了模型会缺少工具结果上下文。5.3 response.output.message.content 正常返回的样子地址改对、Key 有效、模型 ID 存在时最终会走到response.output.message.content终端里打印出文本。此时说明 Converse 请求已经通了。如果只返回tool_use但工具执行后没有后续文本可以在callTool后加一行日志确认toolResponse不是空对象。6. Lo-fi 聊天界面与最小验证6.1 questionPrompt 和 chat 循环原文用 Inquirer.js 做了一个极简聊天界面questionPrompt接收用户输入把消息 push 进conversationchat在 while 循环里不断调用converse直到用户输入空消息退出。async questionPrompt(message: string, conversation: Message[]): Promiseboolean { const answer await input({ message }); if (answer) { const question: Message { role: user, content: [{ text: answer }], }; conversation.push(question); return true; } return false; } async chat() { const conversation: Message[] []; try { if (await this.questionPrompt(Whats up?, conversation)) { while (true) { await this.converse(conversation); if (!(await this.questionPrompt(, conversation))) { break; } } } } catch (error) { if (error instanceof Error error.name ExitPromptError) { // 用户取消忽略 } else { console.error(error); throw error; } } } async function main() { const client new ConverseMcpClient(TAOTOKEN_MODEL_ID); // await client.connectToMcpServer(/path/to/mcp/index.js); await client.chat(); } main().catch(console.error);6.2 build 和 converse 跑起来编辑完src/index.ts后执行npm run build npm run converse如果connectToMcpServer被注释掉就是纯 Converse 验证不涉及 MCP 工具。先确保这条路通再打开 MCP Server 脚本路径。打开后如果listTools返回工具数量大于 0Converse 请求会带上toolConfig。此时多问一句需要调用工具的问题看stopReason是否出现tool_use。验证通过的标准很简单终端里能打印出模型返回的文本并且response.output.message.content不为空。走到这一步说明 Base URL 没有多写/v1Key 也能被通道识别。7. 排障清单401、404、/v1、MCP 工具超时7.1 鉴权失败先回控制台看 Key如果错误是 401 或 403先确认TAOTOKEN_API_KEY是不是从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建的复制时有没有带空格。然后在 控制台 API Keys 里核对这把 Key 是否还在、是否有额度。不要用别的渠道生成的 Key 混填。7.2 地址对照官网链接与接口 Base URL再强调一次地址区别用途地址注册、创建 Key、看模型广场、看用量https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end填进 BedrockRuntimeClient 的 endpointhttps://taotoken.net/api可以带/v1吗不可以末尾不要带/v1如果你在浏览器里打开接口地址发现不是网页那是正常的它是给 SDK 用的。排查时不要用浏览器访问结果来判断接口是否可用。7.3 MCP 工具执行失败不要甩给通道MCP Server 的 stdio 连接、listTools、callTool都由 MCP Client 自己完成。TaoToken 只提供 Key 和 Base URL。如果工具执行超时、脚本路径报错、Python 命令找不到先看connectToMcpServer的 catch 日志再看 MCP Server 脚本本身能不能独立运行。不要在通道地址上反复改/v1那解决不了 stdio 连接问题。8. 跑通之后用同一把 Key 做一次闭环验证Converse 正常返回后可以先用同一把 Key 在 TaoToken 模型对话 里发一条测试消息确认模型 ID 和 Base URL 没填错。如果你打算长期跑 MCP Client可以打开 Coding Plan 看套餐是否够用Key 在 控制台 API Keys 创建和管理。需要把 Converse 请求接到 Claude Code 之类的执行工具时环境变量对照见 Claude Code 接入文档但 MCP Client 本身的 stdio 连接和工具调用仍然留在你自己的 TypeScript 项目里完成。
返回列表