MCP server
The upload.ad MCP server lets AI agents (Claude Code, Claude Desktop, Cursor, and any other MCP client) manage your creatives and ad copy through natural language: "upload everything in ./renders and add a headline to each" becomes tool calls against your account.
The server ships with the uploadad CLI and runs locally over stdio:
uploadad mcpAuthentication
The easiest path is browser sign-in:
uploadad loginThis opens upload.ad in your browser, you approve the device, and the credential is stored in ~/.config/uploadad/config.json. Every config below works with no env block after that.
The remote server (see below) supports OAuth directly: clients like Claude and ChatGPT send you to upload.ad to sign in when you add the connector, no keys involved.
For headless environments (CI, servers), use an API key instead: create one at upload.ad/dashboard/settings/api-keys and set it as the UPLOADAD_API_KEY environment variable. MCP access requires an active subscription either way.
Claude Code
Connect to the remote server; Claude Code opens the browser to sign you in:
claude mcp add --transport http uploadad https://upload.ad/mcpOr run the local server (uses your uploadad login, or an API key):
claude mcp add uploadad -- npx uploadad mcp
claude mcp add uploadad -e UPLOADAD_API_KEY=ua_live_... -- npx uploadad mcpClaude Desktop
Add to claude_desktop_config.json. After uploadad login, no env block is needed; add one with UPLOADAD_API_KEY to use an API key instead.
{
"mcpServers": {
"uploadad": {
"command": "npx",
"args": ["uploadad", "mcp"]
}
}
}Cursor
Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global). Same rule: the env block is only needed if you authenticate with an API key instead of uploadad login.
{
"mcpServers": {
"uploadad": {
"command": "npx",
"args": ["uploadad", "mcp"]
}
}
}OpenClaw
Connect to the remote server, then run the login command; it prints an authorization URL to approve in the browser:
openclaw mcp set uploadad '{"url":"https://upload.ad/mcp","transport":"streamable-http","auth":"oauth"}'
openclaw mcp login uploadadOr run the local server (uses your uploadad login, or UPLOADAD_API_KEY):
openclaw mcp set uploadad '{"command":"npx","args":["uploadad","mcp"]}'Hermes
Add to ~/.hermes/config.yaml, then run hermes mcp login uploadad from a fresh terminal and approve the sign-in in the browser:
mcp_servers:
uploadad:
url: 'https://upload.ad/mcp'
auth: oauthOr run the local server (uses your uploadad login, or UPLOADAD_API_KEY):
mcp_servers:
uploadad:
command: npx
args: ['uploadad', 'mcp']Remote server
For clients that connect to MCP servers over HTTPS instead of launching a local process, upload.ad also hosts the same server at:
https://upload.ad/mcpIt speaks streamable HTTP and supports OAuth: add the URL to any OAuth-capable MCP client and it sends you to upload.ad to sign in and approve access. No keys to create or paste.
API keys still work for scripts and clients without OAuth support, either as a header (Authorization: Bearer ua_live_...) or, for clients that only take a URL, embedded in it:
https://upload.ad/mcp?key=ua_live_...Treat a keyed URL like the key itself. The remote server exposes the same tools as the local one, with one difference: upload_creatives takes urls (publicly reachable file URLs, downloaded server side) instead of filePaths.
ChatGPT
ChatGPT connects to remote MCP servers through custom connectors:
- Enable developer mode under Settings, Apps and connectors, Advanced settings.
- Create a new connector, name it upload.ad, and set the MCP server URL to
https://upload.ad/mcp. - Choose OAuth authentication and sign in when prompted. Alternatively, set authentication to none and use
https://upload.ad/mcp?key=ua_live_...with the key in the URL.
Tools
The server exposes everything you can do in the app, grouped here by area. The catalog is served live from GET /api/v1/tools (each entry includes its JSON schema and a destructive flag), so a tools/list call always reflects the current surface. You can also call any tool directly over REST with POST /api/v1/tools/{name} and the tool's arguments as the JSON body.
| Area | Tools |
|---|---|
| Account | get_account, list_ad_accounts, set_default_ad_account, send_notification (in-app notification to the workspace owner's header bell; href makes it clickable, also usable as an automation action) |
| Uploads | upload_creatives, upload_creative_version (new cut of an existing creative from a URL; takes over the spot, folder, and review flow, older cuts stay behind the version switcher), list_versions (a creative's whole version chain with the current head and per-version review status), list_uploads, get_upload, retry_upload, dismiss_upload |
| Library | list_creatives, search_library (file names, folder names, copy text, and tags; optional tags filter), rename_creative, move_creatives, reorder_creatives, duplicate_creatives, trash_creatives, list_trash, restore_creatives, delete_creatives, set_creative_tags, list_tags, tag_creatives_ai (vision-based auto-tagging from a fixed taxonomy; 1 token per creative, subscribers only), get_creative_performance (per-creative spend/ctr/roas/frequency across Meta and TikTok from the hourly rollup, with winner/loser/fatigued signals), refresh_creative_stats |
| Canvas | arrange_canvas, add_note, draw_shape, list_annotations, delete_annotations |
| Folders | list_folders, create_folder, update_folder, delete_folder |
| Ad copy | generate_copy, list_copy, save_copy, delete_copy |
| Publishing | publish_creatives |
| Performance | get_ads_performance, get_account_series, get_ad, get_ad_insights, get_breakdown |
| Ad account | get_campaigns, get_adsets, list_pixels, list_audiences, search_interests, search_regions, list_languages, search_music, list_lead_forms, list_identities (publishing identities: TikTok custom identities and linked TikTok accounts, Meta the Instagram accounts usable with a managed page including the page-backed one), create_identity (TikTok custom identity from a display name; Meta the page-backed Instagram shadow account for a page), list_partnership_content (Meta: creator posts the business can boost as partnership ads), list_leads (submissions collected by Meta lead ads, pulled in hourly; filter by form or ad, bound by date, paginate with limit/offset; pairs with the lead.received automation trigger), list_catalogs, list_product_sets, list_apps |
| Managing ads | create_campaign, create_adset, create_ad, create_smart_ad, create_catalog_ad, update_ad_object, set_ad_status, duplicate_ad_object, delete_ad |
| Existing posts | list_page_posts (Meta page posts: pass a post id as existingPostId to create_ad to keep its likes and comments), authorize_tiktok_post, list_authorized_posts (TikTok Spark Ads via tiktokItemId on create_ad) |
| Audiences | upload_customer_list (hashed customer lists), create_lookalike_audience, delete_audience |
| A/B tests | list_ab_tests, create_ab_test (Meta ad studies need a connected Business account; TikTok split-test campaigns take one variable plus 2 ad-group arms) |
| Comments | list_ad_comments, set_comment_visibility, reply_to_ad_comment, delete_ad_comment (both platforms; Meta covers the Facebook post and the Instagram media behind each ad, with each comment tagged source: facebook | instagram, and moderation calls take that source back; TikTok moderation calls take the comment's adId), set_comment_liked (Facebook comments only: the Graph API writes just the Like reaction, and Instagram comments cannot be liked) |
| Automations | list_automations, get_automation (returns the flow as YAML code), create_automation (structured graph or a full yaml document), update_automation (edit via YAML; previous state kept in version history), set_automation_enabled, delete_automation — trigger, condition, and action flows (the Automations tab): domain-event triggers (review status changed, comment added or resolved, version or creative uploaded, share link viewed, ad published, lead received, publish failed, member joined, creative test started/finished/promoted/stopped), budget pacing checks, inbound webhooks (POST to /api/automations/hooks/<token>), daily/weekly schedules, and hourly ad-metric checks. Flows may include AI decision/generate steps (1 token per run), wait steps (up to 30 days, resumed by cron), and actions incl. send_notification, channel posts, member emails, signed automation.event webhook deliveries, and start_creative_test |
| Testing | start_creative_test, list_creative_tests, promote_creative_test, kill_creative_test, get_test_pipeline, set_test_pipeline — the creative testing pipeline: creatives launch as active ads in a designated testing ad set, get evaluated hourly against account benchmarks (thresholds + win rule, hard stop at maxSpend), winners promote into the scaling ad set (auto or manual) and losers pause. Lifecycle: queued, launching, testing, won, lost, promoted, killed |
| Budgets | set_budget_plan, list_budget_plans (latest pace snapshot: spent, expected, pace percent, projected; refresh: true recomputes), delete_budget_plan — monthly spend targets per platform (whole account or picked campaigns), recomputed hourly; pair with a budget.pacing automation trigger to act on pace |
| Analytics | workspace_analytics (workspace activity over 7, 30, or 90 days: creation, comment/review, versioning, publishing, testing, storage, and sharing metrics, each with a previous-window comparison and a per-day series, plus a per-member breakdown, share-link engagement, creative outcomes per platform (winner/loser/fatigued counts, top creatives by spend), and automation run stats) |
| Reports | create_report_link (public client-facing performance page at /p/<token>: platform, account or campaign scope, date range, optional password and expiry; data refreshes on view under the workspace's own connection), list_report_links, revoke_report_link |
| Drafts | list_drafts, save_draft, delete_draft, list_scheduled_ads, cancel_scheduled_ad |
| Review | list_comments, add_comment (video timecodes and ranges via durationSeconds, replies via parentId, @Name mentions, team-only notes via internal), react_to_comment, resolve_comment, set_review_status (in_review, changes_requested, approved), create_review_link (optional password, downloads, comments, approvals, expiry) — a public URL where clients view, comment, approve or request changes, compare versions, and download everything as a zip, all without an account, list_review_links, revoke_review_link |
Ad platforms
Every ad tool takes an optional platform argument: meta (Facebook/Instagram, the default) or tiktok. The tool surface is the same on both; the enums differ where the platforms do:
- Objectives: Meta uses
OUTCOME_*values; TikTok usesREACH,TRAFFIC,VIDEO_VIEWS,ENGAGEMENT,LEAD_GENERATION,WEB_CONVERSIONS,APP_PROMOTION,PRODUCT_SALES. - The middle level is an ad set on Meta and an ad group on TikTok; the
adsetlevel value addresses both. - Meta ad sets target ISO country codes; TikTok ad groups need location ids from
search_regions. - TikTok mutations on existing objects (
update_ad_object,set_ad_status,duplicate_ad_object,delete_ad) also needlevel(campaign,adset, orad) since TikTok ids do not encode their type. create_adon TikTok requiresdisplayNameand usesprimaryText(or a saved copy variant) as the ad text; the creative must be published to TikTok first withpublish_creatives.- Breakdown dimensions: Meta supports
age,gender,publisher_platform,platform_position,device_platform,country; TikTok supportsage,gender,country_code,placement. - Delivery estimates are Meta only; TikTok's API does not offer them. Ad previews come from the creative itself: the upload.ad dashboard renders its own TikTok-styled preview, and Meta ads show their creative directly.
- Existing-post ads keep a post's social proof on both platforms: Meta ads expose their
postId(the underlying page post) on reads, andcreate_adaccepts it back asexistingPostId; TikTok Spark Ads usetiktokItemIdfromlist_authorized_posts.duplicate_ad_objectpreserves Spark bindings,deepcopies children on both platforms, andtoParentIdsrolls an ad out into up to 20 other ad sets (or an ad set into other campaigns) in one call. - Meta extras:
pageIdoncreate_adpicks the Facebook page the ad publishes under (any page the connected user manages; defaults to the last used page, andlist_page_posts/list_lead_formsaccept the samepageId), partnership (allowlisted) posts run under the creator's handle withusePageActorOverride,instagramUserId(fromlist_identities) sets the Instagram identity used on Instagram and Threads placements, partnership (branded content) ads discover boostable creator posts withlist_partnership_contentand run them viasourceInstagramMediaIdorpartnershipAdCodewith the sponsor tagged throughpartnershipFbSponsorPageId/partnershipIgSponsorId, manual placements take per-platformpositionsoncreate_adsetandupdate_ad_objecttargeting (e.g.{"facebook":["feed","story"]}; omit a platform for all its positions;whatsapptargets WhatsApp Status and rides on Instagram Stories) plusdevices(mobile/desktop; omit for all),leadFormId(fromlist_lead_forms) makes a lead ad, and catalog ads flow throughlist_catalogs→list_product_sets→create_campaign(catalogId) →create_adset(productSetId) →create_catalog_ad. Catalog ads and A/B tests need a connected Business account or catalog first; the tools say so when missing. - TikTok extras: Spark Ads (
tiktokItemIdoncreate_ad), carousel ads (carouselCards+musicIdfromsearch_music), lead forms (pageIdfromlist_lead_forms), a per-ad publishing identity (identityId+identityTypefromlist_identities; defaults to the workspace identity), Smart Creative ads (create_smart_adin an ad group created withsmartCreative), audience building, and comment management. Catalog ads and app promotion need a TikTok catalog or a registered app first;list_catalogsandlist_appssay so when none exist. - Meta app promotion:
list_apps(platform meta) returns the ad account's advertisable apps; passpromotedAppId+ one of its store URLs asappStoreUrltocreate_adsetinside anOUTCOME_APP_PROMOTIONcampaign (theAPP_INSTALLSoptimization goal requires them).
Campaigns, ad sets, and ads created through tools always start paused unless you explicitly pass activate or schedule a launch. Destructive tools (deleting, activating, budget changes) carry the destructive flag in the catalog so agents know to confirm before acting.
Trying it out
Once connected, ask your agent something like:
Upload banner.png and promo.mp4 to my ad account,
wait for them to finish, then add the headline "Summer sale"
to each new creative.The agent will call upload_creatives, poll with get_upload, and finish with save_copy.