ekl MCP server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ekl MCP serverWhat's our approved procedure for escalating a suspicious transaction?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Enterprise Knowledge Layer: RAG + MCP Reference Architecture
All data is synthetic; Maple Harbour Bank is fictional.
AI assistants are only as trustworthy as the knowledge they are allowed to reach. In a regulated bank, "connect the chatbot to SharePoint" is not an architecture: someone has to guarantee that the model only sees approved, current content, that a teller's assistant cannot read an AML investigation, that customer identifiers never leave the boundary, and that every retrieval can be reconstructed for an auditor. This repository is a small, readable reference implementation of that layer. It exposes internal knowledge to any Model Context Protocol client (Claude Desktop, IDE agents, in-house copilots) through one governed server, with access control, approved-only content, PII redaction and a tamper-evident audit log built in rather than bolted on. About 700 lines of Python, one runtime dependency (the official MCP SDK), 62 tests.
Architecture
flowchart LR
client["MCP client<br/>(Claude Desktop, agent, copilot)"]
subgraph server["ekl MCP server (python -m ekl.server)"]
tools["Tools<br/>search_knowledge · get_document · list_sources"]
policy["Policy / access layer<br/>caller role → max classification"]
retriever["Retriever interface<br/>(BM25 today, vector store tomorrow)"]
redact["PII redaction<br/>(every tool output)"]
end
corpus[("Approved corpus<br/>review_status = approved<br/>sha256 matches manifest")]
manifest[["manifest/manifest.json<br/>(python -m ekl.ingest)"]]
audit[("audit/audit.jsonl<br/>hash-chained, doc_ids only")]
client -- "stdio / HTTP + OAuth" --> tools
tools --> policy --> retriever --> corpus
manifest -. integrity check .-> corpus
retriever --> redact --> client
tools -- "who, what, which doc_ids" --> auditEvery request passes through the same five controls in ekl/knowledge.py, in order:
integrity → access → retrieval → redaction → audit. The MCP server and the CLI demo are thin
channels over that one service, so a new channel cannot accidentally skip a control.
Related MCP server: enterprise-knowledge-integrator
Governance controls
Control | Where | Risk it addresses |
Approved-only serving |
| Draft, pending or retired policy presented to staff as current guidance |
Content integrity (SHA-256 vs. manifest) |
| Unreviewed edits or tampering with source documents after sign-off |
Role-based access by classification |
| Over-exposure of confidential / restricted content (e.g. AML cases) to roles without need-to-know |
Filter before ranking |
| Leakage through scores, snippets or "near-miss" results |
Uniform "not found or not accessible" |
| Confirming the existence of restricted documents by probing ids |
PII redaction on every output (and on logged queries) |
| Personal information (SIN, account, card, email, phone) reaching the model, its logs or the user |
Citations with doc_id, version, effective date |
| Unverifiable answers; inability to trace an answer back to its source |
Hash-chained, append-only audit log |
| Untraceable access; silent alteration or deletion of access records |
Corpus manifest (versioned, per-doc hash) |
| No record of which content versions were in scope at a given time |
These controls are designed to support model-risk management practices such as those in OSFI Guideline E-23 (Model Risk Management, effective May 1, 2027): inventory of grounding sources, data lineage, monitoring and traceability. They do not by themselves make any system compliant with E-23 or any other regulation.
Quickstart
Requires Python 3.10+.
make install # pip install -e ".[dev]" (add --break-system-packages / use a venv as needed)
make ingest # validate front-matter, write manifest/manifest.json
make test # 62 tests
make demo ROLE=teller QUERY="suspicious transaction escalation"
make demo ROLE=compliance QUERY="suspicious transaction escalation"
make verify-audit # check the audit log hash chain
make serve ROLE=analyst # MCP server on stdioThe same query returns different results by role: a teller gets the branch cash-handling standard; compliance additionally gets the restricted AML escalation procedure, with the sample SIN, account number, phone and email redacted.
Roles
Demo identity comes from EKL_USER_ID and EKL_ROLE (default role: public, least privilege).
Role | Max classification | Sees |
| public | Complaint policy, service fees |
| internal | + operations procedures, data classification, expenses |
| confidential | + mortgage pre-approval, model risk policy |
| restricted | + AML escalation procedure |
Other configuration: EKL_CORPUS_DIR (default corpus/), EKL_MANIFEST (default
manifest/manifest.json), EKL_AUDIT_LOG (default audit/audit.jsonl).
Try the integrity check
# 1. Edit an approved document after it was registered
sed -i 's/\$85/$850/' corpus/mhb-fin-008-travel-expense-policy.md
make demo QUERY="meal per diem"
# (excluded MHB-FIN-008: content hash does not match manifest)
# 2. "Approve" the pending draft by editing its front-matter
sed -i 's/review_status: pending/review_status: approved/' corpus/mhb-ai-009-genai-acceptable-use.md
make demo QUERY="generative AI acceptable use"
# still excluded: the hash no longer matches what was reviewed
# 3. Tamper with the audit log
sed -i '1s/"teller"/"compliance"/' audit/audit.jsonl
make verify-audit
# TAMPERING DETECTED: line 1: entry hash mismatch (modified)
git checkout corpus/ && make ingest # restoreRe-running make ingest is the deliberate "publish" step: in a real pipeline it would run only after
the document owner's approval in the content workflow, and the manifest would be signed and stored
outside the reach of document authors.
Connect Claude Desktop
Add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows:
%APPDATA%\Claude\). Use absolute paths; Claude Desktop does not start in the repo directory.
{
"mcpServers": {
"maple-harbour-knowledge": {
"command": "/Users/you/enterprise-knowledge-layer/.venv/bin/python",
"args": ["-m", "ekl.server"],
"env": {
"EKL_USER_ID": "u-1001",
"EKL_ROLE": "teller",
"EKL_CORPUS_DIR": "/Users/you/enterprise-knowledge-layer/corpus",
"EKL_MANIFEST": "/Users/you/enterprise-knowledge-layer/manifest/manifest.json",
"EKL_AUDIT_LOG": "/Users/you/enterprise-knowledge-layer/audit/audit.jsonl"
}
}
}
}Then ask, for example, "What is the process for a credit card dispute? Cite the source."
Identity in production
Reading the role from an environment variable is a demo convenience only: whoever starts the process chooses the role. In production:
Run the server over MCP's streamable HTTP transport behind the bank's identity provider, using the MCP authorization spec (OAuth 2.1). The MCP SDK supports this via a
TokenVerifier.Build the
Callerper request from verified token claims (subject, group / entitlement claims), never from anything the client or the model can set.Map entitlements to clearance in the policy layer, sourced from the bank's access-management system, and extend
can_readwith attributes beyond classification (business line, region, purpose) as needed.Ship audit entries to an immutable store (WORM storage or SIEM) in addition to, or instead of, the local JSONL chain, and periodically anchor the latest hash externally.
Because the rest of the code only ever sees a Caller, this is a change to server.py alone.
Swapping in a vector store
Retrieval sits behind a one-method interface in ekl/retriever.py:
class Retriever(Protocol):
def search(self, query: str, top_k: int, allowed_ids: Iterable[str]) -> list[Hit]: ...To use Amazon Bedrock Knowledge Bases, Azure AI Search, or Postgres with pgvector, implement
search against that service and pass it to KnowledgeService(..., retriever=MyRetriever()).
Translate allowed_ids (or, at scale, the caller's clearance and approval status) into the
service's metadata filter so access control is enforced inside the query, not by
post-filtering top-k results. Keep ingestion writing the same manifest so the integrity and
approval checks continue to apply regardless of where the vectors live.
Project layout
ekl/
config.py environment-driven settings
corpus.py front-matter parsing and validation
ingest.py manifest builder (python -m ekl.ingest)
policy.py roles, clearance, Caller
retriever.py Retriever interface + pure-Python BM25
redact.py PII redaction
audit.py hash-chained audit log (python -m ekl.audit)
knowledge.py the governed service: integrity → access → retrieval → redaction → audit
server.py MCP server (python -m ekl.server)
demo.py CLI demo (python -m ekl.demo)
corpus/ 11 synthetic Maple Harbour Bank documents
manifest/ generated manifest.json
tests/ pytest suiteLimitations
Demo identity. Role comes from environment variables; see Identity in production.
BM25, not semantic search. Lexical matching misses synonyms and paraphrase. That is intentional here to keep the code dependency-free and readable; swap in a vector store or hybrid retriever for real use.
Regex redaction. Catches well-formed identifiers only. Production systems should add a dedicated PII/NER service and treat regexes as a backstop.
Local audit chain. Detects tampering but cannot prevent someone with file access from truncating the tail or rewriting the whole chain; anchor hashes externally in production.
Not production-hardened. No rate limiting, no HA, no secrets management, no load testing, no document-level ACLs beyond classification, and whole-document (not chunk) indexing.
Works with the MCP Python SDK 1.x (FastMCP) and 2.x (renamed MCPServer).
License
MIT. See LICENSE.
Author: Sanjay Singh
All data is synthetic; Maple Harbour Bank is fictional.
Available Tools
3 toolsget_documentA
Fetch the full text of one approved document by doc_id (e.g. MHB-OPS-001).
| Name | Required | Description | Default |
|---|---|---|---|
| doc_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It does disclose two useful behaviors: the return is the full text (not a summary or metadata), and only approved documents are eligible. It says nothing about what happens for unapproved or missing doc_ids, size/truncation limits on full text, or required permissions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no padding. The resource and the scoping constraint (full text, approved, single document) come before the parameter detail, so it is properly front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read tool with no output schema and no annotations, the description covers the essentials: what it returns (full text), which documents are eligible (approved), and the key format. The remaining gap is failure behavior for unapproved or unknown IDs, which is minor but not negligible given the explicit "approved" qualifier.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the lone doc_id parameter is undocumented in structured data. The description partially compensates by giving a concrete format example (MHB-OPS-001), but it does not say where an agent obtains a valid doc_id or whether other identifier schemes are accepted.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
"Fetch the full text of one approved document by doc_id" gives a specific verb (fetch), a specific resource (full text of a document), and the exact key parameter. It implicitly contrasts with the retrieval-by-search sibling, but never names search_knowledge or list_sources, so differentiation from siblings is left to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The word "approved" hints at a precondition, and "by doc_id" implies the identifier must already be known, which suggests this is a follow-up to search_knowledge. However, there is no explicit statement of when to use this versus search_knowledge, nor any stated exclusion or prerequisite.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_sourcesA
List the approved documents the current caller is allowed to see.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It does disclose a meaningful behavioral trait — results are permission-filtered to 'approved documents the current caller is allowed to see' — which is real value beyond the schema. However, it says nothing about ordering, pagination, result size, or what an empty result means, leaving key runtime behavior undocumented.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler, and the core scope constraint (caller visibility) sits at the end of an already short clause. Nothing could be removed without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter list tool with no output schema, the description tells the agent what is returned (approved, caller-visible documents) and implicitly why the set may be smaller than the full corpus. The only shortfall is the absence of any note on result volume or pagination, which matters when there is no output schema to describe shape.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4; there is nothing for the description to clarify beyond the bare schema. It does not introduce any phantom parameters or misleading filter language, which keeps it clean.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') with a specific resource ('approved documents') and adds a scope qualifier ('the current caller is allowed to see'). It is clear enough to distinguish from get_document by implication (plural enumeration vs single retrieval), but it never names search_knowledge or explains how this differs from searching.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the word 'List' suggests a browsing/discovery scenario, but there is no explicit statement of when to call this instead of search_knowledge, and no exclusions or preconditions are given. An agent must infer the routing decision from the verb alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_knowledgeA
Search approved internal knowledge. Returns snippets with doc_id, title, version, classification and a citation. top_k is capped at 10.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| top_k | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it does disclose the return shape (doc_id, title, version, classification, citation) plus a hard constraint that top_k is capped at 10. It stops short of noting permission requirements or whether unapproved content is silently excluded versus erroring, which matters for a knowledge-retrieval tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short, front-loaded sentences with no filler; purpose comes first and the constraint is placed at the end where it is easy to scan. Slightly clipped phrasing ('version, classification and a citation') costs a little clarity but no words are wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter read tool with no annotations and no output schema, the description covers purpose, return fields, and the key limit. The remaining gap is query semantics and the absence of any retrieval-failure behavior, which keeps it just short of complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for both parameters. It explains only top_k, and only partially — the cap of 10 is useful but it never states that top_k is the number of results returned, and the query parameter's expected syntax or matching behavior is never described.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('Search approved internal knowledge') with a meaningful scope qualifier ('approved'). It implicitly separates itself from get_document by promising 'snippets' rather than full documents, but never names that sibling or states the distinction explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The use case is implied by 'snippets' and the approved-content scope, so an agent can infer this is for lookup rather than document retrieval. However, there is no explicit when-to-use guidance and no mention of get_document or list_sources as alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
get_document - First observed
list_sources - First observed
search_knowledge
TDQS
Scored across 3 tools
search_knowledge (query-based retrieval) and list_sources (permission-scoped enumeration) are both discovery-oriented and could momentarily be confused, but the descriptions clearly separate query search from browsing the allowed corpus. get_document is unambiguously a fetch-by-id operation with a distinct input signature.
All three tools follow the same verb_noun snake_case pattern (search_knowledge, get_document, list_sources) with consistent, predictable verb choices for query/fetch/enumerate semantics.
Three tools is at the low end but each maps to a distinct retrieval need (discover by query, enumerate permitted corpus, fetch full text), so there is no padding. Slightly thin for a document platform, but well-scoped for a read-only knowledge endpoint.
The discovery-to-retrieval lifecycle is covered: list what you can see, search it, then fetch full text by doc_id. Minor gaps exist, such as no way to retrieve multiple documents or inspect version history/related documents, but core workflows are not blocked.
Maintenance
Related MCP Connectors
Make your knowledge agent-ready. One MCP endpoint, 5 connectors, 3 search modes.
Knowledge base MCP for AI agents on iknow.dev. Search, read, and maintain via OAuth.
One governed MCP endpoint for 40+ business systems. Read-only, source-cited answers.
Team docs served to AI agents over MCP - search, Markdown reads, version pinning, read audit.
Related MCP Servers
- FlicenseAqualityCmaintenanceProvides read-only MCP tools to search and retrieve evidence-grounded knowledge compiled from video content, including hybrid semantic and lexical search with citations.5-
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to securely search and ingest corporate documents via MCP, offering hybrid retrieval, PII sanitization, role-based access control, and citation validation.MIT
- FlicenseNot gradedqualityBmaintenanceEnables MCP clients to ask plain-language questions and receive answers grounded only in documents the configured role is cleared to read, with the same access-controlled tools available across any client.-
- FlicenseNot gradedqualityCmaintenanceServes a governed knowledge system of record over MCP, letting agents answer questions with citations drawn only from approved documents and explicitly decline when the record does not cover the question.73 npm-