mcp-knowledge-server
README.md
# mcp-knowledge-server
> **Connect an agent to your internal knowledge without leaking across permission
> boundaries.** A Model Context Protocol server that filters every retrieval by the
> caller's identity before anything reaches the model, answers only with grounded
> citations to documents the caller may actually see, and refuses when the answer
> lives behind a permission they lack. Fully offline, keyless, and leak-gated in CI.
[](https://github.com/tahasiddiquii/mcp-knowledge-server/actions/workflows/ci.yml)



"Connect our AI to our internal docs" is the request every enterprise makes, and the
naive version, dumping a folder into a vector database, quietly ignores who is
asking. An engineer must not retrieve compensation bands; a member of the public
must not see the revenue forecast. This server makes that structurally impossible:
the access filter runs before ranking, so an inaccessible document can never enter a
result, be cited, or shape an answer. Built from my RAG and evaluation work.
## What this demonstrates
| Enterprise concern | Where |
| --- | --- |
| Identity and role model, `public` implicit for everyone | [config.py](src/mcp_knowledge/config.py) |
| Per-document access control lists | [corpus.py](src/mcp_knowledge/corpus.py) |
| Access filter applied before ranking | [acl.py](src/mcp_knowledge/acl.py) · [retriever.py](src/mcp_knowledge/retriever.py) |
| Grounded, cited answers with an explicit refusal path | [answerer.py](src/mcp_knowledge/answerer.py) |
| Staleness signal on old sources | [corpus.py](src/mcp_knowledge/corpus.py) |
| ACL enforced at the MCP resource layer too | [server.py](src/mcp_knowledge/server.py) |
| Leak-proof and grounding CI gate | [evals.py](src/mcp_knowledge/evals.py) |
## Architecture
```mermaid
flowchart LR
Q[agent query + caller identity] --> ACL{{access filter}}
D[(documents + ACLs)] --> ACL
ACL -->|accessible only| R[rank by relevance]
R -->|clears the bar| G[grounded, cited answer]
R -->|nothing relevant| X[refuse]
```
## Quickstart
```bash
make dev # venv + install -e ".[dev]"
kb demo # answers and refusals across roles
kb ask "how do I roll back a deployment" --roles engineering
kb ask "what are the salary bands" --roles engineering # refused: HR-only
kb eval # the leak-proof gate
kb serve --roles engineering # live MCP server over stdio
```
No keys, no network. Embeddings are a deterministic hashing vectorizer; set
`KB_EMBEDDER=openai` in production to swap in dense embeddings behind the same
interface, with the access-control logic unchanged.
## The gate that matters
`kb eval` runs labeled queries across roles ([report](reports/kb_report_example.md)):
| metric | value | gate |
| --- | --- | --- |
| **permission_leaks** | **0** | = 0 |
| **ungrounded_citations** | **0** | = 0 |
| recall_at_k | 1.000 | >= 0.80 |
| refusal_rate | 1.000 | >= 0.90 |
The two zeros are the contract. `permission_leaks` counts any answer that cites or
contains a document the caller cannot access, and any case where an inaccessible
gold document is even retrieved. `ungrounded_citations` counts any answer whose
cited snippet is not a verbatim span of its source. Both must be zero or CI fails.
Recall and refusal_rate confirm the server is still useful: it finds the right
document when the caller is entitled to it, and refuses cleanly when they are not.
## What it does
- **Answers within a permission.** An engineer asking how to roll back a deployment
gets the runbook snippet with a citation.
- **Refuses across a permission.** The same engineer asking for salary bands is
refused, and the HR content never appears, not even partially.
- **Grounds every claim.** Each answer carries a document id and a snippet copied
verbatim from that document, so a reviewer can verify it.
- **Flags stale sources.** An answer sourced from a document over a year old is
marked, so nobody acts on outdated terms.
- **Enforces at the resource layer.** Reading `kb://doc/{id}` directly is also
access-checked, so the boundary holds no matter how the client reaches for data.
## Design decisions
- **Filter before rank, always.** Access control is not a post-filter on results;
an inaccessible document is removed from consideration before scoring, so it can
never influence what the model sees.
- **Refuse over guess.** When nothing accessible clears the relevance floor, the
server says so. A wrong-but-confident answer and a leak are the two failure modes
that matter, and refusal avoids both.
- **Citations are verbatim.** The snippet is a real substring of the source, not a
paraphrase, which is what makes grounding checkable rather than aspirational.
- **Identity is explicit.** Roles come from the caller (`KB_ROLES`), and `public` is
always included, so default access is the least-privilege set.
## Layout
```
src/mcp_knowledge/ config · corpus · embed · acl · retriever · answerer · server · evals · cli
data/ documents.jsonl · eval_queries.jsonl
reports/ kb_report_example.md
```
## Related repositories
Part of a portfolio on production ML and LLM engineering:
- [hybrid-graph-rag](https://github.com/tahasiddiquii/hybrid-graph-rag): hybrid and graph retrieval benchmark
- [llm-eval-observability](https://github.com/tahasiddiquii/llm-eval-observability): RAG evaluation and observability
- [mcp-guardrail-gateway](https://github.com/tahasiddiquii/mcp-guardrail-gateway): security gateway for MCP servers
- [ai-harness](https://github.com/tahasiddiquii/ai-harness): multi-stage agent harness
- **mcp-knowledge-server**: this repo.
## License
MIT (c) 2026 Taha Siddiqui
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues