恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
MCP协议实战:构建安全可控的AI智能体广告管理系统
首页
资讯中心
/
MCP协议实战:构建安全可控的AI智能体广告管理系统
MCP协议实战:构建安全可控的AI智能体广告管理系统
发布时间:2026/8/24 11:37:22
最近在探索AI智能体与业务系统集成时发现一个核心痛点如何让AI安全、可控地访问和操作企业内部的关键业务数据与API传统的API集成方式要么权限过粗要么开发复杂难以适应AI智能体灵活、动态的交互需求。而近期一个名为MCPModel Context Protocol的协议及其在X Ads原Twitter Ads广告管理场景的落地为我们提供了一个极具启发性的解决方案。本文将深入拆解MCP协议的核心原理并以X Ads推出MCP服务为案例手把手演示如何构建一个能安全管理广告的AI智能体。无论你是对AI应用开发感兴趣还是希望将AI能力融入现有业务系统这篇文章都将提供从概念到实战的完整路径。1. MCP协议AI智能体的“标准外设接口”在深入广告管理案例之前我们必须先理解MCP是什么以及它为何重要。1.1 MCP是什么解决什么问题你可以把MCP想象成电脑的USB协议。在没有USB之前每个外设打印机、键盘、U盘都需要专门的驱动和接口混乱且不便。USB协议出现后所有外设只要遵循同一套标准就能即插即用。MCPModel Context Protocol之于AI智能体就如同USB之于电脑。它是一个开放协议旨在为大型语言模型LLM和AI智能体提供一种标准化、安全的方式来发现、调用外部工具、数据源和服务统称为“资源”。它核心解决了以下问题工具集成碎片化每个AI应用如Claude Code、Cursor都需要为不同的工具数据库、API、文件系统编写特定的集成代码工作重复且低效。上下文管理复杂如何安全地将庞大的、动态的外部数据如数据库查询结果、API响应纳入AI的上下文窗口同时避免信息过载或泄露敏感数据。权限与安全控制薄弱传统的API密钥方式难以实现细粒度、动态的权限控制AI智能体一旦获得密钥就可能进行越权操作。MCP通过定义一套标准的通信机制让MCP Server资源提供方和MCP ClientAI应用或智能体框架能够相互识别和协作。Server声明自己能提供什么“工具”Tools和“资源”ResourcesClient则可以根据需要去调用它们。1.2 MCP的核心组件与工作流程理解MCP需要掌握三个核心角色MCP Server服务器实际持有工具、数据或服务的程序。例如一个连接公司MySQL数据库的Server一个封装了X Ads广告API的Server甚至是一个提供当前天气信息的Server。它负责向Client“广告”自己的能力。MCP Client客户端能够理解MCP协议的AI应用或智能体运行时环境。例如Anthropic的Claude Code、Cursor编辑器、或是基于LangChain/LlamaIndex构建的自定义智能体。Client负责发现Server提供的功能并在需要时请求执行。MCP Protocol协议定义Server和Client之间如何通信的规范包括连接建立、能力协商、工具调用、数据传递等。通常使用JSON-RPC over stdio标准输入输出或SSE服务器发送事件。一个典型的工作流程如下启动与连接用户或系统启动一个MCP Server例如广告管理Server。AI智能体应用MCP Client按照配置连接到这个Server。能力发现Client向Server发送初始化请求Server回复一个清单列出自己提供的所有“工具”如create_ad_campaign,get_ad_analytics和“资源”如schema://ads/campaign_list。工具调用当用户向AI智能体提出需求时如“请为我创建一个针对科技爱好者的广告系列”智能体分析需求决定调用哪个工具。执行与返回Client向Server发送工具调用请求包含必要的参数。Server执行实际的操作如调用X Ads API创建广告系列然后将结果返回给Client。上下文注入Client将返回的结果作为上下文信息提供给LLM让LLM能够基于真实、结构化的数据生成回答或进行下一步决策。这种架构将AI的“思考”能力与外部系统的“执行”能力解耦使得智能体可以动态扩展其能力范围而无需修改核心模型。2. 环境准备与核心工具在开始构建广告管理智能体之前我们需要准备好开发环境。本文将使用一个模拟的广告管理MCP Server进行演示避免直接操作生产环境API。2.1 基础环境操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)。本文命令以macOS/Linux bash为例Windows用户可使用WSL或Git Bash。Node.jsMCP生态目前有大量工具使用Node.js。请安装Node.js 18和配套的npm。# 检查Node.js版本 node --version # 检查npm版本 npm --versionPython 3.8可选部分MCP Server或AI框架可能使用Python。建议安装。python3 --version pip3 --version2.2 核心工具MCP SDK 与 Claude Code我们将使用 Anthropic 官方提供的MCP SDK来快速构建 Server并使用Claude Code作为 MCP Client 进行测试。Claude Code 是 Anthropic 为 Claude 模型开发的代码编辑器扩展原生支持 MCP。安装 MCP SDK (TypeScript/JavaScript)# 创建一个新的项目目录 mkdir mcp-ads-demo cd mcp-ads-demo # 初始化npm项目 npm init -y # 安装MCP核心SDK和TypeScript相关依赖 npm install modelcontextprotocol/sdk typescript tsx types/node --save-dev # 初始化TypeScript配置 npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir ./dist安装 Claude Code前往 Claude 官网下载并安装 Claude Code 编辑器。安装后Claude Code 内置了 MCP Client 支持无需额外配置。2.3 项目结构创建以下项目结构这有助于我们组织代码mcp-ads-demo/ ├── package.json ├── tsconfig.json ├── src/ │ ├── server.ts # MCP Server 主文件 │ └── types.ts # 类型定义 └── scripts/ └── start-server.mjs # 启动脚本3. 构建模拟广告管理 MCP Server现在我们开始构建一个模拟的广告管理MCP Server。这个Server将提供几个关键工具模拟X Ads广告平台的核心功能。3.1 定义工具与类型首先在src/types.ts中定义广告活动、广告组等数据结构。// src/types.ts export interface AdCampaign { id: string; name: string; objective: AWARENESS | CONSIDERATION | CONVERSION; dailyBudget: number; // 美元 status: ACTIVE | PAUSED | DELETED; startTime: string; endTime?: string; } export interface AdGroup { id: string; campaignId: string; name: string; targeting: { locations?: string[]; interests?: string[]; languages?: string[]; }; bidAmount: number; status: ACTIVE | PAUSED; } export interface AdCreative { id: string; adGroupId: string; title: string; bodyText: string; mediaUrl: string; callToAction?: string; }3.2 实现 MCP Server接下来在src/server.ts中实现Server核心逻辑。我们使用内存存储来模拟数据库。// src/server.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; import { AdCampaign, AdGroup, AdCreative } from ./types.js; // 模拟内存数据库 const campaigns: Mapstring, AdCampaign new Map(); const adGroups: Mapstring, AdGroup new Map(); const creatives: Mapstring, AdCreative new Map(); // 初始化一些模拟数据 function initializeMockData() { const campaignId camp_001; campaigns.set(campaignId, { id: campaignId, name: Q3 Product Launch, objective: CONVERSION, dailyBudget: 100, status: ACTIVE, startTime: new Date().toISOString(), }); const groupId group_001; adGroups.set(groupId, { id: groupId, campaignId, name: US Tech Enthusiasts, targeting: { locations: [United States], interests: [Technology, Software Development], languages: [en], }, bidAmount: 5.0, status: ACTIVE, }); creatives.set(creative_001, { id: creative_001, adGroupId: groupId, title: Build Faster with Our New SDK, bodyText: Cut development time in half. Try our latest developer tools now., mediaUrl: https://example.com/ad-image.jpg, callToAction: Sign Up Free, }); } // 创建MCP Server实例 const server new Server( { name: mock-ads-server, version: 0.1.0, }, { capabilities: { tools: {}, // 声明我们支持工具 }, } ); // 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: [ { name: list_campaigns, description: List all advertising campaigns with optional filters by status., inputSchema: { type: object, properties: { status: { type: string, enum: [ACTIVE, PAUSED, ALL], description: Filter campaigns by status. Default is ACTIVE., }, }, }, }, { name: create_campaign, description: Create a new advertising campaign., inputSchema: { type: object, properties: { name: { type: string, description: Campaign name }, objective: { type: string, enum: [AWARENESS, CONSIDERATION, CONVERSION], description: Marketing objective, }, dailyBudget: { type: number, description: Daily budget in USD }, startTime: { type: string, description: Start time (ISO string) }, }, required: [name, objective, dailyBudget], }, }, { name: update_campaign_status, description: Pause, activate, or delete a campaign., inputSchema: { type: object, properties: { campaignId: { type: string, description: ID of the campaign to update }, status: { type: string, enum: [ACTIVE, PAUSED, DELETED], description: New status, }, }, required: [campaignId, status], }, }, { name: get_campaign_analytics, description: Get performance analytics for a specific campaign., inputSchema: { type: object, properties: { campaignId: { type: string, description: Campaign ID }, dateRange: { type: string, enum: [LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH], description: Time period for analytics, }, }, required: [campaignId], }, }, ], }; }); // 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; switch (name) { case list_campaigns: { const statusFilter (args as any).status || ACTIVE; let filteredCampaigns Array.from(campaigns.values()); if (statusFilter ! ALL) { filteredCampaigns filteredCampaigns.filter(c c.status statusFilter); } return { content: [ { type: text, text: JSON.stringify(filteredCampaigns, null, 2), }, ], }; } case create_campaign: { const { name, objective, dailyBudget, startTime } args as any; const newId camp_${Date.now()}; const newCampaign: AdCampaign { id: newId, name, objective, dailyBudget, status: ACTIVE, startTime: startTime || new Date().toISOString(), }; campaigns.set(newId, newCampaign); return { content: [ { type: text, text: Campaign created successfully!\nID: ${newId}\n${JSON.stringify(newCampaign, null, 2)}, }, ], }; } case update_campaign_status: { const { campaignId, status } args as any; const campaign campaigns.get(campaignId); if (!campaign) { throw new Error(Campaign with ID ${campaignId} not found.); } campaign.status status; campaigns.set(campaignId, campaign); return { content: [ { type: text, text: Campaign ${campaignId} status updated to ${status}., }, ], }; } case get_campaign_analytics: { // 模拟返回分析数据 const { campaignId, dateRange } args as any; const mockAnalytics { campaignId, dateRange: dateRange || LAST_7_DAYS, impressions: Math.floor(Math.random() * 100000), clicks: Math.floor(Math.random() * 5000), conversions: Math.floor(Math.random() * 200), spend: (Math.random() * 500).toFixed(2), ctr: (Math.random() * 10).toFixed(2) %, cpc: $ (Math.random() * 2).toFixed(2), }; return { content: [ { type: text, text: JSON.stringify(mockAnalytics, null, 2), }, ], }; } default: throw new Error(Unknown tool: ${name}); } }); // 启动Server async function main() { initializeMockData(); const transport new StdioServerTransport(); await server.connect(transport); console.error(Mock Ads MCP Server running on stdio...); } main().catch((error) { console.error(Server error:, error); process.exit(1); });3.3 创建启动脚本并运行为了让Claude Code等Client能连接我们需要一个启动脚本。创建scripts/start-server.mjs#!/usr/bin/env node // scripts/start-server.mjs import { spawn } from child_process; import { fileURLToPath } from url; import { dirname, join } from path; const __dirname dirname(fileURLToPath(import.meta.url)); const serverPath join(__dirname, ../dist/server.js); const serverProcess spawn(node, [serverPath], { stdio: [pipe, pipe, inherit], // 继承stderr以便查看错误 }); // 将Server的stdout/stdin连接到当前进程 process.stdin.pipe(serverProcess.stdin); serverProcess.stdout.pipe(process.stdout); serverProcess.on(close, (code) { process.exit(code); });编译并运行Server# 编译TypeScript npx tsc # 给启动脚本执行权限 (Linux/macOS) chmod x scripts/start-server.mjs # 测试运行Server node scripts/start-server.mjs如果看到Mock Ads MCP Server running on stdio...的输出说明Server已成功启动并在等待连接。4. 在 Claude Code 中连接并使用 MCP Server现在我们让AI智能体Claude Code连接到我们刚构建的MCP Server。4.1 配置 Claude Code 的 MCP 设置打开 Claude Code 编辑器。进入设置Settings。通常可以通过菜单或快捷键Cmd,(Mac) /Ctrl,(Windows) 打开。在设置中搜索MCP。找到MCP Servers配置项。它应该是一个JSON对象。添加我们的模拟广告Server配置。配置格式如下{ mcpServers: { mock-ads-server: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/mcp-ads-demo/scripts/start-server.mjs ] } } }关键点mock-ads-server是给这个Server起的名字可以自定义。command和args指定了如何启动我们的Server。必须使用绝对路径。在Windows上如果使用PowerShell或CMDcommand可能是node.exeargs需要包含脚本的完整路径。4.2 与AI智能体协作管理广告配置保存后重启Claude Code。现在ClaudeAI模型就具备了通过MCP调用广告管理工具的能力。场景演示你可以在Claude Code的聊天框中输入自然语言指令“帮我列出所有活跃的广告活动。”Claude会识别出这个请求需要调用list_campaigns工具并自动执行。你会在回复中看到类似这样的结构化数据[ { id: camp_001, name: Q3 Product Launch, objective: CONVERSION, dailyBudget: 100, status: ACTIVE, startTime: 2024-05-27T10:00:00.000Z } ]“我想创建一个新的广告系列目标是提升品牌认知度每日预算50美元名字叫‘Summer Brand Awareness’。”Claude会调用create_campaign工具并提示你确认或补充信息如objective应使用AWARENESS。确认后它会返回创建成功的消息和新活动的ID。“把刚才创建的‘Summer Brand Awareness’系列暂停掉。”Claude需要先找到该活动的ID可能需要调用list_campaigns来查找然后调用update_campaign_status工具将状态设置为PAUSED。“给我看看‘Q3 Product Launch’系列过去7天的表现数据。”Claude会调用get_campaign_analytics工具并返回模拟的展示量、点击量、花费等指标。整个过程你无需记忆API参数也无需切换平台只需用自然语言与Claude对话它就能安全、准确地操作背后的广告系统。5. 从模拟到真实连接X Ads API上面的例子是模拟的。要将它变成一个真正的、能管理X Ads的智能体核心是将MCP Server中的工具实现从操作内存数据改为调用真实的X Ads API。5.1 获取X Ads API凭证访问X原Twitter的开发者门户创建一个项目和应用。为该应用启用“Ads API”权限。获取你的API Key, API Secret, Access Token, 和 Access Token Secret。这些是调用API的凭证。5.2 改造MCP Server我们需要修改src/server.ts中的工具处理函数使用真实的API SDK例如twitter-api-v2或twitter-adsSDK来替换模拟逻辑。安装官方SDK假设使用twitter-api-v2npm install twitter-api-v2修改工具实现以create_campaign为例import { TwitterApi } from twitter-api-v2; // 在Server初始化部分创建API客户端 const adsClient new TwitterApi({ appKey: process.env.TWITTER_ADS_API_KEY, appSecret: process.env.TWITTER_ADS_API_SECRET, accessToken: process.env.TWITTER_ADS_ACCESS_TOKEN, accessSecret: process.env.TWITTER_ADS_ACCESS_SECRET, }).ads; // 在 create_campaign 的case中替换为真实API调用 case create_campaign: { const { name, objective, dailyBudget, startTime, accountId } args as any; try { // 调用X Ads API创建广告系列 const campaign await adsClient.accounts(accountId).campaigns.create({ name, objective: objective.toUpperCase(), // 确保格式匹配API daily_budget_in_micro_currency: dailyBudget * 1000000, // 转换为微货币单位 start_time: startTime ? new Date(startTime).toISOString() : undefined, status: ACTIVE, }); return { content: [{ type: text, text: Campaign created successfully!\nID: ${campaign.id}\nName: ${campaign.name}, }], }; } catch (error: any) { throw new Error(Failed to create campaign: ${error.message}); } }重要安全提示永远不要将API密钥硬编码在代码中。使用环境变量process.env或安全的密钥管理服务如AWS Secrets Manager, HashiCorp Vault来存储凭证。在package.json或.env文件中管理环境变量。5.3 增强错误处理与日志真实API调用会面临网络超时、权限错误、配额限制等问题。必须在Server中增加健壮的错误处理。server.setRequestHandler(CallToolRequestSchema, async (request) { try { // ... 工具调用逻辑 ... } catch (error: any) { // 返回结构化的错误信息给Client而不是抛出异常导致连接中断 return { content: [{ type: text, text: Tool execution failed: ${error.message}. Please check parameters and permissions., }], isError: true, }; } });6. 常见问题与排查思路在开发和集成MCP Server过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案Claude Code 无法连接 Server1. 启动脚本路径错误。2. Node.js环境或依赖缺失。3. Server启动后立即崩溃。1.检查路径在终端手动运行node /ABSOLUTE/PATH/TO/start-server.mjs看能否启动。2.检查依赖运行npm list确认modelcontextprotocol/sdk已安装。3.查看日志Claude Code 通常有输出面板或日志文件查看其中MCP相关的错误信息。AI智能体看不到工具列表1. MCP配置未生效。2. Server的ListToolsRequest响应格式错误。3. 网络策略阻止了本地进程通信。1.重启Client修改MCP配置后重启Claude Code。2.验证协议确保Server返回的tools数组格式符合MCP规范。可以用简单的调试Client测试。3.检查连接确认Server进程是否在运行ps aux工具调用返回权限错误1. API令牌无效或过期。2. 应用权限不足如未启用Ads API。3. 请求参数不符合API要求。1.刷新令牌在X开发者门户检查并刷新Access Token。2.检查权限确认应用已申请并获得了必要的广告API权限。3.查阅文档仔细对照X Ads API官方文档检查参数名、格式和取值范围。Server进程意外退出1. 未捕获的异常。2. 内存泄漏或资源耗尽。3. 父进程如Claude Code终止了连接。1.增加异常捕获在Server的main()函数和所有异步操作外包裹try-catch。2.添加日志在关键步骤和错误处输出日志到文件便于追踪。3.实现重连设计Server在连接断开后能优雅退出或等待重连。7. 最佳实践与工程建议将MCP用于生产级AI智能体开发尤其是涉及广告预算、用户数据等敏感操作时必须遵循以下最佳实践7.1 安全与权限最小权限原则为MCP Server使用的API令牌分配最小必要权限。如果智能体只需要读取广告数据就不要授予它创建或删除的权限。访问控制在MCP Server内部实现额外的业务逻辑层。例如检查发起请求的用户是否有权操作某个广告账户而不是直接传递所有账户的API密钥。审计日志记录所有工具调用的详细信息包括调用者、时间、参数和结果。这对于追溯问题和安全审计至关重要。环境隔离严格区分开发、测试和生产环境的API凭证和配置。禁止在开发环境中使用生产数据。7.2 可维护性与可靠性工具设计原子化每个MCP工具应只完成一件明确、独立的事情。避免创建“超级工具”来处理复杂流程。复杂流程应由AI智能体通过组合多个原子工具来完成。输入验证与清理在Server端对AI传递的所有参数进行严格的验证和类型转换防止注入攻击或API调用错误。版本化管理对MCP Server的接口工具列表和参数进行版本控制。当需要变更时考虑向后兼容或提供新版本的工具。健康检查与监控为MCP Server添加健康检查端点并集成到现有的监控告警系统中确保其可用性。7.3 智能体提示工程提供清晰的工具描述在ListToolsRequest中返回的description字段至关重要。它应该清晰、无歧义地说明工具的用途、参数含义和返回内容。这是AI决定是否及如何调用工具的主要依据。设计上下文资源除了工具MCP还支持“资源”Resources可以将常用的、结构化的数据如广告账户列表、产品目录预加载到AI的上下文中减少不必要的工具调用。处理模糊指令AI可能无法完全理解用户意图。设计智能体在调用工具前先与用户确认关键参数如预算、目标受众而不是盲目执行。通过MCP协议我们为AI智能体安全地打开了一扇通往业务系统的大门。从模拟到真实从概念到实践本文展示了构建一个广告管理智能体的完整路径。这种模式可以推广到任何需要AI介入的业务场景如CRM、ERP、数据分析等。核心在于MCP提供了一种标准化、可控的集成方式让AI的“大脑”能够安全地使用企业的“手脚”。