# ArturLys/ao3-mcp [Health: Active]

**Category:** 🎨 Art & Culture  
**Repository:** https://github.com/ArturLys/ao3-mcp  
**GitHub Stars:** 1  
**Views:** 8  
**Installs:** 3  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/arturlys-ao3-mcp

## Description
Search the Archive of Our Own (AO3) and delegate full-fic reading to a secondary model (Gemini), so the agent recommends from the actual text, not the author's blurb. pip install ao3-mcp

## Tools
Capabilities this server exposes over MCP:

- **search_works** — Search 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_tags** — Resolve 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_work** — Get 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_works** — Have 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.
    

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "ao3-mcp": {
    "command": "uvx",
    "args": ["ao3-mcp"],
    "env": {
      "GEMINI_API_KEY": "",
      "GEMINI_MODEL": "",
      "GEMINI_MODEL_BACKUP": "",
      "AO3_MIN_INTERVAL": ""
    }
  }
}
```

**Requires environment variables:** `GEMINI_API_KEY`, `GEMINI_MODEL`, `GEMINI_MODEL_BACKUP`, `AO3_MIN_INTERVAL` — the values above are empty placeholders; fill in real credentials before running (see the repository for what each one is for).

## Documentation

## What ArturLys/ao3-mcp MCP server does

ArturLys/ao3-mcp MCP server exposes Archive of Our Own search and reading workflows through MCP. It can locate works using fandom, relationship, character, tag, rating, completion, word-count, and sorting filters. Search results contain numeric work IDs, which can be turned into AO3 URLs with the `/works/{id}` pattern.

The server is designed to support recommendations based on the fic itself rather than its summary. A secondary Gemini model reads selected works and returns reports covering plot, characters, style, content notes, and prose samples. For a group of works, the report also compares or ranks them against a supplied question.

## How it works

AO3 access is performed by scraping HTML, as AO3 does not provide a public API. Requests are spaced by a configurable minimum interval and the server honors `Retry-After` responses. Downloads are cached locally for 24 hours, so asking new questions about an already downloaded work does not require another AO3 request.

The intended flow is to search broadly, select up to 20 work IDs, and call `read_works` with a question. The agent receives reader reports, not the full fic text. A single-work request can send up to roughly 150,000 words to the reader; batched works have a lower per-work character cap. `get_work_text` exists as an escape hatch for one work, but it bypasses delegation and can consume substantial context.

## Setup and configuration

Install the package with Python 3.10 or newer:

```bash
pip install ao3-mcp
```

Running ArturLys/ao3-mcp MCP server requires a Gemini API key. Supply it with `--api-key` or set `GEMINI_API_KEY`. The default reader model is `gemini-flash-latest`; `GEMINI_MODEL` and `GEMINI_MODEL_BACKUP` can change the primary and fallback models. `AO3_MIN_INTERVAL` changes the default 0.6-second delay between AO3 requests.

MCP clients should launch the `ao3-mcp` command and pass the key as an argument or environment variable. The README specifically documents configurations for Claude, Cursor, Google Antigravity, and VS Code/Copilot.

## Tools and capabilities

- `search_works`: Search AO3 with optional filters, AO3 query operators, wildcards, and up to five pages of 20 results.
- `find_tags`: Use live autocomplete to resolve fuzzy wording to canonical fandom, relationship, character, or tag names.
- `get_work`: Retrieve a work's metadata, including tags, statistics, summary, and series details.
- `read_works`: Ask Gemini to read one to 20 works and return per-work reports plus batch comparisons.
- `get_work_text`: Retrieve raw text for one work with an optional word limit when exact wording or close reading is necessary.

## Limitations and notes

Fic text is sent to Google's Gemini API for reading; the README states that no other data leaves the local machine and that the server has no telemetry. Gemini's safety filter may refuse some explicit or extreme works, although the server retries with a backup model. AO3 content spans all ratings, so callers should use the rating and warning-related filters when controlling what gets fetched.

Cloudflare challenges can cause AO3 requests to fail. The implementation uses `curl_cffi` with a mobile-Safari TLS fingerprint, but the README notes that the impersonation setting may need adjustment if AO3 returns a challenge response. ArturLys/ao3-mcp MCP server is released under the MIT license.

_Full upstream README: https://allmcps.com/mcp/arturlys-ao3-mcp/readme_

