#!/usr/bin/env node // 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 { z } from "zod"; import * as dotenv from "dotenv"; dotenv.config(); const server = new Server( { name: "mcp-facebook-ads", version: "0.1.0" }, { capabilities: { tools: {} } } ); // Configurações da API const FB_ACCESS_TOKEN = process.env.FB_ACCESS_TOKEN; const FB_ACCOUNT_ID = process.env.FB_ACCOUNT_ID; const FB_API_VERSION = process.env.FB_API_VERSION || "v21.0"; if (!FB_ACCESS_TOKEN || !FB_ACCOUNT_ID) { console.error("Erro: FB_ACCESS_TOKEN e FB_ACCOUNT_ID são obrigatórios no .env"); process.exit(1); } // Schemas das tools const GetCampaignsSchema = z.object({ fields: z.string().optional().describe("Campos a serem retornados (ex: id,name,status,objective)"), limit: z.number().optional().describe("Limite de resultados (padrão: 25)"), }); const GetCampaignInsightsSchema = z.object({ campaign_id: z.string().describe("ID da campanha"), date_preset: z.string().optional().describe("Período pré-definido (ex: last_7d, last_30d, lifetime)"), fields: z.string().optional().describe("Métricas a serem retornadas (ex: impressions,clicks,spend,ctr)"), }); const GetAdCreativesSchema = z.object({ ad_id: z.string().describe("ID do anúncio"), fields: z.string().optional().describe("Campos a serem retornados (ex: name,thumbnail_url,object_story_spec)"), }); const GetAccountInsightsSchema = z.object({ date_preset: z.string().optional().describe("Período pré-definido (ex: last_7d, last_30d, today)"), fields: z.string().optional().describe("Métricas a serem retornadas (ex: impressions,clicks,spend,cpc,cpm,ctr)"), level: z.string().optional().describe("Nível de agregação (account, campaign, adset, ad)"), }); const GetCampaignAdsSchema = z.object({ campaign_id: z.string().describe("ID da campanha"), fields: z.string().optional().describe("Campos a serem retornados (ex: id,name,status,creative)"), limit: z.number().optional().describe("Limite de resultados (padrão: 100)"), }); // Função auxiliar para fazer chamadas à API do Facebook async function callFacebookAPI(endpoint: string): Promise { const response = await fetch(endpoint); if (!response.ok) { const error = await response.text(); throw new Error(`Facebook API Error: ${response.status} - ${error}`); } return response.json(); } // Handler para listar tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "get_campaigns", description: "Retorna lista de campanhas da conta de anúncios", inputSchema: { type: "object", properties: { fields: { type: "string", description: "Campos a serem retornados (ex: id,name,status,objective)" }, limit: { type: "number", description: "Limite de resultados (padrão: 25)" }, }, }, }, { name: "get_campaign_insights", description: "Retorna métricas e performance de uma campanha específica", inputSchema: { type: "object", properties: { campaign_id: { type: "string", description: "ID da campanha" }, date_preset: { type: "string", description: "Período pré-definido (ex: last_7d, last_30d, lifetime)" }, fields: { type: "string", description: "Métricas a serem retornadas (ex: impressions,clicks,spend,ctr)" }, }, required: ["campaign_id"], }, }, { name: "get_ad_creatives", description: "Retorna informações sobre os criativos de um anúncio", inputSchema: { type: "object", properties: { ad_id: { type: "string", description: "ID do anúncio" }, fields: { type: "string", description: "Campos a serem retornados (ex: name,thumbnail_url,object_story_spec)" }, }, required: ["ad_id"], }, }, { name: "get_account_insights", description: "Retorna insights e relatórios da conta de anúncios", inputSchema: { type: "object", properties: { date_preset: { type: "string", description: "Período pré-definido (ex: last_7d, last_30d, today)" }, fields: { type: "string", description: "Métricas a serem retornadas (ex: impressions,clicks,spend,cpc,cpm,ctr)" }, level: { type: "string", description: "Nível de agregação (account, campaign, adset, ad)" }, }, }, }, { name: "get_campaign_ads", description: "Retorna lista de anúncios de uma campanha específica", inputSchema: { type: "object", properties: { campaign_id: { type: "string", description: "ID da campanha" }, fields: { type: "string", description: "Campos a serem retornados (ex: id,name,status,creative)" }, limit: { type: "number", description: "Limite de resultados (padrão: 100)" }, }, required: ["campaign_id"], }, }, ], })); // Handler para executar tools server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (request.params.name === "get_campaigns") { const args = GetCampaignsSchema.parse(request.params.arguments); const fields = args.fields || "id,name,status,objective"; const limit = args.limit || 25; const endpoint = `https://graph.facebook.com/${FB_API_VERSION}/act_${FB_ACCOUNT_ID}/campaigns?fields=${fields}&limit=${limit}&access_token=${FB_ACCESS_TOKEN}`; const data = await callFacebookAPI(endpoint); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } if (request.params.name === "get_campaign_insights") { const args = GetCampaignInsightsSchema.parse(request.params.arguments); const date_preset = args.date_preset || "lifetime"; const fields = args.fields || "impressions,clicks,spend,ctr,cpc,cpm"; const endpoint = `https://graph.facebook.com/${FB_API_VERSION}/${args.campaign_id}/insights?date_preset=${date_preset}&fields=${fields}&access_token=${FB_ACCESS_TOKEN}`; const data = await callFacebookAPI(endpoint); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } if (request.params.name === "get_ad_creatives") { const args = GetAdCreativesSchema.parse(request.params.arguments); const fields = args.fields || "id,name,thumbnail_url,object_story_spec"; const endpoint = `https://graph.facebook.com/${FB_API_VERSION}/${args.ad_id}/creatives?fields=${fields}&access_token=${FB_ACCESS_TOKEN}`; const data = await callFacebookAPI(endpoint); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } if (request.params.name === "get_account_insights") { const args = GetAccountInsightsSchema.parse(request.params.arguments); const date_preset = args.date_preset || "last_7d"; const fields = args.fields || "impressions,clicks,spend,cpc,cpm,ctr"; const level = args.level || "account"; const endpoint = `https://graph.facebook.com/${FB_API_VERSION}/act_${FB_ACCOUNT_ID}/insights?date_preset=${date_preset}&fields=${fields}&level=${level}&access_token=${FB_ACCESS_TOKEN}`; const data = await callFacebookAPI(endpoint); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } if (request.params.name === "get_campaign_ads") { const args = GetCampaignAdsSchema.parse(request.params.arguments); const fields = args.fields || "id,name,status,creative"; const limit = args.limit || 100; const endpoint = `https://graph.facebook.com/${FB_API_VERSION}/${args.campaign_id}/ads?fields=${fields}&limit=${limit}&access_token=${FB_ACCESS_TOKEN}`; const data = await callFacebookAPI(endpoint); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } throw new Error(`Tool desconhecida: ${request.params.name}`); } catch (error) { if (error instanceof Error) { return { content: [{ type: "text", text: `Erro: ${error.message}` }], isError: true, }; } throw error; } }); const transport = new StdioServerTransport(); server.connect(transport);