For a year NextLang lived in its own tab. You came to the site, typed a prompt, got a deck. Meanwhile the people it was built for were doing something else entirely: asking ChatGPT and Claude what a word means, how a phrase is really said, why a sentence uses that tense. The question already happens in a chat. The deck did not. So I stopped asking learners to come to NextLang and brought NextLang to the chat: it is now a remote MCP server, and the same account that works on the website answers from Claude and ChatGPT.
A chatbot can write cards. It cannot hand you a deck
Ask any assistant for twenty airport words in Spanish and you get a tidy list. Then the old production problem comes back in a new costume: the list is text, your flashcard app wants a file, and tomorrow the chat has no idea what you studied today.
The connector closes exactly those gaps. You add NextLang once as a connector, sign in with Google, approve a consent screen, and from then on plain sentences do the work:
- "Quiz me on my Spanish words" - pulls the words that are due from your NextLang vocabulary, asks them one at a time without showing the answer, and saves the round to the same review schedule the website uses.
- "Turn this word list into a Mochi deck" - saves the list you pasted, or wrote together in the chat, as a deck. Free, no credit.
- "Make an Anki deck of 20 airport words" - generates it with NextLang's own generator: your level, real translations, no repeated words. One credit, exactly as on the site.
- "Give me the .apkg for this deck" - a one-hour download link to a real Anki package with the pronunciation audio inside, or a
.mochifor Mochi. - "Add 10 more cards to it", "How many Polish decks do I have?", "Do I already have this word?", "How many credits do I have?"
Eleven tools in total, and nothing installed on the device, so it works in the Claude and ChatGPT phone apps too. The chat is the front door now; the deck, the file and the schedule are still NextLang's.
One pipeline, not a second product
The tempting shortcut was a thin wrapper: let the model write the cards and have the connector save whatever comes back. I did not want a second product with its own rules, so the connector goes through the same code the website does. generate_deck calls the same generator runner as the site's server action, through the same validation gauntlet, the same credit charge, the same refund rules and the same daily caps. A deck made in a chat shows up in your results next to the ones you made in the browser, because it is the same row in the same table.
The one rule I would not bend: the user comes only from the verified token. No tool takes a user id as an argument, so no prompt, however cleverly worded, can make the assistant spend somebody else's credits or read somebody else's vocabulary.
// every tool: the user from the token, a per-user limiter, errors that never carry content
server.registerTool(
"generate_deck",
{ title, description, inputSchema: generateDeckInput, annotations: spends },
guarded("generate_deck", userId, "write", (args) =>
generateDeck(userId, args),
),
);
guarded is small and boring on purpose. It picks the right rate limiter for reads and writes, relays expected failures ("not enough credits", "deck not found") as plain text the assistant can repeat, and reports anything unexpected with the tool name only, never the cards or the words.
File links got the same treatment. A download link is a signed token that lives for one hour, not a stored file; every download rechecks that the deck is yours and that the account is premium, builds the package on the fly and serves it uncached. Disconnecting NextLang in your account cuts the assistant off on its very next request, even if the token it holds has not expired yet, because the server checks the consent on every call instead of trusting the token alone.
The tool descriptions are the real API
On a website, the interface is buttons. In a chat, the interface is a paragraph of English that a model reads before deciding what to do. That paragraph turned out to be the hardest part of the whole connector.
So I measured it. The connector has a tool-selection eval: realistic requests in English and Russian, each sent to Claude together with the real tool list, graded on which tool the model reaches for first and with which arguments. Nothing is executed; a full run costs about a dollar. Before launch it held 27 requests, three runs each, and the baseline was 81 out of 81 on Claude Sonnet and 78-79 on Haiku, and every Haiku miss was on the cautious side: it checked the credit balance or asked a question before a paid call it was allowed to make. A miss that spends a credit unasked would count as a regression; there were none.
Then I tested it live, outside the repository, in Claude Code and Codex CLI, and the chat kept finding things the eval had not:
- "What languages am I learning?" Claude answered from my dictionaries. Codex searched the files on my disk, then opened the NextLang website in a browser and read the page. The fix was not in a tool at all: the server now sends a one-sentence instruction on connect, telling the assistant that questions about my words and decks belong to these tools, not the browser.
- "Turn this list into a Mochi deck: dog - perro, cat - gato." One model put the Spanish on the front, the other put the English there and still named the deck "Spanish Basics". The description now says how to tell which language is being learned when the user does not say it.
- "Maybe make me a deck about food?" Claude asked first. Codex spent a credit. A tentative request is now spelled out as not being an explicit one, and there is an eval case that only passes when the assistant asks.
- The level. One model invented "A1-B1" for a deck generated at the default B1. The tool now returns the level it actually used, so the assistant reports it instead of guessing.
Every one of those fixes went into the server - a description, the instruction sent on connect, or what a tool returns - and never into the test; the two an eval can measure became new cases. The lesson I keep relearning: the model is a user, and it does exactly what the words allow.
OAuth is part of the product surface
Connecting an assistant is an OAuth 2.1 flow, and the flow is something the learner sees, so I treated it as product rather than plumbing. Clients register themselves - Claude Code and Codex by pointing at a client metadata document on their own domain, others like Glama's inspector through dynamic registration - which means anyone can register a client called "Claude". The consent screen therefore shows not just the name a client picked for itself but the host the sign-in goes back to. When you connect from Claude Code, it says localhost, and that is correct: the CLI runs a tiny local server to catch the callback.
Two decisions I am glad I made before launch rather than after. The permission scope was renamed from vocabulary to account while only preview accounts were connected, because by then the tools covered decks, files and credits too, and renaming a scope after launch breaks every connected client. And the connector shipped in two steps: first everything - tools, the landing page, the guide, the privacy and terms sections - with an allowlist still on, then a day of testing on the real stack, then the allowlist came off in its own commit.
Launch day was an audit, not a press release
Before announcing anything I ran a full audit of the connector, and it paid for itself.
- The origin answered around Cloudflare. The auth rate limiter trusted Cloudflare's client-IP header, which is only safe if nothing reaches the server except through Cloudflare. It did. A request sent straight to the server could set that header to anything and get a fresh rate-limit bucket every time. It is closed now at the proxy, for this app only, so the other sites on the same server did not notice.
- Errors could carry words. A failed database insert throws an error whose message includes the query parameters - for a saved deck, the whole deck. Error reports now rebuild that error from the SQL text alone, site-wide.
- A popup could not finish signing in. Glama's inspector opens the login in a popup and waits for the code through
window.opener. The site's strict cross-origin opener policy cut that link, so the popup said "Authentication successful" while the inspector never received anything. The sign-in pages now keep the opener; the rest of the site keeps the strict policy. - ChatGPT's directory has commerce rules. Apps may explain that a feature needs more credits or a different plan, but may not link to a page that starts a purchase. Every "buy credits" message in the connector now explains what is missing and links to a plain page about how credits and Premium work.
None of these would have shown up in a demo. All of them would have shown up in week one.
Where it lives now
A connector nobody can find is a private tool, so distribution was part of the work. NextLang is published in the official MCP registry as co.nextlang/nextlang, with the domain proven by a DNS record. Glama picked it up from there and runs a health check against a test account; Smithery lists it and reads the tools from a static server card, because a crawler cannot log in to see them. There is an open pull request to a community list of remote MCP servers, and the ChatGPT app directory submission is in progress. Anthropic's own directory is the one I cannot join yet: it only accepts submissions from Team and Enterprise organizations.
The connector page (opens in a new tab) has the server address, the setup steps for Claude and ChatGPT and every phrase you can use, and the guide (opens in a new tab) walks through the first deck end to end.
What it taught me, as the engineer and as the owner
As an engineer, the surprise was where the work actually was. The server itself is small: a stateless HTTP endpoint, a handful of tools that call code that already existed. The effort went into everything around it - the consent screen, the rate limits, the signed links, the words in the descriptions, the eval that measures them, and the audit that caught what the tests could not. An MCP server is less an API than a product with a model for a user.
As the owner, it changed how I think about where NextLang is. For a year the question was how to get people to the site. The connector flips it: the site is where the data, the schedule and the files live, and the chat is one more place to use them. The pipeline, the credits and the rules stayed exactly the same, and that is the part I am proudest of - a new front door without a second product behind it.
If you want to try it, the setup takes a minute from the connector page (opens in a new tab). If you are building an MCP server of your own and want to compare notes on evals or OAuth, LinkedIn (opens in a new tab) is the fastest route.