Skip to content

feat(http): serve the openai apps domain verification challenge - #228

Open
Jerod Santo (jerodsanto) wants to merge 1 commit into
mainfrom
feat/openai-apps-challenge
Open

Jerod Santo (jerodsanto) wants to merge 1 commit into
mainfrom
feat/openai-apps-challenge

Conversation

@jerodsanto

@jerodsanto Jerod Santo (jerodsanto) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The OpenAI plugin portal verifies domain control by probing a fixed path and comparing the response body against a token it issues. This adds that route, which is the last hard blocker on the ChatGPT plugin submission.

Inert until configured. With SOCKET_MCP_APPS_CHALLENGE_TOKEN unset the path serves the ordinary 404, so merging and deploying this changes nothing observable.

The route - token in, token out, nothing else

GET /.well-known/openai-apps-challenge returns the configured token as text/plain; charset=utf-8 with an explicit Content-Length. No JSON envelope, no trailing newline, no token list - the portal compares the body byte for byte.

Condition Response
Token set, GET or HEAD 200 with the token (HEAD sends headers only)
Token unset 404 Not found, the existing catch-all
Any write method 404 Not found

A deployment with no token configured can never report itself as verified: the absent case is a 404, not an empty 200.

Why it answers ahead of the OAuth gate

The portal probe carries no credential and no Origin header. The route is placed after the existing origin/host validation, which already admits an absent Origin on an allowed host, and before authentication - an authenticated challenge endpoint could never be verified.

Refactor: writeOauthMetadata extracted

The new branch pushed routeRequest to a cyclomatic complexity of 17 against the repo ceiling of 15. Rather than suppress the rule, the RFC 9728 protected-resource metadata block moved into writeOauthMetadata(), which reports whether it answered. Behavior is unchanged and the existing http-server tests cover it as before.

Validation

Ran: 12 new tests in test/repo/unit/apps-challenge.test.mts, plus the existing http-server and env suites - 74 passing, no routing regression. tsc --noEmit reports 0 errors repo-wide. oxlint is clean on every file touched.

Three of the new tests assert the response body contains no \n, no {, and equals its own trim(), because the portal rejects anything but the bare token.

Did not run: an end-to-end portal verification, which needs the deployed route plus a token that the portal only issues after Connect in the plugin dashboard.

Follow-ups, not in this PR:

  • Set SOCKET_MCP_APPS_CHALLENGE_TOKEN in the deployment environment after the portal issues the token, then click Verify Domain.
  • plugins/chatgpt/ packaging and its validation script, which is a separate branch.
  • review.demo_recording_url in plugin.json, recorded after the developer-mode install.

Note

Low Risk
Adds an optional, opt-in public endpoint with no change to MCP/OAuth behavior when the token is unset; routing refactor preserves existing OAuth metadata semantics.

Overview
Adds OpenAI ChatGPT plugin domain verification on the HTTP MCP server: when SOCKET_MCP_APPS_CHALLENGE_TOKEN is set, GET/HEAD on /.well-known/openai-apps-challenge returns the portal token as plain text/plain (no JSON, no trailing newline). The route is handled after origin/host checks and before OAuth/MCP auth so unauthenticated portal probes succeed; with no token configured the path still 404s, so deploys stay inert until configured.

OAuth protected-resource metadata handling is extracted to writeOauthMetadata() in http-server.mts (behavior unchanged). Docs cover the new env var, endpoint behavior, and release-checklist steps (challenge URL, demo_recording_url).

Unit tests cover request matching, response shape, and full routeRequest integration.

Reviewed by Cursor Bugbot for commit 150e8cb. Configure here.

The OpenAI plugin portal verifies a domain by probing a fixed path and
comparing the body against a token it issues. Serve that token from
SOCKET_MCP_APPS_CHALLENGE_TOKEN at /.well-known/openai-apps-challenge as
text/plain, with no JSON envelope and no trailing newline.

The probe carries no credential and no Origin header, so the route answers
ahead of the OAuth gate. A deployment that configures no token serves the
ordinary 404 rather than an empty 200, so an unconfigured environment can
never report itself as verified.

Extract writeOauthMetadata to keep routeRequest inside the complexity
ceiling the new branch would otherwise exceed. Behavior is unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant