librarian-mcp (2.0.0)
Installation
pip install --index-url --extra-index-url https://pypi.org/simple librarian-mcpAbout this package
MCP server for document indexing and semantic search
Librarian MCP Server
A semantic knowledge graph server implementing the Model Context Protocol (MCP). Librarian transforms your markdown files into a persistent, queryable knowledge base with semantic annotations, full-text search, and graph-based context building.
Features
- Semantic Annotations: Categorized observations and typed relations create a knowledge graph
- Full-Text Search: SQLite FTS5 with BM25 ranking for fast, relevant searches
- Multi-Vault Support: Manage multiple isolated knowledge bases
- Real-Time Sync: File watcher automatically indexes external changes (with polling mode for containers)
- MCP Resources: Notes exposed as standard MCP resources with URI-based navigation
- MCP Tools: 15 tools for creating, editing, searching, and analyzing knowledge
- MCP Prompts: 6 pre-built workflows for common knowledge management tasks
- Local-First: All data stored in plain markdown files you own
Quick Start
Installation
Requires Python 3.12+ and uv.
# Install from the package registry
uv pip install librarian-mcp --index-url https://code.movq.us/api/packages/movq/pypi/simple/
# Or install with pip
pip install librarian-mcp --index-url https://code.movq.us/api/packages/movq/pypi/simple/
Development Setup
# Clone the repository
git clone https://code.movq.us/movq/librarian-mcp.git
cd librarian-mcp
# Install with dev dependencies
uv sync --extra dev
Initialize Your First Vault
# Initialize a vault from an existing directory of markdown files
uv run librarian init ~/Documents/notes --set-default
# Or create a new vault
mkdir -p ~/knowledge-base
uv run librarian init ~/knowledge-base --name main --set-default
Start the MCP Server
# Start with stdio transport (for Claude Desktop)
uv run librarian mcp
# Start with HTTP transport for network access
uv run librarian mcp --transport http --host 0.0.0.0 --port 4242
# Use polling watcher (required in containers with bind mounts)
uv run librarian mcp --poll-watcher
Configure Your MCP Client
Claude Desktop
Add to your Claude Desktop MCP settings (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"librarian": {
"command": "uvx",
"args": [
"--index-url", "https://code.movq.us/api/packages/movq/pypi/simple/",
"librarian-mcp",
"mcp"
]
}
}
}
Usage
Creating Notes with Semantic Annotations
Librarian recognizes two types of semantic annotations in your markdown:
Observations
Categorized facts within notes:
## Observations
- [decision] Chose PostgreSQL for ACID compliance #database #architecture
- [requirement] Must support 10k concurrent users #scale
- [technique] Use connection pooling with pgbouncer #performance
Standard categories: idea, decision, fact, technique, requirement, question, insight
Relations
Directional links between notes using WikiLink syntax:
## Relations
- implements [[System Architecture]]
- requires [[Database Schema]]
- relates_to [[Performance Requirements]]
Standard relation types: relates_to, implements, requires, extends, part_of, contrasts_with, preceded_by, influenced_by
Using MCP Tools
Available Tools
| Tool | Purpose |
|---|---|
| ping | Echo a message (diagnostic) |
| status | Server status information (diagnostic) |
| write_note | Create or update a markdown note |
| edit_note | Modify existing notes (append, prepend, find_replace, replace_section) |
| delete_note | Remove notes or directories |
| move_note | Relocate notes while maintaining WikiLink references |
| read_note | Read a note by title, permalink, or path |
| update_note_properties | Update frontmatter properties on a note |
| get_note_properties | Read frontmatter properties from a note |
| bulk_update_properties | Update properties across multiple notes |
| build_context | Graph traversal to gather related entities |
| recent_activity | View recent changes across the vault |
| find_stale_notes | Find notes not updated in N days |
| canvas_tool | Create visual knowledge maps (Obsidian-compatible) |
| recall | Search the knowledge base (full-text + semantic) |
| remember | Store an observation in the memory layer |
| forget | Remove a remembered observation |
| journal | Create a write-only journal entry |
Example: Creating a Note
// Via MCP tool call
{
"tool": "write_note",
"arguments": {
"title": "API Design Decisions",
"directory": "projects",
"content": `# API Design Decisions
## Context
Evaluated options for the new service API.
## Observations
- [decision] REST over GraphQL for simplicity #api
- [requirement] Must support pagination #api
- [technique] Use OpenAPI 3.0 for documentation
## Relations
- implements [[System Architecture]]
- requires [[Authentication]]`
}
}
Using MCP Resources
Notes are exposed as standard MCP Resources with URI-based navigation:
librarian://notes/{vault}/{permalink} # Full note
librarian://notes/{vault}/{permalink}#Section # Specific section
librarian://notes/{vault}/{permalink}?page=2 # Paginated
Example: Reading a Note Section
{
"method": "resources/read",
"params": {
"uri": "librarian://notes/main/api-design#Observations"
}
}
Using MCP Prompts
Prompts provide pre-built workflows for common tasks:
- restore - Restore session context from the knowledge base
- consolidate - Extract knowledge from current conversation
- done - End session: consolidate + append to journal
- search - Search with guided follow-up instructions
- connect - Find potential connections for a note (TF-IDF keyword extraction)
- review - Surface maintenance issues (orphaned notes, unresolved references, stale content)
Example: Using the Review Prompt
{
"method": "prompts/get",
"params": {
"name": "review",
"arguments": {
"days_stale": 90
}
}
}
Configuration
Vault Management
# List all vaults
uv run librarian vault list
# Add a new vault
uv run librarian vault add work ~/work/knowledge-base --default
# Remove a vault (files are not deleted)
uv run librarian vault remove old-vault
# Get/set default vault
uv run librarian vault default # Show current default
uv run librarian vault default main # Set 'main' as default
# Re-index a vault
uv run librarian vault index # Default vault
uv run librarian vault index main --force # Full re-index
Configuration Files
Located at ~/.config/librarian/:
config.toml:
default_vault = "main"
vaults.toml:
[main]
path = "~/Documents/notes"
[work]
path = "~/work/knowledge-base"
Vault-Level Database
Each vault stores its index in {vault_path}/.librarian/librarian.db by default. Add this to your .gitignore:
.librarian/
The database is regenerated from markdown files, so it doesn't need to be version controlled.
Development
Setup
# Install development dependencies
uv sync --extra dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=librarian --cov-report=term-missing
# Type checking
uv run basedpyright
# Linting and formatting
uv run ruff check src tests
uv run ruff format src tests
# Run pre-commit hooks on all files
uv run pre-commit run --all-files
Running Tests
The project has 1000+ tests covering:
- Domain models (Note, Observation, Relation)
- Database operations (CRUD, indexing, FTS5)
- Search and graph traversal
- MCP tools, resources, and prompts
- MCP client integration tests (full protocol stack)
- File watching and synchronization
- Error handling and edge cases
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/test_models.py
# Run with verbose output
uv run pytest -v
# Run with coverage
uv run pytest --cov=librarian --cov-report=html
open htmlcov/index.html
Project Structure
librarian/
├── src/librarian/
│ ├── models/ # Domain models (Note, Observation, Relation)
│ ├── database/ # SQLAlchemy ORM and repository
│ ├── indexer/ # File-to-database synchronization
│ ├── search/ # Full-text search and graph traversal
│ ├── markdown/ # Markdown parsing and formatting
│ ├── vault/ # Vault and note lifecycle management
│ ├── mcp/ # MCP interface layer
│ │ ├── tools/ # Tool implementations
│ │ ├── resources.py # Resource handlers
│ │ ├── prompts/ # Prompt implementations
│ │ └── templates/ # Handlebars templates for prompts
│ ├── watcher.py # File system monitoring
│ ├── server.py # FastMCP server setup
│ └── cli.py # Command-line interface
├── container/ # Container deployment (Dockerfile, entrypoint)
├── tests/ # Unit tests
│ ├── integration/ # MCP client integration tests
│ └── fixtures/ # Fixture markdown files for integration tests
├── docs/ # Documentation
│ ├── librarian-spec.md # Complete specification
│ └── PLAN.md # Implementation plan
└── CLAUDE.md # Development instructions
Architecture
Librarian follows a layered architecture with dependency injection:
- Domain Layer: Pure Python models (Note, Observation, Relation) that own their markdown syntax
- Database Layer: Repository pattern with SQLAlchemy ORM and FTS5 integration
- Indexer Layer: Bridges markdown files and database
- Vault Layer: Coordinates note lifecycle and cross-note operations
- Search Layer: Graph traversal and semantic search
- MCP Layer: Thin wrappers exposing domain layer via MCP protocol
This design enables:
- Testability: All layers use dependency injection
- Separation of Concerns: Each layer has a single responsibility
- Maintainability: Clear boundaries between components
Container Deployment
Librarian can run in a container (Docker/Podman) with HTTP transport:
# Build the image
podman build -t librarian .
# Run with a bind-mounted vault
podman run -d -p 4242:4242 -v ~/Documents/notes:/app/data librarian
Or use compose.yaml:
services:
librarian:
build: .
ports:
- "4242:4242"
volumes:
- ~/Documents/Librarian/main:/app/data
environment:
LIBRARIAN_VAULT_NAME: main
LIBRARIAN_VAULT_PATH: /app/data
LIBRARIAN_PORT: "4242"
LIBRARIAN_LOG_LEVEL: INFO
# LIBRARIAN_POLL_WATCHER: "false" # Disable polling (e.g., Linux host with local volumes)
The container entrypoint enables --poll-watcher by default because OS-native file watching (inotify) cannot see host filesystem changes through bind mounts on macOS. Set LIBRARIAN_POLL_WATCHER=false if running on a Linux host with local volumes where inotify works natively.
Troubleshooting
Server won't start
# Check if port is already in use (HTTP transport)
lsof -i :4242
# Verify vault configuration
uv run librarian vault list
# Check logs
uv run librarian mcp --log /tmp/librarian.log --loglevel DEBUG
tail -f /tmp/librarian.log
Notes not indexing
# Verify file permissions
ls -la ~/Documents/notes
# Check database exists
ls -la ~/Documents/notes/.librarian/
# Force re-index
uv run librarian vault index --force
# Or delete database and restart server
rm -rf ~/Documents/notes/.librarian/
uv run librarian mcp
File changes not detected in container
If running in a container and file changes aren't being picked up, ensure --poll-watcher is enabled (it is by default in the container entrypoint). Check container logs for "polling mode" in the watcher startup message.
Search not finding notes
- Ensure notes have been indexed (check
.librarian/librarian.dbexists) - Verify FTS5 is enabled in your SQLite build:
sqlite3 :memory: "pragma compile_options;" - Check that your search terms match note content, titles, or tags
Contributing
Contributions are welcome! Please:
- Read
CLAUDE.mdfor development guidelines - Follow the existing code style (enforced by pre-commit hooks)
- Add tests for new features
- Update documentation as needed
- Run the full test suite before submitting PRs
License
MIT License
Links
- Specification - Complete technical specification
- Development Plan - Phased implementation plan
- MCP Protocol - Model Context Protocol documentation
- FastMCP - MCP server framework
Acknowledgments
Built with:
- FastMCP - MCP server framework
- SQLAlchemy - Database ORM
- Click - CLI framework
- Loguru - Logging
- Watchfiles - File system monitoring