cove-book-forge-mcp
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 30 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Turn PDF and EPUB books into cached AI analysis, Obsidian notes, and installable Codex/Claude Agent Skills through an open MCP server.
cove-book-forge-mcp
Turn a PDF, EPUB, or TXT into reusable knowledge—not another one-off chat.
cove-book-forge-mcp is a local-first, open-source MCP server that transforms
books into structured AI analysis, Obsidian notes, chapter-level Skills, or one
complete installable Agent Skill. Analyze a chapter once, reuse the result across
outputs, and bring the finished knowledge into Codex, Claude Code, or another
MCP-capable reading system.
What you can do
- Forge an entire book into one Agent Skill. Import a PDF, EPUB, or TXT, analyze
every chapter through a resumable job, and publish a progressively disclosed
Skill that an agent can use without loading the whole book into every prompt. - Choose the right output for each chapter. Send the same analyzed chapter
to a guarded Obsidian knowledge base or turn it into a reusable chapter Skill. - Analyze once instead of paying twice. Stable fingerprints cache validated
chapter analysis, so Obsidian, chapter-Skill, and whole-book workflows reuse
matching results with zero additional model calls. - Install Skills where you already work. Generated Skills can be installed
for Codex, Claude Code, or a conventional generic Agent Skills directory. - Use your preferred model. Built-in adapters cover OpenAI, DeepSeek and
other OpenAI-compatible endpoints, and Anthropic; applications can inject a
custom provider without changing the forge. - Keep or skip the built-in library. Store managed copies locally, retain
verified references, or let an existing reading system remain authoritative
and send complete chapter snapshots through the public boundary. - Build any reading experience around it. The MCP exposes books, chapters,
analysis, outputs, complete-book jobs, controls, status, and generated Skills
over stdio or loopback-only Streamable HTTP.
From book to reusable intelligence
PDF / EPUB / TXT / existing reading system
│
▼
normalized chapters
│
▼
validated AI analysis + cache
┌──────┼───────────┐
▼ ▼ ▼
Obsidian chapter complete-book
notes Skill Skill
└──────┼───────────┘
▼
Codex / Claude Code /
any compatible client
Why it is different
This is more than a document parser or a prompt that summarizes a book once.
The forge provides a reusable application boundary: deterministic chapters,
schema-validated analysis, persistent caching, atomic managed outputs, and
restartable whole-book jobs. It is useful both as a ready-made MCP backend and
as infrastructure for developers building their own reading interface.
Use it when you want to:
- turn a book you may not read chapter by chapter into a complete reusable Skill;
- keep reading in your existing app while delegating analysis and publication;
- build an AI reading interface without rebuilding ingestion, caching, jobs,
Obsidian output, and Agent Skill generation from scratch; or - make book-derived knowledge available to multiple agents without repeatedly
pasting the source text into chat.
What is included
The current release implements standards-valid EPUB ingestion, text-layer PDF
ingestion, deterministic Unicode TXT ingestion, an optional local SQLite library,
external chapter-snapshot caching, model provider adapters, validated reusable
chapter analysis, guarded Obsidian publication, persistent complete-book Agent
Skill forging, and MCP tools and resources over stdio or loopback Streamable HTTP.
The repository is independent of private Cove/栖渡 code and intentionally does
not prescribe a reading UI. Scanned PDFs still require an external OCR step;
the core fails explicitly instead of silently uploading or guessing at content.
Supported input and safety boundaries
EPUB
- Standards-valid EPUB 2/3 archives are supported.
- Reading order comes from the OPF spine, not filenames or ZIP member order.
EPUB navigation/NCX labels supply chapter titles when available. - Every ZIP member is preflighted before book content is read. Absolute paths,
parent traversal, backslash paths, encrypted entries, archive symlinks, nested
archives, and configured expansion-limit violations are rejected.
- PDFs must contain a meaningful text layer. Page text remains in physical page
order and deterministic chapter ranges keep the normalized book addressable. - Scanned or image-only PDFs fail explicitly with
OCR_REQUIRED. The core does
not download an OCR engine, call a remote service, or silently invoke an OCR
fallback. - Encrypted and malformed PDFs fail with closed, path-safe errors.
TXT
- UTF-8 text, with or without a BOM, and BOM-tagged UTF-16 text are supported.
- A TXT file becomes one deterministic chapter named from the source filename,
preserving paragraphs and line order without guessing chapter boundaries. - Newlines are normalized and invisible or bidirectional control characters are
removed. Empty, malformed, NUL-containing, or known binary content fails closed.
The default ingestion limits are:
| Boundary | Default |
|---|---|
| Source file | 512 MiB |
| PDF pages | 5,000 |
| ZIP members | 10,000 |
| Total expanded ZIP content | 1 GiB |
| Expanded bytes per ZIP member | 128 MiB |
| ZIP compression ratio | 100:1 |
Sources are fingerprinted before and after extraction. A source that changes
during parsing fails with SOURCE_CHANGED and is not partially persisted.
Optional local library
The local cache lives at the configured library.data_dir and useslibrary.sqlite3. Initialization and schema migration are explicit and local.
COPYstores a verified copy beneath the library data directory. The original
source can later move or disappear without affecting the managed copy.REFERENCEstores the resolved source path and its fingerprint without
copying the file. If the source changes or disappears,source_available
becomesFalse; already-normalized chapters remain readable.- Setting
library.enabled: falsedisables managed file import. A complete,
stable externalChapterSnapshotcan still be upserted into the optional
local cache, and survives a new library-service instance. The external system
remains authoritative.
Managed import with public APIs
from pathlib import Path
from cove_book_forge.config import AppConfig
from cove_book_forge.contracts import ImportMode
from cove_book_forge.library import create_book_library
data_dir = Path("/absolute/path/to/local-library")
config = AppConfig.model_validate(
{
"library": {"enabled": True, "data_dir": data_dir},
"model": {"provider": "unused-local", "model": "unused-local"},
}
)
library = create_book_library(config)
imported = library.import_book(Path("/absolute/path/to/book.epub"), ImportMode.COPY)
stored = library.get_book(imported.book)
first_chapter = library.get_chapter(imported.book, 0)
print(stored.metadata.title, first_chapter.title)
Use ImportMode.REFERENCE to retain a reference rather than a managed copy.list_books(), get_book(), and get_chapter() read the same normalized
contracts after constructing another service for the same data directory.
External snapshot upsert while managed import is disabled
from pathlib import Path
from cove_book_forge.config import AppConfig
from cove_book_forge.contracts import BookMetadata, ChapterContent, ChapterSnapshot
from cove_book_forge.library import create_book_library
config = AppConfig.model_validate(
{
"library": {
"enabled": False,
"data_dir": Path("/absolute/path/to/optional-external-cache"),
},
"model": {"provider": "unused-local", "model": "unused-local"},
}
)
library = create_book_library(config)
book = library.upsert_chapter_snapshot(
ChapterSnapshot(
source_system="my-reader",
external_book_id="stable-book-id",
book=BookMetadata(title="External Book", total_chapters=1),
chapter=ChapterContent(
index=0,
title="Chapter 1",
content="Complete normalized chapter text.",
source_locator="my-reader:chapter:0",
),
)
)
print(library.get_chapter(book, 0).content)
Reusable chapter analysis
ChapterAnalyzer turns one normalized ChapterSnapshot into the strict publicChapterAnalysis contract through an injected async ModelProvider. It validates
every structured response against the contract schema and allows at most one
additional generation when the Provider reports invalid model output or the
returned object does not validate. Authentication, rate-limit, availability, and
configuration failures are not repaired and never trigger a fallback Provider.
The analyzer computes a stable fingerprint over normalized chapter title/body,
highlights, notes, annotations, reflections, analysis configuration, prompt and
generator versions, and the ChapterAnalysis schema. A matching persistent cache
entry returns with zero Provider calls, including after recreating both the
library and analyzer. Provider/model/base identity is ignored by default, and can
be included explicitly with analysis.include_provider_in_fingerprint: true.
API-key names and values never enter the fingerprint.
Long chapters are split losslessly at Markdown-aware boundaries. Ordered chunks
contain chapter content only; fenced code and Markdown tables remain atomic, even
when one block exceeds the configured limit. Each chunk receives a validated
analysis, followed by exactly one final merge that receives the validated chunk
analyses and the chapter's highlights, notes, annotations, and reflections. The
full body is not repeated in the merge, and only the final merged analysis is
cached under the complete chapter fingerprint.
from cove_book_forge.analysis import ChapterAnalyzer
analyzer = ChapterAnalyzer(
provider,
library,
config.analysis,
config.model,
)
analyzed = await analyzer.analyze(snapshot)
print(analyzed.analysis.core_idea, analyzed.cache_hit)
The same AnalyzedChapter feeds both implemented outputs without re-analysis.
An existing reading system supplies the complete ChapterSnapshot; its local
cache supplies the matching AnalyzedChapter, so a Skill publication does not
call a Provider again.
Persistent complete-book Skill forging
WholeBookForge accepts either a managed library book_id or a complete ordered
sequence of external ChapterSnapshot values. Planning creates a secret-free
30-minute ForgePlan bound to every chapter analysis fingerprint, the selected
Provider/model, prompt and generator versions, and Skill output configuration.
Its estimate reports tokens and model calls only for uncached analyses; it does
not invent prices.
Starting a plan requires explicit confirmation and an idempotency key. One
controller owns a book's Skill at a time. The persistent SQLite journal records
plans, jobs, and chapter publication checkpoints. Each chapter is analyzed
through ChapterAnalyzer and published through AgentSkillOutput, so cache hits
make no model calls and the final Skill contains the complete chapter set.
Pause and cancellation take effect at chapter boundaries; cancellation preserves
already published chapters. Interrupted or failed jobs can resume/retry without
repeating completed checkpoints, including after process restart.
MCP server
Install the project and start the default stdio server with:
cove-book-forge mcp --config /absolute/path/to/config.yaml
The server exposes library import/read operations, chapter analysis and outputs,
whole-book planning/jobs/control/status, generated Skill discovery, and matchingcove-book-forge:// resources. All tools return Pydantic-defined structured
results. Public errors use safe ForgeErrorDetail data and do not reveal source
paths, chapter text, Provider responses, or credentials.
An explicit local HTTP transport is also available:
cove-book-forge mcp --transport http --host 127.0.0.1 --port 8000 \
--config /absolute/path/to/config.yaml
Unauthenticated Streamable HTTP is restricted to loopback addresses. The server
does not provide a remote unauthenticated mode. Applications can construct anAppContext with a custom ModelProvider; built-in DeepSeek configuration uses
the existing OpenAI-compatible Provider route.
Safe Obsidian output
ObsidianOutput publishes one already analyzed chapter synchronously. The vault
must already exist and be explicitly configured; disk roots, the home directory,
the current working directory and its broad ancestors, symlinked paths, and
unwritable locations fail closed. Publication does not call a Provider or repair
an analysis.
This application-layer function is a type-valid composition of the public async
analysis boundary and synchronous output boundary:
from cove_book_forge.analysis import ChapterAnalyzer
from cove_book_forge.config import AppConfig
from cove_book_forge.contracts import ChapterSnapshot, ObsidianPublishResult
from cove_book_forge.library import BookLibrary
from cove_book_forge.outputs import ObsidianOutput
from cove_book_forge.providers import ModelProvider
async def publish_chapter_to_obsidian(
provider: ModelProvider,
library: BookLibrary,
config: AppConfig,
snapshot: ChapterSnapshot,
) -> ObsidianPublishResult:
analyzer = ChapterAnalyzer(provider, library, config.analysis, config.model)
analyzed = await analyzer.analyze(snapshot)
return ObsidianOutput(config.outputs.obsidian).publish(snapshot, analyzed)
A matching persistent input fingerprint makes analyzer.analyze(...) a
zero-call cache hit, including after the application recreates its library,
analyzer, and output services. Publishing the unchanged result performs no file
rewrite. Provider/model/base identity is excluded from the fingerprint by
default; set analysis.include_provider_in_fingerprint: true when an application
requires provider changes to invalidate cached analysis.
The deterministic managed layout is:
<Vault>/
├── Books/<safe book title>--<stable book key>/
│ ├── <initial safe book title> MOC.md
│ └── Chapters/<01-based index> <safe chapter title>.md
├── Cards/<safe concept or rule title>--<stable card id>.md
└── .cove-book-forge/obsidian/<stable book key>.json
The checksummed manifest preserves chapter coverage, index entries, cards,
frameworks, and topics across separate chapter publications and process
restarts. Human-facing title changes update managed display metadata while the
physical book root and MOC path remain stable. The manifest stores controlled
identities, summaries, relative paths, and hashes—not source chapter bodies,
private notes, prompts, Provider responses, credentials, or absolute paths.
Every managed Markdown file has fixed cove_* frontmatter, including its
ownership identity and body hash. Cove Book Forge updates only files whose
managed marker, identity, and recorded hash still match. Editing a managed note
outside the application causes EXTERNAL_MODIFICATION; v0.1 does not merge
arbitrary Markdown and has no overwrite flag. Keep personal prose in separate,
non-managed notes and link to the generated material.
Chapter notes, relevant concept/rule cards, the aggregate MOC, and manifest are
staged and committed as one recoverable bundle, with the manifest last. A failed
publication restores the last successful visible bundle when it can prove
ownership and never adopts or deletes a competing file. Successful unchanged
publication is a byte-for-byte no-op.
The private Cove reading interface can call the public MCP application boundary
implemented in this repository. The core remains UI-agnostic: other reading
systems can use the same tools and resources, while users without an existing
reader can build their own interface around them.
Generated Agent Skills
AgentSkillOutput turns one already analyzed ChapterSnapshot into a managed,
single-chapter Agent Skill. Configure an existing canonical directory and only
the conventional discovery roots you want to use:
outputs:
skills:
enabled: true
canonical_path: /absolute/path/to/generated-skills
install_to: [codex, claude, agents]
The public synchronous boundary mirrors the Obsidian boundary. It accepts a
cached analysis and therefore never invokes a Provider:
from cove_book_forge.contracts import AnalyzedChapter, ChapterSnapshot
from cove_book_forge.outputs import AgentSkillOutput
result = AgentSkillOutput(config.outputs.skills).publish(snapshot, analyzed)
print(result.skill_slug, result.canonical_path)
Run examples/publish_chapter_skill.py
for a complete, local demonstration. The canonical root remains the source of
truth and has a guarded, recoverable managed layout. Each selected target is
installed as a verified relative symlink when supported; if symlinks are not
available, it receives a verified managed copy. Existing non-managed files,
directories, and links are never overwritten: an installation collision returnsINSTALL_CONFLICT while leaving the canonical Skill usable. Repeating an
unchanged publication reuses the cached analysis and performs a byte-for-byte
no-op.
After installation, invoke the generated skill as $<skill-slug> (for example,$durable-decisions--0123abcd0123abcd) or make a natural-language request
matching the generated Skill's description. Codex uses ~/.codex/skills,
Claude Code uses ~/.claude/skills, and generic agents use ~/.agents/skills;
only roots named in install_to are inspected or changed.
The same output boundary is used by the whole-book forge, which analyzes and
publishes chapters through persistent, resumable jobs. The public MCP server
exposes both chapter-level and whole-book workflows; only the private Cove UI
adapter remains outside this open-source repository.
Model Providers
The async Provider boundary supports openai, openai-compatible, deepseek,
and anthropic as exact built-in names. It returns typed per-call results and
cumulative input/output/total-token usage. Each generation call makes at most
one request: there is no automatic retry, JSON repair, or fallback to another
provider inside an adapter. The implemented chapter-analysis layer owns its
separate bounded schema validation/regeneration policy described above. Trusted
provider usage is counted even when a charged response later fails termination,
content, or JSON-object validation.
API-key values are read only from the named process environment variable. Put
the variable name—not the key—in YAML, and provide the value through your shell,
service manager, or secrets manager. Keys, prompts, URLs, and raw response bodies
are excluded from public errors and are not logged by the Provider layer.openai, deepseek, and anthropic require a configured, non-blank credential;
generic openai-compatible and explicitly registered custom Providers may omit it.
DeepSeek
DeepSeek uses the OpenAI-compatible chat-completions protocol and the built-in
DeepSeek API base:
model:
provider: deepseek
model: deepseek-chat
api_key_env: DEEPSEEK_API_KEY
OpenAI-compatible or self-hosted gateway
An explicit base_url is required. api_key_env is optional for a local gateway;
when configured, its environment value must be non-empty.
model:
provider: openai-compatible
model: local-reader-model
base_url: http://127.0.0.1:11434/v1
# json_mode: true # opt in only when this gateway supports native JSON Mode
# api_key_env: LOCAL_MODEL_API_KEY
Anthropic Claude
Claude uses the Anthropic Messages API:
model:
provider: anthropic
model: claude-sonnet-4-5
api_key_env: ANTHROPIC_API_KEY
OpenAI and DeepSeek default to native JSON-object mode. Genericopenai-compatible defaults to prompt-only JSON; set optional json_mode: true
only for a gateway that supports response_format, or set false to disable
native mode for OpenAI or DeepSeek. Every path adds the controlled direct-object
instruction and strictly accepts one JSON object. Anthropic always advertises
native JSON mode and JSON Schema as unavailable and ignores an OpenAI-style
override. Output capability maxima remain unknown; default_max_output_tokens
is a caller default, not a vendor hard limit. Domain-schema validation and one
bounded invalid-output regeneration are supplied by ChapterAnalyzer, not the
Provider adapter.
Built-in Providers created by one application-owned ProviderRegistry share
configured concurrency and 60-second request limits across models on the same
route and credential identity. Each instance keeps its own cumulative usage.
Separate registries are isolated, so an embedding application should reuse its
registry for one application boundary.
Explicit custom Provider registration
Applications can register a typed local or proprietary Provider explicitly,
without plugin discovery or dynamic import paths:
from your_application.providers import custom_provider_factory
from cove_book_forge.config import ModelConfig
from cove_book_forge.providers import ProviderRegistry
registry = ProviderRegistry({"deterministic-local": custom_provider_factory})
provider = registry.create(ModelConfig(provider="deterministic-local", model="reader-model"))
See the repository/sdist source exampleexamples/custom_provider.py for a complete typed
implementation. It is source material, not a wheel package namespace; copy or
adapt it under your own application package before using the import above. The
standalone doctor command recognizes only the exact built-ins; an embedding
application owns readiness checks for its explicitly registered custom Providers.
Privacy defaults and diagnostics
- Library data and normalized snapshots stay local.
- Telemetry, cloud sync, remote logging, and hidden network fallbacks are
disabled. - API-key values come from environment variables and are never stored in YAML.
- Configured output roots require explicit authorization. Obsidian publication
remains local and writes only its validated managed bundle beneath that vault.
The doctor command is read-only and network-free. It checks configuration,
built-in Provider/base readiness, required environment-variable presence,
EPUB/PDF parser dependencies, configured library-directory readiness, any
existing library.sqlite3 through a read-only SQLite integrity check, and the
enabled Obsidian vault through the same narrow no-follow readiness boundary used
by publication. It never renders or publishes output, calls Provider generation
or health-check APIs, or creates directories, databases, journals, WAL files,
temporary files, manifests, or migrations. Disabled Obsidian output is a
non-failing warning; a configured missing, broad, linked, or unwritable vault is
a safe path-free failure.
uv sync --group dev
uv run cove-book-forge doctor --config /absolute/path/config.yaml
If managed import is disabled and the optional external cache does not yet
exist, doctor reports a non-failing warning when its existing parent is ready.
Unsafe, unreadable, or invalid existing databases remain failures.
Contributor verification
Run the complete local verification sequence before submitting a change:
uv lock --check
uv run --no-sync pytest --cov=cove_book_forge --cov-report=term-missing
uv run --no-sync ruff check .
uv run --no-sync ruff format --check .
uv run --no-sync mypy src/cove_book_forge
uv run --no-sync mypy examples/custom_provider.py
uv build --clear
uv run --no-sync python scripts/verify_distribution.py dist
Provider tests use local mocked transports and deterministic fake Providers only;
the test suite performs no live model request and requires no real account or key.
Acknowledgements
cove-book-forge-mcp is inspired by and builds upon ideas and tooling from
book-to-skill, created by
Virgilio Jr. We are grateful for its document-extraction work, Agent Skill
structure, and open-source contribution. The concrete adapted sanitizer notice
is preserved in THIRD_PARTY_NOTICES.md.
License
MIT. See LICENSE and the bundled third-party terms in
THIRD_PARTY_NOTICES.md.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found