Skip to main content
Glama
README.md
# mcp-markdown-kb

An [MCP](https://modelcontextprotocol.io) server that turns any folder of
Markdown notes into a knowledge base an AI assistant can search and read.
Point it at your Obsidian vault, your `docs/` folder, or a pile of meeting
notes, and Claude can find the right note, follow links between notes, and
quote from them.

No database, no embeddings, no API keys: everything is indexed in memory at
startup with a small BM25 implementation written for this project.

## What it exposes

| Tool | What it does |
| --- | --- |
| `search_notes` | BM25 full-text search. Title, tags and headings are weighted above body text. Optional `tag` filter. Returns path, score and a snippet per hit. |
| `read_note` | Full body plus metadata (title, tags, headings, outgoing links, word count, modified time). |
| `list_notes` | Every note, optionally filtered by tag. |
| `get_backlinks` | Notes that link to a given note through `[[wikilinks]]` or relative `.md` links. |
| `list_tags` | Tag cloud with counts. |
| `reindex` | Re-scan the folder after you edit notes. |

Every note is also published as a resource at `note:///<relative/path.md>`
with `text/markdown` content, so clients that support resources can attach a
note directly to the conversation.

### Note format

Plain Markdown works as-is. The parser additionally understands:

- **Frontmatter** between `---` fences: `title`, `tags: [a, b]` or a `- item` list,
  and any other `key: value` pairs (kept in `frontmatter`). This is a small
  YAML subset, not a full YAML parser.
- **Titles**: frontmatter `title`, else the first `#` heading, else the file name.
- **Links**: `[[Wiki links]]`, `[[Wiki links|alias]]`, `[[Note#heading]]`, and
  `[text](relative/path.md)`. Wikilinks resolve by title, file name, or path,
  case-insensitively.

Hidden folders and `node_modules` are skipped.

## Running it

Requires Node 22.18+.

```sh
npm install
npm run build
node dist/cli.js ~/notes        # or: MARKDOWN_KB_DIR=~/notes node dist/cli.js
```

The server speaks MCP over stdio. Logs go to stderr so they never corrupt the
protocol stream.

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "markdown-kb": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-markdown-kb/dist/cli.js", "/absolute/path/to/notes"]
    }
  }
}
```

### Claude Code

```sh
claude mcp add markdown-kb -- node /absolute/path/to/mcp-markdown-kb/dist/cli.js /absolute/path/to/notes
```

Then ask things like "what do my notes say about rate limiting?" or "which
notes link to the API gateway design doc?".

## Development

```sh
npm run typecheck   # tsc over source and tests
npm test            # node --test, no test framework dependency
npm run build       # emits dist/
npm run dev -- ./test/fixtures/notes   # run from source, no build step
```

Tests spin up the real server and a real MCP client joined by the SDK's
`InMemoryTransport`, so the protocol layer (schemas, tool listing, resource
templates) is exercised end to end without spawning a process.

### How the pieces fit

```
src/markdown.ts   parse one note: frontmatter, title, headings, tags, links
src/search.ts     tokenizer + BM25 index + snippet extraction
src/kb.ts         walk the folder, build the index, resolve links, backlinks
src/server.ts     McpServer: tools and the note:/// resource template
src/cli.ts        argument handling and the stdio transport
```

Design notes:

- `read_note` and the resource handler only accept paths that resolve inside
  the root folder, so a client cannot read arbitrary files.
- Tools are annotated (`readOnlyHint`) so hosts can skip confirmation prompts
  for the read-only ones.
- Only two runtime dependencies: the MCP SDK and Zod (which the SDK uses for
  input validation and JSON Schema generation).

## License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues