# Meta Ads MCP Server

A production-grade Model Context Protocol (MCP) server for the Meta Marketing API with intelligent lazy-loading architecture.

## 🌟 Key Features

- **Lazy Loading Architecture**: Core tools load immediately, advanced features load on-demand
- **Production-Ready**: Comprehensive rate limiting, caching, and error handling
- **Type-Safe**: Full TypeScript with strict mode and Zod validation
- **MCP Annotations**: All tools include proper metadata (readOnly, destructive, idempotent hints)
- **Smart Rate Limiting**: Tracks Meta's three-tier rate limits with exponential backoff
- **Token Management**: Automatic validation, expiry tracking, and refresh support

## 📦 Installation

```bash
npm install
npm run build
```

## ⚙️ Configuration

Create a `.env` file from the example:

```bash
cp .env.example .env
```

Required environment variables:

- `META_ACCESS_TOKEN`: Your Meta Marketing API access token
- `META_APP_ID`: (Optional) Your Facebook App ID for enhanced security
- `META_APP_SECRET`: (Optional) Your App Secret for appsecret_proof
- `META_API_VERSION`: (Optional) API version, defaults to v21.0

### Getting Access Tokens

1. **User Access Token**: Via [Facebook Graph API Explorer](https://developers.facebook.com/tools/explorer/)
   - Select your app
   - Request permissions: `ads_read`, `ads_management`, `business_management`
   - Generate token

2. **Long-Lived Token**: Exchange short-lived for long-lived (60 days)
   - Requires App ID and App Secret
   - Server can auto-refresh using `AuthManager.refreshToLongLivedToken()`

3. **System User Token**: Recommended for production
   - Created in Business Manager
   - Never expires (until revoked)
   - Most secure option

## 🚀 Usage

### Development Mode

```bash
npm run dev
```

### Production Mode

```bash
npm run build
npm start
```

### As MCP Server

Add to your MCP client configuration (e.g., Claude Desktop):

```json
{
  "mcpServers": {
    "meta-ads": {
      "command": "node",
      "args": ["/path/to/meta-ads-mcp/dist/index.js"],
      "env": {
        "META_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
```

## 🏗️ Architecture

### Lazy Loading System

The server uses a two-tier module loading strategy:

#### Tier 1: Core Modules (Load on Startup)
- **Account**: `list_ad_accounts`, `get_account_info`, `get_account_pages`, `get_business_info`, `get_pixel_list`
- **Campaigns**: `list_campaigns`, `get_campaign`, `create_campaign`, `update_campaign`, `toggle_campaign`, `duplicate_campaign`, `delete_campaign`
- **Ad Sets**: `list_ad_sets`, `get_ad_set`, `create_ad_set`, `update_targeting`, `update_ad_set_budget`, `update_schedule`, `toggle_ad_set`
- **Ads**: `list_ads`, `get_ad`, `create_image_ad`, `create_video_ad`, `create_carousel_ad`, `create_dynamic_ad`, `preview_creative`, `upload_media`, `list_creatives`

#### Tier 2: Advanced Modules (Lazy Loaded)
- **Analytics**: Gateway tool `analytics_tools` loads full insights and reporting suite
- **Audiences**: Gateway tool `audiences_tools` loads custom audience management
- **Budget**: Gateway tool `budget_tools` loads optimization and recommendation tools

**How Lazy Loading Works:**
1. Tier 2 modules register a single gateway tool (e.g., `audience_tools`)
2. When called, the gateway loads the full module and re-registers all its tools
3. Client sees: "Module loaded, please retry with specific tool"
4. Subsequent calls use the now-loaded tools directly

### Infrastructure Components

#### `AuthManager` (`src/auth/oauth.ts`)
- Token validation via `debug_token` endpoint
- HMAC-SHA256 `appsecret_proof` generation
- Token expiry tracking
- Long-lived token refresh

#### `RateLimiter` (`src/client/rate-limiter.ts`)
- Parses Meta's three rate limit headers:
  - `x-app-usage`: App-level limits
  - `x-business-use-case-usage`: Business-level limits
  - `x-ad-account-usage`: Account-level limits (200 calls/hour)
- Exponential backoff at 75% and 90% thresholds
- Request queue with priority system
- Configurable concurrency (default: 5)

#### `MetaApiClient` (`src/client/meta-client.ts`)
- Generic `get<T>()`, `post<T>()`, `delete<T>()` methods
- Automatic `appsecret_proof` injection
- Cursor-based pagination: `getAllPages<T>()`
- Typed error handling with `MetaApiError`
- Media upload support (images and videos)

#### `SimpleCache` (`src/client/cache.ts`)
- In-memory TTL-based caching
- Prefix-based invalidation
- `getOrCompute()` for cache-aside pattern
- Max entries enforcement (LRU-style)

## 🛠️ Tool Annotations

Every tool includes MCP annotations:

```typescript
annotations: {
  title: "Human-readable name",
  readOnlyHint: true,        // No state changes
  destructiveHint: false,    // Can't be undone
  idempotentHint: true,      // Safe to retry
  openWorldHint: false,      // Doesn't access external data
}
```

### Tool Categories

**Read-Only Tools**: All list/get operations
**Idempotent Tools**: Updates, toggles (safe to retry)
**Non-Destructive Creates**: Campaign/ad set/ad creation
**Destructive Tools**: Delete operations (require confirmation)

## 📊 Rate Limiting

Meta enforces three types of rate limits:

1. **App-Level**: Calls per hour across all users
2. **Business Use Case**: Per business, per hour
3. **Ad Account**: 200 calls/hour per account ⚠️ Most restrictive

The `RateLimiter` automatically:
- Monitors all three metrics
- Throttles at 75% usage
- Pauses at 90% usage
- Applies exponential backoff
- Respects `estimatedTimeToRegainAccess` headers

## 🔒 Security

- **appsecret_proof**: HMAC signature on every request (when app secret provided)
- **Token Validation**: Checks token validity on startup
- **No Token Logging**: Credentials never logged
- **.env Support**: Secrets in environment, not code

## 📝 TypeScript

Strict mode enabled with:
- No implicit `any`
- Strict null checks
- Unused variable detection
- Comprehensive type definitions in `src/types/meta-api.ts`

## 🧪 Example Workflows

### Create a Campaign with Ad Set and Image Ad

```typescript
// 1. List accounts
list_ad_accounts()

// 2. Create campaign
create_campaign({
  account_id: "act_123456",
  name: "Q1 2025 Campaign",
  objective: "OUTCOME_TRAFFIC",
  daily_budget: "5000", // $50
  status: "PAUSED"
})

// 3. Create ad set
create_ad_set({
  campaign_id: "123456789",
  name: "US Desktop Traffic",
  optimization_goal: "LINK_CLICKS",
  billing_event: "IMPRESSIONS",
  daily_budget: "2000", // $20
  targeting: {
    age_min: 25,
    age_max: 55,
    genders: [1, 2],
    geo_locations: { countries: ["US"] },
    publisher_platforms: ["facebook"],
    device_platforms: ["desktop"]
  }
})

// 4. Upload image (stub - needs file handling)
upload_media({
  account_id: "act_123456",
  file_path: "/path/to/image.jpg",
  media_type: "image"
})

// 5. Create ad
create_image_ad({
  account_id: "act_123456",
  adset_id: "987654321",
  name: "Homepage Ad",
  page_id: "111222333",
  image_hash: "abc123hash",
  link: "https://example.com",
  headline: "Shop Now",
  message: "Limited time offer!",
  call_to_action_type: "SHOP_NOW",
  status: "ACTIVE"
})
```

## 🐛 Debugging

Set `NODE_ENV=development` for verbose logging:

```bash
NODE_ENV=development npm run dev
```

Logs go to stderr (MCP convention):
- `[MCP] Module loaded: campaigns`
- `[MCP] Registered tool: create_campaign`
- Rate limit warnings appear automatically

## 📚 API Documentation

- [Meta Marketing API Reference](https://developers.facebook.com/docs/marketing-apis/)
- [MCP Protocol Specification](https://modelcontextprotocol.io/)
- [Campaign Structure Guide](https://developers.facebook.com/docs/marketing-api/campaign-structure/)

## 🤝 Contributing

1. Follow existing patterns for tool registration
2. Use Zod schemas with `.describe()` on every field
3. Include comprehensive MCP annotations
4. Handle errors gracefully with typed responses
5. Add JSDoc comments for complex logic

## 📄 License

MIT

## 🙏 Credits

Built for Clawdbot by the Meta Ads MCP team.
