A read-only MCP server that lets Claude, or any MCP client, search and read LessWrong and the Alignment Forum: a post, its comment thread or one comment, a user's profile, a tag's wiki page, a sequence's ordered posts, a front-page feed, or a precise slice of posts by author, tag, date and karma. It only reads the sites' public endpoints, so there's no API key and no login.
LessWrong and the Alignment Forum run on the same
ForumMagnum codebase and share a database, so
this one server covers both, and search spans both at once. The Alignment Forum is really a
curated slice of LessWrong rather than a separate site, so everything on it is on LessWrong
too. Every tool but lw_search takes an optional site (lesswrong by default, or
alignmentforum): on alignmentforum the feeds, comment threads and lw_filter_posts cover
only Alignment Forum content. Post karma is LessWrong's score on either site.
Download
lesswrong-mcp.mcpb
(always the newest release's bundle, also attached to the latest
release) and double-click it, or install it from Settings → Extensions →
Advanced settings → Install Extension…. All eight tools show up right away. Everything runs
locally: the bundle uses Desktop's uv runtime, which installs Python and the dependencies
for you.
To update, download the bundle from the latest release and install it. If Settings → Extensions still shows the older version afterwards, remove the LessWrong & Alignment Forum extension there and install the new file again. Editing or rebuilding a checkout doesn't touch the copy Claude Desktop runs.
With uv installed:
claude mcp add --transport stdio --scope user lesswrong -- \
uvx --from git+https://github.com/seankessinger/lesswrong-mcp lesswrong-mcp--scope user makes it available in every project; leave it out to add it to the current
project only.
Any client that launches stdio servers can run it the same way:
{
"mcpServers": {
"lesswrong": {
"command": "uvx",
"args": ["--from", "git+https://github.com/seankessinger/lesswrong-mcp", "lesswrong-mcp"]
}
}
}From a checkout, python -m lesswrong_mcp serves the same thing over stdio (see
Running it locally).
Asked for the Alignment Forum's 2023 interpretability posts with the highest LessWrong karma,
Claude can answer with a single lw_filter_posts call:
{"tags": ["interpretability-ml-and-ai"], "after": "2023-01-01", "before": "2023-12-31",
"alignment_forum_only": true, "sort": "top", "limit": 3}# Filtered posts (3) — tags=interpretability-ml-and-ai, after=2023-01-01, before=2023-12-31, alignment_forum_only=true, sort=top
- [SolidGoldMagikarp (plus, prompt generation)](https://www.lesswrong.com/api/post/aPeJE8bSo6rAFoLqg) — author: [Jessica Rumbelow](https://www.lesswrong.com/api/user/jessica-rumbelow) | karma: 682 | 2023-02-05 | comments: 208
- [Steering GPT-2-XL by adding an activation vector](https://www.lesswrong.com/api/post/5spBue2z2tw4JuDCx) — author: [TurnTrout](https://www.lesswrong.com/api/user/turntrout) | karma: 443 | 2023-05-13 | comments: 98
- [Against Almost Every Theory of Impact of Interpretability](https://www.lesswrong.com/api/post/LNA8mubrByG7SFacm) — author: [Charbel-Raphaël](https://www.lesswrong.com/api/user/charbel-raphael) | karma: 337 | 2023-08-17 | comments: 93
---
[More results — call again with offset=3 to continue.]Each title links the post's Markdown route and each author their profile's, so the next step is
lw_get_post on one of those ids, lw_get_comments for its discussion, or lw_get_user (or
lw_filter_posts(author=…)) on an author's slug.
| Tool | What it does |
|---|---|
lw_search |
Full-text search over posts, comments, tags, users, and sequences, across both forums (pass content_type to search just one kind) |
lw_get_post |
A post's body and metadata, by id or slug |
lw_get_comments |
A post's comment thread, sorted by top, new, or old, or one comment by comment_id |
lw_get_user |
Someone's profile and their best posts |
lw_get_tag |
A tag or wiki page and the posts filed under it |
lw_get_sequence |
A sequence's intro and its ordered list of posts |
lw_list_feed |
A front-page feed: latest, recent, curated, or home |
lw_filter_posts |
Posts filtered by author, tags (all required at once), date range, and karma, with a sort order (Markdown or JSON) |
Most of these just wrap the forum's Markdown API, which the site publishes for exactly
this kind of use, so the responses come back as clean Markdown already. lw_filter_posts
is the one exception: it goes through GraphQL, because that's the only way to get the
precise author + tag + date + karma filtering.
- It's read-only. The forum API can also create and edit drafts, but those endpoints are deliberately left out here.
- Every tool takes bare values: a post
_idor slug, a comment_id, a user or tag slug, a sequence_id, each the segment that follows its kind in the link a result carries for it:/api/post/<x>,/users/<slug>,/w/<slug>,/s/<id>, and/comments/<cid>for a comment (in/posts/<id>/<slug>, the<id>). A link, path or?queryis refused with a message saying which part to pass. - Pagination works a little differently per tool:
lw_searchtakespageandlimit(and ends with a next-page hint while results remain),lw_filter_poststakeslimitandoffset, andlw_get_commentstakeslimit(a comment count, up to 2000, since some threads run to hundreds of comments).lw_get_postandlw_get_commentsreturn at most 40,000 characters of Markdown per call (max_charscan lower that, not raise it), since a client may hide a larger result from the model; a long one is paged withoffset— when a slice stops short, a footer gives the exactoffsetto pass next.lw_filter_postssignals paging the same way: its JSON reportshas_morewith thenext_offsetto pass next, ordepth_limitedonce results run past the backend's ~2000-deep cap (narrow the filters to go further), and it mirrors that as a one-line footer in Markdown. Itscountis the number of posts on that page, not a total. Thehomefeed is the one feed that ignoreslimit: it's a fixed composite page, so reach forlatest/recent/curatedwhen you want a set count. - Comments come back most complete from the default
lesswrongsite, which returns the whole thread. Thealignmentforumview of a post only shows the comments promoted to the Alignment Forum, so it's a subset. For a single comment, passlw_get_commentsitscomment_id: it comes back on its own, without its replies. - Search always covers both forums and can't be narrowed to one, since they share a
single index. For Alignment-Forum-promoted posts, reach for
lw_filter_postswithalignment_forum_only: true(orsite: "alignmentforum", which returns only those). Its karma,min_karmaandsort: "top"use LessWrong's score on either site, not the Alignment Forum's own. - It tries not to be rude to the forum: in-flight requests are capped, and it backs off and retries on rate limits (429), transient server errors and network failures.
- What you ask goes to the forum and nowhere else: search text, ids and slugs are sent to
lesswrong.com or alignmentforum.org, and the server has no telemetry.
manifest.jsonlists LessWrong's privacy policy, the one LessWrong's FAQ links to.
Claude Desktop says "Could not connect to MCP server". There are two known causes, and on
macOS the logs under ~/Library/Logs/Claude/ tell them apart:
- Desktop can't find
uv. It runs this extension throughuv, andmain.logrecords the launch (look for the[UV Discovery]and[UV Runtime]lines). On macOS an app opened from the Dock or Finder gets the system's barePATH, without~/.local/binor Homebrew, so auvinstalled there can be invisible to it. Adding that directory to the PATH GUI apps inherit fixes it, e.g.sudo launchctl config user path "$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"and a reboot. - The extension's display name contains a
/. Desktop builds a log-file path from that name and refuses to connect a server whose name would escape it.main.logthen reports a healthy connection, and the real error (path escape: "…") appears only inclaude.ai-web.log. This bundle is namedLessWrong & Alignment Forum; if yours shows a slash, remove it and install the current bundle, and keep/out ofdisplay_nameif you repackage it.
It needs Python 3.11 or newer.
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
python -m lesswrong_mcp # stdio (any MCP client, or the MCP Inspector)It serves stdio only. The install also provides a lesswrong-mcp command that does the same
as python -m lesswrong_mcp. These optional environment variables tune it:
| Variable | Default | Effect |
|---|---|---|
LW_HTTP_TIMEOUT |
30 |
Per-operation timeout for requests to the forum, in seconds |
LW_HTTP_TOTAL_TIMEOUT |
60 |
Deadline for each attempt's whole round trip, in seconds |
LW_MAX_RETRIES |
2 |
Retries after the first attempt, on a 429, 500, 502, 503 or 504, a network error or a missed deadline; 0 disables them |
LW_CONCURRENCY |
4 |
Maximum concurrent requests to the forum |
LW_LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR or CRITICAL |
A malformed or out-of-range LW_* value falls back to its default, with a warning on stderr.
Logging goes to stderr, never stdout, which carries the protocol stream. Retries, giving
up, a Retry-After over 60 seconds (the server stops retrying rather than wait that long) and
refused redirects are logged as warnings that name the request's path only. A failed tool call
is logged once at INFO, naming the tool and, for arguments the schema rejects, only their
names, never their values. An unexpected fault is logged at ERROR with its traceback, and the
client is told only which tool failed. httpx's own per-request lines are kept out at every
level, since their full URLs carry search queries.
pip install -e '.[dev]' # pytest + ruff + mypy
pytest # hermetic: mocks the HTTP/GraphQL layer, no network
LW_LIVE_TESTS=1 pytest -m live # just the opt-in live-endpoint tests, against the real API
ruff check . && mypy lesswrong_mcp # lint + type-check (also run in CI)The suite lives in tests/ and is hermetic — it stubs the HTTP/GraphQL layer and drives
the async tool handlers directly, so it runs offline and fast. A guard fails any test that
reaches the network unless it is marked live, and only LW_LIVE_TESTS=1 (or
true/yes) runs those. It covers the shared HTTP layer (retry/backoff, Retry-After,
the concurrency cap, the per-attempt deadline, the SSRF redirect guard, and the headers
and query each request carries on the wire), the GraphQL plumbing (error surfacing and
slug→id resolution), each tool's routing and paging, and the selector that
lw_filter_posts builds — in particular that a tag rides on the requested sort view as a
filterSettings "Required" tag, composing with any sort order and the other filters.
Through an in-process MCP client, on both protocol eras the server speaks (the 2025-11-25
handshake and 2026-07-28), it also checks the contract every registered tool keeps: each
sends its own route and parameters to the requested site, failures arrive as tool errors,
results are plain text, unknown or out-of-range arguments are rejected before any request,
and every identifier parameter takes only a bare id or slug, so a link, path or display name
is refused with advice for that kind of value. It also runs python -m lesswrong_mcp as a real
stdio server in a subprocess, which answers an initialize request and exits at end of input,
and checks manifest.json's hand-kept copies (version, tool list, Python range, license,
author, homepage) against pyproject.toml and the registered tools. The opt-in live tests make
one request per Markdown route, check the upstream wording lw_search parses, check that its
results link the ids and slugs the tools take, check that the tag filter of lw_filter_posts
restricts every sort order, and check that top orders posts by karma. CI runs them weekly and
on demand, next to a weekly re-run of the whole suite against the newest dependencies.
mkdir -p dist
npx @anthropic-ai/mcpb@2.1.2 pack . dist/lesswrong-mcp.mcpbmanifest.json and pyproject.toml describe the bundle, and .mcpbignore keeps everything
it doesn't run (the tests, the docs, and so on) out of it.
The bundle ships unsigned. Don't run mcpb sign --self-signed on it: mcpb appends its
signature block after the zip's end-of-central-directory record without recording it as a
zip comment, so strict zip parsers — including the desktop extension installer — reject the
signed file (Invalid comment length … extra bytes at the end). Self-signing also only
yields an "unverified publisher" bundle, so it buys nothing here. If you ever need a
genuinely trusted signature, use a CA-issued certificate (mcpb sign --cert/--key) and
confirm the result still installs.
MIT