{
  "markdown": "# xkcdai\n\nAn **MCP server** that surfaces the right [xkcd](https://xkcd.com) comic during a\nconversation, if one is relevant.\n\n> **Live connector:** `https://xkcdai.onrender.com/mcp` — add it in claude.ai →\n> Settings → Connectors. See [Use the deployed MCP server](#use-the-deployed-mcp-server-as-custom-connector).\n\nIt builds a semantic index over every xkcd comic (title + mouseover text +\ntranscript) using on-device embeddings, then exposes a single `find_xkcd` tool.\nA Claude conversation can call it whenever the topic feels xkcd-shaped; a\nrelevance threshold means weak matches return nothing, so it stays quiet instead\nof forcing a tenuous reference.\n\nThe fetched transcripts, explanations, and the embeddings are currently committed in this repo, under [data/](data/).\n\n\n## How it works\n\n```\nxkcd JSON API ─┐\n               ├─► comics.json + explain.json ─► embeddings.npy ─► find_xkcd ─► Claude\nexplainxkcd  ──┘        (cache)                    (bge-small)     (cosine)    (mentions it\n (transcripts +                                                                 if it fits)\n  explanations)\n```\n\n- **Data:** title + mouseover alt from the official API, plus the community\n  **transcript** and **explanation** from [explainxkcd.com](https://www.explainxkcd.com).\n  The explainxkcd context is essential: the official API dropped transcripts\n  around comic ~1675, so without it the most-shared modern comics (e.g. #2347\n  *Dependency*) are unmatchable — their joke text lives only inside the image.\n- **Embeddings:** `fastembed` (ONNX) with `BAAI/bge-small-en-v1.5` — local, free,\n  offline after first download, no PyTorch. Swap the model in `src/xkcdai/embed.py`\n  (e.g. `BAAI/bge-base-en-v1.5` for marginally better ranking at ~3× the size).\n- **Search:** a normalized numpy matrix + dot product. No vector DB needed for a\n  few thousand comics.\n- **Restraint:** because xkcd has a comic for *almost everything*, a similarity\n  cutoff alone can't judge relevance. `min_score` (default `0.62`) is just a coarse\n  floor; the real \"should I bring this up?\" decision is made by the calling model,\n  guided by the score bands documented on the `find_xkcd` tool.\n\n\n## Use the deployed MCP server (as custom connector)\n\nThe server is deployed at **https://xkcdai.onrender.com** on Render. Add it as a Claude\n**custom connector** to use it in the Claude web and mobile apps (note: the Free plan only allows one custom connector).\nAnyone can add the same URL in their own account.\n\nIn **claude.ai** (web — do this once; it then syncs to the mobile app):\n\n1. **Settings → Connectors → Add custom connector**.\n2. Paste the connector URL, **including the `/mcp` path**: `https://xkcdai.onrender.com/mcp`\n3. Leave OAuth blank (this server needs no auth) and click **Add**.\n4. The connector's `find_xkcd` tool is now available in chats, on desktop and phone.\n   For Claude to suggest comics on its own, also add the instruction from\n   [Make Claude suggest comics proactively](#make-claude-suggest-comics-proactively)\n   to your Profile preferences.\n\n**Notes**\n- The server is **public and unauthenticated** — fine here (read-only comic search,\n  no secrets). Don't reuse this pattern for anything sensitive without OAuth.\n- Hosted from this repo via the [Dockerfile](Dockerfile) and [render.yaml](render.yaml);\n  pushes to `main` auto-redeploy.\n- `GET /healthz` reports liveness, whether the index is loaded, and current RSS —\n  handy on a 512 MB instance:\n  ```bash\n  curl https://xkcdai.onrender.com/healthz\n  ```\n\n\n## Local setup\n\n```bash\npython -m venv .venv\n# Windows (PowerShell):  .venv\\Scripts\\Activate.ps1\n# macOS/Linux:           source .venv/bin/activate\npip install -e .\n\n# Fetch comics + their explainxkcd context, then embed (downloads the model once).\n# First run ~10 min; re-running later only fetches what's new.\nxkcdai build\n```\n\nAdd `--no-enrich` to skip the explainxkcd fetch (faster/offline, weaker matches).\n\nTest it from the command line:\n\n```bash\nxkcdai search \"my code finally compiled after an hour\"\nxkcdai search \"arguing about the correct date format\"\nxkcdai search \"spent more time automating it than doing it by hand\"\n```\n\n\n## Use locally as an MCP server\n\nThe server runs over stdio. Point your MCP host at it.\n\n**Claude Desktop** (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"xkcdai\": {\n      \"command\": \"C:\\\\your\\\\path\\\\to\\\\xkcdai\\\\.venv\\\\Scripts\\\\xkcdai-server.exe\",\n      \"env\": { \"XKCDAI_DATA_DIR\": \"C:\\\\your\\\\path\\\\to\\\\xkcdai\\\\data\" }\n    }\n  }\n}\n```\n\n**Claude Code** (`-s user` makes it available in every project, not just this folder):\n\n```bash\nclaude mcp add xkcdai -s user -e XKCDAI_DATA_DIR=C:\\your\\path\\to\\xkcdai\\data -- C:\\your\\path\\to\\xkcdai\\.venv\\Scripts\\xkcdai-server.exe\n```\n\nAlways set `XKCDAI_DATA_DIR`, since the host launches the server from an arbitrary\nworking directory.\n\n> MCP only gives Claude the *ability* to call `find_xkcd` — it won't volunteer\n> comics on its own. See [Make Claude suggest comics proactively](#make-claude-suggest-comics-proactively).\n\n\n## Make Claude suggest comics proactively\n\nConnecting the server only gives Claude the *ability* to call `find_xkcd`; it\nwon't reach for it unprompted. To make Claude volunteer comics, paste the\ninstruction below wherever that Claude reads persistent instructions:\n\n- **Claude Code** — your global `~/.claude/CLAUDE.md` (applies everywhere) or a\n  per-repo `CLAUDE.md`; restart the session to load changes.\n- **Claude.ai / Claude Desktop** — Settings → Profile → *\"What personal preferences\n  should Claude consider in responses?\"* (every plan, including free; syncs to the\n  mobile app). Each person who uses the connector adds it in their own account.\n\n```text\nWhen a conversation naturally lands on a topic xkcd is known for — programming,\nscience, math, statistics, engineering, the absurdity of standards, relationships,\neveryday life — call the find_xkcd tool (xkcdai) with a short phrase describing the\ntopic. Then judge whether to bring it up:\n- score >= 0.75 — strong match; mention it if it fits the moment\n- 0.66-0.75 — only if it genuinely lands\n- below that — stay silent\nWhen you share one, give just that single comic: its number and title, its URL, and\nquote the alt (mouseover) text — that's half the joke. At most one comic per topic,\nand never force a tangential reference. When in doubt, say nothing.\n```\n\nIt's still Claude's judgment, so it won't fire on every borderline topic — asking\n*\"is there an xkcd for this?\"* always triggers a lookup.\n\n\n## Not just Claude — works with any MCP client\n\nThe examples above use Claude, but `find_xkcd` is a standard\n[Model Context Protocol](https://modelcontextprotocol.io) tool, so any MCP-capable\nhost can use it. Point the client at either transport:\n\n- **stdio:** run `xkcdai-server` locally (see [Use locally as an MCP server](#use-locally-as-an-mcp-server)), or\n- **HTTP:** the deployed URL `https://xkcdai.onrender.com/mcp`.\n\nThe server is **LLM-agnostic** internally, too: matching runs on a local embedding model.\nOnly the host-specific bits differ — how you register the server, and where you put\nthe \"suggest a comic when it fits\" instruction (each client has its own\nsystem-prompt / rules mechanism, e.g. Cursor Rules or a VS Code `.instructions` file).\n\n\n## Configuration\n\n- `XKCDAI_DATA_DIR` — where `comics.json`, `explain.json`, `embeddings.npy`, and\n  `index.json` live.\n- `find_xkcd(context, max_results=3, min_score=0.62)` — lower `min_score` for more\n  (looser) suggestions, raise it to be stricter.\n\n\n## Maintenance\n\nRe-run `xkcdai build` periodically to pick up new comics — it incrementally fetches\nnew comics and their explainxkcd context, then re-embeds:\n\n```bash\nxkcdai build\n```\n\nUse `--force` to rebuild everything from scratch, or `--no-enrich` to skip the\nexplainxkcd fetch. `xkcdai enrich` fetches only the explainxkcd context.\n\n\n## Credits & licensing\n\nThis project bundles content from two sources, each under its own license, so the\n**code** and the **data** are licensed separately:\n\n- **Code** (`src/`, `Dockerfile`, etc.) — [MIT](LICENSE).\n- **Comics & mouseover text** — © [Randall Munroe / xkcd](https://xkcd.com),\n  licensed [CC BY-NC 2.5](https://xkcd.com/license.html): **non-commercial**, with\n  attribution.\n- **Transcripts & explanations** (cached in [data/](data/)) — from\n  [explainxkcd.com](https://www.explainxkcd.com), licensed\n  [CC BY-SA 3.0](https://www.explainxkcd.com/wiki/index.php/explain_xkcd:Copyrights):\n  redistributed here under the same license, with attribution.\n\nBecause `data/` mixes xkcd's NonCommercial content with explainxkcd's ShareAlike\ncontent, treat the **data as non-commercial** and keep any redistribution under\nthese terms. The MIT license covers the source code only — not `data/`. At\nruntime, `find_xkcd` results link back to both xkcd and explainxkcd for per-item\nattribution.\n\nThis is an unofficial fan project, not affiliated with or endorsed by xkcd or explainxkcd.\n",
  "bytes": 8949,
  "sha": "e04c9c8fad6a92d57a291499c58b6abdf2da05d25d543811ddf62ddfd941f7bd",
  "repo_slug": "papjuli/xkcdai",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_papjuli_xkcdai_ed472bfe/readme"
}