Meta Ads MCP supports Streamable HTTP Transport, which allows you to run the server as a standalone HTTP API. This enables direct integration with web applications, custom dashboards, and any system that can make HTTP requests.
# Basic HTTP server (default: localhost:8080)
python -m meta_ads_mcp --transport streamable-http
# Custom host and port
python -m meta_ads_mcp --transport streamable-http --host 0.0.0.0 --port 9000When you run this server yourself, the credential is a Meta access token from your own Meta app — create one at developers.facebook.com. A Pipeboard API token will not work here: the server passes whatever you give it straight to the Meta Graph API.
export META_ACCESS_TOKEN=your_meta_access_tokenSetting this is optional for the HTTP transport if you pass the token per request in a header (see Security Model), but it is convenient for command-line use.
Want to authenticate with a Pipeboard API token instead, and never handle a Meta token? Use the hosted MCP at
https://meta-ads.mcp.pipeboard.co/rather than running this server. (PIPEBOARD_API_TOKENis no longer supported by this package — see Migration.)
The server accepts JSON-RPC 2.0 requests at the /mcp endpoint. Use the Authorization header to provide your token.
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_meta_access_token" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 1,
"params": {
"name": "get_ad_accounts",
"arguments": {"limit": 5}
}
}'| Argument | Description | Default |
|---|---|---|
--transport |
Transport mode | stdio |
--host |
Server host address | localhost |
--port |
Server port | 8080 |
--sse-response |
Return responses as SSE streams instead of JSON | false (JSON) |
# Local development server
python -m meta_ads_mcp --transport streamable-http --host localhost --port 8080
# Production server (accessible externally)
python -m meta_ads_mcp --transport streamable-http --host 0.0.0.0 --port 8080
# Custom port
python -m meta_ads_mcp --transport streamable-http --port 9000The HTTP transport uses a per-request authentication model that is deliberately different from stdio. Understanding it is the difference between a safe deployment and leaking your Meta account to the internet.
-
stdio (local): the server acts as whatever credential is configured in its environment —
META_ACCESS_TOKENor a stored login from the local OAuth flow. Only the local process that launched it can talk to it, so this is expected and safe. If all you want is to use the MCP locally, prefer stdio. -
streamable-http (network): every request must carry its own token header —
Authorization: Bearer <token>,X-META-ACCESS-TOKEN, or the legacyX-PIPEBOARD-API-TOKEN. Requests without one are rejected with401 Unauthorized. The server-sideMETA_ACCESS_TOKENenvironment variable is intentionally not used as an implicit fallback for HTTP requests — if it were, any caller who can reach the port would act as you. This gating is enforced byAuthInjectionMiddleware.
Operational rules:
- Do not expose the raw port to an untrusted network.
--host 0.0.0.0binds to every interface. Put the server behind an authenticating reverse proxy (or a private network / firewall), exactly as the hosted MCP at*.mcp.pipeboard.codoes (localhost-bound Python process behind a proxy). - Avoid setting
META_ACCESS_TOKENon a network-exposed HTTP server. It is an operator-wide, long-lived credential. Have callers pass their own tokens in headers instead. If you must set it (e.g. single-tenant behind a trusted proxy), ensure the port is unreachable by untrusted callers. - The auth gate applies in both response modes — default JSON and
--sse-response. (Historically--sse-responsehad a bug where the gate was not attached to the served app; seeSECURITY.md, GHSA-8353-5qhw-8hfw. Fixed in1.0.119— upgrade if you use--sse-response.) - If you ever exposed an unauthenticated server, rotate the Meta access token and review Graph API access logs.
Which credential you use depends on who runs the server.
When you run this package yourself, supply a Meta access token from your own Meta
app. Create one at developers.facebook.com,
then pass it in the Authorization header:
curl -H "Authorization: Bearer your_meta_access_token" \
-X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'The X-META-ACCESS-TOKEN header is equivalent:
curl -H "X-META-ACCESS-TOKEN: your_meta_access_token" \
-X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'The hosted Remote MCP at https://meta-ads.mcp.pipeboard.co/ accepts a Pipeboard
API token instead, so you never handle a Meta token yourself. Pipeboard resolves
the Meta credential server-side and enforces the API token's account and permission
scoping on every call.
- Sign up at Pipeboard.co
- Generate an API token at pipeboard.co/api-tokens
- Point your MCP client at
https://meta-ads.mcp.pipeboard.co/withAuthorization: Bearer <your_pipeboard_token>
For clients that cannot send headers, the token may be passed as a URL parameter:
https://meta-ads.mcp.pipeboard.co/?token=YOUR_PIPEBOARD_TOKEN
A Pipeboard API token only works against the hosted endpoint. Passing one to a server you run yourself will fail, because that server forwards the token directly to the Meta Graph API.
Base URL: http://localhost:8080
MCP Endpoint: /mcp
| Method | Description |
|---|---|
initialize |
Initialize MCP session and exchange capabilities |
tools/list |
Get list of all available Meta Ads tools |
tools/call |
Execute a specific tool with parameters |
All responses follow JSON-RPC 2.0 format:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
// Tool response data
}
}curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_token" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"id": 1,
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {"roots": {"listChanged": true}},
"clientInfo": {"name": "my-app", "version": "1.0.0"}
}
}'curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_token" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 2
}'curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_token" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 3,
"params": {
"name": "get_ad_accounts",
"arguments": {"limit": 10}
}
}'curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_token" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 4,
"params": {
"name": "get_insights",
"arguments": {
"object_id": "act_701351919139047",
"time_range": "last_30d",
"level": "campaign"
}
}
}'import requests
import json
class MetaAdsMCPClient:
def __init__(self, base_url="http://localhost:8080", token=None):
self.base_url = base_url
self.endpoint = f"{base_url}/mcp"
self.headers = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream"
}
if token:
self.headers["Authorization"] = f"Bearer {token}"
def call_tool(self, tool_name, arguments=None):
payload = {
"jsonrpc": "2.0",
"method": "tools/call",
"id": 1,
"params": {"name": tool_name}
}
if arguments:
payload["params"]["arguments"] = arguments
response = requests.post(self.endpoint, headers=self.headers, json=payload)
return response.json()
# Usage
client = MetaAdsMCPClient(token="your_meta_access_token")
result = client.call_tool("get_ad_accounts", {"limit": 5})
print(json.dumps(result, indent=2))const axios = require('axios');
class MetaAdsMCPClient {
constructor(baseUrl = 'http://localhost:8080', token = null) {
this.baseUrl = baseUrl;
this.endpoint = `${baseUrl}/mcp`;
this.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream'
};
if (token) {
this.headers['Authorization'] = `Bearer ${token}`;
}
}
async callTool(toolName, arguments = null) {
const payload = {
jsonrpc: '2.0',
method: 'tools/call',
id: 1,
params: { name: toolName }
};
if (arguments) {
payload.params.arguments = arguments;
}
try {
const response = await axios.post(this.endpoint, payload, { headers: this.headers });
return response.data;
} catch (error) {
return { error: error.message };
}
}
}
// Usage
const client = new MetaAdsMCPClient('http://localhost:8080', 'your_meta_access_token');
client.callTool('get_ad_accounts', { limit: 5 })
.then(result => console.log(JSON.stringify(result, null, 2)));See Security Model above for the per-request auth model and why it matters. In short:
- Use HTTPS: In production, run behind a reverse proxy with SSL/TLS
- Authentication: Every request must carry its own token header; the server
returns
401otherwise. Do not rely on a server-sideMETA_ACCESS_TOKENas an implicit credential for HTTP callers — it is not used as a fallback. - Network Security: Never expose the raw port to an untrusted network. Bind to localhost behind an authenticating proxy, or restrict with firewalls.
- Avoid
META_ACCESS_TOKENon network-exposed servers: it is an operator-wide credential; prefer per-request header tokens. - Rate Limiting: Consider implementing rate limiting for public APIs
FROM python:3.10-slim
WORKDIR /app
COPY . .
RUN pip install -e .
EXPOSE 8080
CMD ["python", "-m", "meta_ads_mcp", "--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8080"]# Meta access token from your own Meta app. Used for stdio; for HTTP it can
# also be passed per request in the Authorization header.
export META_ACCESS_TOKEN=your_meta_access_token
# Optional (for the local OAuth flow instead of a direct token)
export META_APP_ID=your_app_id
export META_APP_SECRET=your_app_secret
# Optional (for direct Meta token).
# WARNING: on the HTTP transport this is NOT used as a fallback credential for
# incoming requests — callers must pass their own token header (see "Security
# Model"). Setting it on a network-exposed server is an operator-wide credential
# risk; prefer stdio, or keep the port unreachable by untrusted callers.
export META_ACCESS_TOKEN=your_access_token- Connection Refused: Ensure the server is running and accessible on the specified port.
- Authentication Failed: Verify your Bearer token is valid and included in the
Authorizationheader. - 404 Not Found: Make sure you're using the correct endpoint (
/mcp). - JSON-RPC Errors: Check that your request follows the JSON-RPC 2.0 format.
Enable verbose logging by setting the log level in your environment if the application supports it, or check the application's logging configuration. The current implementation logs to a file.
Test if the server is running by sending a tools/list request:
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer your_token" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'If you're currently using stdio transport with MCP clients, you can support both stdio for local clients and HTTP for web applications. The application can only run in one mode at a time, so you may need to run two separate instances if you need both simultaneously.
- Keep existing MCP client setup (Claude Desktop, Cursor, etc.) using stdio.
- Add HTTP transport for web applications and custom integrations by running a separate server instance with the
--transport streamable-httpflag. - Use the same authentication method:
- For stdio, the
META_ACCESS_TOKENenvironment variable is used. - For HTTP, pass the token in the
Authorization: Bearer <token>header.
- For stdio, the
Removed:
PIPEBOARD_API_TOKENused to exchange a Pipeboard API token for the underlying Meta access token. That exchange has been removed, because the Meta token it returned ignored the scoping on the API token that requested it. Either setMETA_ACCESS_TOKENfrom your own Meta app, or use the hosted MCP athttps://meta-ads.mcp.pipeboard.co/.
Both transports access the same Meta Ads functionality and use the same underlying authentication system.