In July I set out to add our MCP server to the ChatGPT app directory. The server was already live, running in Claude and Cursor, with OAuth working. Submitting looked like a formality: describe what already exists, fill in the application, hit Submit.
A month and one rejection later, almost nothing had changed in the server itself. It had been working the whole time. What we ended up fixing was everything around it: how ChatGPT discovers the tools, what the scanner expects, what we'd written in the descriptions, and whether a reviewer could actually log in.
Here's what caught me.
The tool list that freezes solid
The first problem came before submission. I connected the server to ChatGPT — the connector was created, but it showed "no actions available." Zero tools. In Claude and Cursor the same tools loaded fine.
I spent a while looking at SSE and caching. Neither was the problem. ChatGPT reads the tool list when the app is created, and if the server returns zero at that point, the app can get stuck there. "Refresh" is disabled at zero and "Save" is greyed out. In my case, the way out was to delete the app and create it again.
The server returned zero because it required a token for tools/list itself. Claude logs in first and fetches the list afterwards, which is why we'd never seen the problem there. ChatGPT fetches the list before authorization, gets nothing, and turns to stone. So now I check one thing before doing anything else: tools/list needs to return 200 with the tools and no token.
Annotations the scanner demands and the spec doesn't
Next, OpenAI's scanner went through every tool and required explicit readOnlyHint, openWorldHint and destructiveHint values. Four read-only tools didn't have destructiveHint, which made sense according to the MCP spec: it only matters when readOnlyHint: false.
The scanner wanted it anyway. We added destructiveHint: false to those tools and wrote justifications for forty-odd annotation values. The tools themselves didn't change. We just had to make explicit something the spec allowed us to leave implied.
The domain challenge that wants a bare string and lives forever
A small one that's easy to waste time on: OpenAI's domain verification expects the endpoint to return a bare token string. No JSON, no quotes. Return {"token": "..."} and it fails.
It also isn't a one-time verification endpoint. It has to stay there in production, answering the same way afterwards. So we now have a tiny endpoint with one permanent job: return a string when OpenAI asks.
The drift I caught on myself
Then I found something that actually was our mistake.
Our public mcp-server repo defines 18 tools, four of them for LinkedIn analytics: post stats, account stats, followers and profile summary. The live server exposes 16, and none of those analytics tools are among them. They exist in the REST API, but aren't exposed through MCP.
The public repo had fallen behind the live server, and the app description was based on the repo. So we were describing analytics that someone using MCP couldn't actually access. I removed them from the description and release notes.
Then I found the same old description in two more places: the README for the Zed extension and the PR for the Docker MCP Catalog. The Zed PR was also pointing at a commit from before the correction, so merging it would have put the outdated claim into another catalog. We'd updated the product and missed a few places where we'd described it. Easy enough to do when those descriptions live in different repos and submissions.
The rejection that wasn't about the code
Submitted August 5, every wizard step completed in one pass. Rejected August 24.
The message was: "We're unable to complete your sign-in or OAuth flow… ensure valid, working credentials… no additional setup or verification."
Before changing anything, I walked through the flow again. Dynamic client registration returned 201. /authorize sent me to our consent screen. The sign-in page in a clean browser was a normal email-and-password form with no captcha. tools/list without a key returned the tools. API and MCP access were enabled on every plan, including free. Everything worked.
Then we looked at the account we'd given the reviewer.
Our own accounts use Google sign-in. A reviewer can't use our Google account, and even if they tried, the second factor would land on our phone. We'd managed to give them credentials for an account they had no way to get into.
Even worse, Canva had rejected us for the same reason before. Twice was enough to stop calling it bad luck. We simply didn't have "can a stranger actually use these credentials?" on our submission checklist.
What actually fixed it
We created a separate reviewer account: email and password, email confirmed ahead of time, a couple of channels connected, with some posts and drafts already there. We also included eight test cases — five positive and three negative.
The most useful one was a full write-path test through publora-playground. It's a fake publishing target: it validates the post against the same rules as a real one and returns the same kind of response, but throws the request away at the end instead of publishing it anywhere. A reviewer can go through the whole publishing flow without putting a test post on someone's actual timeline.
It took about thirty lines and ended up being one of the most useful things we added for the submission. The reviewer could finally test writes without needing a disposable social account.
We resubmitted on the evening of August 24. Approved September 4.
What I wrote down for next time
The main addition to my submission checklist is pretty basic: go through the product as the reviewer, not as yourself.
The server worked in Claude and Cursor. It followed the spec. OAuth worked when we tested it. But ChatGPT asked for the tool list in a different order, its scanner expected metadata we hadn't needed elsewhere, and the reviewer needed a login that didn't depend on being us. Those were the things that took the month.
I did this with Claude — it wrote the code and configs, while I checked what was happening and decided what needed changing. The funny part is that almost none of those decisions involved changing the server itself. Most of the work ended up being metadata, descriptions and access.
What's gotten your product bounced from a platform review — something actually broken, or something you never thought a reviewer would run into?
Top comments (3)
The tools/list-before-auth thing is the one that'll bite the most people. It's such a small ordering difference between clients but it turns "works everywhere" into "works nowhere" for one specific platform. We're building an MCP discovery layer and this is exactly the class of problem that never shows up in the spec, only in the gap between what different clients assume. The reviewer-account point is underrated too. Testing as yourself is the default failure mode for basically every integration, not just MCP.
Great work!
Thanks!