pinterest-mcp
# pinterest-mcp
A minimal [Model Context Protocol](https://modelcontextprotocol.io) server for the [Pinterest API v5](https://developers.pinterest.com/docs/api/v5/). Reads boards/pins/analytics; publishes new pins and boards. Built for a multi-agent fleet where a human must sign off before anything reaches the public account.
## Setup
```bash
npm install
npm run build
```
Requires a Pinterest Developer App (`https://developers.pinterest.com/apps/`) with a registered redirect URI of `http://localhost:3034/oauth/redirect`. Note the App ID and App Secret.
### Getting a token
```bash
PINTEREST_APP_ID=<id> PINTEREST_APP_SECRET=<secret> \
PINTEREST_CREDENTIALS_FILE=/home/you/.openclaw/credentials/pinterest.env \
node scripts/get-pinterest-token.mjs
```
Approve in the browser. The script writes `PINTEREST_APP_ID`, `PINTEREST_APP_SECRET`, `PINTEREST_ACCESS_TOKEN`, `PINTEREST_REFRESH_TOKEN`, `PINTEREST_TOKEN_EXPIRES_AT` into the file (mode `0600`). Tokens are deliberately **not printed**.
Pass `--port <n>` if `3034` is taken (register the matching redirect URI in the Pinterest app). The listener binds before printing the authorize URL, so an occupied port fails loudly rather than burning the one-time auth code.
### Configuration
```json
{
"mcpServers": {
"pinterest": {
"command": "node",
"args": ["/path/to/pinterest-mcp/dist/index.js"],
"env": {
"PINTEREST_CREDENTIALS_FILE": "/home/you/.openclaw/credentials/pinterest.env"
}
}
}
}
```
The server rewrites `PINTEREST_ACCESS_TOKEN` / `PINTEREST_TOKEN_EXPIRES_AT` in that file when it refreshes (Pinterest access tokens expire after ~30 days). OpenClaw's MCP config bakes env vars at `mcp add` time and cannot rewrite them; the file it points at can be rewritten — same pattern as `x-mcp`.
## Available tools
| Tool | Description |
|---|---|
| `pinterest_get_user_info` | Current account (username, id, account type). Safe read. |
| `pinterest_list_boards` | List boards, paginated. Safe read. |
| `pinterest_get_board` | One board's details by id. Safe read. |
| `pinterest_list_pins` | List pins, optionally scoped to one `board_id`. Safe read. |
| `pinterest_get_pin` | One pin's details by id. Safe read. |
| `pinterest_get_pin_analytics` | Impressions / saves / pin clicks / outbound clicks / video views for our own pin. Safe read. |
| `pinterest_create_board` | **Live.** Create a new board on the account. |
| `pinterest_create_pin` | **Live.** Publish an immediately-visible pin to one of our boards. |
Write tools require `agent_id` (must hold the `social` capability) and `task_id` (task must be Done and carry at least one approved output; brand must match `PINTEREST_SERVER_BRAND`, default `with_nate`). Both are ordinary refusals, not crashes — a gate rejection returns `isError: true` with a readable message.
## Pinterest-specific gotchas
- **Trial mode.** A fresh Pinterest Developer app starts in trial mode, limited to the app owner's own account. That is its own safety net for a first-time integration — write scope cannot touch anyone else's account until the app is submitted for review and approved for standard access.
- **Access-token rotation.** Tokens expire in ~30 days; the server refreshes proactively via `POST /v5/oauth/token` with `grant_type=refresh_token`. The refresh token itself usually persists but is written back to the credentials file if Pinterest ever rotates it.
- **`image_url` must be publicly reachable.** Pinterest fetches the image server-side. A local filesystem path (e.g. a MUSE-generated PNG under `/mnt/d/Fleet/...`) will fail — the file has to be hosted somewhere public first, or uploaded via Pinterest's separate `POST /v5/media` flow (not implemented in this first version).
- **Put the destination URL in `link`, not `description`.** The `link` field is what `pinterest_get_pin_analytics` counts outbound clicks against. For Etsy promotion the Etsy listing URL goes there.
- **PNG or JPEG.** Other formats are rejected by Pinterest at pin-create time.
## Security model: `agent_id` capability gating + brand + publish-clearance
OpenClaw doesn't propagate per-agent caller identity down to MCP tool calls in this version (all agents share the same MCP process — confirmed platform limitation, `openclaw/openclaw#67682`). Write tools ask the fleet board's own capability record for the caller *before* doing anything, via `GET /agents/{id}/capabilities` at `FLEET_BOARD_URL` (default `http://127.0.0.1:8420`).
A second call — `GET /tasks/{id}/publish-clearance?brand=<brand>` — proves both the task's brand matches this server AND that a human has actually closed the task with an approved output. Same endpoint every outward server uses; the board owns the rule.
**Honest limitations:**
- `agent_id` is self-reported by the caller, not cryptographically bound. This turns a *silent* wrong-agent action into a *loud, rejected, auditable* one — it does not stop a determined actor from lying.
- The clearance gate is defence in depth. The load-bearing rule is that the board's own fan-out (`BRAND_CHANNELS` in `pickup.py`) decides whether a publishing subtask is ever created. This gate catches an agent reaching for the tool outside that flow.
**Standalone use:** either stand up a minimal service at `FLEET_BOARD_URL` returning a JSON string array for `GET /agents/{id}/capabilities` and a `{cleared: bool, reasons: string[]}` for `GET /tasks/{id}/publish-clearance`, or remove the `requireCapability` / `requireBrand` calls from `src/index.ts`.
## Deliberately not exposed
- **Deleting a pin** (`DELETE /v5/pins/{pin_id}`). An unreviewed retraction of live content is a real, unrecoverable outward action. Deletions stay with Nathan, via the Pinterest app.
- **Deleting a board.** Same reason.
- **Follows / reactions.** Social actions belong behind an explicit ECHO decision, not incidentally callable.
## Notes on safety
- Every request goes to `api.pinterest.com` or `www.pinterest.com` only — no telemetry, no third-party calls, no dynamic code execution.
- Credentials live in a mode-0600 file rewritten in place. Tokens are never printed by either the server or the OAuth catcher.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 8 tools
Each tool targets a distinct resource and action: single-item reads (pin, board, user), list operations (boards, pins), analytics, and creation (board, pin). The descriptions clarify use cases, e.g., list_boards for finding board_id before create_pin, so there is no real ambiguity.
All tools follow the same 'pinterest_{verb}_{noun}' pattern with lowercase snake_case. Verbs are consistently get, list, or create, and nouns are clear resources. No mixed conventions or vague names.
Eight tools is well-scoped for a Pinterest management server. It covers authentication check, reading user/boards/pins, analytics, and creating boards/pins—enough to perform the advertised workflow without redundancy or bloat.
The set covers the core workflow: list boards to get IDs, create boards and pins, fetch pin data, and retrieve analytics. Missing update/delete operations are a minor gap, but the described use case (publishing live pins and measuring traffic) is fully supported.