Connect. Classify. Empower.
The Linnaeus-inspired MCP server for structured knowledge.
A single Go binary that exposes a private markdown corpus (a Hugo/front-matter
based Linny notebook of thousands of flat .md files) to AI agents over the
Model Context Protocol (MCP).
Linny is named after Carl Linnaeus, the father of modern taxonomy: the notebook is a living taxonomy, and the Hugo indexer this project succeeds is nicknamed Carl.
This repository is the PoC / alpha base. It is consumed downstream as a Nix flake
input in mipnix.
Important
No token value may ever appear in a Nix option. Nix option values land
world-readable in /nix/store. The NixOS module therefore takes a
tokensFile path, never a token literal. Source it from
age.secrets.linny-mcp-tokens.path with owner set to the service user.
Corollaries baked into the code:
- Bearer tokens are compared with
crypto/subtle.ConstantTimeCompare, never==. - Auth failures return
401with an empty body, no detail, and no timing signal. - The token file stores hashes, not raw tokens, where practical.
- Every tool response passes through a gitleaks-style egress redaction filter, so no response can return a credential regardless of what an agent asks for.
- Its own disposable index (SQLite + FTS5), built from front matter, kept in
stateDir. It is a cache: never committed to git, and deletingstateDirand rebuilding is always a valid recovery step. - A standalone indexer (
cmd/lindexer) that parses YAML front matter, builds the taxonomy graph, and emits the JSON index files thatlinny.vimalready consumes, intended to eventually replace the Hugo indexer ("Carl"). - Static bearer-token auth (explicitly not OAuth), behind an
Authenticatorinterface so OIDC can be added later without a rewrite. - Read-only degradation: whenever the git working tree is conflicted or mid-rebase, the server refuses all writes until the tree is clean again.
- A Nix flake exporting a package, a NixOS module with a hardened systemd unit,
overlays, dev shells, and
checks.
| Path | Purpose |
|---|---|
cmd/linny-mcp |
The MCP server binary (+ gen-token helper). |
cmd/lindexer |
The standalone indexer CLI (build/watch/verify). |
internal/index |
Front-matter parsing, taxonomy graph, SQLite/FTS5, JSON emit. |
internal/auth |
Authenticator interface + static bearer tokens. |
internal/authz |
Deny-by-default scopes, SQL-level filtering. |
internal/gitsafe |
Working-tree inspection, degraded mode, atomic writes. |
internal/redact |
Gitleaks-style egress secret redaction. |
internal/mcp |
The versioned MCP tool surface. |
internal/config |
Server configuration (designed for N notebooks). |
nix/ |
Flake package + NixOS module. |
docs/ |
linden-index-spec.md, tools.md, future.md. |
openspec/ |
Change proposals and specs (spec-driven development). |
nix develop # Go toolchain, golangci-lint, hugo
go build ./...
go test ./...
nix flake check # gates go test + golangci-lint for both supported systemsSupported systems: x86_64-linux, aarch64-linux (enumerated explicitly in
flake.nix, no flake-utils).
- Milestones and epics live in beans (
beans list). - Each epic is delivered as an OpenSpec change under
openspec/changes/; its tasks live in that change'stasks.md. A change is implemented, archived, and committed as one increment.
Tests run against a generated synthetic corpus only. The real private
secondbrain corpus is never used by any build, test, or tool.
MIT © Linden Project
