DEV Community

Cover image for We shipped a one-click 'Add to Cursor' button. The hard part wasn't the button.
Eugeniya Ivanova
Eugeniya Ivanova

Posted on

We shipped a one-click 'Add to Cursor' button. The hard part wasn't the button.

My job is getting our product into people's hands. It's built for AI agents, so it has to live where agents pick up their tools — in their catalogs. Slow business: we're in the review queue at the big ones as I write this. And getting listed is only half the job. The other half is making sure connecting isn't a scavenger hunt through config files, pasted keys, and hopeful restarts.

So we started with a button. One click, Publora's in Cursor. Sounds like an evening's work.

It wasn't.

Cursor installs MCP servers from a deeplink — a link that tells the editor to add a given server:

cursor://anysphere.cursor-deeplink/mcp/install?name=Publora&config=<base64>
Enter fullscreen mode Exit fullscreen mode

The config is a base64-encoded JSON holding the server address. Official format, everyone uses it, Asana and Xano included. We built the button, the link generated cleanly, the base64 decoded into exactly the right config. Everything checked out.

You click it, and: connection failed. Before it ever reached authentication.

My first guess was the key. It won't connect, so it must be access, right? Wrong, and that wrong guess is where the time goes.

The button doesn't ask for a key, it drops in a placeholder. Cursor sends that placeholder, the server returns a perfectly correct 401, and instead of reporting a bad key, Cursor falls back to OAuth. It goes looking for OAuth settings at the standard spot, /.well-known/oauth-authorization-server. Our server answered there with a custom response of its own — not the shape the spec describes. Cursor tried to parse it as OAuth metadata, found none of the fields it needed, printed "MCP OAuth provider initialized," and stopped there.

Placeholder instead of a key, then a 401, then a fallback into OAuth, then a failure on our non-standard response. The key was never the problem. It's just the first thing everyone suspects.

Our first fix was simple: type a real key in by hand. Cursor has a little pencil that opens ~/.cursor/mcp.json, you swap the placeholder for a real sk_..., and it goes green. A valid key means the request never reaches OAuth discovery at all — the server just answers and hands over its tools. Fine. But "one click, now paste your key" is a click and a half, and not the button we came to build.

Now, before anyone asks why we didn't do it properly from day one. A key instead of OAuth wasn't us cutting corners. It's where the whole ecosystem started: the MCP spec recommended API keys until March 2025 and only then made OAuth the baseline. The thing has been rewritten three times in nine months — catching up to that is not an evening either. This same .well-known is currently tripping up companies with rather more headcount than us: Atlassian has an open bug about OAuth-metadata validation, and Microsoft's own MCP clients resolve the endpoint wrong. And when one study checked 660 OAuth servers, 27 of them — four percent — did full dynamic registration. Moving from a key to real OAuth isn't the slow kid's homework. It's the part most products just never finish.

So instead of hiding the key, we fixed the place everything kept dying on. Cursor wants OAuth so badly? Give it OAuth. My engineers put proper, to-spec metadata at /.well-known/oauth-authorization-server — issuer, authorize and token endpoints, client registration, PKCE, the whole set the editor kept hunting for. My contribution at this stage was reading closely and sitting next to them waiting for a miracle. It arrived.

The chain runs straight now. Click the button, Cursor fetches the OAuth settings, finds real ones, pops the Publora login, you sign in, and it collects the token itself. Nothing typed by hand. The one click, as advertised.

To reproduce it, it comes down to two things. A deeplink whose config is just { "url": "https://mcp.publora.com/mcp" } — the address, no key, login goes through OAuth. And a server that serves valid metadata at /.well-known/oauth-authorization-server instead of something homemade. If your client prints "OAuth provider initialized" and then quietly dies, put your money on this: it read your .well-known and didn't find what it wanted.

The big catalogs are still ahead of us, moving at their pace and not ours. The Cursor install works today. So if you're in Cursor and you use Publora, poke the button and tell me how it went — genuinely curious, especially if it breaks somewhere new.

Top comments (1)

Collapse
 
mads_hansen_27b33ebfee4c9 profile image
Mads Hansen

The debugging chain here is a great reminder that “authentication failed” is really a protocol trace, not one event. I’d add a contract test that starts from a clean client and validates the entire discovery path: exact well-known URL, issuer consistency, advertised endpoints, PKCE methods, registration behavior, and the final resource audience.

It is also worth testing the less-happy lifecycle cases: cached metadata after an endpoint change, revoked consent, expired refresh tokens, and a user switching accounts. Those are where a one-click install can quietly become a support ticket even though the first-run flow passes.

A small per-stage trace (discovery → registration → authorization → token → MCP request), with secrets redacted, would make the next failure much faster to locate than a single “connection failed” message.