
Photo by Bryn Young on Unsplash
The Braineos connector lets Claude create decks, quiz you and edit cards in your Braineos account. From the outside it's a single URL you paste into Claude. Here's what's behind it, including a couple of things that bit us on the way to production.
What MCP is
The Model Context Protocol is an open standard for giving AI assistants tools. A server describes the tools it offers: their names, what they do, and what input they take. A client like Claude decides when to call them during a conversation.
Our server lives at one endpoint in the Braineos web app, built with Next.js. It offers tools like create_deck, get_due_cards, record_quiz_results, get_deck and update_card. Claude reads their descriptions and works out when to use each one.
Signing in: OAuth 2.1, so Claude never sees your password
A connector that acts in your account needs to know who you are. The MCP spec uses OAuth 2.1 for this, the same family of standards behind "Sign in with Google".
Braineos accounts live in Supabase Auth, which can act as an OAuth 2.1 server. So we didn't build an authorization server. We switched on Supabase's and built the parts that are ours:
- A consent page. When you add the connector, Claude sends you to Braineos to sign in. You then see a page naming the app asking for access, with Approve and Deny buttons. That page asks Supabase for the request's details and passes your decision back.
- Dynamic client registration. Claude registers itself as an OAuth client automatically, so there's no "create an API key" step for you.
- A safe return trip. If you're not signed in when the consent page loads, we send you through the normal login and back again. The "where to go next" path is checked so it can only ever point back into Braineos, not to some other site.
Once you approve, Claude receives an access token for your account. It never sees your password.
Discovery: how Claude knows where to log in
You only paste one URL into Claude. So how does it find the login page?
The first time Claude calls the connector without a token, the server answers 401 Unauthorized with a WWW-Authenticate header. That header points to a small JSON document at a well-known address (the protected resource metadata, RFC 9728). The document names the resource and the authorization server that issues tokens for it. Claude follows that trail to Supabase's OAuth endpoints, registers, and starts the sign-in flow.
That's why opening the connector URL in a browser shows a 401. It's not broken; it's the first step of the handshake.
Keeping connector tokens on a short leash
An OAuth token from Supabase is an ordinary user token with one extra claim: client_id, naming the app it was issued to. That matters.
Our main API is the one the website and mobile app use. It accepts a signed-in user's token and can do everything a user can, including billing and account settings. If a connector token worked there too, giving Claude access to your flashcards would quietly give it access to everything else.
So the main API refuses any token with a client_id claim. Connector tokens only work at the MCP endpoint, which only offers the flashcard tools. It's a few lines of code, and it's the most important design decision in the whole connector.
Every tool goes through the front door
The MCP tools don't talk to the database directly. Each one calls the same API procedures as the website, acting as you.
That means every tool automatically inherits rules we'd already written and tested:
- you can only edit decks you own;
- private decks stay invisible to everyone else;
- making a deck private needs premium;
- you have to accept the Terms of Use before creating content;
- cards are validated the same way however they're made.
A tool can't forget a check, because it doesn't have its own checks to forget.
The tools add a thin layer on top where a conversation needs something different from a form. For example, the website's card editor always sends the whole card, but Claude fixing one typo shouldn't have to resend everything. So update_card takes only the fields that change, fills the rest from the stored card, and then goes through the normal edit procedure.
There's one connector-specific limit: 10 new decks a day. It's generous for real use and stops a runaway loop filling the site with decks.
Describing tools honestly
Claude decides what to do based on the tool descriptions, so they matter more than you'd think. We followed a few rules, partly from Anthropic's review checklist for connectors:
- Say what's true, not what to do. The server's descriptions state facts (limits, visibility rules, what each field means), not instructions about how Claude should behave. Guidance on writing good cards lives in a separate, optional Claude skill.
- Mark every tool as read-only or as changing data. Claude can be more careful with the second kind, for example by asking before deleting a card. A test registers every tool and fails if any is missing a title or that marker, so a new tool can't skip it.
- Explain errors in plain words. If you haven't accepted the Terms of Use, the tool's error tells you where to go to do that, so Claude can pass it on. A bare "FORBIDDEN" wouldn't help anyone.
Two production surprises
Both came from the same lesson: OAuth compares URLs exactly.
- A trailing slash. Our configured site URL ended in
/, so the resource URL we advertised becamehttps://…//api/mcp, with a double slash. That isn't the URL Claude was connecting to, and sign-in failed. The fix was one function that normalises the site URL, used everywhere a public URL is built. - www versus no www. The site was configured as one hostname while people (and Claude) reached it at the other. The advertised resource didn't match the one Claude was talking to. We picked one canonical hostname, configured the app with it, and redirect the other one to it.
Neither showed up on localhost. Both showed up within minutes on production.
What's next
Since launch, the connector has gained tools to read whole decks, edit and delete cards, and attach images. We'd like Claude to be able to tell you how you're progressing over time, which needs more learning history than we store today. As ever, the hard part isn't the protocol. It's deciding what a good tool should be allowed to do.
Try it
Add the Braineos connector in Claude with this URL:
https://braineos.com/api/mcp
On claude.ai, go to Customize → Connectors → Add custom connector. In Claude Code, run claude mcp add --transport http braineos https://braineos.com/api/mcp, then /mcp to sign in. Full details are on the Claude connector page.