Metadata-Version: 2.5
Name: ep-mcp
Version: 0.7.0
Summary: ExpertPack MCP Server — expertise-as-a-service over MCP
Project-URL: Homepage, https://expertpack.ai
Project-URL: Repository, https://github.com/brianhearn/ep-mcp
Author-email: Brian Hearn <brian@easyterritory.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: expertpack,knowledge,mcp,model-context-protocol,rag
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Requires-Dist: click>=8.0
Requires-Dist: google-genai>=0.8
Requires-Dist: httpx>=0.27
Requires-Dist: mcp[cli]<3,>=2
Requires-Dist: numpy>=2
Requires-Dist: openai>=1.50
Requires-Dist: pydantic>=2.12
Requires-Dist: python-dotenv>=1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: sqlite-vec>=0.1
Requires-Dist: starlette>=0.40
Requires-Dist: uvicorn>=0.30
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# ep-mcp

<!-- mcp-name: io.github.brianhearn/ep-mcp -->

**Expertise-as-a-Service over the Model Context Protocol.**

An MCP server that turns any ExpertPack into a live, queryable knowledge service. Any MCP-compatible agent (Claude Desktop, Cursor, Windsurf, Claude.ai, custom hosts) can connect and retrieve high-quality, provenance-rich domain expertise via standardized tools.

