Skip to content

the app directory needs a privacy policy and terms, and both were 404 - #2759

Merged
Flotapponnier merged 4 commits into
devfrom
feat/app-directory-prereqs
Oct 1, 2026
Merged

Flotapponnier merged 4 commits into
devfrom
feat/app-directory-prereqs

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Listing the MCP server at /api/mcp/mcp in the ChatGPT app directory requires four reachable URLs in the submission form: website, support, privacy policy and terms of service. The first two exist. The other two answered 404 on production, so the form could not be filled in at all.

This ships the three missing prerequisites.

/privacy

Read off the code rather than drafted from a template, because overstating what we collect is as wrong as understating it. Every claim traces to a line:

  • posthog-provider.tsx:57-64 — autocapture: false, person_profiles: "identified_only", and nothing in the codebase ever calls identify
  • api_host: "/ingest" — analytics are proxied through our own origin, the browser never contacts the analytics host directly
  • analytics-server.ts:72 — the server-side id is sha256(user-agent + UTC day), $process_person_profile: false, no IP stored
  • rate-limit.ts:21 — buckets live in an in-memory Map, never written to disk, lost on deploy

If any of those change, this page changes in the same commit.

/terms

Two clauses are load-bearing rather than boilerplate:

  • Not financial advice / we never execute transactions / never send us a key. The review requires three negative test cases the app must refuse ("buy SOL for me", "give me investment advice", "test my RPC with my API key"); they point here.
  • The conflict-of-interest disclosure, which already appears in publisher.json and on /partners. All three must keep saying the same thing.

/.well-known/openai-apps-challenge

Verification fetches a token as plain text and the body must be the token alone — no JSON wrapper, no HTML error page. The token stays in OPENAI_APPS_CHALLENGE (Vercel prod env) rather than the repo: committing it would let anyone prove ownership of this domain for their own submission. Until it is set the route 404s, which is the honest state. force-dynamic so rotating it takes effect without a rebuild.

Notes

Both pages are in the sitemap and linked from the footer; neither is reachable from the rail, so without that they would be orphans a reviewer could not follow.

Verified: tsc --noEmit clean, pnpm validate passes, and the /partners and /methodology pages the new copy links to both exist.

Listing the MCP server at /api/mcp/mcp in the ChatGPT app directory requires
four reachable URLs in the submission form: website, support, privacy policy
and terms of service. The first two exist. The other two answered 404 on
production, so the submission could not be filled in at all.

The privacy policy is read off the code rather than drafted from a template,
because overstating what we collect is as wrong as understating it. Every
claim traces to a line: autocapture is off and person profiles are disabled
in posthog-provider.tsx and nothing ever calls identify; analytics are
proxied through our own origin so the browser never contacts the analytics
host; the server-side id in analytics-server.ts is a daily hash of the user
agent with no IP stored; rate-limit buckets live in an in-memory Map that
never reaches disk. If any of those change this page changes in the same
commit.

The terms carry two clauses that are not boilerplate. "Not financial advice,
we never execute transactions, never send us a key" is what the three
negative test cases the review requires will point at. The
conflict-of-interest disclosure repeats what publisher.json and /partners
already say, and all three must stay identical.

The domain challenge route is the third required piece: verification fetches
a token as plain text from /.well-known/openai-apps-challenge and the body
must be the token alone. The token stays in an env var rather than the repo,
because committing it would let anyone prove ownership of this domain for
their own submission; until it is set the route 404s, which is the honest
state. force-dynamic so rotating it needs no rebuild.

Both pages are in the sitemap and linked from the footer: neither is reachable
from the rail, and a reviewer follows those URLs.
…ogue

Three things, all found by calling the live endpoint rather than by reading
the code.

