Skip to main content

MCP Server (ChatGPT / Claude / Cursor)

Upload-Post ships an official Model Context Protocol (MCP) server. Connect it to ChatGPT, claude.ai, Claude Desktop, Claude Code, Cursor, or any other MCP-compatible AI agent and your assistant can publish, schedule, analyze and manage social media on your behalf — without writing any code.

The server exposes nearly 60 tools across the public Upload-Post API, OAuth connectors, media staging, and the hosted Upload Studio.

How authentication works​

The server supports two auth modes, and chooses automatically based on what the client sends:

1. API key (Claude Desktop, Claude Code, Cursor, any MCP client with mcp.json)​

Each MCP client sends its own Upload-Post API key in the Authorization header of every request. The server uses that key for the duration of the session and stores nothing.

Authorization: ApiKey YOUR_UPLOAD_POST_API_KEY

Authorization: Bearer YOUR_UPLOAD_POST_API_KEY is also accepted, for clients that only allow Bearer tokens.

2. OAuth 2.1 (the official Claude connector, the ChatGPT plugin, any "Add via URL" client)​

Clients that don't accept custom headers can connect via the standard OAuth flow — no API key copying required. The server implements:

  • RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource
  • RFC 8414 Authorization Server Metadata at /.well-known/oauth-authorization-server
  • RFC 7591 Dynamic Client Registration at /register
  • RFC 6749 §4.1 + PKCE (RFC 7636) authorization-code flow at /authorize + /token
  • RFC 7009 Token revocation at /revoke

The user is sent to app.upload-post.com/oauth/authorize to log in and approve. The issued access token (prefix up_oauth_) is opaque — the server resolves it to the user's API key on every request via an internal introspection call.

You can view and revoke active OAuth connectors at any time from Connected Apps in the dashboard.

Get your API key​

  1. Sign in to app.upload-post.com.
  2. Open the API Keys section.
  3. Generate a new key and copy it.

Connect Claude (official connector)​

Upload-Post is an official connector in Claude's connector directory, available in claude.ai, Claude Desktop and Claude Cowork:

  1. Open Upload-Post in the Claude directory, or in Claude go to Customize → Connectors, browse the directory and search for Upload-Post.
  2. Click Connect and sign in with your Upload-Post account (OAuth).

There is no URL to paste. On Team and Enterprise, an owner may need to allow the connector first in Organization settings.

Add it manually as a custom connector

In claude.ai or Claude Desktop, open Customize → Connectors, click + Add, then Add custom connector, paste this URL and click Continue:

https://mcp.upload-post.com/mcp

Custom connectors work on every Claude plan, and the Free plan allows one. On Team and Enterprise, an owner adds the connector first in Organization settings.

Either way, Claude will:

  1. Discover the OAuth metadata at /.well-known/oauth-protected-resource.
  2. Dynamically register itself via /register (no manual client ID needed).
  3. Open app.upload-post.com/oauth/authorize in a popup so you can log in and approve.
  4. Exchange the returned authorization code for an access token, and store it.

That's it: the Upload-Post tools become available inside Claude with zero local configuration. Revoke any time from Connected Apps.

In Claude Code, add the server with your API key (see below) or install the official Upload-Post plugin from the Claude directory.

Connect ChatGPT​

Upload-Post is a plugin reviewed by OpenAI, listed in ChatGPT's Plugins (formerly Apps), so the fastest path is to install it from ChatGPT itself:

  1. Open the Upload-Post plugin in ChatGPT, or search for Upload-Post in Plugins.
  2. Click Install, then Connect, and sign in with your Upload-Post account (OAuth).

There is no URL to paste. After approval, ChatGPT can call the Upload-Post tools and open the Upload Studio component for browser-based media uploads. OpenAI decides which plugins each plan, workspace and region can use; in a Business or Enterprise workspace an admin may need to allow it first.

Add it manually as a custom MCP server

You can also add the server by hand as a custom plugin. Custom MCP servers get full access, including write actions, on Business, Enterprise and Edu workspaces, on the web:

  1. In ChatGPT on the web, open Plugins and click +.
  2. Choose Add custom MCP server and paste the hosted endpoint:
https://mcp.upload-post.com/mcp
  1. Set Authentication to OAuth, tick I understand and want to continue, click Create as a plugin and authorize.

Either way ChatGPT uses the same OAuth flow as Claude.

OpenAI dots​

A dot, OpenAI's always-on agent in ChatGPT, uses the plugins you install and connect. Install Upload-Post as above, then open your dot's profile, go to Customize → Plugins, select Upload-Post and review its permissions: permission to read data does not include permission to post. In Customize → Custom rules, a rule set to Ask before taking action makes the dot ask before it publishes. Dots are included with ChatGPT Pro (outside the EEA, Switzerland and the UK) and Business Premium, and Enterprise and Edu workspaces can enable a beta.

Upload Studio is ChatGPT-only

The Studio widget is built on the ChatGPT Apps SDK, so the server only advertises open_upload_studio to ChatGPT. In claude.ai, Claude Desktop, Claude Code, Cursor and other clients the tool is hidden. To publish a file from your computer there, give the assistant a public HTTPS URL of the file, or upload it from the dashboard. Clients that can run HTTP requests themselves (Claude Code, Cursor, scripts) can also stage the file with create_media_upload → PUT → complete_media_upload and pass the returned media_url to upload_video.

Connect Perplexity Computer​

In Perplexity, open Plugins in the sidebar (or Customize → Connectors if you don't see Plugins yet), then Add custom → Connector → Add MCP connector and choose Remote:

  • Name: Upload-Post
  • MCP Server URL: https://mcp.upload-post.com/mcp
  • Transport: Streamable HTTP
  • Authentication: OAuth, or API key with your Upload-Post API key

Tick the acknowledgement box and click Add, then click the Upload-Post card to sign in. On Enterprise, an admin has to allow members to add custom connectors.

Connect Claude Desktop / Claude Code / Cursor​

Add the following entry to your MCP config (~/.claude/mcp.json, ~/.cursor/mcp.json, or your IDE's equivalent):

{
"mcpServers": {
"upload-post": {
"url": "https://mcp.upload-post.com/mcp",
"headers": {
"Authorization": "ApiKey YOUR_UPLOAD_POST_API_KEY"
}
}
}
}

Restart your client. You should see the upload-post tools become available.

In Claude Code you can register it with one command instead:

claude mcp add --transport http upload-post https://mcp.upload-post.com/mcp \
--header "Authorization: ApiKey YOUR_UPLOAD_POST_API_KEY"

What the agent can do​

GroupTools
Uploadupload_video, upload_photos, upload_text, upload_document
Statusget_status, get_job_status, get_history, get_media
Schedulelist_scheduled, cancel_scheduled, edit_scheduled
Analyticsget_analytics, get_total_impressions, get_post_analytics, get_platform_metrics
Usersget_account_info, list_users, create_user, delete_user, generate_jwt, validate_jwt
Pages / boardsget_facebook_pages, get_linkedin_pages, get_pinterest_boards, get_google_business_locations, select_google_business_location, get_reddit_detailed_posts
Commentsget_post_comments, reply_to_comment, public_reply_to_comment
DMssend_dm, list_dm_conversations, manage_autodms
FFmpegsubmit_ffmpeg_job, get_ffmpeg_job, download_ffmpeg_result, get_ffmpeg_consumption
Queueget_queue_settings, update_queue_settings, preview_queue
Media stagingcreate_media_upload, complete_media_upload, get_media_upload, delete_media_upload
Studioopen_upload_studio (ChatGPT only)

Async uploads return a request_id; the agent polls get_status until success: true.

Use it from an agent framework​

Agent frameworks connect to the same hosted endpoint over Streamable HTTP and send the API key in the Authorization: ApiKey <key> header. Each guide has installation steps and a runnable example that lists your profiles and publishes a post.

FrameworkLanguageConnects withGuide
OpenAI Agents SDKPython, TypeScriptMCPServerStreamableHttp, HostedMCPToolOpenAI Agents SDK
CrewAIPythonMCPServerHTTP in mcps=, MCPServerAdapterCrewAI
MastraTypeScriptMCPClientMastra
Google ADKPythonMcpToolset + StreamableHTTPConnectionParamsGoogle ADK
Pydantic AIPythonMCPToolsetPydantic AI
LlamaIndexPythonBasicMCPClient + McpToolSpecLlamaIndex
smolagentsPythonMCPClientsmolagents
LangChain / LangGraphPythonMultiServerMCPClient (langchain-mcp-adapters)LangChain
Vercel AI SDKTypeScriptNative tools package @upload-post/ai-sdkVercel AI SDK

Upload tool parameters​

The four upload tools accept the same options as the REST API, but in camelCase (the MCP is built on the official upload-post npm SDK, which maps them to the snake_case form fields of POST /api/upload, POST /api/upload_photos and POST /api/upload_text).

Common parameters (all upload tools)​

ParameterTypeDescription
userstringRequired. Profile name (Upload-Post user).
platformsstring[]Required. Array of platform identifiers, e.g. ["youtube", "tiktok"].
titlestringCaption / title. Required for YouTube and text posts. Reddit posting is currently unavailable (reddit_unavailable).
descriptionstringDescription (platforms that support it).
firstCommentstringComment posted right after publishing.
scheduledDatestringISO 8601 date for scheduled publishing, e.g. 2026-12-25T10:00:00Z. Omit to post now.
timezonestringIANA timezone for scheduledDate, e.g. Europe/Madrid.
addToQueuebooleanInsert into the profile's posting queue instead of publishing now.
asyncUploadbooleanReturn immediately with a request_id (default true).

Tool-specific media inputs:

  • upload_video — videoPathOrUrl (public/signed HTTPS URL, or absolute local path for self-hosted MCP) or videoBase64 (+ optional videoFilename). Hosted clients cannot read local paths or chat attachments: ChatGPT uses open_upload_studio; claude.ai needs a public URL or the dashboard; clients with a shell can use media staging (create_media_upload → PUT → complete_media_upload).
  • upload_photos — photosPathsOrUrls (array of URLs or local paths), optional altText.
  • upload_text — title is the post text; optional linkUrl for a link-preview card (LinkedIn, Bluesky, Facebook).
  • upload_document — documentPathOrUrl (PDF/PPT/PPTX/DOC/DOCX, LinkedIn only), linkedinVisibility, targetLinkedinPageId.

platformOptions — per-platform overrides​

upload_video, upload_photos and upload_text accept a flat platformOptions object with camelCase keys. Each key maps 1:1 to a snake_case parameter of the REST API (youtubeThumbnailUrl → thumbnail_url, tiktokPrivacyLevel → privacy_level, …); the supported set is the option list of the upload-post SDK. The most used keys:

YouTube (video)

KeyDescription
youtubeThumbnailUrlCustom thumbnail image URL.
youtubeTagsVideo tags — string or array of strings.
youtubeCategoryIdCategory ID (e.g. "22" for People & Blogs).
youtubePrivacyStatuspublic, unlisted or private.
youtubePlaylistIdOne playlist ID, an array, or a comma-separated list to add the video to.
youtubeEmbeddableAllow embedding on other sites.
youtubeLicenseyoutube or creativeCommon.
youtubePublicStatsViewableShow public view stats.
youtubeSelfDeclaredMadeForKidsCOPPA made-for-kids flag.
youtubeContainsSyntheticMediaAI/synthetic content disclosure.
youtubeDefaultLanguage / youtubeDefaultAudioLanguageBCP-47 language of title/description and audio.
youtubeAllowedCountries / youtubeBlockedCountriesComma-separated country codes.
youtubeHasPaidProductPlacementPaid product placement flag.
youtubeRecordingDateRecording date (ISO 8601).
youtubeSubtitlesArray of { language, name?, url? } subtitle tracks (SRT, VTT, SBV, SUB, ASS, SSA, TTML).

TikTok

KeyDescription
tiktokPrivacyLevelPUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY.
tiktokDisableDuet / tiktokDisableComment / tiktokDisableStitchInteraction toggles.
tiktokCoverTimestampCover frame timestamp in ms (video).
tiktokPhotoCoverIndexCover photo index, 0-based (photos).
tiktokAutoAddMusicAuto add music (photos).
tiktokIsAigcAI-generated content flag.
tiktokPostModeDIRECT_POST or MEDIA_UPLOAD. MEDIA_UPLOAD is draft mode; same idea as tiktokUploadToDraft: true.
tiktokUploadToDraftAlias of tiktokPostMode: "MEDIA_UPLOAD".
brandContentToggle / brandOrganicToggleBranded content disclosures.

Instagram

KeyDescription
instagramMediaTypeVideo: REELS or STORIES. Photos: IMAGE or STORIES.
instagramCoverUrlCustom cover image URL (Reels).
instagramThumbOffsetFrame offset for the auto thumbnail.
instagramShareToFeedAlso show the Reel in the feed.
instagramCollaboratorsComma-separated collaborator usernames.
instagramUserTagsComma-separated user tags.
instagramLocationIdLocation ID.
instagramAudioNameAudio track name.
instagramAltTextAlt text per image (max 1000 chars).

Facebook

KeyDescription
facebookPageIdPage ID to publish to (see get_facebook_pages).
facebookMediaTypeREELS, STORIES or VIDEO.
facebookVideoStatePUBLISHED or DRAFT.
thumbnailUrlThumbnail URL for normal page videos (facebookMediaType: "VIDEO").
facebookLinkUrlLink preview URL (text posts).
facebookAltTextAlt text per photo.

LinkedIn

KeyDescription
linkedinPageId / targetLinkedinPageIdOrganization page ID (see get_linkedin_pages).
linkedinVisibilityPUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER.
linkedinLinkUrlLink preview URL (text posts).
linkedinAltTextAlt text per image (defaults to title).

Pinterest

KeyDescription
pinterestBoardIdBoard ID (see get_pinterest_boards).
pinterestLinkDestination link for the pin.
pinterestCoverImageUrlCover image URL (video pins).
pinterestCoverImageKeyFrameTimeKey frame time in ms for the cover.
pinterestAltTextAlt text (photo pins).

X (Twitter)

KeyDescription
xReplySettingsWho can reply: everyone, following, mentionedUsers, subscribers, verified.
xTaggedUserIdsUser IDs to tag in media.
xLongTextAsPostPost long text as a single post instead of a thread.
xThreadImageLayoutImages per thread post, e.g. "4,4".
xQuoteTweetIdTweet ID to quote (text posts).
xPollOptions / xPollDurationPoll with 2–4 options and duration in minutes (text posts).
xCommunityIdCommunity ID.
xAltTextAlt text per image (max 1000 chars).
xPaidPartnershipPaid partnership flag.

Threads / Reddit / Bluesky / Google Business

KeyDescription
threadsLongTextAsPostSingle post instead of a thread.
threadsThreadMediaLayoutMedia items per Threads post, e.g. "5,5".
redditSubredditUnused while Reddit posting is unavailable (503 reddit_unavailable).
redditFlairIdUnused while Reddit posting is unavailable.
blueskyLinkUrlExternal embed link preview.
blueskyAltTextAlt text (string, JSON array, or ||-separated).
threadsAltTextAlt text per image (max 1000 chars).
threadsReplyControlWho can reply.
googleBusinessLocationIdLocation to publish to (see get_google_business_locations).

Per-platform text overrides are also supported: youtubeTitle, tiktokTitle, instagramTitle, …, youtubeDescription, linkedinDescription, …, and youtubeFirstComment, instagramFirstComment, etc. take priority over the generic title / description / firstComment for that platform.

Example: video to YouTube with thumbnail, tags and playlist​

{
"tool": "upload_video",
"arguments": {
"user": "marketing",
"platforms": ["youtube"],
"title": "Spring launch 🌱",
"description": "Everything new this season.",
"videoPathOrUrl": "https://example.com/launch.mp4",
"platformOptions": {
"youtubePrivacyStatus": "public",
"youtubeThumbnailUrl": "https://example.com/thumb.jpg",
"youtubeTags": ["launch", "product"],
"youtubeCategoryId": "22",
"youtubePlaylistId": "PLxxxxxxxxxxxx"
}
}
}

For the complete parameter semantics, validation rules and platform requirements, see the REST references: Upload Video, Upload Photos, Upload Text and Upload Document.

Browser media staging for ChatGPT and Claude​

Hosted AI clients do not reliably hand local file paths to MCP tools. Upload-Post solves this with short-lived R2 media staging, so the browser uploads the binary directly while the MCP only orchestrates the flow.

  1. The client calls create_media_upload with filename, content_type, content_length, media_type, and source.
  2. The backend returns an upload_id and a presigned R2 upload_url.
  3. The browser uploads the file directly to R2 with PUT.
  4. The client calls complete_media_upload to validate the object and receive a temporary signed media_url.
  5. The agent passes that media_url to upload_video, upload_photos, or another upload tool.

The staging object is not the durable source of truth. The normal Upload-Post upload/scheduler flow downloads the temporary URL and stores the media in the existing durable storage path before publishing or scheduling. This keeps the existing /api/upload behavior unchanged.

Staged objects expire after 24 hours whether they were used or not. Once the upload call that used a staged file returns, the media is already copied to durable storage, so clients can call delete_media_upload right away to clean up. It is optional: completed files don't count towards the in-progress limit below.

Media staging limits​

These limits are separate from normal social upload quotas and apply only to the temporary browser-to-R2 staging path. Publishing from a public URL, or uploading the file directly to /api/upload, /api/upload_photos and the other upload endpoints, is not affected.

PlanStaged files / monthMax file sizeMonthly staging volumeUploads in progress at once
Free / default20250 MB2 GB3
Basic / Premium1,000500 MB25 GB10
Professional / Pro5,0001 GB100 GB50
Advanced20,0002 GB500 GB200
Business100,0005 GB2 TB1,000

What each limit counts:

  • Uploads in progress at once: uploads created with create_media_upload that have not been completed with complete_media_upload yet. Completed files never hold a slot, so you can stage and publish a 10-image carousel and immediately stage the next one. An unfinished upload stops holding its slot when its signed PUT URL expires, 15 minutes after it was created, or as soon as you call delete_media_upload.
  • Staged files / month and Monthly staging volume: every file completed this calendar month (UTC), including files you already deleted or that expired. Abandoned uploads that were never completed and files rejected on completion don't count. Both reset on the 1st of the month.

When a limit is hit, create_media_upload returns 429 with an error_code and a message that says what to do:

error_codeMeaningExtra fields
STAGING_UPLOADS_IN_PROGRESSToo many uploads created but not completed. Complete or delete them, or wait for the oldest one's PUT URL to expire.in_progress, pending_limit, next_slot_at, retry_after_seconds and a Retry-After header
STAGING_MONTHLY_FILES_LIMITMonthly staged file count reached.usage, limits, resets_at
STAGING_MONTHLY_BYTES_LIMITMonthly staging volume reached.usage, limits, resets_at

Production deployment notes​

Deploy the backend before the MCP server. The MCP calls the new media staging endpoints, so older backend versions will not support browser uploads.

Backend environment variables are optional because production-safe defaults are built in:

UPLOADPOST_MCP_MEDIA_STAGING_PREFIX=mcp-ingest/tmp
UPLOADPOST_MCP_MEDIA_TTL_HOURS=24
UPLOADPOST_MCP_MEDIA_PUT_EXPIRES_SECONDS=900
UPLOADPOST_MCP_MEDIA_GET_EXPIRES_SECONDS=21600

Set UPLOAD_POST_R2_CONNECT_DOMAIN on the MCP only if the public host used by the presigned R2 URLs is different from the default:

UPLOAD_POST_R2_CONNECT_DOMAIN=https://0de16d5f5e344fe4757ecd62640a9ea3.r2.cloudflarestorage.com

R2 bucket CORS is required only for browser direct uploads. It does not affect API-to-API calls, scheduler workers, or the existing upload flow. Allow:

  • Origins: https://chatgpt.com, https://chat.openai.com, https://app.upload-post.com, https://mcp.upload-post.com
  • Methods: PUT, GET, HEAD
  • Headers: at least Content-Type; use * if the Cloudflare dashboard requires a broad value for presigned S3 requests

Do not add a 24-hour lifecycle rule to the whole bucket. Cleanup is handled by the backend cron and targets only objects under mcp-ingest/tmp.

Troubleshooting​

  • redirect_uri not on allow-list: the Upload-Post OAuth backend must allow the client redirect URI from ChatGPT or claude.ai before /oauth/authorize can complete.
  • Browser CORS error during PUT: check the R2 CORS policy and make sure the MCP UPLOAD_POST_R2_CONNECT_DOMAIN matches the host returned in upload_url.
  • Video file not found from an agent: use media staging instead of sending local filesystem paths to upload_video.

Example prompts​

Once connected, try these in your AI client:

  • "List the users in my Upload-Post account."
  • "Publish this video URL to TikTok and Instagram under the profile marketing with the caption 'Spring launch'."
  • "Schedule a text post on LinkedIn for next Monday at 10:00 Madrid time."
  • "Show me the analytics for my profile marketing over the last month."
  • "Reply privately to the latest comment on my Instagram post."

The model decides which tools to call based on the request; you don't have to name them.

Self-hosting (optional)​

If you prefer to run the server yourself — for an isolated network, custom auth proxy, or stricter compliance requirements — the source repository ships with a multi-stage Dockerfile and a one-click Coolify configuration. See the project README for instructions.

Local-only stdio mode​

For single-user setups (no hosted server), you can run the MCP locally via npx. Once published to npm, the configuration becomes:

{
"mcpServers": {
"upload-post": {
"command": "npx",
"args": ["-y", "@upload-post/mcp"],
"env": { "UPLOAD_POST_API_KEY": "YOUR_UPLOAD_POST_API_KEY" }
}
}
}

Both modes expose the same tools.

Need assistance?​

Open an issue at github.com/Upload-Post/upload-post-mcp/issues or contact our support team.