[The Abstractinator narwhal]
The Abstractinator
Immunology & virology literature, searched across databases at once.
abstractinator.me ·
Connect your AI ·
Self-host it
The Abstractinator turns a literature sweep into one search. It queries Europe PMC, OpenAlex,
ClinicalTrials.gov, bioRxiv/medRxiv, NIH RePORTER and NSF Awards in parallel, removes
duplicates across sources, and tags every record with its most prominent immune cell type
and virus using a weighted title/abstract scoring matrix.
A new search takes about 5–10 seconds; repeat searches within 7 days are instant.
✨ Features
📚 Data sources
Keyed sources are bring-your-own-key: enter keys in Settings (☰). They're held only in your
browser session, never logged or written to disk, and never shared with other visitors.
🤖 Use it with AI
The Abstractinator is available as a remote MCP server, the open standard AI assistants use
to connect to tools.
In Claude: Settings → Connectors → Add custom connector → paste the URL above, and choose
No sign-in for authentication. Then just ask, e.g. "Search the Abstractinator for oncolytic
virus trials in melanoma." Any MCP client that supports remote (streamable HTTP) servers works too.
The server exposes one tool:
search_literature(query, max_results=25, abstract_chars=1500, deep=False)
returns total_found, source_counts, and a source-balanced list of records with title,
abstract, authors, publication_date, doi, url, source, primary_cell,
primary_virus and is_bioinformatics.
The AI connector uses open-access sources only (no keys needed or spent) and is rate-limited
to keep the service available for everyone.
🏗️ How it works
- One search engine, two front doors.
orchestrate_extraction.R checks the cache, starts
every source as a background job (future), then combines, deduplicates and tags the results.
The website calls it asynchronously through Shiny's ExtendedTask, so one visitor's search
never freezes the page for anyone else. The REST API calls the same code synchronously.
- Shared cache. Results are cached for 7 days in
.cache/s3_mimic/ (one .rds per search,
keyed by term, depth and which keyed sources were used). A search by a person makes the same
search instant for an AI, and vice versa.
- Local preprints. bioRxiv/medRxiv are searched from
biorxiv_local_db/ (one Parquet file
per year) via Arrow, instead of hitting their API on every search.
- Hit detection. Titles and abstracts are matched against immune-cell, virus and bacteria
alias lists (
aliases.R); title hits weigh 20, abstract hits 3, and the top scorer becomes
primary_cell / primary_virus / primary_bacteria. Aliases are matched as whole words, so
"salmonella" doesn't match "salmonellosis".
- Swappable pools. The Plotinator's pathogen axis is not hard-wired to viruses: a pool switch
selects which tagged category it plots, and the same search can be viewed through either lens.
Adding a pool (fungi, parasites, drug classes, a lab's own vocabulary) means defining an alias
list in
aliases.R, tagging it with tag_with_alias_list() in orchestrate_extraction.R, and
adding one entry to PATHOGEN_POOLS in app.R — contributions welcome.
- Logging. Website searches write a small JSON record (term, duration, result count, cache
hit, and whether keys were present, never the keys) to
.cache/s3_mimic/logs/. Feedback
submitted through the app is saved to .cache/s3_mimic/feedback/ and, if
DATA_WEBHOOK_URL is set, forwarded to Discord.
- Display safety. Text from external databases is HTML-escaped before display, with only
basic formatting tags (italics, bold, sub/superscript, line breaks) allowed through, and links
restricted to plain
http(s) URLs.
🖥️ Self-hosting
Requirements
- Linux (tested on Ubuntu), R ≥ 4.1, Python ≥ 3.10 (for the MCP server)
- System libraries:
- R packages (install into the system library so Shiny Server can see them):
Using conda? Shiny Server and the services use the system R at /usr/bin/R. Install
packages with /usr/bin/Rscript (or conda deactivate first) so they land in the right place.
1. Get the code and configure
Optional .Renviron in the project root:
No API keys are needed on the server: the website is strictly bring-your-own-key and never
uses keys from .Renviron, and the AI connector uses open-access sources only. (Keys in
.Renviron are only read by command-line scripts you run yourself.)
2. Build the preprint store (once, takes several hours)
Detach with Ctrl+B, D. Searches pick up each year as soon as it's written.
3. Run locally
4. Production setup (what abstractinator.me runs)
Shiny Server, serving the app at the root and only on localhost
(/etc/shiny-server/shiny-server.conf):
API and MCP services (run as the shiny user):
Daily preprint updates (after the initial build finishes):
Public access via a Cloudflare Tunnel
(no open ports): route abstractinator.me → http://localhost:3838 and
mcp.abstractinator.me → http://localhost:8200. The REST API stays private.
Updating the live site
Running tests
Usage logs
Every search, from the website or an AI agent, is logged without personal data.
Searches per day, split by channel:
REST API reference (local only)
GET /search?q=<term>&limit=25&abstract_chars=1500&deep=false
limit: results returned (1–100), picked round-robin across sources
abstract_chars: truncate abstracts; 0 omits them
deep: true for up to 250 results per source
- Optional headers
X-CORE-Key and X-USPTO-Key enable those sources for the request
- Server-wide limit of 20 searches/minute (HTTP 429 when exceeded)
GET /health returns {"status":"ok"}.
🗂️ Repository layout
🛣️ Roadmap
The direction is from retrieval toward curation. The pipeline already applies
consistent editorial rules — when two records are the same work, which version to keep,
which vocabulary labels apply, and how to sample across sources — but it does not yet
judge whether a study is sound or how relevant it is to the question asked. Each item
below adds one more judgment.
- Relevance ranking (best match first, using hit scores and recency)
- Skip caching searches where a source failed
- Log AI-agent searches alongside website searches
- Smarter agent tools backed by a relational knowledge base (gap analysis, "what's under-studied")
⚖️ License & branding
The code is open source under the MIT License: you're welcome to use, modify
and build on it, as long as the copyright notice comes along.
The name and branding are not part of that license. "The Abstractinator", the -inator
feature names (Searchinator, Deduplicatinator, Plotinator, Readinator, Agentinator) and the
look of the site identify the official project at abstractinator.me.
If you run your own copy or fork, please give it your own name and branding, and don't
present it as the official Abstractinator. A "based on The Abstractinator" credit with a link
back is appreciated.
Third-party icons in www/ belong to their creators and are used under the Flaticon license
(see CREDITS.md); they aren't covered by the MIT License.
🙏 Credits
Mascot and icons from Flaticon (narwhal by Smashicons, dinosaur by imaginationlol, frog prince
by Magnific); line-art animations from the Noun Project. Details in CREDITS.md.
📜 Citation
If you use The Abstractinator in your research, please cite:
Kangas, C. (2026). The Abstractinator: Automated Boolean Search Tool for Rapid Acquisition, Classification, and Triage of Immuno-Networks And Therapeutic Oncolytic Research (Version 2.0.0) [Computer software]. https://abstractinator.me
Reproducing the environment
renv.lock records the exact package versions the live deployment runs,
generated from that machine's library (R 4.6.1, 155 packages). To install the
same set:
The lockfile is a record, not an activated renv project: there is no
.Rprofile and no renv/ directory in this repository, both of which are
gitignored. The live server is updated with git pull, and an .Rprofile
arriving that way would switch the Shiny and plumber services onto a project
library that is not populated for the service user, taking the site down.
.github/workflows/tests.yml runs the offline suite on every push against
current package versions; .github/workflows/deps.yml restores the locked
versions weekly and runs the same suite, so drift shows up without slowing
every commit.