Professor' Harvester
This MCP server provides read-only research and harvesting tools for fetching documents and images, browsing archives, discovering scholarly works, and searching a local cache.
Fetch documents (1–50 URLs, local paths, DOIs, ISBNs, PMIDs/PMCIDs, or harvest handles) as clean Markdown, with per-item errors, cache refresh, size-only mode, and paths to complete artifacts.
Fetch images as local files for vision inspection, 1–50 at a time; images are not OCR'd or converted.
Browse archives (.zip, .tar.*, .7z, .rar): list safe members without extracting, or convert one member to Markdown.
Discover scholarly papers and books by title or bibliographic query, returning ranked candidates with handles for fetching.
Search cached documents by regex, returning which cached pages match and their paths rather than full text.
All listed tools are marked read-only.
Enables control of OpenAI Codex chat sessions, including listing, messaging, and coordinating Codex instances across accounts.
Contents
A fleet controller and a discipline layer for Claude Code, Codex, and OpenCode — chats that talk to each other, agents that follow the rules, and a harvester that reads what the web won't show a bot.
Related MCP server: @cyanheads/pubmed-mcp-server
Why Professor?
You already run more than one AI chat. They cannot see each other, they forget the rules the moment context compacts, and a growing share of the web answers them with a 403. Professor is the layer that fixes all three — without touching what your harness is, only what it does.
Without Professor | With Professor | |
Many chats, many accounts | Scattered terminal tabs; a closed tab is a lost chat | One picker — every chat on every harness, live or resumable |
One chat needs another | You copy-paste between windows |
|
The rules, after compaction | Whatever the prompt still remembers | Hooks that refuse, naming the right way in the refusal |
A bot-blocked page | A 403, or an empty page reported as content | A seven-rung fetch ladder; a block is reported as a block |
Claude and Codex rules | Two files that drift apart | One |
An account hits its limit | Start over in a new chat |
|
Six things you can watch it do
Every transcript below is real output from this repository, redacted only of names.
1. See the fleet
pfm ls: every AI chat on the machine — Claude Code, Codex (⬢), and OpenCode — across accounts (🥇🥈), grouped by repo, live (●), resumable (↻), or agent-run (⚙).⇄marks chats that talk to other chats;←hereis the one you are sitting in;✦opens a new one on any harness. Pick one, attach, or fire it a goal without ever attaching. A chat that scrolled off a closed terminal tab is not gone — it is a resumable transcript, and now somebody can find it.
tab once more and the same fleet is drawn as the sky at the top of this page.
Every project is a star its chats orbit; a spawned chat rises as a moon at its parent's angle, so lineage is visible in the sky itself. When chats talk to each other the sky draws an edge between them, read from a durable comms ledger — an edge is a fact, not a guess. The chronoscope replays the last 24h, and a chat that is dead now still renders as the ghost it was back then.
2. Chats that talk to each other
Two panes, two harnesses. You type one line into the Claude chat on the left; the Codex chat on the right receives it as a signed turn and gets to work.
The footer on the Codex side is the signature: who spoke (
sid 0a98b7fe) and the exact command to answer them. A message no sender could be derived for is refused, never delivered anonymously.
pfm chat call is what the Claude chat called — it types a real, signed turn into another chat's pane — under a per-target lock, safe against a busy target (--force-now) and shell-hostile payloads (--file). pfm chat ask waits for the answer, with named exit codes: 0 done · 2 usage · 3 chat dead · 4 no such chat · 5 answer timed out · 6 message not delivered · 7 answered, but another message reached the chat mid-wait. The chat family is registered as chat-professor, with bare MCP tool names such as call and ls, so an agent can spawn, message, read, and retire other chats across all three harnesses.
3. Rules that bite
A subagent tries to run git commit. The git-guard PreToolUse hook answers:
git-guard blocked `git commit -m wip`.
Only gitter writes this: spawn Agent(subagent_type: "gitter") with the repo path and the exact change.The refusal names the right way. Its twin, pcm-guard, refuses a write to a CLAUDE.md, a .claude/ file or a template by anyone but the pcm agent, whether the Edit tools or a sed -i make it. That is one of the mandatory rules every install ships with: only gitter writes git; framework edits go through the pcm agent; and an error never renders as ABSENCE — "we failed to look" is never shown as "nothing there".
4. Read what the web hides from bots
About one in ten of the world's top 10,000 websites now tells AI crawlers to stay out — among news publishers, more than half (HasData AI Crawler Block Index, July 2026). Harvester fetches the way a reader's browser does and climbs a seven-rung ladder — direct → Chrome-fingerprint TLS → reader proxy → extractor → headless-then-headed browser → Wayback → OCR — until it holds real content:
A block is reported as a block and an app shell is never stored as the page — a failure names every source it checked, never an empty success. DOIs, ISBNs, PMIDs and PMCIDs route through twelve open-access resolvers in parallel; harvester ask -p "…" <sources> feeds the full cached artifacts to a Claude or Codex ask engine, failed sources kept visible as receipts. Harvester is a standalone Go plugin in plugins/harvester/. Its native engine plugins register the harvester MCP server and launch harvester mcp as a stdio proxy to the persistent daemon; the plugin guide lists each engine's measured tool names and install route. The browser rung never solves anything interactive.
5. One contract, three runtimes
$ engine-sync codex build .
$ head -1 AGENTS.md
<!-- Generated by engine-sync from CLAUDE.md — edit the source, then run: engine-sync build -->
$ engine-sync codex check .
CODEX CHECK PASSCLAUDE.md compiles to AGENTS.md; .claude/** compiles to .codex/** and .opencode/**; global agents' .toml roles compile straight into ~/.codex/agents/ as regular files — Codex opens a role with O_NOFOLLOW and refuses a symlink, so a link there is a role that cannot spawn. Every mirror is generated, never tracked — a fresh clone has none of them until its compiler runs. The Stop hook recompiles the mirrors and a drifted mirror fails the check, so the three harnesses can never disagree about the law. Even the verification engine is one TypeScript source compiled for both the Claude Workflow runtime and the Codex SDK.
6. Reload without losing the conversation
Every seat's windows on one tab, confirmed against the provider every two seconds — you see 96% before it becomes 100%:
Account 🥇 is at 96% of its 5-hour window. The usage hook warns the chat mid-task; it finishes the step in flight and moves the conversation to 🥈 itself — same pane, same history:
The red banner is the usage hook — it reaches the model before the limit does.
/reloadtyped by a human goes through a hook too, before the model sees it, so the reboot never spends a turn.
The running chat reboots in place — same pane, same history, new account, model, or effort. --then "prompt" hands the baton unattended; /reload typed by a human runs through a hook without spending a model turn; /handoff --branch carries the full context into a detached successor.
Install
Two independent halves; adopt either without the other.
pfm, the fleet CLI — one Go binary, touches only your$HOME. Binary or source: INSTALL.md.The discipline layer — cloned, then scaffolded into your repo through an interview:
REPO=rezzminator/professor
TAG=$(git ls-remote --tags --sort=-v:refname "https://github.com/${REPO}.git" 'v*' \
| grep -v '\^{}' | head -1 | sed 's#.*/##')
git clone "https://github.com/${REPO}.git" "$HOME/.professor"
git -C "$HOME/.professor" checkout "$TAG"
cat "$HOME/.professor/docs/SETUP.md" # the install interview — start hereThe checkout is pinned to the latest semantic version tag. A maintainer checkout also runs git config core.hooksPath .githooks so pfm doctor reports pre-push gate=armed. Upgrading? Follow the update workflow: pfm doctor --project-updates reports UPDATED / NEW / GONE-UPSTREAM / LOCAL-DELETED with the upstream diff under each UPDATED row, you carry what applies into your local file, and pin / ignore / drop record your decision — pfm never rewrites a project file after init.
Jumping several versions on one host? UPGRADING.md prints the host fixes in apply order with pfm install --check --plan, and covers single-account gateway hosts.
Read before opting in: pfm defaults Claude to bypass mode and Codex to approval bypass; machine and per-account configuration can select the prompted posture. The chat-professor MCP server ships disabled through mcp.servers.chat.enabled. Harvester is installed and enabled separately through its native engine plugins; pfm does not control that registration.
The discipline layer (templates/)
Clone it into a repo and you get the complete agent, command, hook and script template set; CLAUDE.md is yours to write, and pfm init points you at /quality:claude-md write CLAUDE.md for it. docs/SETUP.md walks an interview that substitutes your project's names into every placeholder; docs/PLACEHOLDERS.md is the substitution law. Every template is the live source file verbatim — never a skeleton.
The single idea underneath it is the honest-looking absence — an instrument that answers "nothing found" both when nothing is there and when the instrument itself is broken. The rules say it out loud:
An empty enumeration is never a verdict.
One agent writes git.
gitteris the only agent that commits; every other agent is read-only.Framework edits. A change to
.claude/**, a rule file, aCLAUDE.mdortemplates/**goes through thepcmagent; it loads the quality-prompt contract before its first edit.The judge is never the thing being judged. Verdicts are read from disk, never from a brief that asserts green.
The flight pipeline. Work too large to do directly goes to one foreman, its tier picked by difficulty and ambiguity, never size:
flights-foreman-sonnetfor a clear path where the pattern is in the code,flights-foremanfor design choices, ambiguity, unfamiliar code, an unknown cause, several modules or the toughest work. The foreman reads what the build needs and writes the whole change by its own hand, spawning a child foreman only for an area sharing no file with its own. Once every foreman on the worktree has returned, the main chat sends oneflights-lander: it computes its review plan from the diff, spawns cheap hunters, spec walkers and sub-landers in parallel, verifies every candidate on disk, fixes every finding, runs the gate, thengittercommits./flights:doctormeters the flight from transcripts and git — landed lines per million input tokens — never from what an agent said it did.On-demand specifications. The user, or the main chat on the user's request, invokes
flights-speccerfor a spec and token price. It returns to its caller, outside the build/land lifecycle; no lifecycle agent or command invokes it.The persona is load-bearing. The Professor prompt replaces the vendor system prompt; the vendor baselines are pinned by sha256 so
pfm doctorreportsMATCHES / DRIFT / CHECK FAILED / CANNOT CAPTURE— never silence.
Professor ships no GPT agents. claude.gptAgents in the machine config pfm.config.json (default false, disabled) is the switch for the engine-sync GPT-twin feature: it declares the engine-sync capability gpt-agents, and engine-sync installs only agents whose requires that capability satisfies. No agent requires it today. gpt-gateway is a separate model route that serves GPT model IDs over your ChatGPT login.
A legal skill shelf ships for teams that want one.
The fleet CLI (pfm/)
One Go binary with embedded installer assets. Beyond the six moments above:
Limits, honestly. The
Limitstab shows every provider window on the box; a provider it cannot reach never renders as a 0% bar — the panel saysno usage source registeredinstead.Crash-safety by construction.
reap,archive,heal, andinstalldefault to a dry run, and the dry run is the apply's preview.healbacks up the store before it deletes a row.Headless exec.
pfm headless execis one scriptable interface for Claude and OpenCode;--engine codexalso routes to OpenCode. Prompt, system prompt, schema, and timeout go in; normalized results or native streams come out. See headless README.Housekeeping.
doctorruns the dependency registry and fleet DB checks;tokensattributes spend per agent from the telemetry ledger and the transcripts (ledger totals sum telemetry observations that can overlap, never a count of unique calls; telemetry with no session is kept apart from every chat, never guessed into one; transcript totals miss background calls such asauto_modeandprompt_suggestion, which only the telemetry ledger sees; and prices default to direct/global Claude list rates, so a Bedrock or Vertex setup sets its billing route inpfm.prices.json);/context-auditrebuilds and prices what each loop loads before its first tool call;statuslinerenders identity, session and spend; the mirrors' single writer isengine-sync, which the plugin hooks andpfm installrun.Editor.
pfm install --vscodeinstalls the Professor VS Code extension — visible as Professor in the Extensions view and a Professor entry in the terminal+dropdown — and makes thePFMsettings profile (its own icon and colour) the default, so each new integrated terminal opens at the fleet picker. To open a Professor terminal, press Ctrl+Shift+Alt+T (macOS: Cmd+Shift+Alt+T), run Professor: New Chat Terminal, or pick Professor from the terminal+dropdown — all three give the next icon and colour; the default+terminal isPFM. On a prompt file (SKILL.md, an agent, a command,CLAUDE.md,AGENTS.md; settingprofessor.promptLint.globs) the Prompt Lint link above line 1 of the file (it reads "Prompt Lint… Ns · Cancel" while a run goes; the palette's Professor: Prompt Lint does the same) has Claude, on your own subscription throughpfm headless exec, check the editor's text against Professor's prompt law (/quality:prompt, built into the extension's system prompt bypfm install); its errors, warnings and info land in the Problems panel, each suggested rewrite a quick fix.
From pfm doctor's own registry: Linux or macOS, amd64 or arm64, plus tmux ≥ 1.8, git, python3 (checked, never installed by pfm install), sh, bash, zsh, and sleep; setsid on Linux, ps/lsof/launchctl/security on macOS. A clone host also needs the engine-sync binary. Go 1.27.1 or newer for source builds and pfm update. The claude and codex CLIs are optional diagnostics. Themes can be skipped with --skip-themes; the Codex probe with --skip-engine codex. Run the dry preview before applying. Harvester's native install routes, configuration and testing are documented in its plugin guide.
Plugins
Claude Code plugins built with Professor, installable on their own. This repo is their marketplace:
claude plugin marketplace add rezzminator/professor
claude plugin install forward-compaction@professorforward-compaction: the model writes its own handoff through one tool and the plugin installs it at Claude Code's compaction, with facts recorded from typed events and every later message kept verbatim. It replaces the earlier
sub-agent-compactplugin, whose settings it no longer reads (its CHANGELOG maps each one);pfm installdisables an enabledsub-agent-compactcopy, and fails aclaude.pluginsentry that still namessub-agent-compactuntil it is replaced withforward-compaction.cache-live-control: a
/cachecommand that switches the prompt-cache TTL (5m, 1h or automatic) for one chat and its sub-agents, live, with no model turn.auto-effort: reasoning effort for the main chat and for the sub-agents it launches. Each main-chat turn starts at a planned level, the model raises or lowers it with a
set_efforttool, failing tests raise it, and a per-model cache check switches it off where an effort change would break the prompt cache. Start the Agent prompt with[effort: low|medium|high|xhigh|max]to set a sub-agent's effort per spawn; an agent whose frontmatter opts in witheffortControlhas its effort rise at each failing test run or red sub-task and come home once the trouble resolves. It replaces the earlierauto-effort-controlandeffort-live-controlplugins, carrying their settings over (its README and CHANGELOG state the migration).tool-visibility-controller: one
visibilityfield in agent, skill and command frontmatter decides, per agent, which sub-agents, skills, MCP tools and built-in tools it sees and may call — hidden from the listing and refused, never just discouraged.rule-control: one
visible-tokey in a rule file's frontmatter names the loops that receive the rule — the main chat, chosen agent types (globs allowed), or all; a targeted rule leaves the context every loop shares and reaches only those loops, again after each compaction. Claude Code only: engine-sync still carries such a rule to every Codex and OpenCode loop.buddy: a tiny ASCII companion above your prompt — Quack the duck, seven more characters or your own — that comments on each turn, suggests your next prompt and answers
/buddyquestions from what it remembers of the chat.callmeter: records every tool call, model request, sub-agent turn and session event into one local SQLite store — counts and sizes, never your prompts or file contents — and answers which files, commands and agents cost the most.
engine-sync: carries a project's Claude Code agents, commands, skills and MCP servers to Codex and OpenCode —
AGENTS.md,.codex/,.opencode/and the role TOMLs in each Codex home — and keeps them in step after every turn; atpfm install,engine-sync globalalso carries the always-on global rules of~/.claude/rules/to both engines; run it asengine-sync codex ...andengine-sync opencode ....cache-keepwarm: keeps the main chat's prompt cache warm while it waits on background agents, shells, Monitors and workflows — one invisible replay before expiry, admitted only while the forecast spend stays under the rewrite it saves;
/keepwarm on|off|statuscontrols it.
pfm install ensures the plugins claude.plugins in pfm.config.json lists; unset, that is cache-live-control, forward-compaction, auto-effort, tool-visibility-controller and engine-sync. A bare name is one of ours, installed as {plugin}@professor on every build. A release build installs it from this repo's marketplace, rezzminator/professor. An alpha build of pfm installs it instead from a local checkout: the working tree of {plugin}/plugins/{plugin} under the source clone's plugins/, where each plugin is a git submodule on its develop branch (git submodule update --init), or under the directory claude.pluginCheckoutRoot in pfm.config.json names, which wins when set: it copies each into ~/.local/share/professor-dev/plugins/{plugin}, lists it in that directory's own marketplace, also named professor, and installs {plugin}@professor from there. An alpha build also installs every other plugin the source clone's .claude-plugin/marketplace.json lists the same way, so claude.plugins is the enabled set: a listed plugin is enabled, and one the list does not name is installed from its checkout and disabled. A plugin with no checkout is not installed on an alpha build, never from GitHub, and the line names the root it tried; for a listed one that is a failure. Unset on an alpha build, the list is every plugin the marketplace lists, all enabled; a release build's default stays the five above. The install re-points a professor marketplace Claude Code knows from another source with claude plugin marketplace add, never automatically removing it. A printed manual repair may require claude plugin marketplace remove; it deletes the plugins' saved options and data, so run it only if they may go. A plugin installed under an older id, {plugin}@{plugin} from its own GitHub marketplace or {plugin}@{plugin}-dev from ~/.local/share/{plugin}-dev, moves to {plugin}@professor once that is installed: its pluginConfigs options are copied over, each old install is uninstalled with its data kept (one in a project scope is first installed as {plugin}@professor in that project), the old marketplace is removed once nothing is installed from it, ~/.local/share/{plugin}-dev is deleted, and any old key left in enabledPlugins or pluginConfigs is dropped; pfm doctor names each old copy left. The install builds an enabled callmeter's binary from its checkout into {callmeter home}/bin/{version}/callmeter before refreshing its copy, keeping the binary it replaces as callmeter.prev. Claude Code reads an alpha checkout copy straight from its folder (readFromFolder in claude plugin list --json; claude plugin update answers that there is nothing to update), never from its versioned cache, so a checkout edit, committed or not, reaches a new chat or an open chat's /reload-plugins with no version bump, and a running chat's next hook call can already run newly mirrored files. The install also runs claude plugin update for each plugins/installed_plugins.json entry behind the checkout's plugin.json version. On a release build Claude's versioned cache is what runs: a content change ships with a version bump, and an open chat gets it when it restarts. A listed plugin the install leaves off its checkout copy or disabled is a FAIL line naming the plugin, the account dir and each cause, the summary line counts it as failed=N and the install exits non-zero; pfm doctor reports the same as FAIL, and fails when the callmeter binary the install placed is gone. A full name@marketplace id is installed as named, from Claude Code's own marketplace of that name when it knows one; an entry {"id": "name@marketplace", "source": "owner/repo"} adds the marketplace first (source is what claude plugin marketplace add takes: owner/repo, a git or https URL, a path). An id with neither is reported by name, with its repair, in install and doctor. A plugin neither the list nor, on an alpha build, the marketplace names is never touched in any settings.json.
A plugin option is also an environment variable named {PLUGIN}_{OPTION}: the plugin's name and the option in upper snake case, as in AUTO_EFFORT_FLOOR. The variable beats the option, which beats the default. Two plugins differ: forward-compaction has no options and reads its settings from these variables alone, as in FORWARD_COMPACTION_CEILING; buddy reads none and keeps every setting in its own file, ${XDG_CONFIG_HOME:-~/.config}/buddy/config.json (its README). claude.env in pfm.config.json maps each variable to its string value, and pfm install writes the map into the selected config directory's settings.json env (the primary account by default, normally shared through the accounts' links). Doctor checks the last successful install's recorded target, including --config-dir. pfm owns only the variables it wrote, recorded in ~/.local/share/pfm/install/claude-env-ownership.json: a variable dropped from claude.env is removed unless it was changed by hand since, and one pfm never wrote is left alone. Alpha installs default an unset PLUGIN_LOG_DIR in each managed account directory to the shared ~/.local/state/pfm/plugin-logs folder, or the absolute path set by claude.env.PLUGIN_LOG_DIR; an operator-set value is neither overwritten nor taken into ownership. Release installs and uninstall retire generated settings only while they remain pfm-owned, never deleting the logs. A chat writing that settings.json while pfm edits it keeps its write: pfm reads the file again just before replacing it and plans its edit again on what it finds, at most three times, then fails naming the file — rerun pfm install. Apart from the known PLUGIN_LOG_DIR knob, a variable no installed plugin's name prefixes is a warning. claude.env may not set the launch-owned CACHE_LIVE_CONTROL_MAIN_TTL, CACHE_LIVE_CONTROL_AGENTS_TTL, CALLMETER_HOME or ENGINE_SYNC_HOME; every launch sets the main cache TTL from claude.cache1h. pfm doctor fails a variable missing or different from claude.env, and one left behind after its removal.
pfm install installs no engine-sync, and on any host with a Professor clone (the discipline-layer clone above, every pfm update host) or a Codex home it refuses without the binary, naming the missing path; a binary-only install with neither skips the step. So install the plugin before the first pfm install --yes or pfm update: claude plugin marketplace add rezzminator/professor, then claude plugin install engine-sync@professor, then start a new Claude session. At that session start its wrapper downloads the release binary, checks it against the plugin's SHA256SUMS, and links it at ~/.local/share/engine-sync/bin/engine-sync, the binary the plugin's hooks run; test -x on that path confirms it. Once that binary exists, pfm install --yes links it onto PATH as ~/.local/bin/engine-sync (an existing entry there that is not pfm's link stays untouched, and pfm doctor warns).
Origin
Extracted from a live production monorepo, not designed in the abstract. Every rule here exists because something went wrong without it — the gate that reads disk instead of chat exists because an agent once claimed green; the scoped-commit rule exists because two concurrent commits once swallowed each other's files; the prevention step exists because the same bug class shipped twice. The characters exist because a generic agent wasn't good enough to argue with.
Built by @rezzminator.
Help and community
Questions and ideas go to Discussions, bugs to an issue, vulnerabilities privately through SECURITY.md; SUPPORT.md has the details.
Contributing
Work lands on develop; main moves only by release. CONTRIBUTING.md carries the build, the gates and the rules of a repository whose files ship into other people's agent pipelines. Pull requests are welcome.
License
Available Tools
5 toolsarchiveARead-only
Browses a compressed archive — "open / list / what is in this .zip, .tar.gz, .7z, .rar", then "extract member X from it". Not a web page fetcher — a URL to a page goes to fetch. Call archive{source:"https://…/data.zip"} for the member listing (names and sizes, nothing extracted); then archive{source:"…", member:"docs/readme.md"} converts that one member to Markdown. 0 members = an empty archive; an ERROR item = the archive could not be fetched or opened, or the member was refused (traversal, symlink, size).
| Name | Required | Description | Default |
|---|---|---|---|
| member | No | Omit to get the SAFE member listing (names + sizes; nothing is extracted). Give one member name from that listing to fetch just that member, converted to Markdown. | |
| source | Yes | URL or local path of a .zip / .tar(.gz/.bz2/.xz) / .7z / .rar archive. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite readOnlyHint=true, the description goes well beyond annotations: it discloses that listing extracts nothing, that member retrieval converts to Markdown, that 0 members means an empty archive, and that an ERROR item represents fetch/open/refusal failures including traversal, symlink, and size cases. This gives the agent accurate expectations for edge cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: it states the resource, gives example phrasings, excludes the sibling tool, defines the two-phase call pattern, and enumerates error/empty outcomes. No filler or redundant restatement is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter tool with rich annotations, this is complete. There is no output schema, but the description explains the return semantics well enough: listing returns names and sizes, member returns Markdown, 0 members means empty, and ERROR signals the failure modes. An agent has everything needed to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds meaningful workflow semantics beyond the schema by establishing the ordering of listing first and then member selection, and it reinforces the safety distinction between the two modes. This is a modest but real enhancement over the schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb plus resource ('Browses a compressed archive'), then clarifies it is not a web page fetcher by naming the destination for URLs. It clearly distinguishes itself from the fetch sibling while defining both listing and member-extraction modes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says a URL to a page goes to fetch, naming the alternative tool and the condition that routes there. It also provides a concrete two-step usage pattern: call with source first for the listing, then with member to convert that entry to Markdown.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetchARead-only
Retrieves 1–50 documents as Markdown, input order kept. Call fetch{sources:["https://…","doi:10.…","harvest:…"]} — a URL/path, DOI, ISBN, PMID/PMCID, or a handle from findWorks/searchCache; a title → findWorks first. Each item returns content (may be truncated), size, cache status, and the path to the COMPLETE artifact — read the path for the rest. A failing item carries its own error and the others still return; an empty extraction is reported as that item's error, never as a blank body.
| Name | Required | Description | Default |
|---|---|---|---|
| refresh | No | Force a fresh fetch: bypass the cache entirely, re-download, overwrite the cached artifact, and return the NEW content. | |
| sources | Yes | 1–50 things to fetch, each returned as clean Markdown in the SAME order. Each is a LOCATION or an UNAMBIGUOUS identifier of a DOCUMENT: URL / local path / file://; DOI; ISBN; PMID / PMCID; harvest: handle. Use a DIFFERENT tool for a TITLE (findWorks), an IMAGE (fetchImage), or an archive (archive). A failing item returns a descriptive per-item error and the rest still return. | |
| size_only | No | When true, fetch and cache the full content but return NO body—just {size, chars, path}; full content remains cached at path. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond readOnlyHint, the description discloses important behavioral details: content may be truncated and the path to the full artifact is provided, failing items return their own errors while others still succeed, and empty extractions are reported as errors rather than blank bodies. It also surfaces cache-related behavior, which the annotation does not convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and informative: four sentences cover purpose, call syntax, return shape, and failure behavior without filler. Every sentence earns its place, and core operational constraints are front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully explains what each item returns, including truncation, size, cache status, and the path to the complete artifact. Combined with the schema's parameter documentation and the readOnlyHint annotation, an agent has all the context needed to invoke fetch correctly and interpret its results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds meaningful parameter semantics by showing valid source forms (URL, doi:, harvest:) and stating that results preserve input order, which goes beyond the schema's property descriptions. Refresh and size_only remain covered by the schema, so the description did not need to restate them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb and resource: retrieves 1–50 documents as Markdown while preserving input order. It distinguishes itself from siblings by specifying what fetch accepts (URLs, DOIs, ISBNs, PMID/PMCIDs, harvest handles) and explicitly routes titles to findWorks, images to fetchImage, and archives to archive.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives concrete usage guidance: identifiers from locations or unambiguous document identifiers go to fetch, while a title should go to findWorks first, with the resulting handle then passed to fetch. The schema reinforces exclusions for titles, images, and archives, leaving no ambiguity about when this tool is the right choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetchImageARead-only
Fetches images as local files for vision — "get this figure / photo / scanned page". Call fetchImage{sources:["https://…/fig1.png"]}, 1–50 URLs, local paths, or harvest handles, results in input order. Returns each item's path and bytes — open the path with vision; images are never OCR'd or converted. A failing item carries its own error and the rest still return. For a document, PDF, or web page use fetch.
| Name | Required | Description | Default |
|---|---|---|---|
| sources | Yes | 1–50 image URLs or local image paths. Each is downloaded into the type-partitioned cache and its LOCAL FILE PATH is returned in order — images are NOT OCR'd. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint annotation, the description discloses that images are never OCR'd or converted, that results preserve input order and include paths plus bytes, and that a failing item is isolated while the rest still return. This is substantial behavioral context that an agent cannot infer from the schema alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences carry the purpose, a usage example, parameter constraints, return format, partial-failure behavior, and sibling routing with no filler. The vision-oriented use case is front-loaded, and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with no output schema, the description fully covers accepted input types, limits, ordering, return values, error isolation, and the key alternative. An agent has everything needed to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already documents the sources array, its 1–50 item boundaries, and the path/order behavior. The description adds value by including a concrete invocation example and mentioning 'harvest handles' as an accepted input form not listed in the schema. This is meaningful but not essential enrichment over near-total schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific action and resource: fetching images as local files for vision. It includes natural-language triggers ('get this figure / photo / scanned page') and explicitly distinguishes itself from the sibling fetch by routing documents, PDFs, and web pages elsewhere.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states exactly when to invoke fetchImage (for images to be passed to vision) and demonstrates the call shape with an explicit example. It also names the alternative tool, fetch, for non-image content, providing clear selection criteria without ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
findWorksARead-only
Finds scholarly papers and books by TITLE or bibliographic query — "find the paper about X", "is there a PDF of ". No download. Call findWorks{query:"Attention Is All You Need"}. Returns ranked candidates (title, authors, year, kind, access) each with a fetch handle — pass that value unchanged to fetch. Empty candidates = nothing matched (give the exact title); a tool error = discovery itself failed, retry or fetch an exact identifier. For a general web query use search.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of candidate works to return. | |
| query | Yes | A paper or book TITLE, or a free-text bibliographic query. Returns a ranked list of candidate works with a fetch handle; pick one and pass it to fetch. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already indicate readOnlyHint=true, but description goes further: states 'No download' (a behavioral trait not implied by read-only), explains return format (ranked candidates with fields), clarifies empty vs error semantics, and instructs to pass the handle unchanged to fetch. This is rich context beyond annotations, though not exhaustive (e.g., no rate limiting or auth notes).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is moderately long but dense with essential info. It opens with purpose, gives a call example, then details output, error handling, and alternative. Each sentence adds value; no redundancy. Slightly verbose but well-structured and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description fully explains what is returned (ranked candidates with title, authors, year, kind, access) and how to use the fetch handle. It covers error semantics, empty results, and alternative routes. Given the tool's complexity and lack of output schema, this is complete for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers 100% of parameters (query and limit both have descriptions). The description adds value by explicitly defining query as a title or bibliographic query and explaining the output's relationship to the query (ranked candidates with fetch handle). The limit parameter is adequately described in schema, so no extra needed. Overall, description complements schema effectively.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description states a specific verb (Finds) and resource (scholarly papers and books) with examples of acceptable queries. It explicitly distinguishes itself from search for general web queries and from fetch for retrieving documents. This leaves no ambiguity about the tool's role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit call example, explains when to use this tool vs search, and gives guidance on error handling (retry or fetch exact identifier). Also clarifies the workflow: findWorks returns fetch handles to be passed to fetch. This gives clear when-to-use and when-not-to-use instructions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchCacheARead-only
Greps the local cache of already-fetched documents — "did we already fetch X", "which cached pages mention Y". Call searchCache{pattern:"transformer attention"}. Returns WHICH cached pages match (url, md_path, match count, sample line), not their text — read md_path or fetch the url for content. Empty matches = nothing held matches (the web is not searched; search or fetch first); a tool error = the pattern was invalid or the cache could not be read.
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | Regex pattern to search across every cached markdown body in the cache. | |
| ignore_case | No | Case-insensitive search. | |
| max_results | No | Maximum number of matching cached pages to return. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only provide readOnlyHint=true, but the description adds substantial behavior: returns which cached pages match rather than their text, lists the return fields, and explains the difference between empty matches and tool errors. This aligns with and extends the read-only annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense sentences cover target, output shape, empty-result semantics, and error semantics with no wasted words. The core distinction — local cache vs web search — is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
As a read-only tool with no output schema, the description is complete: it specifies the return shape, how to access full content, how to interpret empty results, and what causes errors. An agent has everything required to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already fully documents pattern, ignore_case, and max_results. The description adds a call example for pattern but does not need to compensate for any missing parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Greps the local cache of already-fetched documents', and gives two concrete questions the tool answers. It clearly distinguishes itself from siblings by emphasizing 'local cache' and explicitly noting 'the web is not searched'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent when to use it ('did we already fetch X') and when not to rely on it ('the web is not searched; search or fetch first'). Empty matches are given a defined meaning, so the agent knows to fall back to another tool rather than treating it as an error.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
archive - First observed
fetch - First observed
fetchImage - First observed
findWorks - First observed
searchCache
TDQS
Scored across 5 tools
Each tool has a clearly distinct purpose: archive handles compressed archives, fetch retrieves textual documents, fetchImage handles images, findWorks discovers scholarly works, and searchCache searches the local cache. No two tools overlap in function, and the descriptions explicitly clarify boundaries (e.g., fetchImage for images, fetch for documents).
All tool names follow a consistent lowerCamelCase convention with single-word verbs or verb-noun combinations (archive, fetch, fetchImage, findWorks, searchCache). No mixing of styles like snake_case or inconsistent verb patterns, making the naming predictable and coherent.
With 5 tools, the server is well-scoped for a document harvesting and discovery service. Each tool addresses a core aspect (fetching text, images, archives, discovery, and cache search) without redundancy, and the count is within the ideal 3-15 range.
The tool surface covers the main lifecycle: discovery (findWorks), retrieval (fetch, fetchImage), archive handling (archive), and cache search (searchCache). Minor gaps exist, such as lack of explicit cache management (e.g., clearing or deleting) or a dedicated bulk download tool, but agents can work around these with the provided tools.
Maintenance
Related MCP Connectors
Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms.
Resolve DOIs, search ~188M scholarly works, and fetch references via the Crossref REST API.
Search biomedical literature, get article details, find related articles, and explore MeSH terms
Search biomedical papers, inspect publication records, and traverse citation or semantic graphs.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables searching PubMed and Europe PMC for biomedical literature and fetching article metadata, abstracts, and full text through PMC, Europe PMC, and Unpaywall fallbacks. Also supports citation formatting, identifier conversion, related-article discovery, and MeSH vocabulary lookups over stdio or Streamable HTTP.9 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables users to search PubMed and Europe PMC and retrieve article metadata, full text from PMC/Europe PMC/Unpaywall, citations in multiple formats, related and citing works, MeSH terms, and resolved DOI/PMID/PMCID or partial-citation references. Runs over STDIO or Streamable HTTP, locally or via a public hosted endpoint.9 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables searching biomedical literature across PubMed and Europe PMC, fetching article metadata and open-access full text through PMC, Europe PMC, and Unpaywall fallbacks, formatting citations, resolving partial references and identifiers, and grounding queries in MeSH vocabulary, all over stdio or Streamable HTTP.9 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables searching biomedical literature across PubMed and Europe PMC, fetching article metadata and full text with PMC, Europe PMC, and Unpaywall fallbacks, finding related, citing, and referenced works, converting DOI/PMID/PMCID identifiers, resolving partial citations, formatting references in several styles, and grounding queries in MeSH vocabulary. Runs over STDIO or Streamable HTTP.9 npmMIT