From cfa8dc980efdab6e339f5dbb3819268b52778a1a Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sun, 4 Oct 2026 14:07:02 +0200 Subject: [PATCH 1/2] Point the MCP Registry entry at @transloadit/mcp-server 0.5.0 The registry still lists 0.3.9 (and server.json said 0.3.7/0.3.6), so its npm package entry missed the OAuth-by-URL hosted mode, file params and the result widget released in 0.5.0. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/mcp-server/server.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp-server/server.json b/packages/mcp-server/server.json index c5889114..bed542d2 100644 --- a/packages/mcp-server/server.json +++ b/packages/mcp-server/server.json @@ -3,7 +3,7 @@ "name": "io.github.transloadit/mcp-server", "title": "Transloadit Media Processing", "description": "Process video, audio, images, and documents with 86+ cloud media processing robots.", - "version": "0.3.7", + "version": "0.5.0", "websiteUrl": "https://transloadit.com/docs/sdks/mcp-server/", "repository": { "url": "https://github.com/transloadit/node-sdk", @@ -14,7 +14,7 @@ { "registryType": "npm", "identifier": "@transloadit/mcp-server", - "version": "0.3.6", + "version": "0.5.0", "runtimeHint": "npx", "packageArguments": [ { From 7038b222235de86ab87dd85e843f9d706f25957b Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sun, 4 Oct 2026 14:39:05 +0200 Subject: [PATCH 2/2] Get the MCP server ready for the ChatGPT and Claude directories Fix what OpenAI's plugin review would reject in plugin.json and .codex-plugin/plugin.json: the terms URL (now /legal/terms-of-service/), the missing supportURL, a subtitle over 30 characters, a non-square logo and pricing in the listing text. plugin.json also carries the review test cases and release notes. server.json drops the optional Authorization header from the hosted remote, since OAuth is discovered from the 401 challenge, and gains a square icon that transloadit.com already serves. The result widget now sends openai/widgetDomain (TRANSLOADIT_MCP_WIDGET_DOMAIN, default https://transloadit.com) and lists the result and Console origins as redirect_domains. ui.domain stays unset because Claude only accepts its own value. Result domains accept exact origins, are normalized, and a malformed value fails at startup. Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/mcp-directory-readiness.md | 22 +++++ packages/mcp-server/.codex-plugin/plugin.json | 21 +++-- packages/mcp-server/README.md | 14 ++- packages/mcp-server/assets/icon.png | Bin 17066 -> 17019 bytes packages/mcp-server/assets/logo.png | Bin 20013 -> 36783 bytes packages/mcp-server/plugin.json | 82 ++++++++++++++++-- packages/mcp-server/server.json | 19 ++-- packages/mcp-server/src/cli.ts | 6 ++ packages/mcp-server/src/http-helpers.ts | 2 + packages/mcp-server/src/options.ts | 11 +++ packages/mcp-server/src/server.ts | 18 +++- .../src/ui/assembly-result-widget.ts | 65 ++++++++++++-- .../mcp-server/test/unit/cli-config.test.ts | 6 +- .../test/unit/plugin-manifest.test.ts | 49 ++++++++--- .../mcp-server/test/unit/tool-surface.test.ts | 33 +++++-- .../test/unit/widget-handshake.test.ts | 37 +++++++- 16 files changed, 326 insertions(+), 59 deletions(-) create mode 100644 .changeset/mcp-directory-readiness.md diff --git a/.changeset/mcp-directory-readiness.md b/.changeset/mcp-directory-readiness.md new file mode 100644 index 00000000..94ca0ab6 --- /dev/null +++ b/.changeset/mcp-directory-readiness.md @@ -0,0 +1,22 @@ +--- +"@transloadit/mcp-server": patch +--- + +Prepare the plugin manifests, the MCP Registry entry and the result widget for the ChatGPT and +Claude directories. + +- `plugin.json` and `.codex-plugin/plugin.json` link the terms of service page that exists + (`/legal/terms-of-service/`), add a support URL, use a subtitle of at most 30 characters, ship + square icons, list capabilities and three starter prompts, and no longer mention pricing. + `plugin.json` also carries the review test cases and release notes for OpenAI's submission. +- `server.json` no longer declares an `Authorization` header on the hosted remote, so registry + listings stop asking for a token; OAuth clients find sign-in through the `401` challenge. It adds + a square icon and says the server connects with OAuth. +- The result widget declares `_meta["openai/widgetDomain"]`, which ChatGPT requires for a public + listing. It defaults to `https://transloadit.com` and is set with `TRANSLOADIT_MCP_WIDGET_DOMAIN` + or `widgetDomain`. `ui.domain` stays unset, so Claude keeps rendering the widget. +- The widget lists its result origins and the Console origin as ChatGPT `redirect_domains`, so + download and Console links open from ChatGPT, and opens them without an appended `redirectUrl`. +- `TRANSLOADIT_MCP_RESULT_DOMAINS` and `resultDomains` accept exact origins such as + `https://tmp-us-east-1.transloadit.net` and drop a trailing slash or path. A value that is not + an http(s) origin now stops the server at startup instead of ending up in the widget's CSP. diff --git a/packages/mcp-server/.codex-plugin/plugin.json b/packages/mcp-server/.codex-plugin/plugin.json index 0fa40d7d..f380a976 100644 --- a/packages/mcp-server/.codex-plugin/plugin.json +++ b/packages/mcp-server/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "transloadit", "version": "0.5.0", - "description": "Process video, audio, images and documents with Transloadit: encode, resize, transcribe, convert and deliver files through 86+ Robots.", + "description": "Process video, audio, images and documents with Transloadit: encode, resize, transcribe, convert and deliver files from the chat.", "author": { "name": "Transloadit", "email": "support@transloadit.com", @@ -28,19 +28,28 @@ }, "interface": { "displayName": "Transloadit", - "shortDescription": "Encode, resize, transcribe and convert files in the chat.", - "longDescription": "Drop a file in the chat and let Transloadit process it: HLS and MP4 encoding, image resizing and optimization, background removal, transcription and subtitles, document conversion and thumbnails. Results come back as previews with download links, and any run can be saved as a reusable Template. Connecting signs you in to your Transloadit Workspace through OAuth (a free Community plan is available); every tool, including Robot browsing and linting, runs on that connection. Agent Skills for these workflows are published at https://transloadit.com/.well-known/skills/index.json (source: https://github.com/transloadit/skills).", + "shortDescription": "Encode, resize and transcribe", + "longDescription": "Transloadit processes the files you work with in ChatGPT. Attach a video, image, audio file or document, or paste a public URL, and say what you need: HLS or MP4 encoding, resizing and format conversion, background removal, transcription and subtitles, document conversion, or thumbnails. Results appear as a card with previews and download links. From that card you can open the run in the Transloadit Console or save its steps as a reusable Template for your own app.\n\nIt is built for developers who use Transloadit in their apps and want to try or debug a processing pipeline in the chat, and for anyone who needs a one-off conversion without installing tools.\n\nConnecting opens Transloadit’s consent screen. You sign in or create an account, pick a Workspace and approve three permissions: create Assemblies, read Assemblies and read Templates. Transloadit creates a dedicated key for this connection, and you can revoke it at any time under Connected apps in the Transloadit Console. ChatGPT never sees your Auth Secret.\n\nLimits: result files are deleted after 24 hours unless your instructions export them to your own storage. Inline file contents are capped at 512 KB, so larger files go through an attachment or a URL. The limits of your Transloadit plan apply, such as the maximum file size.", "developerName": "Transloadit", "category": "Productivity", - "capabilities": ["Read", "Write"], + "capabilities": [ + "Encode video to HLS and MP4", + "Resize, convert and optimize images", + "Transcribe audio and video to text or subtitles", + "Convert documents and create thumbnails", + "Save a run as a reusable Template" + ], "websiteURL": "https://transloadit.com", + "supportURL": "https://transloadit.com/support/", "privacyPolicyURL": "https://transloadit.com/legal/privacy/", - "termsOfServiceURL": "https://transloadit.com/legal/terms/", + "termsOfServiceURL": "https://transloadit.com/legal/terms-of-service/", "defaultPrompt": [ "Turn this video into HLS with 720p and 1080p renditions", - "Transcribe this recording and give me an SRT subtitle file" + "Transcribe this recording and give me an SRT subtitle file", + "Resize this photo to 800px wide and convert it to WebP" ], "brandColor": "#1B61A7", + "brandColorDark": "#7DB8F2", "composerIcon": "./assets/icon.png", "logo": "./assets/logo.png", "screenshots": [] diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 8fe9cbec..a865ef07 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -324,7 +324,10 @@ Allowlist tools in `~/.gemini/settings.json`: - `TRANSLOADIT_MCP_UPSTREAM_SECRET` (hosted mode only, set by Transloadit's deployment; sent to API2 as `Transloadit-Mcp-Upstream` next to forwarded bearer tokens) - `TRANSLOADIT_MCP_RESULT_DOMAINS` (optional, comma-separated origins the result widget may load - previews from; default `https://*.transloadit.com,https://*.transloadit.net,https://*.r2.dev`) + previews from and link to, either exact such as `https://tmp-us-east-1.transloadit.net` or with a + wildcard host; default `https://*.transloadit.com,https://*.transloadit.net,https://*.r2.dev`) +- `TRANSLOADIT_MCP_WIDGET_DOMAIN` (optional, default `https://transloadit.com`; the origin ChatGPT + serves the result widget from, sent as `_meta["openai/widgetDomain"]`) - `TRANSLOADIT_MCP_CONSOLE_URL` (optional, default `https://transloadit.com`; Console origin for widget deep links) - `TRANSLOADIT_ENDPOINT` (optional, default `https://api2.transloadit.com`) @@ -339,7 +342,8 @@ Allowlist tools in `~/.gemini/settings.json`: - `npx -y @transloadit/mcp-server http --config path/to/config.json` The JSON config accepts the same keys as `createTransloaditMcpHttpHandler()`, including -`allowedOrigins`, `resourceMetadataUrl`, `signatureAlgorithm`, `resultDomains` and `consoleUrl`. +`allowedOrigins`, `resourceMetadataUrl`, `signatureAlgorithm`, `resultDomains`, `widgetDomain` and +`consoleUrl`. ## Tool surface @@ -381,6 +385,12 @@ download links, an "Open in Console" link and a "Save as Template" shortcut. It `2026-01-26` protocol (`ui/initialize` with `appInfo`) and also reads ChatGPT's `window.openai`. Its CSP allows `https://*.transloadit.com`, `https://*.transloadit.net` and `https://*.r2.dev` (result buckets); override the list with `TRANSLOADIT_MCP_RESULT_DOMAINS` or `resultDomains`. +ChatGPT asks for the exact hosts a widget uses, so a deployment that knows its result buckets +should list them as origins instead of wildcards. ChatGPT also needs `redirect_domains` to open +links from the widget: the server sends the result origins plus the Console origin there. ChatGPT +serves the widget from the origin in `_meta["openai/widgetDomain"]` +(`TRANSLOADIT_MCP_WIDGET_DOMAIN`). `ui.domain` stays unset, because Claude derives that value from +the server URL and rejects any other. ## Input files diff --git a/packages/mcp-server/assets/icon.png b/packages/mcp-server/assets/icon.png index d7f2ae80008f982bf81bf01638bedd85ca434fe9..420eec7e85cf285527fbf8abd32d6e769dfc8eae 100644 GIT binary patch literal 17019 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 z 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 diff --git a/packages/mcp-server/plugin.json b/packages/mcp-server/plugin.json index e6f00193..19a178a2 100644 --- a/packages/mcp-server/plugin.json +++ b/packages/mcp-server/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "transloadit", "version": "0.5.0", - "description": "Process video, audio, images and documents with Transloadit: encode, resize, transcribe, convert and deliver files through 86+ Robots.", + "description": "Process video, audio, images and documents with Transloadit: encode, resize, transcribe, convert and deliver files from the chat.", "author": { "name": "Transloadit", "email": "support@transloadit.com", @@ -25,22 +25,88 @@ "com.openai": { "interface": { "displayName": "Transloadit", - "shortDescription": "Encode, resize, transcribe and convert files in the chat.", - "longDescription": "Drop a file in the chat and let Transloadit process it: HLS and MP4 encoding, image resizing and optimization, background removal, transcription and subtitles, document conversion and thumbnails. Results come back as previews with download links, and any run can be saved as a reusable Template. Connecting signs you in to your Transloadit Workspace through OAuth (a free Community plan is available); every tool, including Robot browsing and linting, runs on that connection. Agent Skills for these workflows are published at https://transloadit.com/.well-known/skills/index.json (source: https://github.com/transloadit/skills).", + "shortDescription": "Encode, resize and transcribe", + "longDescription": "Transloadit processes the files you work with in ChatGPT. Attach a video, image, audio file or document, or paste a public URL, and say what you need: HLS or MP4 encoding, resizing and format conversion, background removal, transcription and subtitles, document conversion, or thumbnails. Results appear as a card with previews and download links. From that card you can open the run in the Transloadit Console or save its steps as a reusable Template for your own app.\n\nIt is built for developers who use Transloadit in their apps and want to try or debug a processing pipeline in the chat, and for anyone who needs a one-off conversion without installing tools.\n\nConnecting opens Transloadit’s consent screen. You sign in or create an account, pick a Workspace and approve three permissions: create Assemblies, read Assemblies and read Templates. Transloadit creates a dedicated key for this connection, and you can revoke it at any time under Connected apps in the Transloadit Console. ChatGPT never sees your Auth Secret.\n\nLimits: result files are deleted after 24 hours unless your instructions export them to your own storage. Inline file contents are capped at 512 KB, so larger files go through an attachment or a URL. The limits of your Transloadit plan apply, such as the maximum file size.", "developerName": "Transloadit", "category": "Productivity", - "capabilities": ["Read", "Write"], + "capabilities": [ + "Encode video to HLS and MP4", + "Resize, convert and optimize images", + "Transcribe audio and video to text or subtitles", + "Convert documents and create thumbnails", + "Save a run as a reusable Template" + ], "websiteURL": "https://transloadit.com", + "supportURL": "https://transloadit.com/support/", "privacyPolicyURL": "https://transloadit.com/legal/privacy/", - "termsOfServiceURL": "https://transloadit.com/legal/terms/", + "termsOfServiceURL": "https://transloadit.com/legal/terms-of-service/", "defaultPrompt": [ "Turn this video into HLS with 720p and 1080p renditions", - "Transcribe this recording and give me an SRT subtitle file" + "Transcribe this recording and give me an SRT subtitle file", + "Resize this photo to 800px wide and convert it to WebP" ], "brandColor": "#1B61A7", + "brandColorDark": "#7DB8F2", "composerIcon": "./assets/icon.png", - "logo": "./assets/logo.png", - "screenshots": [] + "logo": "./assets/logo.png" + }, + "review": { + "test_cases": { + "positive": [ + { + "description": "List the Templates in the connected Workspace.", + "prompt": "List my Transloadit Templates.", + "tools_triggered": "transloadit_list_templates", + "expected_behavior": "Lists the three Templates seeded in the reviewer Workspace (hls-video, image-thumbnails, transcribe-srt) with their IDs. No Assembly is created." + }, + { + "description": "Resize and convert an attached photo.", + "prompt": "Resize this photo to 800px wide and convert it to WebP.", + "tools_triggered": "transloadit_create_assembly, transloadit_wait_for_assembly", + "expected_behavior": "Creates an Assembly from the attachment, waits until it reports ASSEMBLY_COMPLETED, and shows the result card with the original and an 800px-wide WebP plus download links.", + "file_attachment_urls": ["https://demos.transloadit.com/inputs/desert.jpg"] + }, + { + "description": "Encode a video from a public URL to HLS.", + "prompt": "Turn https://demos.transloadit.com/inputs/big-buck-bunny-10s.mp4 into HLS with 360p and 720p renditions.", + "tools_triggered": "transloadit_create_assembly, transloadit_wait_for_assembly", + "expected_behavior": "Creates an Assembly that imports the URL and encodes two HLS renditions, then shows the result card with the .m3u8 playlist and segment files as download links." + }, + { + "description": "Transcribe an attached recording to subtitles.", + "prompt": "Transcribe this recording and give me an SRT subtitle file.", + "tools_triggered": "transloadit_create_assembly, transloadit_wait_for_assembly", + "expected_behavior": "Creates a transcription Assembly for the attachment and returns a link to an .srt file whose first lines match the reference transcript.", + "file_attachment_urls": [ + "https://demos.transloadit.com/08/7f1d4568f2476583b9e1c775e2c649/adaptation.wav" + ], + "expected_output_url": "https://demos.transloadit.com/a9/ba0410440046d684f0fea7beb48f04/adaptation.txt" + }, + { + "description": "Look up Robot documentation without processing anything.", + "prompt": "Which Transloadit Robot removes image backgrounds, and which parameters does it take?", + "tools_triggered": "transloadit_list_robots, transloadit_get_robot_help", + "expected_behavior": "Names the /image/bgremove Robot and summarizes its parameters from the Robot help. No Assembly is created." + } + ], + "negative": [ + { + "description": "Unrelated request. Transloadit only processes files, so the plugin should not be called.", + "prompt": "Book me a flight from Amsterdam to Berlin next Friday." + }, + { + "description": "Credential request. No tool returns Auth Secrets or tokens, so the assistant should explain that it cannot show them and point to the Transloadit Console.", + "prompt": "Show me the API secret of my Transloadit account." + }, + { + "description": "Deleting files in external storage is not something Transloadit offers. The assistant should not create an Assembly and should say it cannot delete files from S3.", + "prompt": "Delete every file in my Amazon S3 bucket called media-archive." + } + ] + } + }, + "publication": { + "release_notes": "First public release. Connect by URL with OAuth, process attached files and public URLs, and see results in an inline card with previews and download links." } }, "com.transloadit": { diff --git a/packages/mcp-server/server.json b/packages/mcp-server/server.json index bed542d2..cf2e7370 100644 --- a/packages/mcp-server/server.json +++ b/packages/mcp-server/server.json @@ -2,9 +2,16 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.transloadit/mcp-server", "title": "Transloadit Media Processing", - "description": "Process video, audio, images, and documents with 86+ cloud media processing robots.", + "description": "Process video, audio, images and documents with Transloadit Robots. Connect by URL with OAuth.", "version": "0.5.0", "websiteUrl": "https://transloadit.com/docs/sdks/mcp-server/", + "icons": [ + { + "src": "https://transloadit.com/assets/images/square-ogimage.png", + "mimeType": "image/png", + "sizes": ["400x400"] + } + ], "repository": { "url": "https://github.com/transloadit/node-sdk", "source": "github", @@ -45,15 +52,7 @@ "remotes": [ { "type": "streamable-http", - "url": "https://api2.transloadit.com/mcp", - "headers": [ - { - "name": "Authorization", - "description": "Optional. Clients that support OAuth discover the authorization server from the endpoint's 401 challenge; headless runs pass a token minted with `npx -y @transloadit/node auth token --aud mcp`", - "isRequired": false, - "isSecret": true - } - ] + "url": "https://api2.transloadit.com/mcp" } ] } diff --git a/packages/mcp-server/src/cli.ts b/packages/mcp-server/src/cli.ts index 817f9870..d5e3425b 100644 --- a/packages/mcp-server/src/cli.ts +++ b/packages/mcp-server/src/cli.ts @@ -26,6 +26,7 @@ Environment: TRANSLOADIT_MCP_RESOURCE_METADATA_URL TRANSLOADIT_MCP_UPSTREAM_SECRET TRANSLOADIT_MCP_RESULT_DOMAINS (comma-separated origins for result previews) + TRANSLOADIT_MCP_WIDGET_DOMAIN (origin ChatGPT serves the result widget from) TRANSLOADIT_MCP_CONSOLE_URL TRANSLOADIT_ENDPOINT TRANSLOADIT_MCP_METRICS_PATH @@ -169,6 +170,9 @@ const main = async (): Promise => { const resultDomains = parseResultDomains( fileConfig.resultDomains ?? process.env.TRANSLOADIT_MCP_RESULT_DOMAINS, ) + const widgetDomain = (fileConfig.widgetDomain ?? process.env.TRANSLOADIT_MCP_WIDGET_DOMAIN) as + | string + | undefined const clientSuffix = process.env.TRANSLOADIT_CLIENT_SUFFIX as string | undefined // Hosted mode delegates token checks to API2, so it may bind publicly without a static token. @@ -192,6 +196,7 @@ const main = async (): Promise => { urlDownloadTimeoutMs: fileConfig.urlDownloadTimeoutMs as number | undefined, signatureAlgorithm, resultDomains, + widgetDomain, consoleUrl, allowedOrigins: fileConfig.allowedOrigins as string[] | undefined, allowedHosts: fileConfig.allowedHosts as string[] | undefined, @@ -228,6 +233,7 @@ const main = async (): Promise => { authSecret: process.env.TRANSLOADIT_SECRET, signatureAlgorithm: parseSignatureAlgorithm(process.env.TRANSLOADIT_SIGNATURE_ALGORITHM), resultDomains: parseResultDomains(process.env.TRANSLOADIT_MCP_RESULT_DOMAINS), + widgetDomain: process.env.TRANSLOADIT_MCP_WIDGET_DOMAIN, endpoint: process.env.TRANSLOADIT_ENDPOINT, consoleUrl: process.env.TRANSLOADIT_MCP_CONSOLE_URL, clientSuffix: process.env.TRANSLOADIT_CLIENT_SUFFIX, diff --git a/packages/mcp-server/src/http-helpers.ts b/packages/mcp-server/src/http-helpers.ts index 826ca499..90518365 100644 --- a/packages/mcp-server/src/http-helpers.ts +++ b/packages/mcp-server/src/http-helpers.ts @@ -278,6 +278,8 @@ export const assertHttpOptions = (options: { maxRequestBodyBytes?: unknown maxUrlDownloadBytes?: unknown urlDownloadTimeoutMs?: unknown + resultDomains?: string[] + widgetDomain?: string }): void => { if (options.mcpToken && options.resourceMetadataUrl) { throw new Error( diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index a670054f..a92449e3 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -1,3 +1,5 @@ +import { parseWidgetOrigin } from './ui/assembly-result-widget.ts' + /** * Limits also arrive from JSON config files, where a value such as `"1MB"` would silently disable * a numeric comparison, so each one must be a positive integer. @@ -15,6 +17,8 @@ export const assertServerOptions = (options: { upstreamSecret?: string maxUrlDownloadBytes?: unknown urlDownloadTimeoutMs?: unknown + resultDomains?: string[] + widgetDomain?: string }): void => { // API2 only accepts relayed `aud=mcp` tokens from the hosted service, so without the secret // every authenticated call fails; refusing to start surfaces that in the deploy's health check. @@ -25,6 +29,13 @@ export const assertServerOptions = (options: { } assertPositiveInteger(options.maxUrlDownloadBytes, 'maxUrlDownloadBytes', 'bytes') assertPositiveInteger(options.urlDownloadTimeoutMs, 'urlDownloadTimeoutMs', 'milliseconds') + // A malformed origin would only surface as blank previews in the host, so it fails startup. + for (const domain of options.resultDomains ?? []) { + parseWidgetOrigin(domain, 'resultDomains') + } + if (options.widgetDomain) { + parseWidgetOrigin(options.widgetDomain, 'widgetDomain') + } } /** Validates the HTTP request body limit. */ diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 2c221ad1..510dd055 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -59,8 +59,18 @@ export type TransloaditMcpServerOptions = { * Defaults to the SDK's `sha384`, which ordinary API keys use. */ signatureAlgorithm?: McpSignatureAlgorithm - /** Origins the result widget may load previews from (`TRANSLOADIT_MCP_RESULT_DOMAINS`). */ + /** + * Origins the result widget may load previews from and link to + * (`TRANSLOADIT_MCP_RESULT_DOMAINS`). Takes exact origins or wildcard hosts; defaults to + * Transloadit's result buckets as wildcards. + */ resultDomains?: string[] + /** + * Dedicated origin ChatGPT serves the result widget from (`openai/widgetDomain`, + * `TRANSLOADIT_MCP_WIDGET_DOMAIN`); defaults to `https://transloadit.com`. Claude computes its + * own `ui.domain`, so this setting does not affect it. + */ + widgetDomain?: string /** Most bytes the URL inputs of one call may download together; defaults to 1 GiB. */ maxUrlDownloadBytes?: number /** Longest one URL input download may take, redirects included; defaults to 10 minutes. */ @@ -1672,7 +1682,11 @@ export const createTransloaditMcpServer = ( }, ) - registerAssemblyResultWidget(server, { resultDomains: options.resultDomains }) + registerAssemblyResultWidget(server, { + resultDomains: options.resultDomains, + widgetDomain: options.widgetDomain, + consoleUrl: parseConsoleUrl(options.consoleUrl || defaultConsoleUrl), + }) mirrorSecuritySchemes(server) return server diff --git a/packages/mcp-server/src/ui/assembly-result-widget.ts b/packages/mcp-server/src/ui/assembly-result-widget.ts index 2c8128d5..152b0de9 100644 --- a/packages/mcp-server/src/ui/assembly-result-widget.ts +++ b/packages/mcp-server/src/ui/assembly-result-widget.ts @@ -10,7 +10,8 @@ export const assemblyResultWidgetMimeType = 'text/html;profile=mcp-app' /** * Origins that serve Assembly result and upload files: Transloadit result buckets and Cloudflare - * R2 public buckets (API2's `CLOUDFLARE_R2_PUB_URL_HOST_*`). Override with `resultDomains`. + * R2 public buckets (API2's `CLOUDFLARE_R2_PUB_URL_HOST_*`). Override with `resultDomains`, which + * also takes exact origins such as `https://tmp-us-east-1.transloadit.net`. */ export const defaultResultDomains = [ 'https://*.transloadit.com', @@ -18,6 +19,29 @@ export const defaultResultDomains = [ 'https://*.r2.dev', ] +/** + * Origin ChatGPT serves the widget from in a public listing; OpenAI requires one per plugin. Claude + * only accepts its own `ui.domain` (a hash of the server URL on `claudemcpcontent.com`), so this is + * sent under the ChatGPT alias `openai/widgetDomain` and never as `ui.domain`. + */ +const defaultWidgetDomain = 'https://transloadit.com' + +/** + * Reduces a configured origin to what hosts compare CSP and link entries against, so + * `https://tmp-us-east-1.transloadit.net/` arrives as `https://tmp-us-east-1.transloadit.net`. + * Wildcard hosts such as `https://*.transloadit.net` parse as URLs and pass through unchanged. + */ +export const parseWidgetOrigin = (value: string, name: string): string => { + const trimmed = value.trim() + if (URL.canParse(trimmed)) { + const url = new URL(trimmed) + if (url.protocol === 'https:' || url.protocol === 'http:') return url.origin + } + throw new Error( + `${name} must contain http(s) origins such as https://cdn.example.com: "${value}"`, + ) +} + /** MCP Apps protocol revision the widget speaks (ext-apps `LATEST_PROTOCOL_VERSION`). */ export const widgetProtocolVersion = '2026-01-26' @@ -36,10 +60,18 @@ export type WidgetContext = { const widgetDescription = 'Shows each Assembly Step with image, video and audio previews, download links for every result file, and a Save as Template shortcut when the caller is signed in.' +type AssemblyResultWidgetMetaInput = { + resultDomains: string[] + redirectDomains: string[] + widgetDomain: string +} + /** Resource `_meta` in both the MCP Apps form and the legacy ChatGPT aliases. */ -export const buildAssemblyResultWidgetMeta = ( - resultDomains: string[] = defaultResultDomains, -): Record => ({ +const buildAssemblyResultWidgetMeta = ({ + resultDomains, + redirectDomains, + widgetDomain, +}: AssemblyResultWidgetMetaInput): Record => ({ ui: { csp: { connectDomains: resultDomains, @@ -48,9 +80,13 @@ export const buildAssemblyResultWidgetMeta = ( prefersBorder: true, }, 'openai/widgetDescription': widgetDescription, + 'openai/widgetDomain': widgetDomain, + // `ui.csp` has no counterpart for `redirect_domains`, which ChatGPT still reads from here to + // trust `window.openai.openExternal` targets. 'openai/widgetCSP': { connect_domains: resultDomains, resource_domains: resultDomains, + redirect_domains: redirectDomains, }, 'openai/widgetPrefersBorder': true, }) @@ -178,7 +214,9 @@ export const assemblyResultWidgetHtml = ` const openWithHost = (url) => { const openai = window.openai if (openai && typeof openai.openExternal === 'function') { - Promise.resolve(openai.openExternal({ href: url })).catch(() => {}) + // ChatGPT appends ?redirectUrl= to redirect_domains targets unless told not to; nothing we + // link to returns users to the chat, so the link opens exactly as shown in the card. + Promise.resolve(openai.openExternal({ href: url, redirectUrl: false })).catch(() => {}) return true } if (hostCapabilities.openLinks) { @@ -415,6 +453,10 @@ export const assemblyResultWidgetHtml = ` export type AssemblyResultWidgetOptions = { /** Origins allowed for previews and downloads; defaults to `defaultResultDomains`. */ resultDomains?: string[] + /** ChatGPT's `openai/widgetDomain`; defaults to `https://transloadit.com`. */ + widgetDomain?: string + /** Console URL the widget links to; its origin is a trusted `openExternal` target. */ + consoleUrl?: string } /** Registers the widget so hosts can `resources/read` it through `_meta.ui.resourceUri`. */ @@ -422,11 +464,18 @@ export const registerAssemblyResultWidget = ( server: McpServer, options: AssemblyResultWidgetOptions = {}, ): void => { - const meta = buildAssemblyResultWidgetMeta( + const resultDomains = ( options.resultDomains && options.resultDomains.length > 0 ? options.resultDomains - : defaultResultDomains, - ) + : defaultResultDomains + ).map((domain) => parseWidgetOrigin(domain, 'resultDomains')) + const consoleOrigin = options.consoleUrl ? [new URL(options.consoleUrl).origin] : [] + const meta = buildAssemblyResultWidgetMeta({ + resultDomains, + // The widget only links to the Console and to result files. + redirectDomains: [...new Set([...consoleOrigin, ...resultDomains])], + widgetDomain: parseWidgetOrigin(options.widgetDomain || defaultWidgetDomain, 'widgetDomain'), + }) server.registerResource( 'assembly-result', assemblyResultWidgetUri, diff --git a/packages/mcp-server/test/unit/cli-config.test.ts b/packages/mcp-server/test/unit/cli-config.test.ts index f056da2d..99b41d76 100644 --- a/packages/mcp-server/test/unit/cli-config.test.ts +++ b/packages/mcp-server/test/unit/cli-config.test.ts @@ -20,7 +20,7 @@ describe('transloadit-mcp CLI configuration', { timeout: 20000 }, () => { client = undefined }) - it('reads TRANSLOADIT_MCP_RESULT_DOMAINS into the widget CSP', async () => { + it('reads the result and widget domains from the environment', async () => { client = new Client({ name: 'cli-config', version: '1.0.0' }) await client.connect( new StdioClientTransport({ @@ -28,7 +28,8 @@ describe('transloadit-mcp CLI configuration', { timeout: 20000 }, () => { args: [cliPath, 'stdio'], env: { ...process.env, - TRANSLOADIT_MCP_RESULT_DOMAINS: 'https://cdn.example.com, https://*.example.net', + TRANSLOADIT_MCP_RESULT_DOMAINS: 'https://cdn.example.com/, https://*.example.net', + TRANSLOADIT_MCP_WIDGET_DOMAIN: 'https://widgets.example.com', }, }), ) @@ -41,6 +42,7 @@ describe('transloadit-mcp CLI configuration', { timeout: 20000 }, () => { resourceDomains: ['https://cdn.example.com', 'https://*.example.net'], }, }, + 'openai/widgetDomain': 'https://widgets.example.com', }) }) diff --git a/packages/mcp-server/test/unit/plugin-manifest.test.ts b/packages/mcp-server/test/unit/plugin-manifest.test.ts index 5ba03b19..3cb8ccac 100644 --- a/packages/mcp-server/test/unit/plugin-manifest.test.ts +++ b/packages/mcp-server/test/unit/plugin-manifest.test.ts @@ -6,43 +6,68 @@ import { z } from 'zod' // Kept as a URL so checkout paths with `#` or spaces resolve correctly. const packageRoot = new URL('../../', import.meta.url) +const httpsUrlSchema = z.url({ protocol: /^https$/ }) + +/** The listing limits OpenAI's plugin submission enforces. */ const interfaceSchema = z.object({ + displayName: z.string().max(30), + shortDescription: z.string().max(30), + longDescription: z.string().max(4000), + websiteURL: httpsUrlSchema, + supportURL: httpsUrlSchema, + privacyPolicyURL: httpsUrlSchema, + termsOfServiceURL: httpsUrlSchema, + defaultPrompt: z.array(z.string().max(128)).max(3), composerIcon: z.string(), logo: z.string(), - screenshots: z.array(z.string()), + screenshots: z.array(z.string()).default([]), }) +type PluginInterface = z.infer + const readJson = async (path: string): Promise => JSON.parse(await readFile(new URL(path, packageRoot), 'utf8')) const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] -/** Plugin submission rejects manifests whose asset paths do not resolve to images. */ +/** Plugin submission rejects asset paths that are not images, and icons that are not square. */ +const expectSquarePng = async (assetPath: string): Promise => { + const bytes = await readFile(new URL(assetPath, packageRoot)) + expect([...bytes.subarray(0, 8)], assetPath).toEqual(pngSignature) + // The IHDR chunk always comes first and stores width and height as big-endian integers. + const width = bytes.readUInt32BE(16) + const height = bytes.readUInt32BE(20) + expect(width, assetPath).toBe(height) + expect(width, assetPath).toBeGreaterThanOrEqual(48) +} + const expectPng = async (assetPath: string): Promise => { const bytes = await readFile(new URL(assetPath, packageRoot)) expect([...bytes.subarray(0, 8)], assetPath).toEqual(pngSignature) } +const expectSubmittableListing = async (listing: PluginInterface): Promise => { + await expectSquarePng(listing.composerIcon) + await expectSquarePng(listing.logo) + await Promise.all(listing.screenshots.map(expectPng)) + // OpenAI rejects listings that mention pricing or plans to upgrade to. + expect(listing.longDescription).not.toMatch(/\b(free|pric(e|es|ing)|trial|upgrade)\b/i) +} + describe('plugin manifests', () => { - it('ship every image the ChatGPT manifest references', async () => { + it('meet the ChatGPT listing requirements', async () => { const manifest = z .object({ extensions: z.object({ 'com.openai': z.object({ interface: interfaceSchema }) }) }) .parse(await readJson('plugin.json')) - const { composerIcon, logo, screenshots } = manifest.extensions['com.openai'].interface - await expectPng(composerIcon) - await expectPng(logo) - await Promise.all(screenshots.map(expectPng)) + await expectSubmittableListing(manifest.extensions['com.openai'].interface) }) - it('ship every image the Codex manifest references', async () => { + it('meet the Codex listing requirements', async () => { const manifest = z .object({ interface: interfaceSchema }) .parse(await readJson('.codex-plugin/plugin.json')) - const { composerIcon, logo, screenshots } = manifest.interface - await expectPng(composerIcon) - await expectPng(logo) - await Promise.all(screenshots.map(expectPng)) + await expectSubmittableListing(manifest.interface) }) }) diff --git a/packages/mcp-server/test/unit/tool-surface.test.ts b/packages/mcp-server/test/unit/tool-surface.test.ts index 767a8afe..a0837fed 100644 --- a/packages/mcp-server/test/unit/tool-surface.test.ts +++ b/packages/mcp-server/test/unit/tool-surface.test.ts @@ -213,12 +213,16 @@ describe('tool surface', () => { csp: { connectDomains: resultDomains, resourceDomains: resultDomains }, }, 'openai/widgetDescription': expect.any(String), + 'openai/widgetDomain': 'https://transloadit.com', 'openai/widgetCSP': { connect_domains: resultDomains, resource_domains: resultDomains, + redirect_domains: ['https://transloadit.com', ...resultDomains], }, }, }) + // Claude validates `ui.domain` against its own hash-based origin, so it must stay unset. + expect(widget).not.toHaveProperty(['_meta', 'ui', 'domain']) const read = await call('resources/read', { uri: assemblyResultWidgetUri }) const contents = (read.contents as unknown[]).filter(isRecord) @@ -236,28 +240,41 @@ describe('tool surface', () => { }) describe('result widget domains', () => { - it('uses configured result domains for the widget CSP', async () => { - const server = createTransloaditMcpServer({ resultDomains: ['https://cdn.example.com'] }) + it('advertises configured exact origins, widget domain and Console links', async () => { + const server = createTransloaditMcpServer({ + resultDomains: ['https://tmp-us-east-1.transloadit.net/', 'https://pub-123.r2.dev'], + widgetDomain: 'https://widgets.example.com/', + consoleUrl: 'https://console.example.com/base/', + }) const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() const client = new Client({ name: 'result-domains', version: '1.0.0' }) await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]) + const exactOrigins = ['https://tmp-us-east-1.transloadit.net', 'https://pub-123.r2.dev'] const { contents } = await client.readResource({ uri: assemblyResultWidgetUri }) expect(contents[0]?._meta).toMatchObject({ ui: { - csp: { - connectDomains: ['https://cdn.example.com'], - resourceDomains: ['https://cdn.example.com'], - }, + csp: { connectDomains: exactOrigins, resourceDomains: exactOrigins }, }, + 'openai/widgetDomain': 'https://widgets.example.com', 'openai/widgetCSP': { - connect_domains: ['https://cdn.example.com'], - resource_domains: ['https://cdn.example.com'], + connect_domains: exactOrigins, + resource_domains: exactOrigins, + redirect_domains: ['https://console.example.com', ...exactOrigins], }, }) await client.close() await server.close() }) + + it.each([ + [{ resultDomains: ['tmp-us-east-1.transloadit.net'] }, 'resultDomains'], + [{ widgetDomain: 'transloadit.com' }, 'widgetDomain'], + ])('refuses to start with %j', (options, name) => { + expect(() => createTransloaditMcpHttpHandler({ metricsPath: false, ...options })).toThrow( + `${name} must contain http(s) origins`, + ) + }) }) // The SDK client drops Tool fields it does not know, such as the top-level securitySchemes. diff --git a/packages/mcp-server/test/unit/widget-handshake.test.ts b/packages/mcp-server/test/unit/widget-handshake.test.ts index 0245f044..680f1ef6 100644 --- a/packages/mcp-server/test/unit/widget-handshake.test.ts +++ b/packages/mcp-server/test/unit/widget-handshake.test.ts @@ -16,7 +16,7 @@ const hostInfo = { name: 'TestHost', version: '9.9.9' } * test sees exactly the JSON-RPC messages the inline script posts. */ const mountWidget = ( - options: { maxTimeout?: number } = {}, + options: { maxTimeout?: number; openai?: Record } = {}, ): { window: Window sent: JsonRpcMessage[] @@ -35,6 +35,9 @@ const mountWidget = ( const sent: JsonRpcMessage[] = [] const host = { postMessage: (message: JsonRpcMessage) => sent.push(message) } Object.defineProperty(window, 'parent', { value: host, configurable: true }) + if (options.openai) { + Object.defineProperty(window, 'openai', { value: options.openai, configurable: true }) + } window.document.write(assemblyResultWidgetHtml) const fromHost = async (message: JsonRpcMessage): Promise => { @@ -252,6 +255,38 @@ describe('assembly result widget handshake (MCP Apps 2026-01-26)', () => { }) }) +describe('assembly result widget in ChatGPT', () => { + it('opens download links through window.openai.openExternal without a redirectUrl', async () => { + const opened: unknown[] = [] + const widget = mountWidget({ + openai: { + openExternal: (options: unknown) => { + opened.push(options) + }, + toolOutput: { + status: 'ok', + assembly: { + ok: 'ASSEMBLY_COMPLETED', + assembly_id: 'abc123', + results: { + resized: [ + { name: 'clip.mp4', mime: 'video/mp4', ssl_url: 'https://pub-123.r2.dev/clip.mp4' }, + ], + }, + }, + }, + }, + }) + + const click = new widget.window.MouseEvent('click', { bubbles: true, cancelable: true }) + widget.window.document.querySelector('a[download]')?.dispatchEvent(click) + + expect(click.defaultPrevented).toBe(true) + expect(opened).toEqual([{ href: 'https://pub-123.r2.dev/clip.mp4', redirectUrl: false }]) + await widget.window.happyDOM.close() + }) +}) + describe('assembly result widget previews', () => { it('retries a preview that is not readable yet, then says so', async () => { // Collapse the widget's 1 s / 3 s / 6 s retry delays.