
dadof3bytes/pinterest-ads-mcp
Developer toolsMCP server for businesses to manager their Pinterest Ads accounts.
Overview
Connect your AI assistant (Cursor, Claude Desktop, etc.) to so it can read performance data, run account audits, and make changes (preview first, then confirm). You do not need to write code. You will copy settings, run a few terminal commands, click through Pinterest login once, and paste a small JSON config into your AI tool. a small web app plus an (Model Context Protocol) bridge. MCP is how compatible AI apps call structured “tools” instead of guessing from screenshots. a multi-brand agency dashboard, billing manager, or delete-anything admin panel. One deployment = one ad account. 1. Clone or download this repository to a folder you will keep (e.g. ~/pinterest-ads-mcp). 2. Open Terminal and go to that folder: 1. Copy the template: 2. Open .env.local in a text editor and fill in values (see below). Generate strong random secrets (run in Terminal, paste output into .env.local): Use values for TOKEN_ENCRYPTION_KEY, MCP_AUTH_TOKEN, and OAUTH_STATE_SECRET.
README
Pinterest Ads MCP — setup guide
Connect your AI assistant (Cursor, Claude Desktop, etc.) to one Pinterest ad account so it can read performance data, run account audits, and make guarded changes (preview first, then confirm).
You do not need to write code. You will copy settings, run a few terminal commands, click through Pinterest login once, and paste a small JSON config into your AI tool.
What this is: a small web app plus an MCP (Model Context Protocol) bridge. MCP is how compatible AI apps call structured “tools” instead of guessing from screenshots.
What this is not: a multi-brand agency dashboard, billing manager, or delete-anything admin panel. One deployment = one ad account.
Before you start — checklist
Gather these before installing:
| Requirement | Why you need it |
|---|---|
| Pinterest Business account | Ads API access is tied to business identity. |
| Ad account you will manage | This server is locked to a single PINTEREST_AD_ACCOUNT_ID. |
| Ad account role: Admin or Campaign Manager (Analyst is read-only) | Write tools need create/update permission; Analyst → 403 on applies. |
| Valid ads billing on the account (for live ads) | Pinterest may block creation or delivery without billing. |
| Pinterest developer app | OAuth client ID and secret for login. |
| Node.js 20+ and npm | Runs the server locally (nodejs.org). |
| Terminal access on your Mac/PC | For npm install and npm run dev. |
Optional later:
| Requirement | Why |
|---|---|
| Vercel (or similar) account | Host the app on a URL instead of only localhost. |
| Strong secrets | Protect OAuth and MCP endpoints in production. |
Part 1 — Local setup (recommended first)
Step 1 — Get the project on your machine
- Clone or download this repository to a folder you will keep (e.g.
~/pinterest-ads-mcp). - Open Terminal and go to that folder:
cd /path/to/pinterest-ads-mcp
Step 2 — Install dependencies
npm install
Step 3 — Create your environment file
- Copy the template:
cp .env.example .env.local - Open
.env.localin a text editor and fill in values (see Environment variables below).
Generate strong random secrets (run in Terminal, paste output into .env.local):
# 32+ characters — used to encrypt the Pinterest refresh token on disk
openssl rand -base64 32
# 16+ characters — protects the MCP HTTP endpoint
openssl rand -base64 24
# 16+ characters — signs OAuth “state” during login (CSRF protection)
openssl rand -base64 24
Use different values for TOKEN_ENCRYPTION_KEY, MCP_AUTH_TOKEN, and OAUTH_STATE_SECRET.
Step 4 — Create a Pinterest developer app
-
Go to Pinterest Developers and sign in with the same business identity that owns the ad account.
-
Create an app (or use an existing one).
-
Note the App ID →
PINTEREST_APP_IDand App secret →PINTEREST_APP_SECRET. -
Add an OAuth redirect URI that matches exactly (including
httpvshttps, path, no trailing slash unless you configured one):Where you run the app Redirect URI to register Local (default) http://localhost:3000/api/oauth/pinterestCloud host https://YOUR-DOMAIN/api/oauth/pinterestMismatch is the most common setup failure (“redirect_uri mismatch”).
-
Enable / request access for the Ads API scopes your app needs (Pinterest’s developer console labels may vary; the server requests the list below at login).
Step 5 — Required OAuth scopes
When you click Start Pinterest OAuth on /connect, the server asks Pinterest for these scopes (comma-separated in the authorize URL):
| Scope | Purpose |
|---|---|
user_accounts:read |
Confirm which user authorized the app. |
ads:read |
Read campaigns, ad groups, ads, analytics, audit data. |
ads:write |
Create/update campaigns, ads, keywords (via guarded tools). |
pins:read |
Read Pin metadata linked to ads. |
pins:write |
Create Pins when building new ads. |
boards:read |
Board context for Pins. |
boards:write |
Pin creation may require board access. |
catalogs:read |
Catalog / shopping audit evidence. |
catalogs:write |
Catalog-related writes if your workflows need them. |
If Pinterest shows fewer scopes after login, your developer app may not be approved for Ads API yet — fix that in the developer portal before expecting tools to work.
Step 6 — Find your ad account ID
In Pinterest Ads Manager, open the ad account you want this server to use. The account ID is often visible in the URL or account settings (numeric string). Set:
PINTEREST_AD_ACCOUNT_ID=your_numeric_ad_account_id
Use pinterest_list_ad_accounts later to confirm the ID matches the connected user.
Step 7 — Start the server
npm run dev
You should see Next.js running on http://localhost:3000.
Step 8 — Connect Pinterest (OAuth)
- In a browser, open http://localhost:3000/connect.
- Check the page:
- Sandbox mode on/off should match
PINTEREST_USE_SANDBOXin.env.local(see Sandbox vs live). - Ad account id should show your configured ID.
- Sandbox mode on/off should match
- Click Start Pinterest OAuth.
- Log in to Pinterest and Allow the requested permissions.
- Pinterest sends you back to the app. Success looks like:
- Green message: “Pinterest connected successfully.”
- A Stored connection section with connected time and scope list.
- No refresh token shown on the page (by design).
Where tokens live:
- After OAuth, an encrypted refresh token is saved in
.data/oauth.jsonon the machine running the server. - That file is secret. It is listed in
.gitignore— never commit it or paste it into chat. - Decryption uses
TOKEN_ENCRYPTION_KEYfrom.env.local. If you lose the key, you must run/connectagain after generating a new key (old file becomes unusable).
Alternative (advanced): put a plain PINTEREST_REFRESH_TOKEN or PINTEREST_REFRESH_TOKEN_ENCRYPTED in env instead of using .data/oauth.json — useful for cloud hosts without persistent disk.
Part 2 — Connect your AI tool
Two supported paths. Pick one (HTTP is easier when the app stays running; stdio is fine for all-local setups).
Option A — HTTP MCP (/api/mcp)
The server exposes MCP at:
http://localhost:3000/api/mcp
Authentication:
- If
MCP_AUTH_TOKENis unset, anyone who can reach the URL can use your tools — only acceptable on a locked-down machine. - If
MCP_AUTH_TOKENis set:- On localhost, requests without a token are still allowed (convenience for local dev).
- On a public URL (e.g. Vercel), every request must include
Authorization: Bearer YOUR_MCP_AUTH_TOKENor you get 401 Unauthorized.
Cursor mcp.json example (adjust path and token):
{
"mcpServers": {
"pinterest-ads": {
"url": "http://localhost:3000/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_MCP_AUTH_TOKEN"
}
}
}
}
Keep npm run dev running while you use HTTP MCP locally.
For production URL:
"url": "https://YOUR-DOMAIN/api/mcp"
Always use the Authorization header on non-localhost hosts.
Option B — stdio MCP (npm run stdio)
Runs MCP over standard input/output — no browser URL. Your AI app launches the process.
Cursor mcp.json example:
{
"mcpServers": {
"pinterest-ads": {
"command": "npm",
"args": ["run", "stdio"],
"cwd": "/absolute/path/to/pinterest-ads-mcp",
"env": {
"PINTEREST_APP_ID": "…",
"PINTEREST_APP_SECRET": "…",
"PINTEREST_AD_ACCOUNT_ID": "…",
"TOKEN_ENCRYPTION_KEY": "…",
"PINTEREST_USE_SANDBOX": "true"
}
}
}
}
stdio does not load .env.local automatically. Either:
- Put required variables in the
"env"block (as above), or - Export them in your shell before Cursor starts.
OAuth for stdio: complete npm run dev once and visit /connect, or set PINTEREST_REFRESH_TOKEN in the "env" block after connecting elsewhere.
Part 3 — Optional: deploy to Vercel (or similar)
Use cloud hosting when you want MCP available without keeping your laptop on.
- Push the repo to GitHub (do not commit
.env.localor.data/). - Import the project in Vercel as a Next.js app.
- Set all environment variables from
.env.localin the Vercel project settings. - Update:
NEXT_PUBLIC_APP_URL→https://YOUR-DOMAINPINTEREST_REDIRECT_URI→https://YOUR-DOMAIN/api/oauth/pinterest
- Add the same redirect URI in the Pinterest developer app.
- Deploy, then visit
https://YOUR-DOMAIN/connectand complete OAuth on the deployed site.
Important for cloud (Vercel):
- Vercel cannot write
.data/oauth.jsonunder/var/task. After OAuth succeeds,/connectshows a yellow One more step for Vercel panel withPINTEREST_REFRESH_TOKEN_ENCRYPTED. Copy it into Vercel → Settings → Environment Variables, then redeploy. - Use the same
TOKEN_ENCRYPTION_KEYin Vercel that was set when you completed OAuth (the encrypted blob only decrypts with that key). - Do not rely on
.data/on serverless; tokens must live in env vars. - Set a strong
MCP_AUTH_TOKENand always send the Bearer header from your AI client. - Do not share the public MCP URL publicly without that token.
Build/start commands (if your host asks):
| Script | Command |
|---|---|
| Install | npm install |
| Build | npm run build |
| Start | npm run start |
Environment variables
| Variable | Required when | What it is | If missing / wrong |
|---|---|---|---|
PINTEREST_APP_ID |
OAuth + API | Developer app ID | Cannot start OAuth or call API. |
PINTEREST_APP_SECRET |
OAuth + API | Developer app secret | Token exchange fails. |
PINTEREST_REDIRECT_URI |
OAuth | Must match Pinterest app setting | Redirect or token exchange errors. |
PINTEREST_AD_ACCOUNT_ID |
Most tools | Single ad account for this deployment | Tools error on account resolution. |
PINTEREST_USE_SANDBOX |
Always set explicitly | true / false — picks sandbox vs production API host |
Wrong environment, confusing empty data or auth errors. |
TOKEN_ENCRYPTION_KEY |
Storing token in .data/oauth.json |
32+ char secret; encrypts refresh token on disk | Cannot save or read OAuth file. |
OAUTH_STATE_SECRET |
OAuth login | 16+ char HMAC secret for login CSRF | /connect shows “Set … OAUTH_STATE_SECRET”. |
MCP_AUTH_TOKEN |
Recommended | 16+ char Bearer token for /api/mcp |
Open MCP on public URLs; localhost still works without header. |
DRAFT_SIGNING_SECRET |
Optional | Signs write “draft” tokens (defaults to TOKEN_ENCRYPTION_KEY) |
Write previews fail if neither is set. |
NEXT_PUBLIC_APP_URL |
Links / redirects | Base URL of this app (e.g. http://localhost:3000) |
Wrong redirect targets in some flows. |
PINTEREST_REFRESH_TOKEN |
Optional dev | Plain refresh token in env | Bypasses .data/oauth.json; dev-only risk. |
PINTEREST_REFRESH_TOKEN_ENCRYPTED |
Optional cloud | Encrypted token blob + TOKEN_ENCRYPTION_KEY |
Used instead of file store. |
Sandbox vs live (PINTEREST_USE_SANDBOX)
| Setting | Behavior |
|---|---|
PINTEREST_USE_SANDBOX=true |
API calls go to api-sandbox.pinterest.com. Use for testing without touching live spend. |
PINTEREST_USE_SANDBOX=false |
Production API and real ad account data. |
On /connect, you can toggle sandbox OAuth vs production OAuth via the link on the page. The token exchange uses that choice. Keep PINTEREST_USE_SANDBOX aligned with how you connected; otherwise refresh and API hosts may disagree.
Default in .env.example is sandbox (true) — switch to false only when you intend to manage live ads.
What your AI can do — tools by use case
Account & structure (read)
| Tool | Use |
|---|---|
pinterest_get_account |
Account details, user summary, terms-of-service state. |
pinterest_list_ad_accounts |
Confirm which ad accounts the login can see vs your configured ID. |
pinterest_list_campaigns / pinterest_get_campaign |
Campaign list and detail. |
pinterest_list_ad_groups |
Ad groups, optionally filtered by campaign. |
pinterest_list_ads |
Ads in the account. |
pinterest_list_keywords |
Keywords for ad groups. |
Reporting & analytics
| Tool | Use |
|---|---|
pinterest_get_analytics |
Quick sync stats for short date ranges (≤31 days), ≤100 entities, not hourly. |
pinterest_create_report |
Start a large or slow report (custom body, or template). |
pinterest_get_report |
Poll report job status and download link. |
pinterest_list_metrics |
Valid metric column names for reports. |
pinterest_metrics_ready |
Whether conversion metrics are ready for a date. |
Tip: If sync analytics refuses the request or times out, use pinterest_create_report then pinterest_get_report.
Audit & compliance evidence
| Tool | Use |
|---|---|
pinterest_audit_snapshot |
Read-only bundle for ads-pinterest PN-* style controls (tags, catalogs, campaigns, etc.). Observations only — no “health score.” |
Targeting research
| Tool | Use |
|---|---|
pinterest_get_targeting_options |
Interests, locations, geos, audiences, etc. for planning ad groups. |
Create & change ads (guarded writes)
Nothing deletes campaigns/ads via API here. New entities default to PAUSED.
Workflow: always draft → review preview → apply with confirm: true. Draft tokens expire in about 5 minutes.
| Draft tool | Apply tool | What it does |
|---|---|---|
pinterest_draft_campaign |
pinterest_apply_campaign |
Create/update campaigns. New campaigns: objectives SALES or LEADS only. |
pinterest_draft_ad_group |
pinterest_apply_ad_group |
Create/update ad groups. |
pinterest_draft_pin |
pinterest_apply_pin |
Create a Pin (image URL or media id). |
pinterest_draft_ad |
pinterest_apply_ad |
Create/update ads. |
pinterest_draft_keyword |
pinterest_apply_keyword |
Keywords including negatives. |
pinterest_draft_entity_status |
pinterest_apply_entity_status |
Set ACTIVE / PAUSED / ARCHIVED on campaign, ad group, or ad. |
Money: tool inputs/outputs use major currency (e.g. dollars); the server converts to Pinterest’s micro-currency internally.
Objectives: Pinterest is migrating to simplified objectives. You may still see legacy types (CATALOG_SALES, WEB_CONVERSION, AWARENESS, etc.) when reading old campaigns. Creating new campaigns through this server only allows SALES and LEADS. Ask your AI to map legacy setups to the new model when planning creates.
Quick verification after setup
In your AI chat (with MCP connected), ask it to run these in order:
- List tools — you should see about 25+
pinterest_*tools (read, report, audit, draft/apply). pinterest_get_account— returns your ad account payload without “No Pinterest refresh token”.pinterest_audit_snapshot— returns a JSON evidence bundle (may take a few seconds).
If all three succeed, reporting and draft tools are ready to use.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| redirect_uri mismatch | Pinterest app redirect ≠ PINTEREST_REDIRECT_URI |
Copy the URI character-for-character into the developer app; redeploy if you changed domain. |
| 401 / 403 on ads tools | Missing scopes, app not approved for Ads API, or user lacks ad account role | Re-check scopes table; confirm you’re admin on PINTEREST_AD_ACCOUNT_ID; re-authorize at /connect. |
| No Pinterest refresh token / tools say not connected | Never finished OAuth, deleted .data/oauth.json, missing PINTEREST_REFRESH_TOKEN_ENCRYPTED on Vercel, or wrong TOKEN_ENCRYPTION_KEY |
Visit /connect and complete OAuth again; on Vercel, paste encrypted token from setup panel into env and redeploy. |
ENOENT mkdir /var/task/.data |
Hosted on Vercel without env token storage | Redeploy with this fix, run OAuth again, copy PINTEREST_REFRESH_TOKEN_ENCRYPTED from /connect into Vercel env. |
| Token refresh failed | Revoked app, expired refresh, sandbox/live mismatch | Re-run /connect; align PINTEREST_USE_SANDBOX with connection mode. |
| 401 Unauthorized MCP request | Missing/wrong Bearer on a public URL, or wrong token on localhost | Add Authorization: Bearer matching MCP_AUTH_TOKEN. On localhost you can omit the header, but a wrong Bearer still fails. |
| sync_request_too_large / analytics timeout | Range too long, too many IDs, or hourly granularity | Use pinterest_create_report + pinterest_get_report. |
| Invalid create objective | Draft used CATALOG_SALES, WEB_CONVERSION, etc. |
Use SALES or LEADS for new campaigns only. |
| Apply failed / confirm required | Skipped draft step or expired draft | Run pinterest_draft_* again, then apply within ~5 minutes with confirm: true. |
| Set PINTEREST_APP_ID… on /connect | Incomplete .env.local |
Fill app ID, secret, redirect URI, OAUTH_STATE_SECRET; restart npm run dev. |
| stdio tools fail immediately | Env vars not visible to subprocess | Add "env" in mcp.json or export variables before launching Cursor. |
Security notes (please read)
.env.localholds app secrets. Do not email it, commit it to GitHub, or paste into Slack/ChatGPT..data/oauth.jsonis as sensitive as a password. Back up only encrypted, store offline, never in git.- MCP HTTP URL on the internet is a remote control for your ad account if
MCP_AUTH_TOKENis weak or missing. Use a long random token and HTTPS. - Do not paste refresh tokens,
MCP_AUTH_TOKEN, or app secrets into AI chats — even “private” threads. - Who can run writes: anyone who can call your MCP server can run apply tools after a draft. Lock down network access and tokens.
- This project intentionally excludes agency/business-access APIs, billing APIs, and delete tools.
NPM scripts reference
| Command | Purpose |
|---|---|
npm run dev |
Local server + /connect + HTTP MCP at /api/mcp |
npm run stdio |
MCP over stdio for AI apps that launch a subprocess |
npm run build |
Production build (cloud deploy) |
npm run start |
Run production build |
npm test |
Automated tests (optional) |
Out of scope
Multi-tenant token vaults, Pinterest Business Access / partner APIs, billing management, entity delete tools, and automated “account health scores.”
For technical architecture (OpenAPI version, libraries), see comments in the repository source.
Install
This server does not publish a one-line install command.
Open the repository installation guideConfiguration
{
"mcpServers": {
"pinterest-ads": {
"url": "http://localhost:3000/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_MCP_AUTH_TOKEN"
}
}
}
}