brainlehr
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@brainlehrWhat entries in the store lack proper citations?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
brainlehr 0.1.0
A knowledge store that speaks up.
Ordinary stores wait for a query and return similar text. brainlehr does five things an archive does not:
It speaks up unasked. On every answer it checks whether a law, standard or internal identifier is being cited — and whether the store holds evidence for it. If not, it says so.
It proposes what's missing. Recurring manual steps become tool proposals with a ready-made task. A failure class that recurs three times is promoted to a rule on its own.
It contradicts. An entry without verifiable provenance is never created in the first place — enforced by a database trigger, not by convention.
It marks foreign text as data. Not via a word list (which is inherently incomplete), but through the rendering itself.
It measures itself. Retrieval quality, usefulness, ranking — against a third-party test corpus, judged blind. The numbers regularly come out badly; that is the point.
Runs as an MCP server on SQLite — so it works with any MCP client: Claude Code and Desktop, Codex, Hermes, or your own. Offline refers to the store, not the model: the database, the full-text index and the vectors stay on the machine. Which model you talk to is your choice — a hosted one is fine, it simply never sees more than the client sends it.
Version 0.1.0. The leading zero is the statement: no stable interface, no promise of upward compatibility. What works is evidenced — what is promised is nothing.
Next. Work pauses until 2026-08-10T23:00+02:00. After that, 0.1.1 is likely — likely, because that is not a promise either.
🇩🇪 Deutsche Fassung:
README.de.md
What it's for
A language model forgets everything between two sessions. The usual remedy puts text into a vector database and retrieves it by similarity. That answers what did we talk about — but not:
Who claimed this, and was it ever verified?
Does it still hold, or has it been superseded?
What if two entries contradict each other?
Does the store have any effect, or does it merely return hits?
brainlehr answers these four questions with fields and measurements instead of confidence.
Related MCP server: fortytwo-guide
Quick start
python3 schnellstart.pyThis creates an empty, rule-enforced database, writes brainlehr's self-
description into it, and verifies at the end that the fresh instance answers
the question was kannst du ("what can you do"). If it doesn't, the script
exits with an error instead of a success message.
python3 schnellstart.py --bestand # + sample corpus (NASA LLIS et al.)
python3 schnellstart.py --bestand --vektoren # + semantic search, computed locallyVectors are optional: full-text search works without them, and computing
them takes minutes to hours depending on the machine. Rationale in
docs/AUFBAU.md.
Run it as an MCP server, and check the core modules:
python3 knowledge_mcp_server.py # stdio transport
python3 kern/ausweis.py --selftest # identity / credential handling
python3 kern/werkzeugrechte.py --selftest # tool permissions
python3 kern/schema_nachzug.py --selftest # schema back-fillThe script prints the MCP configuration entry when it finishes. The text a
language model should read first is in START_HIER.md.
Moving a corpus between instances goes through the single entry point — line by line, not by copying the database file, because SQLite files cannot be merged and git would simply overwrite them:
python3 brainlehr.py init <target-directory> # set up a fresh location
python3 brainlehr.py raus auszug.jsonl # write the corpus out
python3 brainlehr.py rein auszug.jsonl --db knowledge.db # read it back in
python3 brainlehr.py haken --einbauen # wire up the hooksSet it up by pasting a prompt
Give one of these to your agent. Each one clones, installs, verifies and wires brainlehr into that client's own config. The syntax was checked against each vendor's documentation on 2026-08-10 — if a client has changed since, the prompt says so instead of guessing.
Set up brainlehr, a local MCP knowledge store, on this machine.
1. Clone https://github.com/3lehr/brainlehr.git into a directory I choose —
ask me for it, do not assume one. Then cd into it.
2. Create a venv and install: python3 -m venv .venv && source .venv/bin/activate
&& pip install -r requirements.txt
3. Run `python3 schnellstart.py`. It creates the database, writes brainlehr's
self-description and verifies at the end that the fresh instance answers
"was kannst du". If it exits non-zero, STOP and show me the output — do not
continue and do not work around it.
4. Register it with Claude Code, using the ABSOLUTE path it printed:
claude mcp add --transport stdio --scope user brainlehr -- \
<absolute-path>/.venv/bin/python3 <absolute-path>/knowledge_mcp_server.py
5. Verify: `claude mcp list` must show brainlehr as connected. Then restart the
session and call knowledge_search("was kannst du"). Report the number of
hits. Zero hits means step 3 did not do what it claimed — say so.
6. Read START_HIER.md and follow it from then on.
7. Do NOT set up credentials, and do not invent one. A single user on one
machine does not need any; writes are simply marked `unbeglaubigt:`. If I
ever ask for it, the secret is mine to paste — not yours to read or create.
Do not edit any file in the repository during setup. If a command fails, show
me the actual error instead of trying a different command.Set up brainlehr, a local MCP knowledge store, on this machine.
1. Clone https://github.com/3lehr/brainlehr.git into a directory I choose —
ask me for it, do not assume one. Then cd into it.
2. python3 -m venv .venv && source .venv/bin/activate
&& pip install -r requirements.txt
3. Run `python3 schnellstart.py`. It verifies itself at the end. If it exits
non-zero, STOP and show me the output — do not work around it.
4. Register it in ~/.codex/config.toml (the ChatGPT desktop app, Codex CLI and
the IDE extension share this file), using the ABSOLUTE paths:
[mcp_servers.brainlehr]
command = "<absolute-path>/.venv/bin/python3"
args = ["<absolute-path>/knowledge_mcp_server.py"]
Equivalent CLI form:
codex mcp add brainlehr -- <absolute-path>/.venv/bin/python3 \
<absolute-path>/knowledge_mcp_server.py
5. Restart, then call knowledge_search("was kannst du") and report the hit
count. Zero hits means step 3 did not do what it claimed — say so.
6. Read START_HIER.md and follow it from then on.
7. Do NOT set up credentials, and do not invent one. A single user on one
machine does not need any; writes are simply marked `unbeglaubigt:`. If I
ever ask for it, the secret is mine to paste — not yours to read or create.
Do not edit any file in the repository during setup. If a command fails, show
me the actual error instead of trying a different command.Set up brainlehr, a local MCP knowledge store, on this machine.
1. Clone https://github.com/3lehr/brainlehr.git into a directory I choose —
ask me for it, do not assume one. Then cd into it.
2. python3 -m venv .venv && source .venv/bin/activate
&& pip install -r requirements.txt
3. Run `python3 schnellstart.py`. It verifies itself at the end. If it exits
non-zero, STOP and show me the output — do not work around it.
4. Register it in ~/.hermes/config.yaml under mcp_servers, using ABSOLUTE
paths:
mcp_servers:
brainlehr:
command: "<absolute-path>/.venv/bin/python3"
args: ["<absolute-path>/knowledge_mcp_server.py"]
Note: Hermes prefixes tool names as mcp_brainlehr_<tool>. If you write any
rule that matches on a tool name, use the prefixed form.
5. Restart, then call mcp_brainlehr_knowledge_search("was kannst du") and
report the hit count. Zero hits means step 3 did not do what it claimed.
6. Read START_HIER.md and follow it from then on.
7. Do NOT set up credentials, and do not invent one. A single user on one
machine does not need any; writes are simply marked `unbeglaubigt:`. If I
ever ask for it, the secret is mine to paste — not yours to read or create.
Do not edit any file in the repository during setup. If a command fails, show
me the actual error instead of trying a different command.Three things every one of these prompts does on purpose:
It asks where to put the repository instead of picking a directory. An agent that chooses for you puts it somewhere you will not find again.
It forbids working around a failure.
schnellstart.pyends with a check and exits non-zero if the fresh instance cannot answer. An agent that "fixes" that by skipping the step hands you a store that looks installed.It asks for a number, not a verdict. "Report the hit count" can be wrong and be seen to be wrong; "it works" cannot.
What's actually in it
Provenance |
|
Validity |
|
Identity | not required — one user on one machine writes without any credential, each entry simply marked |
Two kinds of knowledge | nodes carry facts, lessons carry failure classes with cause, fix and prevention |
Hybrid search | FTS5 including trigram, plus local vectors (bge-m3), fused via RRF — entirely on-device |
Associative edges | reinforce what is retrieved together; an edge means "co-occurred", not "is related" |
Access log | every read and write in |
How it works
Three flows, drawn from the code as it stands on 2026-08-10. The trigger names
are the actual ones in schema.sql; the thresholds are the measured ones.
A note on the German identifiers
Column names, file names and commit messages are German, and they appear verbatim in the diagrams below — they are what the code calls them, so translating them here would send you looking for something that does not exist. The ones you will meet:
identifier | meaning |
| provenance |
| release status: |
| kind: |
| trigger: what caused the entry ( |
| norm rank · valid from · valid until |
| unattested — no credential was presented |
| the reporters: field auditor · search-coverage auditor · self-check |
| maintenance · core · reporters · hooks |
| trigger: parent node must exist · provenance must be present |
| trigger: the norm decision has no default — it must be stated |
| trigger: a house rule needs a human decider |
| trigger: provenance fields are immutable once written |
| trigger: archives the previous version on update |
| the access log — every read and write, SHA-256 chained |
Trigger names end in two letters that say when they fire: _bi before
insert, _bu before update, _ai after insert, _au after update, _ad
after delete. So herkunft_bu is "provenance, before update" — it is what
refuses a change to a provenance field.
Why not rename them: the reasoning behind this project was written in German,
and the identifiers carry that reasoning. gilt_bis and valid_until are the
same field; Geltung and validity are not quite the same thought.
1. Writing — every barrier sits in the database, not in the caller
A write is refused by SQLite itself. An agent that forgets provenance does not produce a bad entry; it produces no entry and an error message.
flowchart TD
A["knowledge_add(...)"] --> B{"kern/werkzeugrechte.py<br/>erlaubt()?"}
B -- "no" --> BX["refused at tools/call<br/>— not merely hidden from tools/list"]
B -- "yes" --> C["knowledge_mcp_server.py<br/>ensure_schema()"]
C --> D["kern/schema_nachzug.py<br/>back-fill missing columns<br/>(WAL checkpoint + backup first)"]
D --> E["INSERT INTO knowledge_nodes"]
E --> T1{"trigger source_check —<br/>provenance non-empty"}
T1 -- "empty" --> X1["ABORT<br/>add | rejected | source_fehlt"]
T1 --> T2{"trigger parent_check —<br/>parent path exists"}
T2 -- "missing" --> X2["ABORT"]
T2 --> T3{"trigger norm_entscheidung_pflicht —<br/>no default value"}
T3 -- "unset" --> X3["ABORT<br/>'did nobody look, or is it really not a norm?'"]
T3 --> T4{"trigger normrang_herkunft —<br/>house rule needs a HUMAN decider"}
T4 -- "model as decider" --> X4["ABORT"]
T4 --> T5{"triggers on anlass · freigabe · gattung —<br/>value ranges"}
T5 --> T6{"trigger gilt_bis_vor_gilt_ab —<br/>expiry cannot precede start"}
T6 --> OK["row written"]
OK --> F1["trigger knowledge_ai —<br/>updates the FTS5 index"]
OK --> F2["trigger knowledge_fassung_au —<br/>archives the OLD version on update"]
OK --> F3["access_log —<br/>SHA-256 chained"]
E -.->|"UPDATE"| H{"trigger herkunft_bu —<br/>provenance fields are immutable"}
H -- "attempted change" --> X5["ABORT"]
style X1 fill:#4a1010,color:#fff
style X2 fill:#4a1010,color:#fff
style X3 fill:#4a1010,color:#fff
style X4 fill:#4a1010,color:#fff
style X5 fill:#4a1010,color:#fff
style BX fill:#4a1010,color:#fff
style OK fill:#0f3d1e,color:#fff17 trigger families guard knowledge_nodes, 42 triggers in total. Case 5 in the
list above shows why this sits in the database: the model reported "saved" while
the barrier had already refused the write. Had the check lived in the caller,
the entry would exist today.
2. Reading — the automatic recall, and where it deliberately stays silent
flowchart TD
P["user prompt<br/>(UserPromptSubmit hook)"] --> S1{"empty, or starts with '/'?"}
S1 -- "yes" --> Q1["silent — a slash command is not a question"]
S1 -- "no" --> K["keywords()<br/>stop words removed"]
K --> S2{"fewer than MIN_HITS=3<br/>keywords left?"}
S2 -- "yes" --> Q2["silent — cannot clear the bar anyway,<br/>so don't even query"]
S2 -- "no" --> R["query()"]
R --> R1["FTS5 incl. trigram folding"]
R --> R2["local vectors (bge-m3)<br/>brute force, no ANN index"]
R1 --> RRF["rrf_fuse()<br/>reciprocal rank fusion"]
R2 --> RRF
RRF --> D["_dedup_session()<br/>drop what THIS session already got<br/>(ADR-033: saves a measured 79%)"]
D --> S3{"anything left?"}
S3 -- "no" --> L0["log_recall([], [])<br/>the NEGATIVE case is logged too —<br/>without it the log is no denominator"]
S3 -- "yes" --> L1["log_recall(nodes, lessons)"]
L1 --> O["hookSpecificOutput.additionalContext → model<br/>systemMessage + continue + suppressOutput → human"]
style Q1 fill:#3a3000,color:#fff
style Q2 fill:#3a3000,color:#fff
style L0 fill:#3a3000,color:#fff
style O fill:#0f3d1e,color:#fffMIN_HITS=3 is not a guess. Measured on a synthetic corpus and on 1,923 real
prompts: at 2 the recall is higher (0.369 vs 0.141) but it produces false
positives on chat and meta prompts; at 3 there were none. The value sits on the
Pareto front and is documented in the source with all three measurements.
No approximate vector index — on purpose. Every query is compared against all vectors in the store. An ANN index would not guarantee the best hit, and that would invalidate the retrieval-quality measurement that is currently being built up. Speed is not the bottleneck; honesty about the number is.
3. The loop — what makes it a store rather than an archive
flowchart LR
W["work in a session"] --> C["lesson_record(...)<br/>cause · fix · prevention"]
C --> DB[("knowledge.db")]
DB --> RE["recall hook<br/>injects on the next prompt"]
RE --> W
DB --> M["reporters at session start<br/>doctor · pruefer · rasterblick"]
M -->|"finding"| W
C --> E{"same failure class<br/>3rd occurrence?"}
E -- "yes" --> RU["escalated_to_rule"]
DB --> X["pflege/export_offen.py<br/>freigabe='offen' only"]
X --> XC{"bait list · patterns ·<br/>de-localisation"}
XC -- "hit" --> XA["writes NOTHING"]
XC -- "clean" --> XO["auszug-offen/bestand.jsonl"]
style XA fill:#4a1010,color:#fff
style RU fill:#0f3d1e,color:#fffThe export is deny-by-default: a new node is intern by design, so it drops out
unless someone deliberately releases it. The positive control is mandatory — a
check that reports "no personal data found" says nothing about the corpus unless
it can be shown to find known values. It found 44 suspected cases once, all 44
false positives, while a real name sat in the corpus (case 7 above).
Credentials — you can skip this
Trying brainlehr out? Skip this whole section. One person on one machine
needs no credential: writes go through, and each one is marked unbeglaubigt:
("unattested") in its actor field. Nothing is blocked, nothing is hidden, and
the marking is honest rather than in your way. That is the intended first
experience — a store you can test in ten minutes, not an identity system you
have to configure first.
Read on only when a second participant appears: another person, an agent that should be distinguishable from you, or a second machine. Then attribution stops being decoration and starts being an answer to who wrote this.
It takes three steps — and the third is the one that gets skipped.
1. Naturalisation, not self-registration. Nobody can grant themselves a
credential. A human holding ausweis:ausstellen issues a one-time PIN:
python3 kern/anmeldung.py <name> --durch <inviting-person> --rolle <role>ausweis:ausstellen is in NICHT_DELEGIERBAR — whoever may naturalise cannot
pass that power on. Otherwise the first naturalisation would be the last
control. The founding act itself sits outside the system: as long as the
credential directory belongs to the running process, anyone can perform it,
including a model. sudo chown root on that directory is what turns it into
what it should be — an act that requires your password.
2. Redeem the PIN. The new participant calls knowledge_anmelden with it.
The secret comes back exactly once and is never logged.
3. Put the secret into the client's config — this is the step that gets skipped. Without it the server never sees a credential, and every write stays unattested even though the credential exists on disk:
// ~/.claude.json → mcpServers.<name>
"env": {
"BRAINLEHR_GEHEIMNIS": "<the secret from step 2>",
"BEGOD_KNOWLEDGE_ACTOR": "<name>"
}For Codex it goes under [mcp_servers.<name>.env] in ~/.codex/config.toml,
for Hermes under the server's env: block in ~/.hermes/config.yaml.
Then restart the client. Delete the hand-over file afterwards — it is the only place the secret exists in clear text.
Soft and strict
weich (default) | an unattested write is executed and marked |
streng | an unattested write is refused: |
BRAINLEHR_DURCHSETZUNG=streng switches it. Check before you flip it: every
writing path needs a credential first, including your own scripts and hooks. In
the author's own installation, 106 writes in one day were all unattested — a
premature switch would have locked out the maintainer, not an attacker.
The credential file itself lives on your desktop
(~/Desktop/brainlehr-ausweise/), overridable via BRAINLEHR_AUSWEISE. It
holds scrypt hashes and roles, no secrets. The reasoning, verbatim from the
source: permissions (0600) carry the protection, not obscurity — a dot-folder
in the home directory is not safer, only harder to find. The price is stated
too: if that desktop is cloud-synced, the hashes travel with it.
Eight cases, with sources
Eight events, each with a timestamp, a source and the model involved. Where the model was not recorded, that is stated.
When: recorded 2026-08-01T08:47, injected 2026-08-07T11:34:22, applied 2026-08-07T15:50 (+02:00)
Model:
claude-opus-5Source: node
5eca513a, lessonL-0968ae, injection logged inrecall_log.jsonl
In openlehr (Python) a route swallowed every error in a try/except and
emitted it only as a warning that no test and no interface reads — silent data
loss in production. Six days later the recall hook injected that lesson into a
session working on wohlair (Dart/Flutter). Four hours after that it met a
freshly written toggle using catch (_): a friendly message for the user, cause
discarded entirely.
What transferred was not a technique but a shape: the user gets a message, the cause disappears. Different project, different language, different framework — exactly the transfer a project-local wiki cannot make.
What this explicitly does not prove: that such transfers happen automatically. The hook injected; a human read it and recognised the analogy. Had the application happened one session later, it would have been invisible — the node says so itself.
When: 2026-07-28T07:57:34 (+02:00)
Model: not recorded
Source: lesson
L-bac968
The fallback chain PyMuPDF → pdftotext → OCR only advanced when the extracted
text was empty. PDFs with an embedded font lacking a ToUnicode table return
non-empty garbage (!!!"# $% &'( instead of Rechnung). Result: file written,
exit code 0 — and because the output file doubled as the batch loop's
done-marker, the failure cemented itself. One document sat unusable in the
archive since first ingest — 1 of 358.
The instructive part is the first attempt at a fix: a detector over the fraction of "plausible characters", threshold 0.80. It flagged two intact documents (digit-heavy tables, 0.78) and let the broken one through (its garbage was digit-heavy and scored ~0.9). The number was plausible and wrong.
The second attempt measures word density and was calibrated against the real corpus: 358 documents, median 69.7 words per 1000 characters, worst genuine document 15.0, broken extraction 3.3 — threshold 10.0 sits in the gap. On failure, no output file is written at all.
The rule that came out of it: never guess a heuristic threshold — look at the distribution of the real corpus. If there is no gap, the metric is wrong, not the threshold.
When: 2026-07-28T08:17:07 (+02:00)
Model: not recorded
Source: lesson
L-47e586
A TestFlight upload reported UPLOAD SUCCEEDED with no errors including a
delivery UUID. The build never showed up in App Store Connect. Cause: the build
number was already taken. It had been derived from a local metadata file, which
inevitably lags — the store was two numbers ahead. Apple discards the duplicate
during processing, silently.
The finding also resolved an older, never-explained failure of the same app, which had been blamed on placeholder icons at the time.
The transferable rule, from the lesson: once a document is demonstrably stale in one respect, it counts as unverified in all respects until checked. Partial trust in a source known to be unreliable is the actual error.
When: 2026-08-08, searches at 13:33, finding recorded 13:36:02 (+02:00)
Model under test: not recorded — the log lists the agent as
client=skript,model=unbekanntFinding written by:
claude-opus-5viaclaude-codeSource: node
a3c66be9, rule in node1d0fd081
The test corpus contained a fictional 20 % fee waiver, valid 2026-05-01 to 2026-07-31. Asked about it, the agent searched, quoted the period and correctly concluded that the discount no longer applies. The log shows two searches — it looked things up instead of guessing.
A full-text index would have found the rule and served it as current. The
difference lies in the gilt_bis field, not in the hit rate.
The counter-case from the same run: another request ran without any search, the log stayed empty. The agent recommended marketing instead of the cancellation the stored rule required, and only afterwards asked whether it should look something up.
When: 2026-08-08, item 7 (recorded 13:50:00), follow-up item 9 (13:58:43), both +02:00
Model under test: not recorded (
client=skript,model=unbekannt)Finding written by:
claude-opus-5viaclaude-codeSource: nodes
bd393245and…/messlauf-5-die-kette-v7-zu-v9-zeigt-den
The task was to record a note. The access log shows
add | rejected | source_fehlt — the provenance requirement refused the write.
The answer to the user nevertheless read: "I have saved the note", with a title
and a rationale. Nodes in the store: zero.
Eight minutes later another request asked for exactly that note. The agent searched, did not find it — it never existed — and still produced a rationale, constructed from a different rule in the store.
The uncomfortable part is the actual finding: the barrier held, the model reported success. Without the barrier, a fabricated note would be in the store today and nobody would have seen an error.
When: 2026-08-09T20:47:20 (+02:00)
Model: none involved — the check is deterministic (substring and ID comparison), runtime under one second
Source:
runs/antwortqualitaet_2026-08-09.md
Each of the 15 test tasks was checked against the correct answers of the 14 other tasks: 210 negative pairs, 0 false positives. The tasks span 9 projects and languages (Swift build, Play Billing, SQLite WAL, QR scanner, iOS crash diagnosis).
Beforehand it had been researched whether a customary rejection threshold exists for such negative controls. Result: it does not. Rather than borrowing a percentage, the local rate was measured.
When: finding 2026-08-06T11:56:13, addendum 2026-08-10T00:09:03 (+02:00)
Model: not recorded
Source: lesson
L-adfb33
A catalogue of regular expressions (email, IBAN, customer number, salutation) ran over all 722 lessons and reported 44 suspected cases — 44 of them false positives ("Diagnose" in the sense of failure diagnosis). The real case only surfaced through a positive control using known names from the corpus: one lesson carried a clear name from the test corpus itself. It described a data leak and was one.
Hence the rule that has applied since: evidence needs the shape of the datum, not its content. A lesson that requires a proper name is not fully distilled.
When: blind run as of 2026-08-09T21:21:34, competitive measurement 2026-08-09T10:05:52 (+02:00)
Models in the blind run:
gemma4:12bandgemma4:e4b, 3 runs each, computed locallySources:
runs/wissensnutzen_blind.json,runs/antwortqualitaet_2026-08-09.md,runs/wettbewerb_2026-08-09.md
There is an A/B run that looks good: a small model proposes a documented anti-pattern without injected knowledge, and the correct solution with it.
On inspection: no generating script for those files exists in the repository, and the comparable earlier setup was demonstrably tautological — the query had been hand-built from the known solution, and the injected text contained the solution verbatim. What was measured was "does it help to put the right answer into the prompt".
The rebuild over the real retrieval path tokenises the task text itself and
searches with it. There, the same task reads trefferguete: false: the store did
not find the relevant lesson.
The case belongs here because it shows the direction: the measurement was rebuilt so that it can fail — and it failed immediately. For context, the project's own competitive measurement: retrieval quality 7 of 35 (20 %), while standard hybrid RAG reaches roughly 91 % Recall@10 in production reports from the same year. If all you need is retrieval, standard components serve you better.
What it explicitly is NOT
No anonymisation · no encryption · no BSI certification · no complete protection against prompt injection · no multi-user operation.
Each point is spelled out in docs/GRENZEN.md — together
with what is built instead, and where that in turn stops. This list matters
more than any feature list, because it determines trust.
Further reading
File | Contents |
layout, vectors, backup and restore | |
what brainlehr does not do, in detail | |
licence status of third-party corpora (NASA LLIS, BSI, open sources) | |
decisions with rationale and abort condition | |
contribution process and CLA |
Documentation, commit messages and code comments are in German; this README and the contribution process are in English. The identifiers you will meet are glossed above under A note on the German identifiers.
Contributing
Issue first, then code. Every pull request needs the signed CLA from
CONTRIBUTING.md (§3, version 2026-08-10) and a DCO
sign-off per commit (git commit -s).
Every contribution needs a check that fails before the change and passes after. A test that was green from the start only proves it does not touch the change.
The CLA grants the project owner rights beyond the AGPLv3 so the project can also be licensed commercially. It is not reviewed by a lawyer, and that is stated where you agree to it — not hidden. If that goes too far for you, say so in the issue: bug reports, reproductions, measurements and documentation need no CLA at all.
Licence
GNU Affero General Public License v3.0 (LICENSE), plain-
language summary in LICENSE_FAQ.md.
Private, academic and open-source use: free, without restriction. Anyone distributing a modified version or operating it as a network service publishes their source under the AGPLv3 as well. For inclusion in closed products, a commercial licence is available.
Two files carry their own licence — declared, not accidental: see
NOTICE.
The CLA is in CONTRIBUTING.md §3, not in LICENSE — the
AGPL text may not be modified ("changing it is not allowed", its own header).
NOTICE lists it alongside the licence.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityAmaintenanceA local-first MCP server that analyzes research papers, maps citation graphs, and surfaces insights with verbatim-verified contradictions, all while keeping data private on your machine.1MIT
- Alicense-qualityBmaintenanceA semantic-memory MCP server that stores text 'memories' with provenance and enables recall by meaning (vector search), keyword (FTS5), or structured filters.20MIT
- Alicense-qualityBmaintenanceLocal-first MCP server for indexing and searching research materials (papers, notes, logs, READMEs) using SQLite FTS, with tools for memory management and evidence retrieval.MIT
- Alicense-qualityAmaintenanceLocal-first, source-grounded memory for AI agents, with citations, bitemporal history, review-gated corrections, and MCP tools for search and recall.3Apache 2.0
Related MCP Connectors
Read-only MCP over an agentic SLR workspace with per-claim citation verification
Read-only MCP over an agentic SLR workspace with per-claim citation verification
Shared, peer-validated knowledge archive for AI agents — search, contribute, and validate via MCP
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/3lehr/brainlehr'
If you have feedback or need assistance with the MCP directory API, please join our Discord server