**[🌐 expertpack.ai](https://expertpack.ai)** · **[📦 ExpertPack Framework](https://github.com/brianhearn/expert-pack)** · **[📖 Architecture](ARCHITECTURE.md)**

97.6% retrieval hit rate / 0 full misses on a 22-question benchmark against a production v4.1 atomic-conceptual pack (658 chunks). Prior v4.0 baseline on the same pack and eval set: 65.1%.

## Features

- **Schema-aware hybrid retrieval**: BM25 + vector search (sqlite-vec), metadata boosting, MMR re-ranking, adaptive threshold filtering, length penalty
- **`requires:` expansion (schema v4.1 atomic-conceptual)**: When a top-K atom's frontmatter declares `requires: [B, C]`, the referenced atoms are auto-appended to results as additional `SearchResult`s flagged `requires_expanded=True`. Directional (A→B does not imply B→A), transitive up to 2 hops, capped at 3 atoms and 3,500 cumulative tokens, appended after the `max_results` slice so it never displaces top-K.
- **Intent-aware routing**: Automatic query classification (ENTITY/HOW/WHY/WHEN/GENERAL) adjusts vector/BM25 fusion weights per query — no LLM required
- **Query embedding cache**: SQLite-backed cache keyed by `(model, dimension, sha256(query))` — warm query latency drops from ~4s (live Gemini API) to ~1ms
- **Deep graph traversal**: Optional multi-hop BFS expansion over knowledge graph edges with per-hop score decay and drift prevention
- **Reserved-slot graph merge**: Configurable protected slots in the final top-K reserved for graph-expanded neighbors so related files surface even against dominant core results
- **BM25 fallback mode**: Embedding provider outage degrades gracefully to lexical-only search (coverage + density + path + length scoring) instead of a hard error
- **Cross-encoder reranker**: Optional second-pass precision reranker (disabled by default, ~70ms warm latency when enabled)
- **Provenance-first**: Every result includes `id`, `content_hash`, `verified_at`, `source_file`
- **Reconstruct mode (RFC-003)**: Opt-in `reconstruct=true` / `reconstruct: true` enriches results with `fragment_id`, `line_range`, `original_markdown`, span-level `content_hash`, `excerpt`, `stale`, and byte offsets so agents can prove exactly what source text grounded an answer
- **EP-native chunking**: Files are treated as atomic retrieval units (split at `##`/`###` only for oversized content). Split chunks keep the original span as `content`/`span_hash`, carry a computed `line_range`, and record a `heading_path` (0.7.0+)
- **Heading paths (0.7.0+)**: every hit carries `heading_path` (e.g. `["Routing", "Setup", "Ports"]`) so an H3 hit keeps its H2 parent; `ep_read(path, section=..., neighbors=true)` reads just that section plus its previous/next siblings
- **Freshness (0.7.0+, ExpertPack Core 4.2)**: hits and `ep_read` carry `verified_age_days`, `age_stale` (`verified_at` older than the file's `refresh` or the manifest `freshness.refresh_cycle`), `expired` and `expires_at` (volatile TTL). `stale` keeps its old meaning — **index drift** (file changed since indexing, reconstruct mode only). Optional down-rank via `retrieval.freshness_penalty` (default `0.0` = off)
- **Skills over MCP (0.7.0+, opt-in)**: set `mcp.skills.enabled: true` in a pack's `manifest.yaml` to declare the `io.modelcontextprotocol/skills` extension (SEP-2640). Each pack becomes one skill at `skill://{slug}/SKILL.md` (generated) with every non-`on_demand` file served verbatim at `skill://{slug}/{path}`; `skills/list` / `skills/get` publish the manifest with SHA-256 digests, sizes, `ttlMs` and `cacheScope`. Host support is still thin, so the `ep_*` tools remain the primary path
- **Frontmatter aware**: Strips metadata for embedding, extracts `type`, `tags`, `requires`, etc. for boosting/expansion/filtering
- **Incremental indexing**: Content-hash based staleness detection — only re-embeds changed files
- **Multi-pack support**: Path-based routing at `/packs/{slug}/mcp`
- **Graph-aware retrieval**: optional post-retrieval expansion via `_graph.yaml` adjacency, including accepted ontology/entity nodes when graph export provides them
- **`context_prefix` (RFC-004)**: sidecar situating strings are prepended at index time for embed + BM25 only; returned/reconstruct text stays the original atom body
- **Tier / navigation filter**: `retrieval_strategy: navigation`, `_index.md`, `source-coverage.md`, and `context.on_demand` files are excluded from the RAG pool (still readable via `ep_read` / resources)
- **Tools**:
  - `ep_search` — primary hybrid retrieval tool
  - `ep_read` — load a whole atom by path or provenance id (consume-loop step 2)
  - `ep_list_topics` — browse pack structure and available content types
  - `ep_graph_traverse`
  - **Multi-pack stdio support**: All tools accept a `pack` parameter for routing when multiple packs are configured. Single-pack configs make `pack` optional (defaults to the only configured pack).
- **Resources**:
  - `ep://{slug}/manifest`
  - `ep://{slug}/authority`
  - `ep://{slug}/files`
  - `ep://{slug}/file/{+path}` (raw content with frontmatter)
  - `skill://{slug}/SKILL.md`, `skill://{slug}/{+path}` (only when `mcp.skills.enabled: true`)
- **Transports**: Streamable HTTP (primary, cloud-ready), stdio (local dev)
  - **stdio multi-pack support (0.6.0+)**: When using `--transport stdio` with multiple packs configured, all tools accept an optional `pack` parameter (slug string) to specify which pack to query. For single-pack configurations, the `pack` parameter is optional and defaults to the only configured pack. This allows MCP hosts (Cursor, Claude Desktop) to use a single stdio process for multiple packs instead of requiring one process per pack.
- **HTTP endpoint**: `GET /search` (for non-MCP HTTP clients)
- **Golden retrieval eval (0.7.0+)**: `tests/eval/` — YAML query sets (`expected` best-first, `must_not`, `category`) scored as recall@1/3/5, MRR and must_not violations per pack and category. Skipped in the default run; `pytest tests/eval -m golden --golden <dir> --packs-root <dir>` with `EP_EVAL_TODAY=2026-10-20` and optional `EP_EVAL_MIN="recall@5=0.9,mrr=0.7"`, or `python tests/eval/golden_eval.py --golden <dir> --today 2026-10-20`
- **Auth**: API key (Phase 1), designed for OAuth 2.1 (Phase 2)

## Quick Start

### Installation

```bash
git clone https://github.com/brianhearn/ep-mcp.git
cd ep-mcp
pip install -e .[dev]
```

### Configuration

Create `config.yaml` with at minimum:

```yaml
server:
  host: "127.0.0.1"
  port: 8000
  log_level: "info"
  # Required for public Host headers (SDK v2 DNS-rebinding protection).
  # See DEPLOYMENT.md — omit and Streamable HTTP returns 421.
  mcp_allowed_hosts:
    - "localhost"
    - "localhost:*"
    - "127.0.0.1"
    - "127.0.0.1:*"

packs:
  - slug: "my-pack"
    path: "/path/to/your/expertpack"
    api_keys:
      - "your-secret-key-here"

embedding:
  provider: "gemini"           # "gemini" (default) or "azure-openai"
  model: "gemini-embedding-001"
```

For the full configuration reference — embedding providers, retrieval tuning (`mmr_lambda`, `min_score`, `default_max_results`, graph expansion, reranker, `requires:` expansion, etc.), production systemd setup, and deployment gotchas — see **[DEPLOYMENT.md](DEPLOYMENT.md)**.

### Running

```bash
ep-mcp serve --config config.yaml
```

The server builds/updates the SQLite index (FTS5 + sqlite-vec) on startup. **0.5.0 requires a rebuild** of existing indexes (`index_features=context_prefix_v1+nav_filter_v1`); startup does this automatically, or delete `<pack>/.ep-mcp/` first.

The GET `/search` HTTP endpoint is available at:

`GET /search?q=<query>&pack=<slug>&n=10`

Add `&reconstruct=true` to include original markdown spans and provenance blocks for verification.

with header `Authorization: Bearer <key>`.

## Environment Variables

Only the key for your configured embedding provider is required.

| Variable                  | Required | Description |
|---------------------------|----------|-------------|
| `GEMINI_API_KEY`          | If `embedding.provider: gemini` | Gemini embedding API key |
| `AZURE_OPENAI_ENDPOINT`   | If `embedding.provider: azure-openai` | Azure OpenAI resource endpoint |
| `AZURE_OPENAI_API_KEY`    | If `embedding.provider: azure-openai` | Azure OpenAI API key |
| `EP_MCP_KEY_{SLUG}`       | Optional | Per-pack API key override (uppercase slug, hyphens→underscores) |
| `EP_MCP_REMOTE_HOST`      | Optional | Deploy target for `scripts/deploy.sh` |
| `EP_MCP_REMOTE_SRC`       | Optional | Remote source path for `deploy.sh` |
| `EP_MCP_REMOTE_SITE_PKG`  | Optional | Remote site-packages path for `deploy.sh` |

## Tech Stack

- **Language**: Python 3.12+
- **MCP Framework**: official `mcp` SDK v2 (`MCPServer`, spec 2026-07-28, dual-era with 2025 clients)
- **Storage**: SQLite with FTS5 + [sqlite-vec](https://github.com/asg017/sqlite-vec)
- **Embeddings**: Gemini (default), configurable Azure OpenAI / OpenAI providers
- **Web**: Starlette + Uvicorn (for Streamable HTTP)
- **CLI**: Click

See `pyproject.toml` for exact dependencies.

## Project Structure

See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed module breakdown, retrieval pipeline, indexing strategy, and design decisions.

## Related

- **[expertpack.ai](https://expertpack.ai)** — Framework website, community packs, and documentation
- **[ExpertPack](https://github.com/brianhearn/expert-pack)** — Schema framework, validation tools, Obsidian compatibility, community packs
- **[ExpertPacks](https://github.com/brianhearn/expert-packs)** — Published knowledge packs (private)
- **EasyTerritory MCP** (future) — Domain-specific layer built on top of this repo + `ezt-designer` pack

## License

Apache 2.0 © 2026 Brian Hearn

## Author

**Brian Hearn** — Built in 3 days (April 10–11, 2026).
