{
  "markdown": "# vocabit-mcp\n\n[![CI](https://github.com/JohnBilousov/vocabit-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/JohnBilousov/vocabit-mcp/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/vocabit-mcp)](https://www.npmjs.com/package/vocabit-mcp)\n[![license](https://img.shields.io/npm/l/vocabit-mcp)](./LICENSE)\n\nAn MCP server for [Vocabit](https://apps.apple.com/app/id6758019550), a flashcard app.\nIt lets an AI assistant **write a study set into a real app on a real phone, and then read\nback how the learner actually did with it**.\n\nMost MCP servers read from an API. This one closes a loop:\n\n```mermaid\nflowchart LR\n    A[\"Assistant<br/>teaches a topic\"] --> B[\"create_study_set\"]\n    B --> C[\"Set appears in the<br/>Vocabit app\"]\n    C --> D[\"Learner works<br/>through it\"]\n    D --> E[\"get_set_results\"]\n    E -->|weak cards| A\n```\n\nThe interesting tool is not `create_study_set` — anything can generate flashcards.\nIt is `get_set_results`: which cards the learner marked *hard*, which they never reached,\nhow many reviews each one took. The next set is built out of that, not out of a guess.\n\n## Try it in 30 seconds\n\nNo backend, no account, no API key:\n\n```bash\nnpx -y vocabit-mcp --demo\n```\n\nDemo mode runs the same server against an in-memory Vocabit with two seeded sets.\nCreate a set, ask for results, and a deterministic stand-in learner will have worked\nthrough it — flagged in the response as simulated, so it is never mistaken for real data.\n\nTo poke at it with a UI:\n\n```bash\nnpx @modelcontextprotocol/inspector npx -y vocabit-mcp --demo\n```\n\n## Install\n\nListed in the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.JohnBilousov/vocabit-mcp`, so clients that read the registry can find it on their own.\n\n<details open>\n<summary><b>Claude Code</b></summary>\n\n```bash\nclaude mcp add vocabit -- npx -y vocabit-mcp\n```\n\n</details>\n\n<details>\n<summary><b>Claude Desktop / any MCP client</b></summary>\n\n```json\n{\n  \"mcpServers\": {\n    \"vocabit\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"vocabit-mcp\"],\n      \"env\": {\n        \"VOCABIT_BASE_URL\": \"https://your-vocabit-backend.example.com\",\n        \"VOCABIT_AGENT_KEY\": \"your-agent-key\"\n      }\n    }\n  }\n}\n```\n\nDrop the `env` block to run in demo mode.\n\n</details>\n\n## Tools\n\n| Tool | What it does |\n|---|---|\n| `vocabit_health` | Check the connection and which mode the server is in. |\n| `create_study_set` | Publish a set to the learner's app. Returns a deep link that opens it on the device. |\n| `list_study_sets` | Recent sets, newest first, each with a progress summary. |\n| `get_study_set` | Full contents of one set, plus the topic and notes the assistant attached. |\n| `get_set_results` | **The feedback half.** Per-card status, `weakCards`, `untouchedCards`, due cards. |\n| `update_study_set` | Retitle, retag, or append cards — typically the follow-up after reading results. |\n| `notify_learner` | Telegram ping that a set is waiting. |\n| `delete_study_set` | Remove a set from the app. Study history is kept. |\n\nAlso exposed: the `vocabit://set/{setId}` **resource** (a set as JSON, listable) and a\n`study-session` **prompt** that walks the whole loop.\n\n### Card states\n\nProgress comes from the app's spaced-repetition engine, not from the assistant:\n\n| Status | Meaning |\n|---|---|\n| `new` | Never reviewed. |\n| `struggling` | Learner marked it *hard*. |\n| `learning` | Marked *good*. |\n| `mastered` | Marked *easy*. |\n\nA set reports `completed: true` once no card is left in `new`.\n\n## Live mode\n\nPoint the server at a Vocabit backend that has the agent API enabled:\n\n```bash\nexport VOCABIT_BASE_URL=https://your-vocabit-backend.example.com\nexport VOCABIT_AGENT_KEY=...   # must match one of AGENT_API_KEYS on the backend\nnpx -y vocabit-mcp\n```\n\n| Variable | Purpose |\n|---|---|\n| `VOCABIT_BASE_URL` | Backend base URL. |\n| `VOCABIT_AGENT_KEY` | Sent as `X-Agent-Key`. |\n| `VOCABIT_USER_ID` | Firebase UID of the learner. Optional; the backend has a default. |\n| `VOCABIT_TERM_LANGUAGE` / `VOCABIT_DEFINITION_LANGUAGE` | Defaults for new sets, e.g. `de` / `en`. |\n| `VOCABIT_TELEGRAM_ID` | Recipient for `notify_learner`. |\n| `VOCABIT_TIMEOUT_MS` | Request timeout, default `20000`. |\n| `VOCABIT_DEMO` | `1` forces demo mode. |\n\nSet neither URL nor key and the server starts in demo mode. Set exactly one and it\nrefuses to start — half a configuration is a mistake, not a hint.\n\n## Design notes\n\n**Demo mode is a first-class client, not a stub.** `HttpVocabitClient` and\n`DemoVocabitClient` implement the same `VocabitClient` interface, so no tool has a\nbranch for \"are we pretending?\". A reviewer can run the server before they have\ncredentials, and the test suite exercises the real tool surface over a real MCP\ntransport rather than mocking the SDK.\n\n**Errors are recoverable, not fatal.** A failed call comes back as `isError` with the\nbackend's own message plus a hint aimed at the model — `404` says \"call\n`list_study_sets` to see which sets exist\", `401` says \"or run with `VOCABIT_DEMO=1`\".\nMutually exclusive arguments are rejected with an explanation instead of a guess.\n\n**Output schemas stay loose on the edges.** Identifying fields are required; everything\nelse is optional, so a backend that grows a field does not turn a working tool into a\nvalidation error.\n\n**Annotations are honest.** `delete_study_set` is marked `destructiveHint`, the read\ntools `readOnlyHint`. `notify_learner` messages a real person, and its description says\nto use it sparingly.\n\n## Development\n\n```bash\ngit clone https://github.com/JohnBilousov/vocabit-mcp && cd vocabit-mcp\nnpm install\nnpm run build\nnpm test          # tool surface + full loop, plus the HTTP client against a mocked fetch\nnpm run lint      # eslint\nnpm run format    # prettier --write\nnpm run inspect   # demo mode in the MCP Inspector\n```\n\nCI runs `typecheck`, `lint`, `format:check`, `test`, and `build` on every push and pull request.\n\n```\nsrc/\n  index.ts        CLI entry, stdio transport\n  config.ts       env → Config, demo-mode resolution\n  server.ts       tool / resource / prompt registration\n  schemas.ts      zod input and output shapes\n  format.ts       human-readable summaries next to structuredContent\n  client/\n    types.ts      wire types + VocabitClient contract\n    http.ts       live backend\n    mock.ts       in-memory backend for demo mode\ntest/\n  server.test.ts       tool surface + full loop — over an in-memory MCP transport\n  client/\n    http.test.ts       query encoding, error-body parsing, timeouts — against a mocked fetch\n```\n\n## Releasing\n\nPublishing uses npm's [trusted publishing](https://docs.npmjs.com/trusted-publishers/) (OIDC) —\nno `NPM_TOKEN` secret, nothing that can leak or expire. One-time setup on npmjs.com, under the\npackage's **Settings → Trusted publishing → GitHub Actions**: organization `JohnBilousov`, this\nrepository, workflow filename `publish.yml`.\n\nTo cut a release: bump the version in `package.json`, `server.json`, and `VERSION` in\n`src/server.ts` together (a test asserts they can't drift), commit, push, then publish a GitHub\nRelease with a matching `vX.Y.Z` tag. That triggers\n[`.github/workflows/publish.yml`](.github/workflows/publish.yml), which runs the test suite and\npublishes to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements) — the\npackage page shows a verified link back to this exact commit and workflow run, not just a name on\nthe registry.\n\n## Roadmap\n\n- [ ] Streamable HTTP transport alongside stdio\n- [ ] Multi-learner support without a backend default UID\n- [ ] Audio pronunciation cards\n\n## License\n\nMIT © Ivan Bilousov\n",
  "bytes": 7616,
  "sha": "3257056b3ebd55242c68055b7a653abbd92db081daa21a7b0c0542e1661309cf",
  "repo_slug": "johnbilousov/vocabit-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_johnbilousov_vocabit_mcp_caac57fa/readme"
}