使用说明
Academic MCP Server
中文版:README.zh-CN.md
A Model Context Protocol (MCP) server that exposes a uniform paper-discovery and full-text-extraction API on top of eleven academic / patent sources:
| Code | Source | Engine |
|---|---|---|
ARXIV |
arXiv (preprints) | Camoufox (Firefox) |
CNKI |
中国知网 (China National Knowledge Infrastructure) | Playwright Chromium |
IEEE |
IEEE Xplore | Camoufox (Firefox) |
ACM |
ACM Digital Library | Camoufox (Firefox) |
SD |
ScienceDirect (Elsevier) | Camoufox (Firefox) |
AIAA |
AIAA Aerospace Research Central | request-first HTTP + Camoufox auth |
MDPI |
MDPI journals | request-first HTTP |
WOS |
Web of Science Core Collection | API-level WoS fetch + Chromium auth |
GS |
Google Scholar | Camoufox (Firefox) |
PATYEE |
Patyee 专利之星 | Playwright Chromium |
DAWEI |
大为专利 (pat.daweisoft.com) | Playwright Chromium |
Every source is wrapped by a per-platform scraper that surfaces a consistent result schema, and every download/conversion is routed through a local MongoDB-backed library so repeated queries are served from cache, browser state is shared across concurrent MCP clients, and downloaded PDFs/Markdown are deduplicated on disk.
The whole thing is meant to be embedded as a tool provider in an LLM agent (Claude Desktop, Codex, etc.) via MCP, but the underlying tools are also useful as a CLI/library on their own.
Architecture
┌──────────────────────────────────────────────────────────────────────┐
│ MCP client (Claude / Codex / ...) │
└────────────────────────────┬─────────────────────────────────────────┘
│ MCP tool calls
▼
┌──────────────────────────────────────────────────────────────────────┐
│ server.py — FastMCP entry, dispatches to per-platform scrapers │
│ ┌──────────────────────────┐ ┌────────────────────────────────┐ │
│ │ Library (library.py) │◄──►│ Scrapers (11 modules) │ │
│ │ Mongo: papers, │ │ - one Camoufox/Chromium │ │
│ │ search_queries, │ │ persistent context per │ │
│ │ browser_state │ │ platform (singleton) │ │
│ │ Files: .repo/{platform} │ │ - pooled per-PID profile │ │
│ │ /{native_id}/ │ │ when canonical is busy │ │
│ └──────────────────────────┘ └────────────────────────────────┘ │
└────────────────────────────┬─────────────────────────────────────────┘
│ optional
▼
┌──────────────────────────────────────────────────────────────────────┐
│ library_web/ — FastAPI/Jinja2 management console (port 5577) │
│ / /papers /papers/{p}/{id} /searches /browser-state /file │
└──────────────────────────────────────────────────────────────────────┘
Every MCP tool call passes through the Library first:
- Search: the (platform, query, filters) tuple is hashed; on cache hit the cached result is returned without launching a browser.
- Details / Download / Read: the paper is identified by
(platform, native_id)extracted from the URL. If the metadata / PDF / Markdown is already on disk, it is mirrored to the caller's requestedoutput_dirwithout re-fetching. - Browser state: each Camoufox scraper pins a single fingerprint per platform and reuses verification cookies (cf_clearance / DataDome / …), so one manual CAPTCHA carries across reboots and across concurrent MCP clients.
Components
| Path | Role |
|---|---|
server.py |
FastMCP server. Exposes the five MCP tools and wires them through the Library. |
runtime_config.py |
Loads mcp_runtime_config.json, resolves project paths, computes profile dirs (with suffix). |
mcp_logging.py |
UTF-8-safe stderr printer that doesn't break when the MCP transport is JSON-RPC over stdio. |
library.py |
MongoDB DAO for papers, search_queries, browser_state + helpers for filesystem mirroring. |
scraper_utils.py |
Shared helpers: goto_with_retry, venue_matches, profile pool, fingerprint + cookie sharing. |
pdf_utils.py |
convert_pdf_to_markdown() — uses pymupdf4llm, falls back to per-page images for scanned PDFs. |
{platform}_scraper.py |
One module per source. All expose search_papers / get_paper_details / download_paper / read_paper_content / close. |
library_web/ |
FastAPI app + Jinja2 templates for the management console. |
library_web_{start,stop}.{bat,ps1} |
Helper scripts to launch / stop the console (port from mcp_runtime_config.json). |
.repo/ |
Canonical file store: .repo/{PLATFORM}/{safe_native_id}/paper.pdf and paper.md. |
.{platform}_profile/ |
Per-platform browser profile. Canonical profile + per-PID ephemeral copies as needed. |
Requirements
- Python 3.10+ (tested on 3.11/3.12). 3.10 is the lower bound for the PEP 604 union syntax used in a few places.
- MongoDB 3.6+ locally reachable (tested against
mongo:4.4in Docker). Collections and their indexes are created automatically on first start; JSON-shaped fields (keywords,extra,results,filters,cookies,fingerprint) are stored as native BSON documents/arrays. - Camoufox kernel (auto-fetched into
%LOCALAPPDATA%\camoufoxon Windows; under~/.cache/camoufoxon Linux).
Installation
python -m venv .venv
.\.venv\Scripts\activate # Windows
# source .venv/bin/activate # POSIX
pip install -r requirements.txt
python -m camoufox fetch # downloads the patched Firefox kernel
Create .env from .env.example and point it at your MongoDB:
# Full connection URI (recommended — supports auth / replica sets / options)
MONGODB_URI=mongodb://127.0.0.1:27017
MONGODB_DATABASE=academic_mcp
# For an auth-enabled server, include the credentials + authSource, e.g.
# MONGODB_URI=mongodb://user:pass@127.0.0.1:27017/?authSource=admin
Alternatively, leave MONGODB_URI unset and provide discrete
MONGODB_HOST / MONGODB_PORT / MONGODB_USER / MONGODB_PASSWORD parts.
The library creates the database, collections and indexes on first start. If MongoDB is unreachable the Library disables itself with a clear warning and the server falls back to passthrough mode (no caching, no shared browser state — but everything still works).
Configuration
mcp_runtime_config.json (committed) holds non-secret tunables:
| Key | Type | Default | Purpose |
|---|---|---|---|
playwright_browsers_path |
string | ".ms-playwright" |
Where Playwright stores its Chromium download. |
override_playwright_browsers_path |
bool | true |
Force-export the path so the right Chromium is picked up by Playwright on every launch. |
set_cwd_to_project_root |
bool | true |
chdir to the project root on import — keeps relative paths sane in MCP servers. |
allow_headful_fallback |
bool | false |
Master switch: on anti-bot block, may we relaunch with a visible browser for human verification? |
allow_headful_fallback_platforms |
list | ["ACM","SD","AIAA","WOS"] |
Subset of platforms allowed to do the headful CAPTCHA fallback. |
manual_verification_timeout_seconds |
int | 180 |
How long we wait (in headful mode) for a human to click through the CAPTCHA. |
library_enabled |
bool | true |
Master switch for the MongoDB+filesystem Library. Set to false to disable caching. |
library_root |
string | ".repo" |
Where canonical PDFs/MD/images live (relative to project root or absolute). |
library_web_host / library_web_port |
string/int | 127.0.0.1:5577 |
Bind address for the management console. |
profile_suffix |
string | "" |
Optional shared suffix; per-client override via MCP_PROFILE_SUFFIX env var (see below). |
Secrets live in .env (gitignored). Only the MongoDB connection so far.
MCP tools
All five tools are registered on the FastMCP server (server.py) and
appear to the LLM with the docstrings preserved verbatim.
search_papers(query, platform="CNKI", ...)
| Parameter | Type | Description |
|---|---|---|
query |
str | The search terms. Free text; per-platform syntax (e.g. IEEE supports ("Publication Title":"...")) is allowed. |
platform |
str | One of CNKI, IEEE, ARXIV, ACM, SD, AIAA, MDPI, WOS, GS, PATYEE, DAWEI. |
search_field |
str | Restricts the query field. Platform-specific (see docstring); defaults to each platform's "all". |
db_scope |
str | Database. CNKI: 总库 / 中文 / 外文. WOS: the "Search in" database (default WOSCC). Verified: WOSCC, ALLDB, CCC, CSCD, KJD, MEDLINE, PPRN, PQDT, RC, SCIELO. Not supported: DIIDW (Derwent), GRANTS. editions apply to WOSCC only. |
source_type |
str | Document type filter (research-article, conference, journal, …). Platform-specific. |
journal |
str | None | Restrict to a publication. Server combines URL-level filters with a client-side fuzzy venue_matches check that tolerates Google-Scholar-style "…" truncations. |
start_year |
int | None | Inclusive lower bound on publication year. |
end_year |
int | None | Inclusive upper bound on publication year. |
sort_by |
str | relevance (default), citations, or date_desc. |
start_index |
int | Pagination offset; pass e.g. 20 to skip the first page of 10–20 results. |
limit |
int | Max number of results to return (default 10). |
editions |
str | WOS only. Restrict the WOSCC "Editions"; empty = All. Comma-separated codes/labels: SCI-EXPANDED(SCI), SSCI, AHCI, CPCI-S, CPCI-SSH, ESCI, CCR-EXPANDED, IC. |
Returns a JSON string with:
{
"total_results": "1707",
"papers": [
{
"id": "<8-hex hash of detail_link>",
"title": "...",
"author": "...",
"source": "...", // raw publisher line (Volume / Issue / etc.)
"venue_name": "...", // canonical journal / conference name when extractable
"doi": "10.1145/...", // ACM only; others fill in from get_paper_details
"date": "March 2026",
"db_type": "Research article",
"detail_link": "https://..."
}
],
"_cache_hit": true // server-only flag; true when served from MongoDB cache
}
Cache key is (platform, normalized query, filters, pagination). Cache
entries never expire by default (the user picked permanent caching during
the initial design). To force a refresh, delete the row from the
search_queries table or via the web console.
get_paper_details(url, platform="CNKI")
Fetches abstract / keywords / DOI for a specific paper. Re-uses cached
data when the same paper has already been seen (abstract column in
papers). Returns:
{
"url": "...",
"abstract": "...",
"keywords": ["..."],
"doi": "...",
"_cache_hit": true
}
get_paper_details is wrapped in goto_with_retry — transient Firefox
network aborts (NS_ERROR_ABORT, NS_ERROR_NET_INTERRUPT, ...) get up to
two automatic retries with exponential backoff before the failure
propagates.
As a side effect, every successful details fetch also stores an
EndNote-importable RIS record on papers.ris_text (see
RIS auto-store below).
download_paper(url, output_dir, platform="CNKI")
Downloads the PDF. If the file is already on disk under
.repo/{PLATFORM}/{native_id}/...pdf it is just mirrored to output_dir.
On a fresh download, it is written into the canonical location AND
mirrored to output_dir. Returns the local filesystem path.
The current download stack is request-first. For protected platforms, the
browser is used to establish a valid verification state and, when the site
requires it, to reach the page state that mints a signed asset URL. The
actual data path then uses direct HTTP, in-page fetch, response-body
capture, or Playwright's download event instead of button-click automation.
Platform-specific behavior:
ACM: attempts the DOI PDF endpoint with verified cookies. If both the detail page and/doi/pdf/...expose only abstract HTML, the item is classified asunavailableinstead of a scraper failure.SD: search/details prefer direct HTTP. PDF retrieval navigates only far enough to trigger the signed ScienceDirect asset stream, then saves bytes through programmatic download/body capture. A solved PDF-host challenge can be reused by later requests until the publisher invalidates the token.AIAA: search, details, PDF, and RIS are request-first against Atypon endpoints (/action/doSearch,/doi/...,/doi/pdf/...,/action/downloadCitation). The browser is only used to refresh the Cloudflare verification state; direct requests reuse that state with the Firefox 135 network fingerprint required by the issuedcf_clearance.MDPI: search/details/PDF/RIS are request-first. The scraper handles MDPI's Akamai interstitial in-process by parsing thebm-verifypayload and posting to/_sec/verify?provider=interstitial, then reuses the resulting session cookie for HTML,/pdf, and/exportrequests.WOS: discovery-only. Search calls the captured/api/wosnx/core/runQuerySearchNDJSON endpoint and parses records, abstracts, citation counts, DOI, source, and document type metadata.search_field="ADVANCED"enables native Web of Science query syntax, for exampleTS=("aeroelastic flutter") AND PY=(2020-2026). Normal fielded search mirrors the WoS dropdown:ALL,TS,TI,AU,SO,PY,OG,FO,PUBL,DOP,AB,UT,AD,AI,AK,CF,DT,DO,ED,FG,GP,KP,LA,PMID, andWC, with English/Chinese aliases for the common labels. Boolean basic-search rows are supported either as JSON rows, for example[{"field":"TS","text":"flutter"},{"op":"OR","field":"TI","text":"aeroelastic"},{"op":"NOT","field":"AU","text":"Smith"}], or as multiline shorthand:TS=flutter,OR TI=aeroelastic,NOT AU=Smith. In the tested Tongji VPN route, plain Python/curl HTTP stacks fail during TLS while Chromium succeeds, so the scraper uses a verified Chromium profile to issue API-levelfetch()requests without clicking through the UI. PDF download and full-text reading are intentionally unsupported; use DOI/publisher links from the result with the native publisher scraper.
read_paper_content(url, output_dir, platform="CNKI")
Three-level cache:
- If Markdown is already in the library → mirror MD + images/ to
output_dirand return immediately. - If only the PDF is cached → re-run
pdf_utils.convert_pdf_to_markdownagainst the cached PDF. - Cache miss → full scrape, store PDF + MD + images, mirror to
output_dir.
Returns the MD file path and a 1000-character preview.
convert_local_pdf(pdf_path, output_dir)
Standalone helper to convert a user-supplied PDF to Markdown using the same image-extraction pipeline. Does not touch the Library.
The Library (MongoDB + filesystem)
Collections
papers unique index (platform, native_id); integer `id` (web UI)
title, author, source, venue_name, pub_date, db_type, doi,
detail_link, abstract, keywords (array),
pdf_path, md_path, images_dir, extra (subdocument),
ris_text (platform-native or synthesized RIS export),
created_at, updated_at
search_queries unique index (platform, md5(query + filters)); integer `id`
query_text, filters (subdocument), total_results,
results (array of subdocuments), fetched_at
browser_state _id == platform
fingerprint (subdocument: BrowserForge Fingerprint),
cookies (array: Playwright context.cookies()),
user_agent, verified_at, updated_at, note
counters _id == collection name; seq -> emulates AUTO_INCREMENT so the
integer `id` the web UI relies on stays stable.
JSON-shaped fields are stored as native BSON documents/arrays (no more
text round-tripping). The integer id on papers/search_queries is
assigned from the counters collection only on genuine inserts, so the
web UI's numeric paper-export / search-delete keys stay stable.
Filesystem layout
.repo/
ARXIV/2605.16230/
arxiv_2605_16230.pdf
arxiv_2605_16230.md
images/
arxiv_2605_16230.pdf-0002-08.png
...
IEEE/9266228/
ieee_9266228.pdf
ieee_9266228.md
images/...
CNKI/CJFD|...
native_id is normalized to a filesystem-safe slug. The canonical
location is the single source of truth; every download mirrors to the
caller's output_dir afterward.
RIS auto-store
Every get_paper_details call that produces a usable abstract also
populates papers.ris_text with an EndNote-importable RIS record.
The server prefers the platform's own RIS export endpoint (richer
pagination, ISSN, conference proceedings name) and falls back to
synthesizing one from the stored metadata (ris_utils.synthesize_ris)
when the platform doesn't expose an endpoint or the network call fails.
Either way the column is populated idempotently — re-running details
on a cached row doesn't refetch.
| Platform | Source | Endpoint / strategy |
|---|---|---|
| IEEE | platform | /rest/search/citation/format, with /xpl/downloadCitations form POST fallback. |
| ACM | platform | /action/downloadCitation?format=ris&include=abs. |
| SD | platform | /sdfe/arp/cite?format=application/x-research-info-systems — issued via page.evaluate("fetch(url)") so DataDome sees an in-app XHR (a direct context.request.get returns a 403 challenge page). |
| AIAA | platform | /action/downloadCitation form POST with format=ris&include=abs. |
| MDPI | platform | /export form POST with articles_ids[] and export_format_top=ris. |
| ARXIV / CNKI / WOS / GS / PATYEE / DAWEI | synthesized | ris_utils.synthesize_ris infers TY from db_type + venue, splits AU, parses PY/DA from pub_date, and tags AN - {platform}:{native_id} for round-tripping. |
A bundled export is available from the web console: filter /papers by
state, tick the checkboxes, and POST to /papers/export-ris.
Rows that are missing ris_text (e.g. a synthesizer was added after the
row was first seen) are synthesized on demand during the export and the
result is written back, so the next export hits cache.
Browser state pool (concurrency + verification reuse)
Firefox/Camoufox refuses to open the same profile directory from two processes simultaneously. Without coordination, running two MCP clients (e.g. one inside Claude Desktop and one inside Codex) would either hang on the "Firefox is already running" modal or force every client to clear its own CAPTCHA.
The browser-state pool solves both:
-
Pooled profile dirs. The first MCP server to claim a platform uses the canonical
.<platform>_profile/. Concurrent servers transparently fall back to an ephemeral per-PID copy.<platform>_profile__p<pid>/(auto-removed onclose()). A PID-aware sentinel file (.mcp_owner) prevents stale locks from crashed processes from blocking new launches. -
Pinned fingerprint. Cloudflare's
cf_clearanceand DataDome tokens are bound to the browser fingerprint. We generate ONEbrowserforgeFingerprint per platform withos="windows", store it pickled+base64 inbrowser_state.fingerprint, and pass it to everyAsyncCamoufox(fingerprint=...)launch. All concurrent clients on a platform present the same identity. -
Shared cookies. After a successful pass of CAPTCHA / Cloudflare / DataDome, the scraper captures
context.cookies()intobrowser_state.cookies. The next launch (any client, any MCP server process) injects those cookies viacontext.add_cookiesbefore the first navigation. One manual verification covers all clients until the platform invalidates the token.
A row in browser_state is marked verified_at = NOW() whenever the
captured cookies contain a known clearance cookie (cf_clearance,
datadome, __ddg, incap_ses, reese84).
Limitations
cf_clearance is bound to (IP, fingerprint, TLS JA3). We pin
fingerprint; your IP is your local IP; but Camoufox's outgoing TLS
fingerprint is not byte-identical across launches. In practice this
means most launches reuse the verification, but a tightened Cloudflare
policy can still occasionally force a re-verify. This is still strictly
better than the unfixed state (every client, every restart needs
manual interaction).
Web console pages
| URL | Purpose |
|---|---|
/ |
Counts per platform + totals. |
/papers |
Paginated listing with platform / keyword / state filter. Checkbox-select rows and POST /papers/export-ris for a bundled .ris download. |
/papers/{platform}/{native_id} |
Full metadata, abstract, links to served PDF / Markdown. |
/searches |
Cached search queries; delete per row to force refetch. |
/browser-state |
Per-platform fingerprint / cookies / verification status; clear or full reset. |
/file?path=... |
Static file server scoped to .repo/. Path traversal is rejected. |
/health |
JSON: {enabled, reason, library_root}. |
Running
MCP server (production)
Wire the server into your MCP client's configuration. For Claude
Desktop's mcp.json (or Codex's equivalent):
{
"academic-mcp": {
"command": "E:\\Projects\\CNKI-MCP\\.venv\\Scripts\\python.exe",
"args": ["E:\\Projects\\CNKI-MCP\\server.py"],
"env": {
"MCP_PROFILE_SUFFIX": "claude"
}
}
}
MCP_PROFILE_SUFFIX is optional. Leave it unset for the default
behavior (one canonical profile, automatic per-PID fallback for the
second client). Set it differently in each client only if you want each
MCP server to have its own dedicated profile set
(.ieee_profile_claude, .ieee_profile_codex, etc.). The DB-backed
browser state is shared regardless.
MCP server (standalone, for debugging)
.\.venv\Scripts\python.exe server.py
The server speaks MCP over stdio, so a raw invocation will just hang waiting for JSON-RPC. Useful only when attached from an MCP client.
Management console
library_web_start.bat :: starts uvicorn on the configured port, opens the browser
library_web_stop.bat :: kills the listener on that port
PowerShell equivalents (library_web_start.ps1 /
library_web_stop.ps1) are also provided. The console runs read-write
(it can delete cache rows / clear browser state) but is bound to
127.0.0.1 by default — change library_web_host in
mcp_runtime_config.json if you really want remote access.
Multi-client guidance
| Scenario | What happens | What you should do |
|---|---|---|
| One MCP client at a time | Canonical profile, full caching, one CAPTCHA per platform per token TTL. | Nothing. |
| Two clients, same platform, same time | First client gets canonical profile; second gets per-PID ephemeral copy. Both share cookies. | Nothing. |
| Two clients, you want fully isolated profiles | Set MCP_PROFILE_SUFFIX=claude / MCP_PROFILE_SUFFIX=codex in each client's env. |
Edit MCP client config. |
| CAPTCHA appeared and you solved it once | Cookies captured to browser_state. Subsequent launches inject them and skip the challenge. |
Nothing. |
| Platform banned the fingerprint | All clients fail equally because they all present the same identity. | /browser-state → "完全重置". |
| Just want to drop stale cookies | verified_at will go stale; new searches may take the manual path again. |
/browser-state → "清 Cookie". |
Validation and performance
Latest full-platform integration smoke run: scratch/full_platform_test.py,
run id 20260614_full_final (2026-06-14). AIAA was added and tested
separately with run id 20260628_aiaa_final_relevance (2026-06-28);
MDPI was added and tested with run id 20260628_mdpi_retry (2026-06-28). Each
downloadable platform was tested with up to three fresh sample downloads.
WOS was added as a discovery-only platform and smoke-tested with run id
20260704_wos_fields_boolean (2026-07-04), covering native advanced-search
payloads plus basic-field dropdown mappings and Boolean rows.
These runs completed with 0 hard failures. unavailable means the
publisher or the tool semantics did not expose a downloadable PDF for that
sample; it is not counted as a transport or parser failure.
| Platform | Search | Details | Download classification | Read |
|---|---|---|---|---|
ARXIV |
6.584 s | 4.976 s | 3/3 OK: 15.065, 12.841, 10.203 s | success, 20.405 s |
CNKI |
1.587 s | 0.198 s | 3/3 OK: 1.488, 1.479, 1.325 s | success, 2.178 s |
IEEE |
4.165 s | 1.149 s | 3/3 OK: 29.498, 8.352, 2.809 s | success, 36.236 s |
ACM |
25.385 s | 3.034 s | 3/3 unavailable: sampled articles exposed no PDF link | unavailable |
SD |
15.312 s | 4.913 s | 3/3 OK: 4.835, 5.381, 3.819 s | success, 22.149 s |
AIAA |
1.879 s | 0.828 s | 3/3 OK: 8.993, 5.554, 3.004 s | success, 14.402 s |
MDPI |
2.388 s | 0.976 s | 3/3 OK: 4.977, 11.925, 19.477 s | success, 7.186 s |
WOS |
9.026 s | 0.000 s | not applicable by design | not applicable |
GS |
2.443 s | 0.000 s | not applicable by design | not applicable |
PATYEE |
1.695 s | 0.374 s | 1/3 OK, 2/3 unavailable | success, 8.778 s |
DAWEI |
2.551 s | 0.755 s | 3/3 OK: 3.115, 3.284, 2.598 s | success, 4.768 s |
Operational notes:
- ACM, SD, and AIAA require valid verification state for some requests. Once the visible challenge has been solved, subsequent retrievals reuse the stored browser identity and cookies where the publisher accepts them.
- SD PDF success depends on the separate
pdf.sciencedirectassets.comchallenge as well as the ScienceDirect detail-page DataDome state. - AIAA accepts direct programmatic requests only when the request fingerprint
matches the Firefox profile that produced
cf_clearance; the implemented path usescurl_cffiwithfirefox135. - AIAA's latest-date book-chapter samples may return publisher purchase
pages from
/doi/pdf/...; these are classified asunavailable, not transport failures (20260628_aiaa_date_sort_classifiedhad 0 hard failures). - MDPI is open-access and uses a lightweight Akamai interstitial; the challenge is solved programmatically and does not require a browser warmup profile.
- WOS may require hCaptcha/passive verification. Run
warmup_platform_auth(platforms="WOS")once when it returnsServer.passiveVerificationRequired; after that, searches use the WoS NDJSON endpoint through the verified Chromium profile. WoS is not a PDF source in this MCP. - Google Scholar is search-oriented in this server; PDF download/read is intentionally reported as not applicable unless a downstream source is added for a specific result.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Failed to launch the browser process / exitCode=0 on Camoufox |
Stale parent.lock in profile dir from a crashed previous run. |
Auto-cleaned on next launch by scraper_utils.acquire_profile. If still stuck, delete .<platform>_profile/parent.lock. |
TargetClosedError: Target page, context or browser has been closed |
Cloudflare timeout left the persistent context wedged. | Auto-handled: _context_is_alive detects and rebuilds on next call. If repeated, restart the MCP server. |
ProfileInUseError: ... already in use by another MCP server |
A second client tried to claim a profile already held by a live PID. | Either close the other client or set MCP_PROFILE_SUFFIX to a different value in this client's env. |
| SD PDF page is visible but no file is saved | Firefox opened the PDF internally instead of emitting a download. | Auto-handled: the SD profile forces application/pdf to save-to-disk and the scraper captures the download event. |
AIAA direct requests return Just a moment... / 403 |
The Cloudflare cookie is missing, stale, or tied to a different browser fingerprint. | Run warmup_platform_auth(platforms="AIAA"); the scraper then reuses the verified state via direct firefox135 HTTP requests. |
| MDPI returns a short Akamai interstitial page | The session has not completed MDPI's bm-verify check yet. |
Auto-handled: the scraper posts the parsed challenge to /_sec/verify?provider=interstitial and retries the original request. |
WOS returns Server.passiveVerificationRequired |
WoS requires hCaptcha/passive verification for the current institution/IP session. | Run warmup_platform_auth(platforms="WOS"), complete verification in Chromium, then retry the search. |
| WOS direct Python/curl HTTP fails during TLS | This Tongji VPN/Clash route accepts Chromium networking but drops generic TLS clients before HTTP. | Expected for WOS; the scraper sends API-level fetch() requests from the verified Chromium profile instead of UI-click scraping. |
| ACM download returns unavailable | The article page and /doi/pdf/... endpoint redirect to abstract HTML with no PDF link. |
Treated as publisher-unavailable, not a scraper failure. Try a different ACM article/DOI. |
_cache_hit=true but the result looks stale |
Permanent search cache returned a fossil row. | /searches → delete the row, re-run the search. |
journal= filter returned 0 results on CNKI |
CNKI relevance ordering may not surface the target journal in page 1. | Increase limit, narrow the query, or accept that CNKI's cross-journal coverage is sparse for general queries. |
Library disabled at startup ([Library] Disabled: ...) |
MongoDB config missing / wrong / unreachable. | Check .env. The server still runs in passthrough mode — caching and shared state are just turned off. |
Project conventions
scratch/is git-ignored. Debug scripts, dump HTML, screenshots, etc. belong there.- Local handoff notes (
HANDOFF.md,NEXT_SESSION_PROMPT.md) are ignored and should not be committed. .{platform}_profile/, suffixed profile dirs such as.sd_profile_codex/,.repo/,.venv/,.envare all git-ignored.- The
*_scraper.pymodules export ascraper_instancesingleton (server.pyimports those directly) so we keep a long-lived Camoufox/Chromium context per platform per MCP server process. - Per-platform native IDs (used as the library's primary identity):
ARXIV:2605.16230(the arXiv ID, version suffix kept if present)IEEE:9266228(thearnumberfrom/document/<n>/)ACM:10.1145/3597503.3608128(the DOI, parsed straight from/doi/...)SD:S0263224125035523(the PII)AIAA:10.2514/6.2022-2883(the DOI, parsed from/doi/...)MDPI:2072-666X/17/7/785(the article path; DOI is stored as metadata)WOS:WOS:001064481000002(the Web of Science accession number)CNKI:DBCODE|FileNameGS: SHA1 of the result URL (external link → hashed)PATYEE: thepn=valueDAWEI: thePNM(publication number)
License
See repository root.