A Model Context Protocol (MCP) server that provides file operations with automatic change detection and instruction file discovery for LLM agents.
I've published a post talking about why I made this tool. Take a look if you're interested.
Lineage MCP is a file operation server for LLM coding agents that solves two critical problems:
-
Change Blindness - Standard file tools don't tell agents when files have been modified externally. Lineage tracks file modifications and reports exactly which lines changed since a file was last read.
-
Context Loss - Large projects use instruction files (AGENTS.md, CLAUDE.md, etc.) to provide localized context, but agents don't know to look for them. Lineage automatically discovers and includes these files from all parent directories.
| Feature | Description |
|---|---|
| File Operations | List, search, read, modify, and delete files |
| Batch Modify | Apply multiple create/overwrite/append/replace operations in a single call |
| Change Detection | Line-level diff detection for externally modified files |
| Instruction Discovery | Auto-finds AGENTS.md, CLAUDE.md, etc. in parent directories |
| Security | Path traversal protection keeps operations within the workspace |
| Image Reading | Returns images to the LLM as visible content (png, jpg, gif, webp) |
| Partial Reads | Read specific line ranges for large files (offset/limit) |
| Line Numbers | Optional line number formatting for precise code references |
| Session Cooldown | Prevents redundant cache clears during initial AI tool bursts |
- Python 3.10 or higher
- pip (Python package manager) or uv
# Clone the repository
git clone https://github.com/imattpeters/lineage-mcp.git
cd lineage-mcp
# Install dependencies
pip install -r requirements.txt# Clone the repository
git clone https://github.com/imattpeters/lineage-mcp.git
cd lineage-mcp
# Install dependencies with uv
uv pip install -r requirements.txtuv is a fast Python package manager written in Rust. It's fully compatible with requirements.txt and offers significantly faster dependency installation.
{
"mcpServers": {
"lineage": {
"command": "python",
"args": ["/path/to/lineage-mcp/lineage.py", "/your/workspace"]
}
}
}{
"mcpServers": {
"lineage": {
"command": "uv",
"args": [
"run",
"python",
"/path/to/lineage-mcp/lineage.py",
"/your/workspace"
]
}
}
}Using uv run ensures dependencies are installed and the script runs in the correct environment.
{
"mcpServers": {
"lineage": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-v",
"/your/workspace:/data",
"lineage-mcp"
]
}
}
}Docker arguments explained:
run- Start a new container-i- Enable interactive mode (required for MCP's stdin-based communication)--rm- Automatically remove the container when it exits (prevents clutter)-v /your/workspace:/data- Mount your project directory to/datainside the container (the server uses/dataas the base directory)lineage-mcp- The Docker image name
Windows paths: Use forward slashes (C:/git/project) or escaped backslashes (C:\\git\\project).
Building the Docker image:
Before using Docker, build the image from the lineage-mcp directory:
docker build -t lineage-mcp .OpenCode uses a plugin system for hooks. See Client Hooks below for setup instructions.
| Tool | Description | Parameters |
|---|---|---|
list |
List directory contents | path (optional) |
search |
Search files by glob pattern | pattern, path (optional) |
read |
Read file with change tracking | file_path, show_line_numbers, offset, limit, cursor |
read_image |
Read an image as visible model content | file_path, resize (optional, pass false to skip downscaling) |
modify |
Modify one or more files | operations, on_error (optional) |
delete |
Delete file or empty directory | file_path |
clear |
Clear all session caches | (none) |
read("src/app.py", show_line_numbers=True)
# Output:
# 1→import os
# 2→from pathlib import Path
# 3→Perfect for large generated files:
# Read lines 100-150 of a large file
read("types.gen.ts", offset=100, limit=50, show_line_numbers=True)
# Output:
# 101→export type User = {
# 102→ id: string;
# ...# Find all Python files
search("**/*.py")
# Find config files in src/
search("*.config.*", path="src")# Returns the image as visible content (image content block)
read_image("assets/screenshot.png")
# Skip the automatic downscaling to 1024px wide
read_image("assets/photo.jpg", resize=False)Images wider than readImageMaxWidth (default 1024) are downscaled with the
aspect ratio preserved, so a 4000px phone photo does not flood the model's
context. Images within the limit are returned untouched. Supported types:
png, jpg, jpeg, gif, webp.
# Replace one exact string
modify([
{
"file_path": "config.py",
"operation": "replace",
"match_text": "old_value",
"text": "new_value",
}
])
# Replace all occurrences
modify([
{
"file_path": "config.py",
"operation": "replace",
"match_text": "DEBUG = True",
"text": "DEBUG = False",
"occurrence": "all",
}
])
# Mix multiple operations in one call
modify(
operations=[
{"file_path": "src/app.py", "operation": "replace", "match_text": "v1", "text": "v2"},
{"file_path": "src/config.py", "operation": "replace", "match_text": "DEBUG = True", "text": "DEBUG = False"},
{"file_path": "CHANGELOG.md", "operation": "append", "text": "\n- Updated docs\n"},
],
on_error="abort",
)Lineage tracks every file you read and reports when external changes occur the next time any lineage tool is used by appending to the response:
[CHANGED_FILES]
- src/app.py (modified): lines 15-23 (5.2s ago)
- config/settings.json (modified): lines 1-EOF (12.8s ago)
How it works:
- Files are tracked after being read
- Line-level diffs are calculated on subsequent interactions and file changes are appended to the response
- Only external changes are reported
This allows LLM agents to detect when humans or other processes modify files and respond accordingly.
When reading any file, Lineage walks up the directory tree and appends all instruction files to the response:
your-project/
├── AGENTS.md ← Included automatically
├── src/
│ ├── CLAUDE.md ← Included automatically
│ └── app/
│ └── handler.py ← File you're reading
The response includes:
--- content of file ---
[CLAUDE.md from src]
# Instructions for this module
[AGENTS.md from .]
# Project-wide instructions
Note: Instruction files at the workspace root (BASE_DIR) are excluded on the first session — they're already visible to most coding agents. After context compaction (when the session cache has been cleared at least twice), they are automatically included again since the LLM may have lost that context.
Update the appsettings.json to customize instruction file names and priority:
{
"instructionFileNames": [
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md",
"QWEN.md",
"AGENT.md",
".cursorrules"
]
}Files are checked in priority order - first match per folder is used.
Images wider than the configured width are downscaled before reaching the
model. read_image accepts resize=false to return the original bytes.
{
"readImageMaxWidth": 1024
}By default, all file operations are restricted to the base directory. To allow access to any path on the system (e.g., for cross-project references), set allowFullPaths to true:
{
"allowFullPaths": true
}When enabled, absolute paths like /var/log/app.log or C:\other\project\file.txt are accepted. Relative paths still resolve against the base directory.
LLM systems periodically "compact" or summarize conversation history to stay within context limits. When this happens, the detailed content from instruction files (AGENTS.md, CLAUDE.md, etc.) gets compressed or lost. The server's cache still thinks these files were "already provided" and won't re-send them.
Cache clearing is handled automatically:
- System tray: Click "Clear Cache" in the tray menu
- Client hooks: Automatically triggered on session start and during context compaction
- Explicit
clear()tool: Call directly when needed
clear() # Instruction files will be re-provided on next readWhen caches are cleared:
- Server-side caches are cleared
- Instruction files will be re-appended to responses
- Change detection restarts from a clean slate
Cache clears within a 30-second window are silently ignored to prevent redundant clears during bursts of activity.
- The cooldown is configurable via
newSessionCooldownSecondsinappsettings.json - The explicit
clear()tool always works regardless of cooldown - Set to
0to disable the cooldown entirely
Hook scripts automatically clear lineage-mcp caches on session start and before context compaction. The tray application must be running for hooks to work - it routes cache clear requests to the correct session using ancestor PID matching.
Create .github/hooks/clearcache.json in your project root:
{
"hooks": {
"SessionStart": [
{
"type": "command",
"command": "python \"/path/to/lineage-mcp/hooks/clearcache.py\" \"Visual Studio Code\"",
"timeout": 10
}
],
"PreCompact": [
{
"type": "command",
"command": "python \"/path/to/lineage-mcp/hooks/clearcache.py\" \"Visual Studio Code\"",
"timeout": 10
}
]
}
}Note: Enable agent hooks in VS Code settings:
"chat.hooks.enabled": true
Copy the pre-built plugin into your project's .opencode/plugins/ directory:
mkdir -p .opencode/plugins
cp /path/to/lineage-mcp/plugins/opencode/local/lineage-clearcache.ts .opencode/plugins/Edit the CLEARCACHE_SCRIPT path in the copied file to point to your lineage-mcp installation:
const CLEARCACHE_SCRIPT = "/path/to/lineage-mcp/hooks/clearcache.py";The plugin hooks into three events:
session.created- Clears stale caches when a new session startsexperimental.session.compacting- Clears caches before context compactionsession.compacted- Fallback clear after compaction completes
Add the plugin type dependency to .opencode/package.json:
{
"dependencies": {
"@opencode-ai/plugin": "latest"
}
}Lineage MCP includes an optional system tray application (lineage-mcp-tray) that provides real-time visibility and control over running sessions.
cd lineage-mcp-tray
pip install -e .
python -m lineage_trayThe tray connects to all running MCP server instances via a named pipe (Windows) or Unix socket (macOS/Linux) and shows:
- Active sessions grouped by workspace
- Number of files tracked per session
- Most recent tool call
- Controls: Clear Cache, Interrupt, and Resume
Clicking Interrupt in the tray immediately halts the LLM's tool calls. The next tool invocation returns only the interrupt message and performs no operations. The LLM must stop and check with the user. Click Resume to restore normal operation.
The interrupt message is configurable via interruptMessage in appsettings.json.
- All paths are validated against the base directory
- Path traversal (
..) outside the workspace is blocked - Symlinks are resolved and validated
- Only operations within the mounted workspace are allowed
# Using pip/standard Python
python -m pytest tests/ -v
# Using uv
uv run pytest tests/ -v
# Run specific test module
python -m pytest tests/test_read_file.py -v
# Run with coverage
python -m pytest tests/ --cov=. --cov-report=html
# Generate example responses
python tests/generate_examples.pyContributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
Created by Matt Peters - Senior Software Architect.
Built with FastMCP from the Model Context Protocol team.