The values were wrong. Latency benches declaring unit "s" store milliseconds
by the fmtUnit convention, and this endpoint published the stored number
beside the declared unit: {"value":443.494,"unit":"s"} for a head lag the
site and /api/stat both render as 0.44 s. An agent citing value and unit
together was off by a thousand, under provider names, on three benches
including number 001. valueInDeclaredUnit already existed for exactly this
reason, its docstring names this same bench, and /api/stat already called it;
the MCP server never did. Every value crossing the boundary now goes through
it, and rankings carry both the stored `ms` and a `value` in the declared
unit so either read is right.

list_benchmarks returned the catalogue. All 229 live benches in one response,
142,555 characters, about 35,638 tokens, for a question as broad as "what do
you measure". A model handed that spends its context on JSON instead of on
the answer. It is now compact, capped and filterable: 25 rows of nine short
fields, about 2,400 tokens, 94% less, and it says how many it held back.

The tools were named for the REST surface they mirrored, not for the
questions people ask, and a client routes on the description text. Nobody
thinks "list benchmarks"; they ask which Solana RPC is fastest, or whether
Alchemy beats QuickNode. So: search_benchmarks takes their words and ranks
the catalogue against them, including by the provider names a bench measures,
so "is Helius faster than Triton" finds the Solana bench without anyone
knowing its slug. compare_providers takes the names they said and returns a
provider it does not measure as `missing` rather than as a loser, because
those are different answers. recommend_provider takes the goal they stated
and returns the caveat with it, since "read p99 not p50 for a bot" is usually
worth more than the ranking.

query_prom is gone. Behind its allowlist it was safe enough for a developer
tool, but no end user asks for a PromQL passthrough, it was attack surface
with no reader, and every extra tool dilutes the routing the useful ones
depend on. Its allowlist and validator went with it.

Shaping lives in src/lib/mcp-tools.ts so it can be tested without a server:
13 tests cover the unit conversion in both directions, provider-name search,
the cap, the longest-phrase use-case match, and the missing-vs-loser rule.
Read-only annotations and structuredContent on every tool.
…reqs

# Conflicts:
#	src/components/site-footer.tsx
Found by auditing the diff rather than by a test. I converted every value on
the tool path and left the one on the resource path, which serves the same
benchmark to the same clients under the same field names: an agent pinning
openchainbench://benchmark/aggregator-head-lag still read 443.494 next to
unit "s".

Both its value and its leader now go through valueInDeclaredUnit, and its
rankings carry a declared-unit `value` beside the stored `ms` like the tool
response does. Nine conversions in the file now, one for every raw read.
@Flotapponnier
Flotapponnier merged commit ca0bc42 into dev Oct 1, 2026
2 checks passed
Flotapponnier added a commit that referenced this pull request Oct 1, 2026
Listing the MCP server in the ChatGPT app directory requires four reachable
URLs, and two of them answered 404 on production: /privacy and /terms. They
shipped to dev in #2759 and were waiting on a release that has twenty-odd
unrelated commits in it. A reviewer clicking straight through to an error
page is a rejection, so they come across on their own.

The privacy policy is read off the code rather than drafted from a template,
because overstating what we collect is as wrong as understating it. Every
claim traces to a line: autocapture off and person profiles disabled in
posthog-provider.tsx with nothing ever calling identify; analytics proxied
through our own origin so the browser never reaches the analytics host; the
server-side id a daily hash of the user agent with no IP stored; rate-limit
buckets in an in-memory Map that never reaches disk.

The terms carry two clauses that are load-bearing rather than boilerplate.
"Not financial advice, we never execute transactions, never send us a key" is
what the three negative test cases the review requires point at, and the MCP
server was measured against those cases today. The conflict-of-interest
disclosure repeats what publisher.json and /partners already say; all three
must keep saying the same thing.

The domain challenge route comes with them: verification fetches a token as
plain text from /.well-known/openai-apps-challenge and the body must be the
token alone. The token stays in an env var rather than the repo, because
committing it would let anyone prove ownership of this domain for their own
submission. Until it is set the route 404s, which is the honest state, so
shipping it now costs nothing.

Both pages are in the sitemap and linked from the footer; nothing in the rail
or the body reaches them otherwise.
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