Repository navigation
feat(http): serve the openai apps domain verification challenge - #228
Conversation
|
[agent] Minor maintainability concern: Could we extract a small public-route dispatcher as part of this change? Explicit path/method checks would be sufficient; no framework or generic route registry needed. That would keep the main request flow easier to read and give subsequent endpoints a consistent home. Preserve the existing ordering, particularly health bypassing origin validation and public metadata/challenge routes answering before MCP authentication. |
|
Vasco Santos (@vasco-santos) good call — done in
One deliberate deviation from the ask: two dispatchers rather than one. The ordering you asked to preserve is the reason. Nothing else moved: the spine runs parse → health → origin/host → CORS/OPTIONS → public routes → 404 → authenticate → MCP dispatch, in that order.
While pinning the ordering I found the existing coverage did not actually test it. Verification: 99 tests across the routing suites, 483 in the full unit run, types and lint clean including the complexity rule. CI on this PR is red on fleet payload hydration ( |
|
[agent] The updated routing looks good to me and addresses the maintainability concern. The two dispatchers keep the health exception explicit and preserve origin/CORS checks before public metadata and challenge handling. All 42 local HTTP checks passed against the updated code, covering token responses, rejected origins, the health bypass, and the MCP authentication requirement. CI is still red, but it fails during fleet payload setup before the tests run: John-David Dalton (@jdalton) could you take a look at the CI setup failure? I found no new actionable issues in the routing change. |
|
We landed some fixes Jerod Santo (@jerodsanto) and Vasco Santos (@vasco-santos), update the branch and see if it passes 🙏 |
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.
routeRequest carried request validation, CORS, public endpoint dispatch, and the authenticated MCP path in one function, and the domain verification endpoint only fit by extracting OAuth metadata handling to stay under the complexity ceiling. That extraction treated the symptom. Move the endpoints that answer without an MCP credential into http-public-routes.mts behind two dispatchers, each reporting whether it answered. Ordering is unchanged: health still answers before origin validation, and the metadata and challenge probes still answer after CORS and before authentication. Two dispatchers rather than one because that ordering is the point. A single entry point could only cover all three routes by moving one across the trust boundary or by taking a stage flag, and the boundary is what a reader needs to see.
469e2e7 to
4102dea
Compare
|
Yup all good John-David Dalton (@jdalton), thanks! I'm going to merge this then I'll work with Vasco Santos (@vasco-santos) on getting the new env var all set up |
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_TOKENunset 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-challengereturns the configured token astext/plain; charset=utf-8with an explicitContent-Length. No JSON envelope, no trailing newline, no token list - the portal compares the body byte for byte.GETorHEAD200with the token (HEADsends headers only)404 Not found, the existing catch-all404 Not foundA 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
Originheader. The route is placed after the existing origin/host validation, which already admits an absentOriginon an allowed host, and before authentication - an authenticated challenge endpoint could never be verified.Refactor: writeOauthMetadata extracted
The new branch pushed
routeRequestto a cyclomatic complexity of 17 against the repo ceiling of 15. Rather than suppress the rule, the RFC 9728 protected-resource metadata block moved intowriteOauthMetadata(), which reports whether it answered. Behavior is unchanged and the existinghttp-servertests cover it as before.Validation
Ran: 12 new tests in
test/repo/unit/apps-challenge.test.mts, plus the existinghttp-serverandenvsuites - 74 passing, no routing regression.tsc --noEmitreports 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 owntrim(), 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:
SOCKET_MCP_APPS_CHALLENGE_TOKENin 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_urlinplugin.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_TOKENis set,GET/HEADon/.well-known/openai-apps-challengereturns the portal token as plaintext/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()inhttp-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
routeRequestintegration.Reviewed by Cursor Bugbot for commit 150e8cb. Configure here.