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.
- Hosted endpoint:
https://mcp.upload-post.com/mcp - Source: github.com/Upload-Post/upload-post-mcp (MIT)
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
- Sign in to app.upload-post.com.
- Open the API Keys section.
- 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:
- Open Upload-Post in the Claude directory, or in Claude go to Customize → Connectors, browse the directory and search for Upload-Post.
- 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:
- Discover the OAuth metadata at
/.well-known/oauth-protected-resource. - Dynamically register itself via
/register(no manual client ID needed). - Open
app.upload-post.com/oauth/authorizein a popup so you can log in and approve. - 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:
- Open the Upload-Post plugin in ChatGPT, or search for Upload-Post in Plugins.
- 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:
- In ChatGPT on the web, open Plugins and click +.
- Choose Add custom MCP server and paste the hosted endpoint:
https://mcp.upload-post.com/mcp
- 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.
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
| Group | Tools |
|---|---|
| Upload | upload_video, upload_photos, upload_text, upload_document |
| Status | get_status, get_job_status, get_history, get_media |
| Schedule | list_scheduled, cancel_scheduled, edit_scheduled |
| Analytics | get_analytics, get_total_impressions, get_post_analytics, get_platform_metrics |
| Users | get_account_info, list_users, create_user, delete_user, generate_jwt, validate_jwt |
| Pages / boards | get_facebook_pages, get_linkedin_pages, get_pinterest_boards, get_google_business_locations, select_google_business_location, get_reddit_detailed_posts |
| Comments | get_post_comments, reply_to_comment, public_reply_to_comment |
| DMs | send_dm, list_dm_conversations, manage_autodms |
| FFmpeg | submit_ffmpeg_job, get_ffmpeg_job, download_ffmpeg_result, get_ffmpeg_consumption |
| Queue | get_queue_settings, update_queue_settings, preview_queue |
| Media staging | create_media_upload, complete_media_upload, get_media_upload, delete_media_upload |
| Studio | open_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.
| Framework | Language | Connects with | Guide |
|---|---|---|---|
| OpenAI Agents SDK | Python, TypeScript | MCPServerStreamableHttp, HostedMCPTool | OpenAI Agents SDK |
| CrewAI | Python | MCPServerHTTP in mcps=, MCPServerAdapter | CrewAI |
| Mastra | TypeScript | MCPClient | Mastra |
| Google ADK | Python | McpToolset + StreamableHTTPConnectionParams | Google ADK |
| Pydantic AI | Python | MCPToolset | Pydantic AI |
| LlamaIndex | Python | BasicMCPClient + McpToolSpec | LlamaIndex |
| smolagents | Python | MCPClient | smolagents |
| LangChain / LangGraph | Python | MultiServerMCPClient (langchain-mcp-adapters) | LangChain |
| Vercel AI SDK | TypeScript | Native tools package @upload-post/ai-sdk | Vercel 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)
| Parameter | Type | Description |
|---|---|---|
user | string | Required. Profile name (Upload-Post user). |
platforms | string[] | Required. Array of platform identifiers, e.g. ["youtube", "tiktok"]. |
title | string | Caption / title. Required for YouTube and text posts. Reddit posting is currently unavailable (reddit_unavailable). |
description | string | Description (platforms that support it). |
firstComment | string | Comment posted right after publishing. |
scheduledDate | string | ISO 8601 date for scheduled publishing, e.g. 2026-12-25T10:00:00Z. Omit to post now. |
timezone | string | IANA timezone for scheduledDate, e.g. Europe/Madrid. |
addToQueue | boolean | Insert into the profile's posting queue instead of publishing now. |
asyncUpload | boolean | Return 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) orvideoBase64(+ optionalvideoFilename). Hosted clients cannot read local paths or chat attachments: ChatGPT usesopen_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), optionalaltText.upload_text—titleis the post text; optionallinkUrlfor 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)
| Key | Description |
|---|---|
youtubeThumbnailUrl | Custom thumbnail image URL. |
youtubeTags | Video tags — string or array of strings. |
youtubeCategoryId | Category ID (e.g. "22" for People & Blogs). |
youtubePrivacyStatus | public, unlisted or private. |
youtubePlaylistId | One playlist ID, an array, or a comma-separated list to add the video to. |
youtubeEmbeddable | Allow embedding on other sites. |
youtubeLicense | youtube or creativeCommon. |
youtubePublicStatsViewable | Show public view stats. |
youtubeSelfDeclaredMadeForKids | COPPA made-for-kids flag. |
youtubeContainsSyntheticMedia | AI/synthetic content disclosure. |
youtubeDefaultLanguage / youtubeDefaultAudioLanguage | BCP-47 language of title/description and audio. |
youtubeAllowedCountries / youtubeBlockedCountries | Comma-separated country codes. |
youtubeHasPaidProductPlacement | Paid product placement flag. |
youtubeRecordingDate | Recording date (ISO 8601). |
youtubeSubtitles | Array of { language, name?, url? } subtitle tracks (SRT, VTT, SBV, SUB, ASS, SSA, TTML). |
TikTok
| Key | Description |
|---|---|
tiktokPrivacyLevel | PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. |
tiktokDisableDuet / tiktokDisableComment / tiktokDisableStitch | Interaction toggles. |
tiktokCoverTimestamp | Cover frame timestamp in ms (video). |
tiktokPhotoCoverIndex | Cover photo index, 0-based (photos). |
tiktokAutoAddMusic | Auto add music (photos). |
tiktokIsAigc | AI-generated content flag. |
tiktokPostMode | DIRECT_POST or MEDIA_UPLOAD. MEDIA_UPLOAD is draft mode; same idea as tiktokUploadToDraft: true. |
tiktokUploadToDraft | Alias of tiktokPostMode: "MEDIA_UPLOAD". |
brandContentToggle / brandOrganicToggle | Branded content disclosures. |
| Key | Description |
|---|---|
instagramMediaType | Video: REELS or STORIES. Photos: IMAGE or STORIES. |
instagramCoverUrl | Custom cover image URL (Reels). |
instagramThumbOffset | Frame offset for the auto thumbnail. |
instagramShareToFeed | Also show the Reel in the feed. |
instagramCollaborators | Comma-separated collaborator usernames. |
instagramUserTags | Comma-separated user tags. |
instagramLocationId | Location ID. |
instagramAudioName | Audio track name. |
instagramAltText | Alt text per image (max 1000 chars). |
| Key | Description |
|---|---|
facebookPageId | Page ID to publish to (see get_facebook_pages). |
facebookMediaType | REELS, STORIES or VIDEO. |
facebookVideoState | PUBLISHED or DRAFT. |
thumbnailUrl | Thumbnail URL for normal page videos (facebookMediaType: "VIDEO"). |
facebookLinkUrl | Link preview URL (text posts). |
facebookAltText | Alt text per photo. |
| Key | Description |
|---|---|
linkedinPageId / targetLinkedinPageId | Organization page ID (see get_linkedin_pages). |
linkedinVisibility | PUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER. |
linkedinLinkUrl | Link preview URL (text posts). |
linkedinAltText | Alt text per image (defaults to title). |
| Key | Description |
|---|---|
pinterestBoardId | Board ID (see get_pinterest_boards). |
pinterestLink | Destination link for the pin. |
pinterestCoverImageUrl | Cover image URL (video pins). |
pinterestCoverImageKeyFrameTime | Key frame time in ms for the cover. |
pinterestAltText | Alt text (photo pins). |
X (Twitter)
| Key | Description |
|---|---|
xReplySettings | Who can reply: everyone, following, mentionedUsers, subscribers, verified. |
xTaggedUserIds | User IDs to tag in media. |
xLongTextAsPost | Post long text as a single post instead of a thread. |
xThreadImageLayout | Images per thread post, e.g. "4,4". |
xQuoteTweetId | Tweet ID to quote (text posts). |
xPollOptions / xPollDuration | Poll with 2–4 options and duration in minutes (text posts). |
xCommunityId | Community ID. |
xAltText | Alt text per image (max 1000 chars). |
xPaidPartnership | Paid partnership flag. |
Threads / Reddit / Bluesky / Google Business
| Key | Description |
|---|---|
threadsLongTextAsPost | Single post instead of a thread. |
threadsThreadMediaLayout | Media items per Threads post, e.g. "5,5". |
redditSubreddit | Unused while Reddit posting is unavailable (503 reddit_unavailable). |
redditFlairId | Unused while Reddit posting is unavailable. |
blueskyLinkUrl | External embed link preview. |
blueskyAltText | Alt text (string, JSON array, or ||-separated). |
threadsAltText | Alt text per image (max 1000 chars). |
threadsReplyControl | Who can reply. |
googleBusinessLocationId | Location 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.
- The client calls
create_media_uploadwithfilename,content_type,content_length,media_type, andsource. - The backend returns an
upload_idand a presigned R2upload_url. - The browser uploads the file directly to R2 with
PUT. - The client calls
complete_media_uploadto validate the object and receive a temporary signedmedia_url. - The agent passes that
media_urltoupload_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.
| Plan | Staged files / month | Max file size | Monthly staging volume | Uploads in progress at once |
|---|---|---|---|---|
| Free / default | 20 | 250 MB | 2 GB | 3 |
| Basic / Premium | 1,000 | 500 MB | 25 GB | 10 |
| Professional / Pro | 5,000 | 1 GB | 100 GB | 50 |
| Advanced | 20,000 | 2 GB | 500 GB | 200 |
| Business | 100,000 | 5 GB | 2 TB | 1,000 |
What each limit counts:
- Uploads in progress at once: uploads created with
create_media_uploadthat have not been completed withcomplete_media_uploadyet. 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 signedPUTURL expires, 15 minutes after it was created, or as soon as you calldelete_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_code | Meaning | Extra fields |
|---|---|---|
STAGING_UPLOADS_IN_PROGRESS | Too 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_LIMIT | Monthly staged file count reached. | usage, limits, resets_at |
STAGING_MONTHLY_BYTES_LIMIT | Monthly 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/authorizecan complete.- Browser
CORSerror duringPUT: check the R2 CORS policy and make sure the MCPUPLOAD_POST_R2_CONNECT_DOMAINmatches the host returned inupload_url. Video file not foundfrom an agent: use media staging instead of sending local filesystem paths toupload_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
marketingwith 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
marketingover 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.