MCP server

MCP server

Upload, attach, and search files from any MCP client.

Connect #

The hosted server gives an agent upload tools without a local shell or checkout. With it an agent can:

The server works with any MCP client that supports remote servers over HTTP. Add it to yours:

claude mcp add --transport http uploads https://agents.uploads.sh/mcp

The client opens a browser to sign in on first use.

Have the CLI? uploads install registers the server with Claude Code, Codex, and Grok if they are installed.

Using claude.ai or Cowork? Add uploads from the Claude directory instead.

Authentication #

The server accepts two kinds of bearer token.

  • OAuth (recommended). Clients that support MCP authorization find the authorization server on their own. You sign in at uploads.sh and choose one workspace for the grant.
  • Workspace token. Send the up_<workspace>_… token that uploads login creates, or a ups_<workspace>_… service token that a workspace admin creates for CI. The server reads the workspace from the token.

Send the token in the Authorization header:

Authorization: Bearer up_<workspace>_…

Most tools need one scope. The tool reference shows it next to each tool.

Scope Grants
files:read List, search, and read files, metadata, usage
files:write Upload, promote, unstage, set metadata, galleries, feeds. Also delete a gh/ file the server stamped as this token’s. Older uploads need files:delete
files:delete Delete any file, and purge expired ones

Client developers: discovery, client registration, and token details are in auth.md.

Stage, then promote #

An agent usually takes screenshots before the pull request exists. The hosted server has no git checkout, so pass repo and branch to put to stage each file for that branch:

put arguments
{
"contentUrl": "https://cdn.example/settings-after.png",
"repo": "acme/web",
"branch": "feat/settings",
"state": "after"
}

After the agent opens the PR, call promote with the same branch. It copies the staged files into the PR and updates the attachments comment:

promote arguments
{ "repo": "acme/web", "pr": 12, "branch": "feat/settings" }

PR already open? Pass pr and repo to put to attach in one call.

If the GitHub App is installed, it promotes staged files when the PR opens, and promote is safe to call again.

unstage clears that branch without deleting the files. Pass repo and branch, or keys. Set dryRun to preview. A file marked unstaged is skipped by promote.

Hosted vs. local #

The CLI also runs an MCP server on your machine with uploads mcp. It reads local files and fills in repo and branch from git. Most tools are the same on both servers.

Server Only this server has
Hosted promote, list_activity, list_repo_files, repo_link_status
Local attach, staged, screenshot, doctor, report

To register the local server instead, see Skill vs. MCP.

Tools #

Every tool on the hosted server, as tools/list returns it. Tools marked “Hosted only” are not on the local server.

Upload and attach

putUpload file

Can overwrite or deletefiles:write

Upload a file (or files) and get a public URL plus GitHub-ready markdown. Prefer the returned embedUrl in GitHub markdown. All uploads are public. Pass pr+repo to attach to a PR (stable key + managed comment). comment.held counts files on the PR or issue held for review; see list_held. Pass branch+repo to stage for a PR that does not exist yet. When the bytes are already at a public HTTPS URL, pass contentUrl instead of base64 — the server fetches them. Accepts images (PNG, JPEG, GIF, WebP, AVIF), video (MP4, WebM, MOV), PDF, zip, gzip, and text (plain, markdown, CSV, JSON). SVG and XML are accepted only on storage lanes verified to serve them sandboxed (see the workspace's storage settings); HTML is rejected.

Returns workspacekeyurlembedUrlsizecontentTypereplacedmarkdownprovenancemetadatavisibilityholdCheckfileuploadsfailurescommentcommentErrorpromotionpromoteErroroptimizeframedryRunhint

ParameterTypeDescription
contentBase64stringBase64-encoded file content to upload (must be non-empty). Exactly one of contentBase64, contentUrl, or files is required.
contentUrlstringPublic HTTPS URL of an already-hosted file to fetch and re-host. Filename is optional when the URL path has a leaf. No auth headers are forwarded; private/internal hosts are rejected. Exactly one of contentBase64, contentUrl, or files is required.
filenamestringFilename for the content (drives the key and content type). Required with contentBase64; optional with contentUrl when the URL path has a leaf.
filesobject[]Multiple files in one call (max 20). Each item needs contentBase64 or contentUrl. Filename is optional with contentUrl when the URL path has a leaf. Cannot combine with contentBase64, contentUrl, filename, or key.
keystringOverride the object key. Cannot combine with pr/issue/branch.
prefixstringKey prefix for the default key layout (default: screenshots).
repostringGitHub owner/name. Required with pr/issue/branch. Optional otherwise (default: misc).
refstringOptional key segment (default: today). Cannot combine with pr/issue/branch.
prnumberAttach to this pull request (stable gh/ key). Mutually exclusive with issue. Requires repo. With branch, also promotes that branch's staged files.
issuenumberAttach to this issue (stable gh/ key). Mutually exclusive with pr and branch. Requires repo.
branchstringGit branch to stage under (no comment yet). With pr, also promote this branch's staged files into the PR. Requires repo.
commentbooleanWith pr or issue: create or update the managed attachments comment after upload. Default true. Pass false to skip. A comment failure never fails the upload.
altstringAlt text for the markdown (default: filename).
widthnumberEmit <img width=…> markdown instead of a plain image embed.
metadataobjectQueryable tags for later search (key→value). Use path for the route (e.g. /settings), not route/page/screen. Omit to leave existing tags; pass an object (even {}) to replace them. state and app have their own fields. On pr/issue/branch, gh.* is set for you.
state"before" | "after" | "empty" | "error" | "loading"The UI state this image shows. Set it whenever the image is one side of a comparison — before/after is the most useful pair in a PR, and is what makes find_files with state=after work later.
appstringWhich surface is shown: web, ios, android, cli. Worth setting only when the same route exists on more than one surface.
replacebooleanOverwrite an existing object on a non-gh/ key. Default false. No effect on gh/ keys, which always overwrite.

promotePromote staged attachments

Can overwrite or deletefiles:writeHosted only

Copy staged or existing files into a PR's stable gh/…/pull/… prefix, then refresh the managed comment. Pass branch and/or keys. Requires repo and pr.

Returns promotionattachedattachFailurescommentcommentError

ParameterTypeDescription
reporequiredstringGitHub owner/name. Required — this server cannot infer it from git.
prrequirednumberPull request number to promote/attach into. Never applies to issues.
branchstringStaged branch to copy into the PR.
keysstring[]Already-uploaded object keys or uploads.sh URLs to copy into the PR. One bad source does not abort the rest.
movebooleanFor keys only: delete each source after a successful copy. Default false. Ignored for the branch sweep.
commentbooleanRefresh the managed attachments comment after a successful copy. Default true. A comment failure never fails the copy.

commentSync attachments comment

Can overwrite or deletefiles:read

Refresh the managed attachments comment on a GitHub PR or issue without re-uploading. Use after deleting media. Requires repo and exactly one of pr/issue. held counts files there held for review; see list_held.

Returns postedreasonmessagefixUrlrequiredactioncountcommentUrlheld

ParameterTypeDescription
reporequiredstringGitHub owner/name. Required — this server cannot infer it from git.
prnumberPull request number. Mutually exclusive with issue.
issuenumberIssue number. Mutually exclusive with pr.

Files and metadata

listList files

Read-onlyfiles:read

List uploaded objects in the workspace, optionally filtered by key prefix. Paginate with cursor; each item includes its public URL when the workspace has one.

Returns itemscursorprefixes

ParameterTypeDescription
prefixstringKey prefix filter.
limitnumberPage size (default 100, max 1000).
cursorstringPagination cursor from a previous call.

unstageUnstage files

Writesfiles:write

Clear staged files for a branch, or specific keys, without deleting them. Sets gh.status=unstaged so they drop out of auto-promotion. Pass repo and branch, or keys. Needs files:write.

Returns dryRunrepobranchfilestruncated

ParameterTypeDescription
repostringGitHub owner/name. Required — this server cannot infer it from git.
branchstringBranch whose staged files to clear. Requires repo. Do not combine with keys.
keysstring[]Object keys to unstage. Do not combine with branch.
dryRunbooleanReport what would change without writing.

find_filesFind files

Read-onlyfiles:read

Search files by metadata (filters) and/or filename substring (name). At least one is required.

Returns itemscursortruncated

ParameterTypeDescription
filtersobjectEquality filters; all must match. Use path for the route (e.g. /settings), not route/page/screen. Optional when name is set.
namestringCase-insensitive substring match on object keys (1–128 chars). Optional when filters is non-empty.
prefixstringKey prefix filter, combinable with filters/name.
limitnumberPage size (default 50, max 500).
cursorstringOpaque continuation from a previous call's cursor. Pass it back unchanged with the same filters/name to get the next page; a null cursor means there are no more pages.

get_metadataGet metadata

Read-onlyfiles:read

Read the queryable tags on one file. Returns { metadata } (empty when none).

Returns metadata

ParameterTypeDescription
keyrequiredstringObject key to inspect.

set_metadataSet metadata

Can overwrite or deletefiles:write

Set or delete queryable tags on an existing file. set wins over delete for the same key. Tags appear on the public file page — do not store secrets. Requires set and/or delete.

Returns metadata

ParameterTypeDescription
keyrequiredstringObject key to update.
setobjectKeys to set or overwrite. Use path for the route (e.g. /settings), not route/page/screen.
deletestring[]Keys to remove.

list_metadata_keysList metadata keys

Read-onlyfiles:read

List metadata keys in the workspace (with counts). Pass key to list that key's values instead. Use before find_files.

Returns keystruncatedkeyvalues

ParameterTypeDescription
keystringWhen set, return distinct values for this metadata key (with counts) instead of the key list.

deleteDelete file

Can overwrite or deletefiles:delete

Permanently delete one uploaded file by key, along with its metadata. A files:write token can delete a gh/ object the server stamped as its upload. Older files and any other object need files:delete. Deleting a PR or issue attachment refreshes the managed comment.

Returns keydeleteddryRuncommentRefreshedcommentHint

ParameterTypeDescription
keyrequiredstringObject key to delete.

Held files

list_heldList held files

Read-onlyfiles:read

List uploads held for review. The classifier holds a file it thinks shows a secret (an API key, a password, a token) or sensitive personal data (an ID, a card number). A held file stays out of the managed PR comment, feeds, galleries, and its /f/ page, and its raw URL may 404, until a workspace member approves it with approve. Pass pr/issue + repo for one PR or issue, or prefix. Each item has reason (secret: api_key, pii: financial) and area, an approximate box around the flagged content as fractions of the image (null when unknown).

Returns items

ParameterTypeDescription
prnumberHeld files on this pull request.
issuenumberHeld files on this issue.
repostringGitHub owner/name. Required — this server cannot infer it from git.
prefixstringOnly keys under this prefix.

approveApprove held files

Writesfiles:write

Release uploads held for review (see list_held): pass keys, or pr/issue + repo to approve every held file there. The file returns at the same URL and the managed PR comment re-syncs. Check the file first: if it shows a real credential, rotate it and delete the file instead of approving. Same as uploads approve.

Returns files

ParameterTypeDescription
keysstring[]Held keys to approve.
prnumberApprove every held file on this pull request.
issuenumberApprove every held file on this issue.
repostringGitHub owner/name. Required — this server cannot infer it from git.

Repo activity

list_activityList recent activity

Read-onlyfiles:readHosted only

Recent GitHub-tagged activity in this workspace, grouped by pull request (default) or by repo. Pull rows are pull requests that received media tagged gh.kind=pull, newest last_media_at first. The default window is 90 days. Set all to true to lift it. Optional repo and state (open, closed, or merged) narrow those rows. A title is included only when the repo is linked to this workspace. Repo rows are distinct gh.repo values, newest metadata updated_at first. These times are not the object's uploaded-at. Untagged uploads are absent. This does not publish a feed. Pass cursor back unchanged with the same arguments for the next page. Use list_repo_files to open one repo's files, and find_files for tag or filename search in key order.

Returns byrepospullscursor

ParameterTypeDescription
by"pull" | "repo"Group the page by pull request ("pull", the default) or by repository ("repo").
repostringWhen by is pull, keep rows for this owner/repo only. Ignored values are rejected when by is repo.
state"open" | "closed" | "merged"When by is pull, keep rows in this state. Rows with no stored state drop out. Rejected when by is repo.
allbooleanWhen by is pull, include pull requests whose last media is older than 90 days. Default false. Rejected when by is repo.
limitnumberPage size. Pulls default to 20 (max 100). Repos default to 20 (max 50).
cursorstringOpaque cursor from the previous page. Send it back with the same by, repo, state, and all.

list_repo_filesList files in a repo

Read-onlyfiles:readHosted only

Files tagged with gh.repo for one repository, newest metadata updated_at first. That time is not the object's uploaded-at. repo is owner/name. Optional pr matches gh.number. Optional path matches the path tag exactly. type is a filename suffix class: screenshot, video, or other. PDFs are other. Promoted copies (gh.status=promoted) are omitted. This does not publish a feed. Pass cursor back unchanged with the same arguments for the next page.

Returns itemscursor

ParameterTypeDescription
reporequiredstringGitHub repository as owner/name. Matching is case-insensitive.
prnumberWhen set, keep files tagged with this pull-request number (gh.number).
pathstringWhen set, keep files whose path tag equals this string.
type"screenshot" | "video" | "other"Filename suffix class. screenshot is png/jpg/jpeg/webp/gif/avif, video is mp4/webm/mov, and other is everything else (including PDF).
limitnumberPage size (default 50, max 100).
cursorstringOpaque cursor from the previous page. Send it back with the same repo, pr, path, and type.

Galleries

Live links

feed_createCreate change feed

Writesfiles:write

Create a public newest-first screenshot feed (same product as CLI uploads feed create). Pass repo alone for the whole GitHub repo. Pass pr plus repo — or github as owner/repo#123 / a GitHub PR URL — to scope that same feed to one pull request. issue does the same for an issue. Optional path filters by page-path metadata. Creating the same scope again returns the existing URL. This is a live query, not a curated gallery. Anyone who knows the URL can view it. See https://uploads.sh/docs/feeds.

Returns idurlworkspacerepopathnumberkindtitlecreatedAtupdatedAtitems

ParameterTypeDescription
repostringGitHub owner/repo, for example acme/app. Required unless github is set.
printegerScope the feed to this pull request. Same product as a repo feed — one extra filter. Mutually exclusive with issue and github.
issueintegerScope the feed to this issue. Mutually exclusive with pr and github.
githubstringScope via owner/repo#number or a GitHub issue/PR URL. Supplies repo when omitted. Mutually exclusive with pr and issue.
pathstringOptional exact page-path metadata filter, for example /settings.

feed_getGet change feed

Read-onlyfiles:read

Get a workspace-owned change feed by ID, including its current newest-first screenshots and the canonical public /c/<id> URL. Each item includes pageUrl (/c/<id>/<item>) for the pager. The feed may be repo-wide or scoped to one pull request / issue (see number and kind on the result). Anyone with the URL can view the media.

Returns idurlworkspacerepopathnumberkindtitlecreatedAtupdatedAtitems

ParameterTypeDescription
feedIdrequiredstringOpaque feed ID.

Workspace

whoamiWho am I

Read-onlyAny token

Show the current uploads.sh identity: the workspace this token acts in and its scopes. A successful result also means the server is up.

Returns okworkspacescopesuserIdserviceTokensignedInapiUrl

usageShow usage

Read-onlyfiles:read

Workspace storage and monthly upload counters (and remaining headroom when budgets are configured).

Returns workspacebytesobjectssharedBytessharedObjectsuploadsInPeriodperiodStartupdatedAtstorageBudgetBasismaxStorageBytesstorageRemainingBytesmaxUploadsPerPerioduploadsRemainingscopesplanstorage

reconcileReconcile usage

Writesfiles:write

Rebuild usage ledger bytes/objects from storage (source of truth). Preserves the monthly upload counter. Requires files:write.

Returns workspacebytesobjectspreviouschangedusageunprefixedBucket

purge_expiredPurge expired files

Can overwrite or deletefiles:delete

Delete objects older than the workspace retentionDays setting, then reconcile. Skips if retention is unset. Requires files:delete.

Returns skippedreasonworkspaceretentionDayscutoffdeletedfreedByteskeyskeysTruncatedreconcile

Docs and changelog

search_docsSearch uploads.sh docs

Read-onlyAny token

Search the public uploads.sh documentation. Use this to answer questions about uploads.sh product workflows, including: attaching screenshots and video to GitHub PRs and issues; staging files before a PR exists; galleries; the GitHub App; comment config (.uploads.yml); hosted MCP and agent setup; screenshot capture and annotate; plans and limits; bring-your-own bucket. Same as uploads docs. Returns titles, URLs, and snippets. Pass page to fetch the full markdown of one page (slug, path, or URL). Omit query to list the catalog.

Returns urlqueryresultstotal

ParameterTypeDescription
querystringSearch query (e.g. 'stage before a PR'). A single slug like 'attach' fetches that page.
pagestringFetch one page as markdown. Accepts a slug (attach), path (/docs/agents), or https://uploads.sh URL.
limitnumberHow many search hits to return (default 5, max 50). Ignored when fetching one page.

changelogProduct changelog

Read-onlyAny token

Read recent uploads.sh product updates (platform and CLI). Returns the latest entries with titles, dates, summaries, and a link to the full changelog at https://uploads.sh/changelog. Same as uploads changelog.

Returns urlfeedentries

ParameterTypeDescription
limitnumberHow many entries to return (default 5, max 50).

Protocol details #

These matter only if you write your own client.

  • Transport. Stateless Streamable HTTP. Each POST carries one JSON-RPC message and gets a plain JSON reply. There are no sessions and no SSE stream, so GET and DELETE return 405.
  • Headers. Every POST must send Content-Type: application/json and Accept: application/json, text/event-stream. A missing content type returns 415, and a missing Accept returns 406.
  • Spec versions. The server speaks the 2026-07-28 revision and the earlier initialize-based flow on the same endpoint.
  • Limits. The REST API’s upload limits apply, and writes are rate limited per workspace. See Plans & limits.
  • Discovery. The server card is at /.well-known/mcp/server-card.json.