engineering-knowledge-mcp
# Engineering Knowledge MCP
A very lightweight local MCP server that gives coding agents (Claude Code, GitHub
Copilot, etc.) a shared engineering knowledge base to search and update — internal
conventions, API details, infra config, auth flows, local dev setup, and so on.
Knowledge lives as plain Markdown files in this Git repo. The MCP server is a thin,
stateless read/write layer over the filesystem — nothing more.
## 1. What this does
- Coding agents can **search** the knowledge base instead of guessing at internal
conventions or asking the user to repeat themselves.
- Agents can **capture** new facts with almost no friction (one tool call, no need
to know where the fact belongs).
- Agents can **create** and **update** structured knowledge documents deterministically.
- Everything is Markdown in Git, so it's useful even without the MCP server — grep it,
read it, edit it, review diffs, commit it, PR it, exactly like code.
## 2. Architecture
```
engineering-knowledge-mcp/
├── knowledge/ # the knowledge base itself (Markdown, organized by topic area)
│ ├── api/
│ ├── cloud/
│ ├── data/
│ ├── frontend/
│ └── general/
├── inbox/
│ └── knowledge-inbox.md # low-friction capture target; triage manually into knowledge/
├── src/
│ ├── index.ts # MCP server entrypoint (stdio transport)
│ ├── paths.ts # path sanitization / traversal protection
│ ├── knowledge.ts # search, get, create, update, capture logic
│ └── tools/index.ts # MCP tool registration + input schemas
├── test/ # node:test unit tests
├── CLAUDE.md # agent instructions auto-loaded by Claude Code when working in this repo
├── package.json
└── tsconfig.json
```
Design choices, deliberately:
- **MCP over stdio only.** No HTTP server, no Express — the client (Claude Code,
Copilot, MCP Inspector) spawns this process and talks JSON-RPC over stdin/stdout.
- **No database, no embeddings, no vector store.** Search is case-insensitive token
matching over Markdown sections, computed on demand. This is fine at the scale of
tens-to-hundreds of small documents, and it means there's no index to keep in sync
with the files on disk — the files *are* the source of truth, always.
- **No in-memory index, no filesystem watching.** Every tool call reads what it needs
from disk at call time. Simpler, and cheap at this scale.
- **No automatic git commits.** Tool calls only touch the working tree. Review and
commit/push are up to you. (The design leaves room to add auto-commit or PR
creation later without changing the tool contracts.)
### Official SDK note
The brief mentioned `@modelcontextprotocol/server`; the actual published package is
[`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk)
(v1.30+), which is what this project uses (`McpServer` + `StdioServerTransport`).
## 3. How knowledge is stored
Each document is a Markdown file under `knowledge/<area>/<topic>.md`, with optional
minimal frontmatter:
```markdown
---
title: APIM
tags:
- api
- apim
---
# APIM
## Base paths
Internal modelling APIs use ...
## Authentication
...
## Local development
...
```
No required schema beyond that — frontmatter is optional, headings are just normal
Markdown `##` sections. `search_knowledge` and `update_knowledge` use `##`-level (and
deeper) headings as the unit of a "section," so structuring documents with clear
headings makes both search results and updates more precise.
Captured-but-untriaged knowledge goes to `inbox/knowledge-inbox.md` as timestamped
entries. Periodically (by hand, or by asking an agent to help) move/organize entries
from the inbox into proper `knowledge/` documents.
## 4. Running it
Requires Node.js 20+.
```bash
npm install
npm run build
npm start
```
For local iteration (runs directly from TypeScript via `tsx`, no build step):
```bash
npm run dev
```
Both start the server on stdio and wait for a client to connect — you won't see
protocol traffic on the terminal; only startup/diagnostic logs (written to stderr,
never stdout, since stdout is reserved for MCP protocol messages).
## 5. Testing with MCP Inspector
Interactive UI:
```bash
npx @modelcontextprotocol/inspector npm run dev
```
This opens a browser UI where you can call `search_knowledge`, `list_knowledge_topics`,
`get_knowledge`, `capture_knowledge`, `create_knowledge`, and `update_knowledge` by
hand and inspect their JSON Schemas and responses.
Non-interactive / scriptable:
```bash
npx @modelcontextprotocol/inspector --cli node dist/index.js --method tools/list
npx @modelcontextprotocol/inspector --cli node dist/index.js \
--method tools/call --tool-name search_knowledge --tool-arg query="apim authentication"
```
## 6. Example MCP client configuration
Claude Code / most MCP clients use a config block like:
```json
{
"mcpServers": {
"engineering-knowledge": {
"command": "node",
"args": ["/absolute/path/to/engineering-knowledge-mcp/dist/index.js"]
}
}
}
```
For GitHub Copilot's MCP support, use the equivalent `command`/`args` stdio server
entry in its MCP configuration file. Run `npm run build` first so `dist/index.js`
exists, or point `command`/`args` at `npx tsx /absolute/path/to/src/index.ts` to run
from source directly.
## 7. How an agent should use the tools
This repo ships a [CLAUDE.md](CLAUDE.md) with the instruction below, which Claude
Code loads automatically whenever it's working inside this repo. For other clients
(Copilot, etc.), add the equivalent instruction to their system prompt / instructions
file:
> Before asking the user about internal engineering conventions, infrastructure,
> APIs, authentication, platform configuration, or established development patterns,
> search the engineering knowledge MCP. Do not invent internal configuration values.
> If the user explicitly asks to remember, capture, or add durable engineering
> knowledge, use the knowledge MCP write tools.
Tool-by-tool guidance:
- **`search_knowledge(query)`** — first stop for "how do we usually...", "what's our
convention for...", "what's the base URL / auth flow for...". Returns ranked
sections with file paths, not whole documents. Also searches not-yet-triaged
entries in `inbox/knowledge-inbox.md`, so a recent capture is findable even
before it's been filed under a proper topic. Among documents that already match
on body/heading text, one whose frontmatter `tags` also match a query word
ranks higher — tags boost ranking, they don't create a match on their own.
- **`list_knowledge_topics()`** — no arguments; lists every document's path, title,
and tags without full content. Use this to browse what exists when you don't have
a good search term yet, or to check whether a topic already exists before calling
`create_knowledge`.
- **`get_knowledge(topicOrPath)`** — once you know (or `search_knowledge` told you)
which document you want, fetch it in full. Accepts loose references: `"apim"`,
`"api/apim"`, or `"knowledge/api/apim.md"`.
- **`capture_knowledge(content, suggestedTopic?)`** — use when the user says
"remember this" / "note that" / states a fact worth keeping, and you don't want to
make them figure out where it belongs. It just appends to the inbox.
- **`create_knowledge(topic, title, content)`** — use when adding a genuinely new
topic that doesn't exist yet. Fails loudly if the topic already exists (use
`update_knowledge` instead).
- **`update_knowledge(topicOrPath, heading, content, mode)`** — the deliberately
*not* natural-language write tool. See the design note below.
### Why `update_knowledge` takes `heading` + `mode` instead of a free-text `change`
The brief flagged this as something needing careful design: the goal is for the
*agent* (which has an LLM) to decide what a natural-language change means, not for
this server to run its own AI interpretation of instructions. So `update_knowledge`
takes a structural, deterministic target instead:
- `topicOrPath` — which document.
- `heading` — the exact `##`/`###`/etc. heading text identifying a section. If it
doesn't exist, a new `##` section with that heading is appended at the end of the
document (so updates never silently fail against slightly-stale documents).
- `content` — the literal Markdown to write.
- `mode`: `"append"` (default) adds `content` to the end of the section, `"replace"`
overwrites the whole section body.
This means the calling agent is expected to have already turned "update the
local-dev section to mention the new port" into concrete Markdown content and picked
`append`/`replace` — exactly the kind of judgment call an LLM-backed client is
positioned to make, and exactly the kind of judgment call this lightweight server
should not be making from a raw string.
## 8. Importing an existing knowledge base
If you already have notes somewhere (a personal wiki, a folder of `.md` files, a
Notion export, a big "tribal knowledge" doc, Slack threads you've saved, etc.), there's
no import tool and no special format to convert to — this is deliberately just a
folder of Markdown files. Two ways to get started, roughly in order of how much of
your existing structure is worth preserving:
**A. Drop files in directly (best when your notes are already reasonably organized)**
1. Copy your existing `.md` files into `knowledge/`, sorted into whichever of
`api/ cloud/ data/ frontend/ general/` fits best (or add new topic folders —
nothing enforces the initial five).
2. Add minimal frontmatter (`title`, optionally `tags`) to each if it doesn't have
any — not required, but it's cheap and `get_knowledge`/search results read better
with a title.
3. Break very long documents into headed `##` sections if they aren't already —
`search_knowledge` and `update_knowledge` both operate at the heading level, so a
10,000-word single-section wall of text will search/update worse than the same
content split under a few clear headings.
4. Run `npm test` (sanity check nothing broke) and try a few `search_knowledge` /
`get_knowledge` calls via the Inspector (§5) against your real content.
5. Review the diff and commit it yourself, same as any other change to this repo.
**B. Let an agent do the migration for you (best for messy/unstructured source material)**
Point Claude Code (or another coding agent, once this MCP is configured for it) at
your existing notes and ask it to migrate them using the write tools. For example:
> I have engineering notes in `~/notes/engineering/`. Read through them and use
> `create_knowledge` to turn them into proper documents under `knowledge/`, grouped
> by topic. Where something doesn't cleanly fit an existing topic, use
> `capture_knowledge` instead so it lands in the inbox for me to review.
This works well because turning messy prose into "a title, some tags, a few clear
`##` sections" is exactly the kind of judgment call an LLM-backed agent is good at —
the same reasoning behind why `update_knowledge` pushes that judgment to the caller
rather than the server (see §7). The agent still can't write outside `knowledge/`/
`inbox/`, and every resulting file shows up as a normal untracked/modified file for
you to review before committing — nothing is auto-committed.
Either way, treat the first pass as a rough draft: it's fine (expected, even) to
have `capture_knowledge` produce a long inbox you triage over a few sessions rather
than trying to get a perfect taxonomy up front.
## 9. Security notes
- All reads/writes are restricted to `knowledge/` and `inbox/` under the repo root.
Every caller-supplied path goes through `safeResolve` (`src/paths.ts`), which
rejects absolute paths, `..` traversal, null bytes, and anything that resolves
outside the allowed directory.
- `create_knowledge` sanitizes the `topic` into a safe filename segment before use.
- No document content is ever executed, evaluated, or shelled out to.
- No tool ever runs a shell command based on MCP input.
- Errors are explicit (e.g. "no knowledge document found matching X") rather than
falling back to guesses.
TDQS
Scored across 6 tools
All six tools have distinct purposes: searching, listing, retrieving, capturing to inbox, creating new docs, and updating existing docs. No overlapping or ambiguous functions.
Tool names follow a consistent verb_noun pattern (search_knowledge, list_knowledge_topics, get_knowledge, capture_knowledge, create_knowledge, update_knowledge). Verbs clearly indicate actions and nouns correctly describe the target.
Six tools is a well-scoped number for a knowledge management server, covering search, browse, read, capture, create, and update without unnecessary redundancy.
The tool set covers create (capture and create), read (search, list, get), and update, but lacks a delete/remove operation. This is a minor gap since knowledge bases may need to retire outdated entries, but core workflow is supported.