Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to the MCP server Hosted mode (`TRANSLOADIT_MCP_RESOURCE_METADATA_URL`) answers unauthenticated MCP requests with `401` and `WWW-Authenticate: Bearer resource_metadata="…"`, keeps the bare-GET health probe, and limits browser Origins to ChatGPT, Claude, Transloadit and loopback unless `allowedOrigins` is set. Self-hosted `TRANSLOADIT_MCP_TOKEN` behavior is unchanged. Every tool now carries a title, annotations and per-tool `securitySchemes` (top-level via a `tools/list` override, mirrored in `_meta`). Auth failures return `isError` results with `_meta["mcp/www_authenticate"]`. New `transloadit_get_profile` tool for multi-account hosts, `attachments` on `transloadit_create_assembly` for `_meta["openai/fileParams"]`, and the MCP Apps widget `ui://transloadit/assembly-result` linked from the Assembly tools. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
README leads with the hosted endpoint and OAuth clients, moves minted bearer tokens to the CI section and drops the roadmap TODO. `plugin.json`, `mcp.json` and `.codex-plugin/plugin.json` describe the ChatGPT and Codex plugin and reference the skills catalog by URL. The task note checklist and devdock paths are updated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
API2 only accepts relayed `aud=mcp` bearer tokens on ordinary endpoints when the request comes from the Transloadit-hosted MCP service. `TRANSLOADIT_MCP_UPSTREAM_SECRET` (option and JSON config key `upstreamSecret`) is sent as `Transloadit-Mcp-Upstream` next to a forwarded bearer token, never in key/secret mode, and is redacted from logs. `@transloadit/node` gains an `extraHeaders` client option to carry the fixed header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The hosted
https://api2.transloadit.com/mcpendpoint must point clients at API2's authorization server (401+WWW-Authenticate: Bearer resource_metadata=…) and meet the ChatGPT plugin and Anthropic connector directory requirements (per-toolsecuritySchemes,title,readOnlyHint/destructiveHint, Origin validation). Phase 1 of the plan addsopenai/fileParamsontransloadit_create_assemblyand an MCP Apps result widget.Plan: ChatGPT plugin + agent OAuth plan (Content). Task note for this branch:
docs/prompts/2026-09-30-mcp-oauth-discovery.md.What landed
Transport (hosted mode,
TRANSLOADIT_MCP_RESOURCE_METADATA_URL)401,WWW-Authenticate: Bearer resource_metadata="<url>", JSON body{ error: "unauthorized", error_description }. BareGETwithoutAccept: text/event-streamstill returns the friendly200. Self-hostedTRANSLOADIT_MCP_TOKENbehavior unchanged; with neither variable set nothing changes.allowedOrigins:https://chatgpt.com,https://chat.openai.com,https://claude.ai,https://claude.com,https://transloadit.com,https://*.transloadit.com,https://transloadit.dev:*,https://*.transloadit.dev:*, loopback (localhost,127.0.0.1,[::1], any port). Other browser Origins get403; requests withoutOriginpass.allowedOriginsentries now accept*.and:*wildcards.WWW-Authenticateis exposed through CORS; the server card advertisesschemes: ["oauth2", "bearer"]plusresourceMetadataUrl.TRANSLOADIT_MCP_RESOURCE_METADATA_URL,TRANSLOADIT_MCP_CONSOLE_URL; hosted mode may bind to non-localhost withoutTRANSLOADIT_MCP_TOKEN.Tools
title+readOnlyHint/destructiveHint(alwaysfalse)/idempotentHint/openWorldHint(trueonly fortransloadit_create_assembly) on every tool.securitySchemes(via atools/listoverride, sinceregisterTool()cannot emit it) mirrored in_meta.securitySchemes:noauthfor list_robots/get_robot_help/lint;oauth2assemblies:write(create),assemblies:read(status, wait),templates:read(list_templates),oauth2with no scopes for the profile tool.isErrorresults with_meta["mcp/www_authenticate"]: ["Bearer resource_metadata=\"…\", error=\"…\", error_description=\"…\""](array of header strings, per OpenAI's documented shape) plus readable text.insufficient_scopewhen no credentials,invalid_tokenwhen API2 answers 401.transloadit_get_profile(_meta["openai/profile"]: true) returning{ id, name?, nickname? }, derived from the Workspace's latest Assembly or an owned Template.transloadit_create_assemblygainsattachments(_meta["openai/fileParams"]: ["attachments"]), exactly the OpenAI file object; each entry maps onto the existing URL-input path.files(url/base64) unchanged.ui://transloadit/assembly-result(text/html;profile=mcp-app, inline HTML/JS, CSPconnectDomains/resourceDomains=https://*.transloadit.com,https://*.transloadit.net, plusopenai/widgetCSP/openai/widgetDescriptionaliases), linked from create/wait via_meta.ui.resourceUriand_meta["openai/outputTemplate"]. Results carry_meta["transloadit/widget"]withauthenticated,assembly_console_urlandnew_template_urlfor the Open in Console / Save as Template buttons.openai/toolInvocation/invoking/invokedstrings (≤ 64 chars) on the Assembly and Template tools.Upstream identification (follow-up from the devdock QA run)
aud=mcpbearers on ordinary endpoints (TOKEN_INVALID_AUDIENCE) unless the request comes from the hosted MCP service. New env varTRANSLOADIT_MCP_UPSTREAM_SECRET(option / JSON config keyupstreamSecret) is sent as the headerTransloadit-Mcp-Upstream: <secret>on every API2 call that relays a forwarded bearer token; never in self-hosted key/secret mode, never logged (redactor scrubs the header and the listed value), never in errors or the server card.@transloadit/nodegains a narrowextraHeadersclient option (fixed headers merged into every_remoteJsonrequest) so the MCP server can carry that header; changeset patches@transloadit/nodeandtransloadit.test/unit/upstream-secret.test.ts(nock asserts the header is on the wire with a forwarded bearer, absent without a secret and in key/secret mode; secret absent from tool errors and the server card; log redaction) and a node SDK test forextraHeaders.Docs and packaging
packages/mcp-server/plugin.json,mcp.json,.codex-plugin/plugin.json(skills referenced by catalog URL, not bundled). Changeset: minor for@transloadit/mcp-server.Deviations from the cross-repo contract
_meta["mcp/www_authenticate"]is an array ofWWW-Authenticateheader strings rather than an object; that is the shape OpenAI documents for the linking UI. Missing credentials useerror="insufficient_scope"(OpenAI's own example) instead of the non-RFCunauthorized.attachments, notfiles: the OpenAI validator wantsitemsto be exactly the file object, andfilesmust keep acceptingkind: "url" | "base64"inputs for existing clients._meta.ui.domainis not set yet (needs a dedicated, verifiable origin decision before plugin submission).Related PRs
/c/oauth/authorize, QA scenario, plan): https://github.com/transloadit/content/pull/6207Test plan
corepack yarn --cwd packages/mcp-server check(115 unit tests: 401/metadata, origins, security schemes and annotations for every tool, file-param mapping, widget resource and CSP, auth errors, profile tool) andcorepack yarn checkat the root.packages/mcp-server/dist→api2/node_modules/@transloadit/mcp-server/distandpackages/node/dist→api2/node_modules/@transloadit/node/dist(the published@transloadit/node4.14.0 there has noextraHeaders, so without the second copy theTransloadit-Mcp-Upstreamheader is silently dropped).mcp-serverservice restarted withTRANSLOADIT_MCP_RESOURCE_METADATA_URL,TRANSLOADIT_MCP_UPSTREAM_SECRETandTRANSLOADIT_ENDPOINTset; endpoint probed with curl.transloadit_list_templatessucceeded end to end (2 templates returned). The Opus QA scenariomcp-oauthin Content covers the remaining clients.Release coupling for API2
API2 must bump both
@transloadit/node(patch, addsextraHeaders) and@transloadit/mcp-server(minor) together once this branch publishes. Bumping only the MCP server keeps the old SDK in itsnode_modulesand the upstream header is never sent, so relayedaud=mcptokens are rejected again.Status: draft, iterating against local devdock until all three clients connect by URL alone.
🤖 Generated with Claude Code