A side-by-side look at the Ao3 MCP and MCP Imslp MCP servers: transport, auth, tools, quality signals, and the config to install either one in Claude, Cursor, Windsurf or VS Code.
Verdict: choose Ao3 MCP for art & culture work as a local process; choose MCP Imslp for art & culture as a local process. You can also run both side by side in one mcpServers block.
GEMINI_API_KEY, GEMINI_MODEL, GEMINI_MODEL_BACKUP, AO3_MIN_INTERVAL.search_works, find_tags, get_work.search_works, search_people, get_work.Rows where the two servers differ are marked.
| Spec | ||
|---|---|---|
| Category | Art & Culture | Art & Culture |
| Quality signal | 60/100 · Good | 60/100 · Good |
| Install command | Confirmed | Confirmed |
| Transport | Local subprocess (stdio) | Local subprocess (stdio) |
| Auth | API key required | Not declared |
| Pricing | Free | Not declared |
| Required env vars | GEMINI_API_KEYGEMINI_MODELGEMINI_MODEL_BACKUPAO3_MIN_INTERVAL | None listed |
| Verified owner | Community listing | Community listing |
| GitHub stars | 2 | 0 |
| Directory activity | 10 views 3 installs 0 upvotes | 2 views 0 installs 0 upvotes |
| Tools | 5 | 7 |
| Full listing | View Ao3 MCP | View MCP Imslp |
search_worksSearch AO3 for works. All filters optional; combine freely.
RECOMMENDATION WORKFLOW — reading before recommending is MANDATORY, and the
reading is done by a SEPARATE model, not you. Blurbs are author-written ads;
never recommend, rank, or summarize a fic from its blurb alone. Cast a wide
net (pages=2-3, i.e. 40-60 blurbs), shortlist the promising ones, then hand
the top ≤20 ids to `read_works` — a second AI reads them and reports back.
Recommend ONLY fics that came back from `read_works`. Do not read fic text
yourself; delegating it is the entire point of this server.
SEARCH STRATEGY — searching is cheap and reading is delegated, so the
winning move is always to OVER-FETCH and let `read_works` brute-force the
shortlist, never to craft one perfect narrow query. Filters multiply: each
one you add cuts the pool, and stacked filters routinely cut it to zero.
USE WILDCARDS LIBERALLY — abuse them. A `*` matches any run of characters
and works in EVERY name field (`fandom`, `relationship`, `character`, `tags`)
and in `query`. Wrapping a term in stars is the single best defence against
AO3's exact-canonical-name trap: `fandom="Genshin Impact (Video Game)"`
returns ZERO (the canonical tag is actually "原神 | Genshin Impact (Video
Game)"), but `fandom="*Genshin Impact*"` returns the whole fandom. Likewise
`relationship="*Kazuha*Scaramouche*"`, `tags="*Enemies to Lovers*"`. When you
don't know the exact canonical name — which is most of the time — reach for a
wildcard first instead of guessing the literal string.
IF YOU GET 0 (or few) RESULTS, that is almost always your query being too
narrow, NOT the content missing from AO3. Recover instead of giving up:
- FIRST, wildcard the name fields (`*Genshin Impact*`). This fixes the most
common cause — an exact-match field that didn't match the canonical tag —
in one retry, without a separate `find_tags` round-trip.
- Still unsure of a name? `find_tags` resolves it, or move the idea into
`query` as free text (fuzzy, no canonical spelling needed).
- Drop filters one at a time and retry: `word_count` first, then
`complete_only`, then `rating`. Re-add only what the user insisted on.
- Concepts don't need to be tags at all: "slow burn rivals in a bakery"
works fine as free-text `query` even if no such tag exists.
- Still thin? Search the broad version (fandom + category, sort by kudos),
fetch 2-3 pages, and let the blurbs + `read_works` do the filtering.
A human reader has to search narrowly because they can only read a few
fics; you can read twenty at once, so breadth costs you nothing.
Results show numeric work ids, not URLs. When relaying a work to the
user, build the link yourself: https://archiveofourown.org/works/{id}
Each result shows a kudos-to-hits ratio (k/h) — AO3's most honest quality
proxy, since kudos are one-per-reader but hits count every visit. Compare
it only within similar works: multi-chapter fics accumulate hits on every
chapter visit, so long WIPs run structurally lower ratios than one-shots.
Args:
query: free-text search. Supports AO3's full operator syntax
(case-sensitive, space after colon required where shown):
`"exact phrase"`, `AND` / `OR` / `NOT`, `-term` to exclude;
`words>10000`, `words:1000-5000`, `kudos>500` (same for hits/
comments/bookmarks); `sort:kudos`, `sort:hits`, `sort:>posted`
(oldest first); `otp: true` (exactly one ship, no side pairings);
`creators: username` / `-creators: username`; `summary: "phrase"`;
`expected_number_of_chapters: 1` (one-shots only);
`series.title: *` (part of a series); `language_id: en`.
Also supports `*` wildcards, e.g. `*coffee shop*`.
⚠️ query is a FULL-TEXT match on the fic body, AND'd with every
other filter — so it narrows HARD. Do NOT stuff mood/concept
synonyms here ("nuzzle OR forehead kiss OR won't let go"): that
demands the prose literally contain one of those strings on top of
your tag/fandom filters, and routinely collapses a healthy 60-result
search to 0. Concepts belong in `tags` (wildcarded), not here. Use
query for author names, quoted title/summary phrases, or the numeric
operators above — leave it EMPTY when a tag already covers the vibe.
title: words in the work title.
author: author/creator name.
fandom: fandom name, e.g. "Naruto" (comma-separate several). Exact
canonical match — but `*` wildcards work here: prefer
"*Genshin Impact*" over the literal name to survive canonical tags
with prefixes/aliases (e.g. "原神 | Genshin Impact (Video Game)").
relationship: ship tag. Format: "A/B" romantic, "A & B" platonic,
canonical name order, e.g. "Kakashi Hatake/Iruka Umino". Wildcards
work: "*Kazuha*Scaramouche*" beats guessing the exact tag order.
character: character name(s), comma-separated. Wildcards work here too.
tags: freeform tags, comma-separated, EXACT canonical spelling
(use find_tags to resolve, or wildcard it: "*Enemies to Lovers*"). Popular canonical tags: Fluff; Angst;
Hurt/Comfort; Emotional Hurt/Comfort; Angst with a Happy Ending;
Hurt No Comfort; Enemies to Lovers; Friends to Lovers; Enemies to
Friends to Lovers; Slow Burn; Mutual Pining; Fake/Pretend
Relationship; There Was Only One Bed; Idiots in Love; Getting
Together; Established Relationship; First Kiss; Found Family;
Fix-It; Time Travel; Kid Fic; Domestic Fluff; Tooth-Rotting Fluff;
Crack; Crack Treated Seriously; 5+1 Things; POV Outsider; Soulmates;
Smut; Plot What Plot/Porn Without Plot; Alpha/Beta/Omega Dynamics;
Dead Dove: Do Not Eat; Canon Compliant; Post-Canon; Alternate
Universe - Modern Setting; Alternate Universe - Canon Divergence;
Alternate Universe - Coffee Shops & Cafés; Alternate Universe -
College/University; Alternate Universe - Soulmates.
rating: one of: general, teen, mature, explicit, not rated.
categories: comma-separated relationship categories to include:
F/F, F/M, Gen, M/M, Multi, Other. Empty = all.
complete_only: only finished works.
word_count: range like "10000-50000", ">5000" or "<20000".
sort_by: relevance | kudos | hits | comments | bookmarks | words | date_updated | date_posted.
page: which result page to start from (for paging through results).
pages: result pages to fetch, 20 works each (1-5). For a targeted
lookup 1 is enough; for a recommendation hunt fetch 2-3 pages
(40-60 blurbs) so the read_works shortlist has real competition.
find_tagsResolve fuzzy wording to canonical AO3 tag names (live autocomplete).
Use before search_works when unsure of exact spelling — e.g. "coffee shop"
resolves to "Alternate Universe - Coffee Shops & Cafés".
Args:
term: partial/fuzzy tag text, e.g. "enemies to", "coffee", "kakashi".
kind: what to complete: tag | fandom | relationship | character.
get_workGet the full metadata card for one work: tags, stats, summary, series info.
Args:
work_id: the numeric AO3 work id (from search results or a URL like
archiveofourown.org/works/12345).
read_worksHave the mini reader (a separate AI) read full fics and report on each.
Works for a single fic or up to 20 at once. You never receive fic text —
only structured reader reports, one per work. The reader answers your query
directly (anything works: "is the ending happy?", "how explicit is it?",
"which of these should I read first?") plus gives a general digest of plot,
characters, style, and content notes. When given several fics, it ends with
a comparison section ranking them against your query.
This is the ONLY approved way to read a fic. A separate model does the
reading so a whole novel never touches your context. You MUST send fics
here before you recommend, rank, summarize, or judge them — search blurbs
are not enough, and reading raw text yourself defeats the entire point of
this server. Shortlist from blurbs, read here, then recommend.
Reading depth: a single-fic call sends the reader up to ~150k words (whole
novels fit); in a batch each fic is capped at ~100k characters. If a long
fic's report matters, read it alone. Batches that exceed the token budget
are split internally, then a final reduce pass still produces ONE global
comparison across the whole batch.
Content refusals: the reader is Gemini, which has a non-configurable safety
filter that occasionally refuses explicit or extreme fics — that fic's report
comes back as "(mini reader returned no text …)". The server already retries
once on the backup model, but the block is intermittent, so if a fic you care
about is refused: read it ALONE (a single fic isn't dragged down by an extreme
one sharing its batch), or just retry. In a mixed batch, one refused fic does
not sink the others — their reports still return.
Args:
work_ids: 1-20 numeric AO3 work ids (from search results or URLs).
query: the question to answer about each fic.
get_work_text⚠️ NOT RECOMMENDED — escape hatch only. Returns the raw full text of ONE
fic directly to you, bypassing the mini reader.
Prefer `read_works` in almost every case. A fic can run 150k+ words; pulling
that into your own context buries everything else, burns your tokens, and
throws away the whole reason this server exists — delegating reading to a
cheap second model. `read_works` hands you a structured report plus verbatim
prose samples, which is enough to judge, compare, and recommend a fic without
the fic ever entering your context.
Only reach for this when you genuinely need exact wording a report can't carry
— e.g. the user explicitly asks you to quote or close-read a specific passage.
If you just want to know what a fic is like or whether it's good: use
`read_works` instead.
Args:
work_id: the numeric AO3 work id.
max_words: cap the text to the first N words (0 = whole fic). Set a limit
to sample a fic's opening instead of dumping the entire thing into
your context — a few thousand words is usually plenty to judge voice.
search_worksSearch the works of IMSLP by title, by composer, or by words printed on their pages. Each row names the page of a work, which is how get_work and list_work_files address it: a title is written 'Work (Composer)', and the row splits it into the work and the person it names. IMSLP publishes no count of what a search matched, so 'total' is always null; 'has_more' and 'next_offset' say whether the library held more rows and where to read on from. A snippet is words taken from the page around the match. The library writes its pages in wikitext, so a snippet sometimes shows the name of a file rather than a sentence, and some rows carry none at all. The library titles a work in the language its composer used, so 'Die Zauberflöte' and not 'The Magic Flute', 'Sinfonia' and not 'Symphony'. A title translated into another language finds nothing, or finds only the works written about it: rows that look thin for a famous work are a sign the work sits under its original title, not that the library lacks it. Searching for a person by name finds the works naming them; search_people finds the person. When you show a result to a user, credit IMSLP and link the page.search_peopleFind the composers, editors, arrangers and performers of IMSLP by name. The library addresses a person by a category, written 'Category:Surname, Forename', and that is what list_person_works and get_person take. A name is written the library's own way, so searching finds it where guessing at the spelling does not. Some rows stand for another category rather than for a person: the library keeps a redirect for a name spelled differently, and 'redirect_to' names the category it leads to. Follow it rather than reading works under the redirect, which holds none. IMSLP publishes no count of what a search matched, so 'total' is always null. When you show a person to a user, credit IMSLP and link the page.get_workRead one work on IMSLP: its title and alternative titles, the composer, the opus and catalogue numbers, the year it was written and the year it was first published, the dedication, the key, the instrumentation, the style and period, and how many scores and recordings the page holds. Address a work by its page title, written 'Work (Composer)', for example 'Nocturnes, Op.9 (Chopin, Frédéric)', or by the page id. Dates and durations come back in the wording the page used: 'ca.1830' stays 'ca.1830', because a bare year would state a certainty the library declined to state. A facet the page leaves empty comes back null rather than guessed. A work holding a few editions comes back with them, files included. One holding many comes back with 'editions' null and 'editions_truncated' true, and list_work_files pages through them. The copyright of a score is stated per jurisdiction: 'Public Domain - Non-PD US' is free in Canada and the European Union and protected in the United States, so never report a score as public domain without saying where. No score file is downloaded or linked; the work page is what an answer links to. When you show a work to a user, credit IMSLP and link that page.list_work_filesRead the scores and recordings a work on IMSLP holds, edition by edition. An edition is a set of files published under one set of terms: the publisher, the editor and the copyright statement belong to the edition, and its files are the entries under it. A block of recordings carries performers instead, and no copyright statement at all. Address the work by its page title, written 'Work (Composer)', or by its page id, and read it a page at a time with 'limit' and 'offset'. 'editions_on_page' counts the editions the page holds, so it says how far a reading can go. Restrict to one part of the page with 'section', using the names the page itself prints: 'Scores', 'Parts', 'Arrangements and Transcriptions', 'Recordings', 'Full Scores' and others. A restriction matching nothing comes back with the sections the page does hold rather than as a work without scores. The copyright of a score is stated per jurisdiction: 'Public Domain - Non-PD US' is free in Canada and the European Union and protected in the United States, so never report a score as public domain without saying where. No score file is downloaded or linked; the work page is what an answer links to. When you show an edition to a user, credit IMSLP and link that page.list_person_worksRead the works IMSLP files under a person: what a composer wrote, and also what an editor, an arranger or a performer is credited on. The person is named by the category the library addresses them by, written 'Category:Surname, Forename', which search_people finds. The prefix may be left out. IMSLP publishes no count of what a category holds, so 'total' is always null; 'has_more' and 'cursor' say whether the library held more rows and how to read them. A category the library does not hold answers exactly as an empty one does, so an answer with no rows means one of the two rather than a person without works. When you show a work to a user, credit IMSLP and link the page.get_personRead what IMSLP holds about a person: the name as its page prints it, the life dates it states, the other names it files them under, the registers holding a record of them, and the addresses it points to off the site. The person is named by the category the library addresses them by, written 'Category:Surname, Forename', which search_people finds. The prefix may be left out. Life dates come back in the wording the page used, since the library writes '1861-1934' on one page and '4 March 1861 — 2 November 1934' on another, and a page holding none says so. This reads the person; list_person_works reads what they wrote. When you show a person to a user, credit IMSLP and link the page.browse_categoryRead the works IMSLP files under a category: a genre, a key, an instrumentation or a period. The library writes these names itself, and get_work hands them back for a work under 'genre_categories': 'Nocturnes', 'For piano', 'Scores featuring the piano', 'B-flat minor'. Pass one of those rather than a name of your own, since a category the library does not hold answers exactly as an empty one does. The 'Category:' prefix may be left out. The library cannot cross two categories, so a question naming both a genre and an instrument is answered by browsing one and reading the other off each work. IMSLP publishes no count of what a category holds, so 'total' is always null; 'has_more' and 'cursor' say whether more rows remain and how to read them. When you show a work to a user, credit IMSLP and link the page.Paste either block (or both) into your client config, e.g. claude_desktop_config.json or ~/.cursor/mcp.json. We only show a config when the install command is confirmed.
{
"mcpServers": {
"arturlys-ao3-mcp": {
"command": "uvx",
"args": [
"ao3-mcp"
],
"env": {
"GEMINI_API_KEY": "YOUR_GEMINI_API_KEY_HERE",
"GEMINI_MODEL": "YOUR_GEMINI_MODEL_HERE",
"GEMINI_MODEL_BACKUP": "YOUR_GEMINI_MODEL_BACKUP_HERE",
"AO3_MIN_INTERVAL": "YOUR_AO3_MIN_INTERVAL_HERE"
}
}
}
}
{
"mcpServers": {
"mcp-imslp": {
"command": "npx",
"args": [
"-y",
"mcp-imslp"
]
}
}
}