基于MCP协议构建AI开发工具代理:实现成本控制与审计追踪
1. 项目概述为什么我们需要为AI开发工具装上“刹车”和“行车记录仪”最近在深度使用Cursor这类AI驱动的代码编辑器时我遇到了一个很实际的问题团队协作时如何管理AI助手比如Cursor内置的Claude或GPT的调用成本并清晰地追踪谁在什么时候、为了什么目的调用了它这听起来像是个管理问题但其实是个技术活。当你的工具链深度集成了AI能力每一次代码补全、每一次对话解释背后都可能是一次API调用产生实实在在的费用。放任不管月底的账单可能会让你大吃一惊而缺乏审计出了问题比如生成了不安全的代码也根本无从追溯。这就是“Cursor MCP Proxy Setup”这个项目要解决的核心痛点。MCP即Model Context Protocol你可以把它理解为一套让不同AI工具和应用之间安全、标准化通信的“普通话”。而“Proxy Setup”就是为这套通信建立一个“中间站”或“网关”。这个网关的核心使命有两个预算控制和审计追踪。想象一下你给团队的AI工具使用装上了“预算锁”和“黑匣子”既能防止成本超支又能记录每一次“飞行数据”。这个指南适合所有在团队环境中使用Cursor、Claude Desktop或其他支持MCP的AI工具的开发者、技术负责人和DevOps工程师。无论你是想控制个人项目的零星开销还是需要管理一个几十人团队的AI资源使用这套方案都能提供一个清晰、可落地的技术路径。接下来我会拆解整个搭建过程从原理到实操并分享我踩过的一些坑和优化技巧。2. 整体架构与核心组件选型解析在开始动手之前我们必须先理解我们要构建的是什么以及为什么选择这些组件。整个系统的目标是在Cursor客户端和AI模型服务如Anthropic的Claude API之间插入一个我们自己掌控的代理服务器。2.1 为什么是MCP代理MCP协议的本质是定义了一套标准的JSON-RPC接口用于工具如Cursor和模型服务器之间交换提示词、上下文和工具调用信息。直接连接时Cursor会通过MCP协议直接与官方的Claude API服务器对话。而代理模式则是让Cursor先连接到我们自己的服务器再由我们的服务器去转发请求到真正的API终点。这样做的好处显而易见集中控制点所有流量都经过我们的服务器这是实施预算和审计的黄金位置。协议透明MCP基于HTTP和JSON-RPC是标准的Web协议易于用常规的Web技术进行拦截、分析和修改。与客户端解耦无需修改Cursor或任何客户端的代码只需改变其配置中的连接地址对终端用户完全透明。2.2 核心组件技术选型我们需要构建两个核心功能模块代理转发和控制逻辑。以下是经过实践验证的选型方案及其理由1. 代理服务器框架Node.js Express理由MCP通信本质是HTTPNode.js的异步非阻塞特性非常适合处理大量并发的API转发请求。Express是Node.js生态中最成熟、最灵活的Web框架中间件机制可以让我们轻松地插入审计日志、速率限制和成本计算逻辑。相较于Python的Flask/FastAPINode.js在处理JSON-RPC这种纯HTTP/JSON的流水线作业时通常更轻量、启动更快。备选方案Go (Gin/Echo框架) 是另一个高性能选择适合对并发和资源消耗有极致要求的场景但初期开发速度可能略慢于Node.js。2. 审计日志存储SQLite (开发/轻量) 或 PostgreSQL (生产)理由审计数据需要结构化存储以便查询分析。SQLite无需单独部署数据库服务一个文件搞定非常适合个人或小团队初期使用。它的简单性让我们能快速搭建原型。当数据量增大或需要团队协作访问时可以无缝迁移到PostgreSQL。审计日志的关键字段应包括请求ID、时间戳、用户标识如API Key或用户名、模型类型、提示词Token数、完成Token数、估算成本、请求状态和原始请求/响应的摘要或哈希值注意隐私可能不存全文。注意切勿将完整的提示词和响应内容可能包含敏感代码或业务逻辑明文存入日志应只存元数据或进行哈希处理。3. 预算控制实现内存存储 定期持久化理由预算检查需要极低的延迟和高频的读写每次API调用前都要检查。使用内存如JavaScript的Map或对象来存储用户/团队的实时预算消耗是最快的。同时我们需要一个后台进程定期例如每5分钟将内存中的数据快照持久化到上述的SQLite/PostgreSQL中防止服务器重启导致数据丢失。对于分布式部署则需要引入Redis等分布式缓存来共享预算状态。关键设计预算检查必须是一个原子操作。在高并发下需要防止“超卖”两个请求同时读取余额都判断为足够然后都扣费导致透支。在单进程Node.js中可以利用其单线程事件循环的特性配合异步队列来简化这个问题。更严谨的做法是使用数据库的行锁或Redis的原子操作INCRBY/DECRBY。4. 用户/团队标识API Key 体系理由我们需要区分不同用户或团队的流量。最通用的方式是为每个用户或团队生成一个唯一的API Key。Cursor在配置MCP服务器时可以将这个Key作为连接参数或放在请求头中如Authorization: Bearer api_key。我们的代理服务器在收到请求后首先验证这个Key的有效性并将其作为预算归属和审计日志的标识。实操技巧API Key可以设计成有不同权限等级如只读、标准、管理员并可以设置启用/禁用状态。Key的生成可以使用crypto.randomBytes生成高强度随机字符串并哈希后存储仅在一次性地将明文Key返回给用户。3. 分步搭建MCP代理服务器理论清晰后我们开始动手搭建。我将以Node.js Express SQLite的技术栈为例展示从零到一的构建过程。3.1 初始化项目与依赖安装首先创建一个新的项目目录并初始化。mkdir cursor-mcp-proxy cd cursor-mcp-proxy npm init -y安装核心依赖npm install express dotenv axios sqlite3 bcryptjs jsonwebtoken npm install --save-dev nodemonexpress: Web服务器框架。dotenv: 管理环境变量如数据库路径、API密钥、预算限额。axios: 用于向真实的AI API端点如api.anthropic.com转发请求。sqlite3: 操作SQLite数据库。bcryptjs: 用于哈希API Key如果存数据库。jsonwebtoken: 可选项用于生成和验证更复杂的JWT Token作为身份凭证。nodemon: 开发工具代码变动时自动重启服务器。创建项目基础结构cursor-mcp-proxy/ ├── .env ├── .gitignore ├── package.json ├── server.js # 主入口文件 ├── config/ │ └── index.js # 配置管理 ├── middleware/ │ ├── auth.js # API Key认证中间件 │ ├── audit.js # 审计日志中间件 │ └── budget.js # 预算检查中间件 ├── services/ │ ├── db.js # 数据库连接与初始化 │ ├── budgetService.js # 预算管理逻辑 │ └── auditService.js # 审计日志逻辑 └── routes/ └── mcp-proxy.js # MCP代理路由3.2 配置管理与数据库初始化在.env文件中配置关键信息PORT3000 NODE_ENVdevelopment DB_PATH./data/audit.db ANTHROPIC_API_KEYyour_actual_anthropic_api_key_here ANTHROPIC_BASE_URLhttps://api.anthropic.com DEFAULT_BUDGET_MONTHLY_USD50.00 # 默认月度预算美元在config/index.js中集中管理配置require(dotenv).config(); module.exports { port: process.env.PORT || 3000, nodeEnv: process.env.NODE_ENV || development, dbPath: process.env.DB_PATH, anthropicApiKey: process.env.ANTHROPIC_API_KEY, anthropicBaseUrl: process.env.ANTHROPIC_BASE_URL, defaultBudget: parseFloat(process.env.DEFAULT_BUDGET_MONTHLY_USD) || 50.0, };在services/db.js中初始化SQLite数据库和审计表const sqlite3 require(sqlite3).verbose(); const path require(path); const config require(../config); let db; function initDatabase() { const dbPath path.resolve(__dirname, .., config.dbPath); // 确保数据目录存在 const fs require(fs); const dir path.dirname(dbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } db new sqlite3.Database(dbPath, (err) { if (err) { console.error(Could not connect to database, err); } else { console.log(Connected to SQLite database.); createTables(); } }); } function createTables() { // 审计日志表 db.run(CREATE TABLE IF NOT EXISTS audit_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, request_id TEXT NOT NULL, api_key_id TEXT NOT NULL, -- 关联的API Key标识 timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, model TEXT, prompt_tokens INTEGER, completion_tokens INTEGER, estimated_cost_usd REAL, status_code INTEGER, path TEXT, user_agent TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )); // API Key管理表简化版 db.run(CREATE TABLE IF NOT EXISTS api_keys ( id TEXT PRIMARY KEY, -- 例如key_abc123 hashed_key TEXT NOT NULL, -- 存储哈希值非明文 name TEXT, monthly_budget_usd REAL DEFAULT 50.00, current_spent_usd REAL DEFAULT 0.00, reset_date DATE, -- 预算重置日期如每月1号 is_active BOOLEAN DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )); // 预算快照表用于持久化内存中的实时花费 db.run(CREATE TABLE IF NOT EXISTS budget_snapshots ( api_key_id TEXT NOT NULL, snapshot_date DATE NOT NULL, spent_usd REAL NOT NULL, PRIMARY KEY (api_key_id, snapshot_date) )); } function getDb() { if (!db) { initDatabase(); } return db; } module.exports { initDatabase, getDb, };注意这里存储的是API Key的哈希值而不是明文。当客户端传来API Key时我们需要用同样的算法哈希后与数据库中的hashed_key比对。bcryptjs库非常适合做这个。永远不要在日志或响应中泄露明文API Key。3.3 实现核心中间件认证、审计与预算中间件是Express处理请求的管道我们将功能模块化。1. 认证中间件 (middleware/auth.js):const bcrypt require(bcryptjs); const { getDb } require(../services/db); async function authenticateApiKey(req, res, next) { // 从请求头中获取API Key例如Authorization: Bearer sk_proxy_abc123 const authHeader req.headers.authorization; if (!authHeader || !authHeader.startsWith(Bearer )) { return res.status(401).json({ error: { message: Missing or invalid Authorization header } }); } const apiKey authHeader.substring(7); // 去掉Bearer 前缀 const db getDb(); // 这里简化处理实际应查询数据库比对哈希值 // 为了演示我们假设有一个内存中的有效Key映射 // 实际项目中这里应该查询 api_keys 表用 bcrypt.compareSync(apiKey, hashedKey) const isValidKey await validateApiKeyFromDb(apiKey); if (!isValidKey) { return res.status(403).json({ error: { message: Invalid API key } }); } // 将验证后的Key信息附加到请求对象供后续中间件使用 req.apiKeyId isValidKey.id; // 假设返回对象包含id req.apiKeyBudget isValidKey.monthly_budget_usd; req.apiKeySpent isValidKey.current_spent_usd; next(); } // 模拟数据库验证函数 async function validateApiKeyFromDb(apiKey) { // 实际应从数据库查询并比对哈希 // 此处返回模拟数据 return { id: team_dev, monthly_budget_usd: 100.00, current_spent_usd: 23.50, }; } module.exports { authenticateApiKey };2. 预算检查中间件 (middleware/budget.js):// 内存中的预算缓存键为 apiKeyId值为已花费金额美元 const budgetCache new Map(); function checkBudget(req, res, next) { const { apiKeyId, apiKeyBudget } req; const estimatedCost req.estimatedCost || 0; // 这个值需要在审计中间件中计算并附加 if (!apiKeyId) { return next(new Error(API Key信息缺失请先通过认证中间件)); } const currentSpent budgetCache.get(apiKeyId) || 0; const projectedSpent currentSpent estimatedCost; // 检查是否超预算 if (projectedSpent apiKeyBudget) { return res.status(429).json({ error: { message: Budget exceeded. Monthly budget: $${apiKeyBudget}, already spent: $${currentSpent.toFixed(2)}, this request would cost ~$${estimatedCost.toFixed(2)}., type: budget_limit } }); } // 预算充足将预估成本暂存待请求成功后再扣减 req.projectedCost estimatedCost; next(); } // 一个简单的函数用于在请求成功后更新内存缓存 function updateBudgetCache(apiKeyId, cost) { const current budgetCache.get(apiKeyId) || 0; budgetCache.set(apiKeyId, current cost); } // 定期将内存缓存持久化到数据库的函数需另设定时任务 async function syncBudgetToDb() { // ... 遍历 budgetCache更新 api_keys 表的 current_spent_usd 字段 } module.exports { checkBudget, updateBudgetCache, budgetCache };3. 审计日志中间件 (middleware/audit.js):这是最复杂的部分它需要拦截请求和响应计算成本并记录日志。const { getDb } require(../services/db); const { v4: uuidv4 } require(uuid); // 需要安装 uuid 包 async function auditLog(req, res, next) { const startTime Date.now(); const requestId uuidv4(); req.requestId requestId; // 捕获原始响应发送方法 const originalSend res.send; let responseBody; res.send function(body) { responseBody body; originalSend.call(this, body); }; // 响应完成后记录日志 res.on(finish, async () { const duration Date.now() - startTime; const { apiKeyId, path, method } req; const userAgent req.get(User-Agent) || ; // 解析请求体估算Token和成本简化版 let estimatedCost 0; let model unknown; let promptTokens 0; let completionTokens 0; try { // MCP请求体通常是JSON RPC格式我们需要解析其中的参数 if (req.body req.body.params req.body.params.messages) { // 这是一个非常粗略的估算实际应根据模型定价和准确的Token数计算。 // 例如Claude 3 Opus: $15 / 1M input tokens, $75 / 1M output tokens model req.body.params.model || claude-3-opus-20240229; // 假设我们有一个函数 estimateTokens 来估算消息的token数 promptTokens estimateTokens(req.body.params.messages); // 输出Token数需要从响应体中获取 if (responseBody typeof responseBody string) { const resp JSON.parse(responseBody); if (resp.result resp.result.content) { completionTokens estimateTokens(resp.result.content); } } // 简单成本计算示例价格需替换为实际 const inputCostPerMillion 15.0; // $15 per 1M input tokens const outputCostPerMillion 75.0; // $75 per 1M output tokens estimatedCost (promptTokens / 1_000_000) * inputCostPerMillion (completionTokens / 1_000_000) * outputCostPerMillion; } } catch (err) { console.error(Failed to estimate cost:, err); } // 将估算的成本附加到请求对象供预算中间件使用注意这是响应后预算检查在之前 // 更合理的架构是在转发请求前根据请求内容预先估算一个成本用于预算检查。 // 这里为了流程清晰先记录。 // 记录到数据库 const db getDb(); const stmt db.prepare( INSERT INTO audit_logs (request_id, api_key_id, model, prompt_tokens, completion_tokens, estimated_cost_usd, status_code, path, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ); stmt.run( requestId, apiKeyId || unknown, model, promptTokens, completionTokens, estimatedCost, res.statusCode, path, userAgent ); stmt.finalize(); // 如果请求成功更新内存中的预算缓存 if (res.statusCode 200 res.statusCode 300 apiKeyId estimatedCost 0) { const { updateBudgetCache } require(./budget); updateBudgetCache(apiKeyId, estimatedCost); } console.log([Audit] ${method} ${path} - ${res.statusCode} - ${duration}ms - Cost: ~$${estimatedCost.toFixed(4)}); }); next(); } // 一个非常粗略的Token估算函数实际应用应使用tiktoken库或模型提供商提供的SDK function estimateTokens(textOrMessages) { // 简化按字符数除以4估算英文近似。中文等语言不同。 let totalChars 0; if (Array.isArray(textOrMessages)) { textOrMessages.forEach(msg { if (msg.content) totalChars String(msg.content).length; }); } else if (typeof textOrMessages string) { totalChars textOrMessages.length; } return Math.ceil(totalChars / 4); } module.exports { auditLog };3.4 构建代理路由与主服务器现在我们将中间件和转发逻辑组合起来。代理路由 (routes/mcp-proxy.js):const express require(express); const axios require(axios); const { authenticateApiKey } require(../middleware/auth); const { checkBudget } require(../middleware/budget); const { auditLog } require(../middleware/audit); const config require(../config); const router express.Router(); // 关键解析JSON请求体。MCP使用JSON-RPC over HTTP。 router.use(express.json()); // 应用中间件链认证 - 审计记录开始- 预算检查 - 转发 - 审计记录结束 router.all(*, authenticateApiKey, auditLog, checkBudget, async (req, res) { try { // 构建转发到真实Anthropic API的请求 const targetUrl ${config.anthropicBaseUrl}${req.path}; const headers { Content-Type: application/json, x-api-key: config.anthropicApiKey, // 使用我们自己的Anthropic主Key anthropic-version: 2023-06-01, // 根据实际情况调整 // 可以选择性传递一些客户端头 ...(req.headers[user-agent] { User-Agent: req.headers[user-agent] }), }; // 转发请求 const response await axios({ method: req.method, url: targetUrl, headers: headers, data: req.body, // 可以设置超时等参数 timeout: 120000, // 120秒 }); // 将响应返回给客户端如Cursor res.status(response.status).json(response.data); } catch (error) { console.error(Proxy error:, error.message); // 处理错误将上游错误信息适当返回给客户端 const status error.response?.status || 500; const message error.response?.data?.error?.message || error.message; res.status(status).json({ error: { type: proxy_error, message: Proxy request failed: ${message}, } }); } }); module.exports router;主服务器文件 (server.js):const express require(express); const config require(./config); const { initDatabase } require(./services/db); const mcpProxyRouter require(./routes/mcp-proxy); // 初始化数据库 initDatabase(); const app express(); const PORT config.port; // 全局中间件可选 app.use((req, res, next) { console.log(${new Date().toISOString()} - ${req.method} ${req.url}); next(); }); // 挂载MCP代理路由。所有发往 /v1/ 的请求这是Anthropic API的典型路径都由代理处理。 app.use(/v1, mcpProxyRouter); // 健康检查端点 app.get(/health, (req, res) { res.json({ status: ok, timestamp: new Date().toISOString() }); }); // 一个简单的管理端点查看当前预算缓存生产环境需要加权限 app.get(/admin/budget-cache, (req, res) { const { budgetCache } require(./middleware/budget); res.json(Object.fromEntries(budgetCache)); }); app.listen(PORT, () { console.log(Cursor MCP Proxy Server running on http://localhost:${PORT}); console.log(MCP endpoint: http://localhost:${PORT}/v1); });在package.json中添加启动脚本scripts: { start: node server.js, dev: nodemon server.js }现在运行npm run dev你的MCP代理服务器就在http://localhost:3000上运行了。4. 配置Cursor客户端连接代理服务器搭好了现在需要告诉Cursor去使用它。Cursor通过其配置文件来定义MCP服务器。找到Cursor配置Cursor的配置通常位于用户目录下的一个JSON文件中。例如在macOS上路径可能是~/Library/Application Support/Cursor/User/globalStorage/mcp.json或通过Cursor的设置界面进行配置。请查阅Cursor的最新文档确认。编辑MCP配置你需要添加或修改一个MCP服务器配置将其指向你的代理服务器。配置可能如下所示{ mcpServers: { my-anthropic-proxy: { command: npx, args: [ -y, modelcontextprotocol/server-anthropic, --api-key, sk_proxy_abc123def456, // 这是你代理服务器颁发的API Key不是Anthropic的 --api-url, http://localhost:3000/v1 // 指向你的代理服务器 ] } } }关键点command和args是启动MCP服务器的命令。这里我们假设使用一个官方的或社区的Anthropic MCP服务器实现它通过命令行参数接收API Key和自定义URL。--api-key参数的值应该是你从自己的代理服务器管理后台生成的Key如sk_proxy_xxx而不是原始的Anthropic API Key。你的代理服务器会验证这个Key。--api-url参数至关重要它告诉这个MCP服务器客户端即Cursor启动的进程将请求发送到你的代理地址localhost:3000/v1而不是默认的api.anthropic.com。重启Cursor保存配置后完全重启Cursor使其加载新的MCP服务器设置。现在当你在Cursor中使用AI功能时请求流将变为Cursor - 本地MCP服务器进程 - 你的代理服务器(localhost:3000) - 真实的Anthropic API。你的代理服务器完成了认证、审计和预算检查的全流程。5. 生产环境部署与进阶优化本地开发环境跑通只是第一步。要服务于团队你需要考虑生产部署。5.1 部署方案选择传统VPS/云服务器在DigitalOcean、AWS EC2、Google Cloud Compute Engine等上部署。你需要配置Node.js环境。使用pm2或systemd管理进程保证服务持续运行。配置Nginx或Apache作为反向代理处理SSL/TLS加密HTTPS。非常重要Cursor等客户端很可能要求HTTPS连接。绑定域名并配置DNS。容器化部署推荐使用Docker将你的代理服务器封装成镜像。# Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . EXPOSE 3000 USER node CMD [node, server.js]然后可以在任何支持Docker的环境如自有服务器、Kubernetes集群中运行或者使用云服务商的容器托管服务如AWS ECS、Google Cloud Run、Azure Container Instances。这种方式环境一致易于扩展。Serverless函数将代理逻辑拆分为函数如AWS Lambda API Gateway。这对于突发流量成本优化有好处但需要重新架构将状态如内存预算缓存存储到外部服务如Redis复杂度较高。5.2 安全性强化HTTPS是必须的使用Let‘s Encrypt免费证书或云服务商提供的证书通过Nginx配置SSL。API Key管理实现一个简单的管理界面或命令行工具来生成、吊销、查看API Key及其使用情况。Key应使用bcrypt等强哈希算法存储。请求速率限制使用express-rate-limit等中间件防止单个Key滥用。输入验证与过滤虽然你是代理但也应对转发的请求体做基本检查防止注入攻击或异常请求。日志脱敏确保审计日志不记录完整的提示词和响应尤其是可能包含密码、密钥、个人信息的对话。5.3 预算与审计功能增强预算重置策略实现按周期月、周自动重置预算。可以在api_keys表中设置reset_date并创建一个每日运行的定时任务检查是否需要重置将current_spent_usd归零并更新reset_date。实时成本估算在checkBudget中间件中你需要一个更准确的预扣费估算。可以解析请求中的model和messages使用对应模型的定价和Token估算库如anthropic-ai/tokenizerfor Claudetiktokenfor GPT进行快速估算。预扣费成功后请求转发请求完成后根据实际返回的Token数从响应头或响应体获取进行结算多退少补调整内存缓存和数据库。审计仪表盘构建一个简单的Web页面让团队成员可以查看自己的使用量、成本趋势、常用模型等。可以使用Chart.js等库可视化数据。告警机制当预算使用达到80%、90%、100%时通过邮件、Slack或钉钉发送通知。5.4 性能与可靠性连接池与超时配置axios的HTTP Agent复用到底层API的连接提升性能。设置合理的超时如连接超时、响应超时。错误重试对于上游APIAnthropic的瞬时失败5xx错误可以实现指数退避的重试机制。高可用如果团队规模大考虑部署多个代理实例前面用负载均衡器如Nginx, HAProxy分发流量。此时预算缓存必须使用共享存储如Redis。6. 常见问题与故障排查实录在实际搭建和运行过程中你几乎一定会遇到下面这些问题。这里是我的排查笔记。Q1: Cursor连接代理失败提示“无法连接到MCP服务器”或“认证失败”。检查步骤代理服务器是否在运行curl http://localhost:3000/health看是否返回{“status”:”ok”}。Cursor配置的URL和端口是否正确确认--api-url参数指向了正确的地址。如果是远程服务器确保是HTTPS且域名可解析。防火墙/安全组规则确保服务器防火墙如ufw或云服务商安全组开放了代理服务器的端口如3000。认证中间件日志查看代理服务器的控制台输出看认证中间件是否报错。检查API Key的哈希比对逻辑。MCP服务器命令路径确保Cursor配置中command指定的命令如npx在Cursor的运行环境中可用。Q2: 请求能转发但Anthropic API返回403或401错误。原因你的代理服务器没有正确地将自己的Anthropic主API Key设置到转发请求的头部。排查在代理服务器的转发代码中打印出即将发送的请求头注意不要打印出真实的Key。确保x-api-key头被正确设置且其值是你的有效Anthropic API Key。检查Key是否有调用对应模型的权限。Q3: 预算控制不准确感觉扣费比实际多或少。原因Token估算不准或成本计算模型不对。解决使用官方Tokenizer放弃简单的字符数估算集成Anthropic官方提供的Token计算库如Javascript版的anthropic-ai/tokenizer在审计中间件中精确计算Prompt Tokens。从响应头获取实际用量Anthropic API的响应头通常包含anthropic-input-tokens和anthropic-output-tokens字段。用这个实际值来计算成本比估算准确得多。在审计日志的res.on(‘finish’)回调中你可以访问res.get(‘anthropic-input-tokens’)来获取。更新定价表定期检查Anthropic官网的定价页面及时更新代码中的inputCostPerMillion和outputCostPerMillion变量。Q4: 服务器重启后内存中的预算缓存清零了。原因如设计所述内存缓存是易失的。解决实现syncBudgetToDb函数并设置一个定时任务例如使用node-cron库每1分钟或5分钟将budgetCache中的数据同步到数据库的api_keys表的current_spent_usd字段。服务器启动时从数据库读取各API Key的current_spent_usd来初始化budgetCache。Q5: 高并发下出现了预算超支两个请求同时通过检查。原因预算检查不是原子操作。解决单机版可以利用Node.js单线程特性将预算检查与扣减逻辑放入一个异步队列如async/await配合一个全局的Promise锁确保同一API Key的请求串行处理预算。但这会影响性能。解决推荐引入Redis使用Redis的INCRBY命令的原子性。伪代码如下const currentSpent await redisClient.incrByFloat(budget:${apiKeyId}, estimatedCost); if (currentSpent budgetLimit) { // 如果超了需要回滚刚才的增加 await redisClient.incrByFloat(budget:${apiKeyId}, -estimatedCost); return res.status(429).json(...); } // 预算充足继续处理请求这确保了检查和扣减是一个不可分割的操作。搭建这样一个MCP代理初期可能会觉得繁琐但一旦运行起来它带来的成本可见性和控制力是巨大的。它不仅是财务上的“刹车”更是技术管理上的“仪表盘”。你可以清楚地看到哪个团队、哪个项目、哪种类型的任务消耗了最多的AI资源从而优化使用策略让宝贵的AI算力花在刀刃上。