TypeScript · Model Context Protocol · v0.3.0
ian-brain
A single authenticated HTTP endpoint that gives a language model structured access to a personal markdown wiki, a long-term memory service, a derived knowledge graph, a fleet of machines, and a credential vault — thirty tools, eight modules, one bearer token.
The system, as a graph — every node is a section
- storage substrate
- read paths
- write paths
- transport and trust
- wiki substrate
- A directory of markdown files with
[[wikilinks]]is treated as a graph. How pages are walked, slugs resolved, titles and snippets extracted, and neighbours reconstructed. - retrieval router
- Four retrieval strategies with very different cost profiles, the decision rule embedded in every tool description, and the three-deep fallback chain when the search backend is unavailable.
- write path
wiki_append— slug routing by character similarity, Jaccard de-duplication, a lock file, a git commit, and an append-only JSONL journal.- memory + wiki
- Two stores with different half-lives. Memory banks for episodic recall and reflection; curated markdown for durable facts. Where the boundary is drawn and who enforces it.
- tool surface
- Thirty tools across eight registrars, plus the federation client that mounts entire downstream MCP servers behind a name prefix and four generic tools.
- the gateway
- Express, bearer authentication, a minimal OAuth authorization-code server with PKCE, a SQLite audit ledger that hashes its arguments, and the subprocess layer everything else stands on.
What this actually is
The Model Context Protocol gives a model a list of tools and a way to call them. Most MCP servers wrap one product. This one wraps a person's working context: the notes they keep, the decisions they have made, the machines they own, and the secrets those machines need. It is the difference between an assistant that can search the web and an assistant that can search your filing cabinet.
The design commitment that shapes everything else is stated in the server's own instruction block, sent to every client on connect:
It does not preload private data; call tools only when needed.
src/server.ts:79 — server instructions
Nothing is pushed into the model's context by default. There is no system-prompt dump of the wiki, no pre-warmed memory summary. Instead the server ships a router: a short block of routing rules, repeated verbatim inside five tool descriptions, that tells the agent which retrieval strategy fits which question. Context is assembled on demand, cheapest tool first. The retrieval router section takes that apart.
The shape of the process
One Express application. Two public routes that matter — POST /mcp and
GET /health — plus an OAuth pair and a registry card. A fresh
McpServer instance is constructed per request and torn down when the
response closes, so the process holds no session state at all. Long-lived state lives in
exactly two places: the SQLite audit ledger, and the in-memory map of connected downstream
servers.
reason arguments exist — see
the gateway.
Booting it
Four runtime dependencies: the MCP SDK, Express 5, better-sqlite3 for the audit
ledger, and Zod for input schemas. The build is tsc with no bundler. The server
refuses to start without a token — the check happens before the HTTP listener is created.
$ npm install $ npm run build > ian-brain@0.3.0 build > tsc $ node dist/index.js FATAL: IAN_BRAIN_TOKEN env var is required # src/server.ts:32 — checked before app.listen(), exit code 1 $ IAN_BRAIN_TOKEN="$(cat ~/.config/ian-brain/token)" node dist/index.js [federation] no downstreams config at ./config/downstreams.json, federation disabled [ian-brain v0.3.0] listening on 127.0.0.1:5050
The default bind address is 127.0.0.1 (src/server.ts:28). Public exposure is
somebody else's job — a tunnel or reverse proxy in front, never the Node process itself.
Reading the repository
Eight modules under src/tools/ each export a single register*(server)
function; src/tools/index.ts calls all eight in order. There is no plugin discovery,
no decorator magic, no dependency injection. Adding a tool means writing a Zod schema and a
handler and adding one line to a barrel file.
| File | Lines | Responsibility |
|---|---|---|
src/index.ts | 6 | Entry point. Calls startServer(), exits non-zero on a rejected promise. |
src/server.ts | 155 | Express app, routes, the client instruction block, graceful shutdown on SIGTERM/SIGINT. |
src/auth.ts | 28 | Bearer check against a set of one or two valid tokens. |
src/oauth.ts | 169 | Authorization-code flow with PKCE, in-memory code store, redirect allowlist. |
src/audit.ts | 44 | SQLite ledger in WAL mode; arguments stored only as a truncated hash. |
src/federation/client.ts | 211 | Downstream MCP clients, cached tool catalogs, lazy reconnect. |
src/tools/files.ts | 487 | Wiki walking, search, index, neighbours, whole-wiki read, file read/write, ingest. |
src/tools/machines.ts | 555 | Host inventory, five-step command routing, file copy, log tail, access guide. |
src/tools/wiki_append.ts | 207 | The guarded wiki write path: routing, dedup, lock, commit, journal. |
src/tools/creds.ts | 179 | Vault reads and writes through the op CLI, keyed by alias. |
src/tools/projects.ts | 166 | Generic list / search / schema / call over federated namespaces. |
src/tools/memory.ts | 181 | Recall, retain and reflect against a memory service over HTTP. |
src/tools/graph.ts | 124 | Query, path and explain over a pre-built graph JSON. |
src/tools/actions.ts | 113 | GitHub CLI, Workspace CLI, and a raw local shell. |
src/tools/shell.ts | 80 | The spawn wrapper: timeout, output cap, SIGKILL, result formatting. |
Three observations from the source
Advertised but not registered
The registry card served at /.well-known/registry-card names
discover_namespaces as the introspection tool
(src/server.ts:62-65). No tool by that name is registered by
registerAllTools. The working equivalent is projects_list. The
federation client's error text points at a discover_tools that is likewise absent
(src/federation/client.ts:167).
The wiki write path was retrofitted
patch.cjs at the repository root is a 32-line one-shot script that inserts the
wiki_append registration into the tool barrel and splices five lines into the
server's instruction block. It writes a .bak-wikiappend-* backup and is idempotent
by string match. Both edits are already present in the committed source — the script is a
fossil of how the write path was added.
No test suite
There is no test directory, no test runner in devDependencies, and no
test script in package.json. The correctness argument rests on
TypeScript strict mode, Zod validation at the tool boundary, and the audit ledger
as a post-hoc record. Every claim on this site is drawn from reading the source, not from
running it.
Documentation compiled by reading the source of a private repository. Tool names, schemas,
defaults, limits and control flow are quoted from that source with file and line references.
Wiki contents, memory contents, credential aliases, machine hostnames, account identities and
service endpoints have been deliberately omitted; where an example needs a value, a placeholder
such as <slug> or <host> appears instead.