Skip to main content
Glama
niallr12

engineering-knowledge-mcp

by niallr12
README.md
# 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

A4.4/5.0

Scored across 6 tools

Disambiguation5/5

All six tools have distinct purposes: searching, listing, retrieving, capturing to inbox, creating new docs, and updating existing docs. No overlapping or ambiguous functions.

Naming Consistency5/5

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.

Tool Count5/5

Six tools is a well-scoped number for a knowledge management server, covering search, browse, read, capture, create, and update without unnecessary redundancy.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues