Writing your own backend
The brain backend is defined by a single TypeScript interface in mcp-server/src/adapters/index.ts:
export interface BrainAdapter { readonly name: string;
ping(): Promise<void>;
search(query: string, opts?: { limit?: number; tag?: string }): Promise<BrainNote[]>;
resolvePath(path: string, opts?: { create?: boolean }): Promise<string>;
getContent(id: string): Promise<string>;
create(input: CreateNoteInput): Promise<{ id: string; path: string }>;
setContent(id: string, body: string): Promise<void>;
addTag(id: string, tag: string): Promise<void>;}That’s the whole API surface. Implement these seven methods, drop the file in mcp-server/src/adapters/, register it in the factory in index.ts, ship.
Steps
- Create
mcp-server/src/adapters/<your-backend>.tsfollowing the shape ofobsidian.ts(simplest reference) ortrilium.ts(most feature-rich reference). - Decide your storage idiom:
- File-based? See
obsidian.ts—id= relative path, paths = directories. - API-based? See
notion.ts—id= backend-native uuid, paths walked via API calls. - Database? Either works.
- File-based? See
- Decide your tag mechanism:
- First-class labels? (Trilium)
- Inline
#tagsin body? (Obsidian) - Properties / multi-select? (Notion)
- Decide your search strategy:
- The interface only requires “free-text search returning matching notes”. Use the most natural fit for your backend (full-text search, grep, vector embeddings, …).
- Add your backend to the factory in
adapters/index.ts:import { MyBackendAdapter } from './my-backend.js';export function loadAdapter(): BrainAdapter {const backend = (process.env.BRAIN_BACKEND ?? 'trilium').toLowerCase();switch (backend) {case 'trilium': return new TriliumAdapter();case 'obsidian': return new ObsidianAdapter();case 'notion': return new NotionAdapter();case 'my-backend': return new MyBackendAdapter();default:throw new Error(`Unknown BRAIN_BACKEND="${backend}"`);}} - Document required env vars in your file’s top-of-file comment.
- Open a PR — the adapter test suite (
mcp-server/tests/adapters/) covers the interface contract, run it against your new adapter to validate behaviour.
Things to watch
- Idempotency:
resolvePath(path, { create: true })should not throw if the path already exists. brain_rememberenforcement: the MCP layer enforces the what / why / evidence structure fordecisionsandlessons. Your adapter doesn’t need to.- Tag semantics: every
brain_remembercall asks the adapter to attach#claude-brain. Make sure your search respects it as a filter (or fake it via convention). getContentshould return clean text: strip HTML / markdown / block formatting where reasonable. The MCP shows excerpts; users read them.