# LibreChat over MCP: self-host the chat, then self-host what it writes

> LibreChat is MIT-licensed and self-hosted, but its conversations stay in MongoDB. Connect it to a Baalda vault over MCP so the output lands in markdown.

Source: https://baalda.com/blog/librechat-second-brain
Site: Baalda, https://baalda.com
Published: 2026-10-12

**Short answer.** Yes, LibreChat is an MCP client. Add Baalda under `mcpServers` in `librechat.yaml` as a `streamable-http` server pointing at `POST /api/mcp`, with each user supplying their own vault token through `customUserVars`. The assistant then reads and writes the same markdown files your team edits, within that person's own permissions.

A self-hosted LibreChat means no vendor holds your team's conversations. That is worth doing, and it is less than it sounds, because the thing a team wants to keep is not the conversation. It is the two paragraphs somebody arrived at after forty messages, and those stay inside the thread, in a database only the person who had the thread ever opens. LibreChat is an MCP client, so there is a way out: point it at a vault of markdown files the whole team can read and write, and the useful part of a conversation lands somewhere other than the conversation.

## Can LibreChat read and write my team's notes?

Yes. LibreChat takes MCP servers under an `mcpServers` key in `librechat.yaml`, and its documentation lists four transports: `stdio`, `websocket`, `sse` and `streamable-http`. For anything you run for a team it is specific about which one to pick: "For production environments, only MCP servers with 'Streamable HTTP' transports are recommended. Unlike SSE which maintains long-running connections, Streamable HTTP offers stateless options that are better suited for scalable, multi-user deployments."

Baalda serves MCP at `POST /api/mcp` over Streamable HTTP, which is the transport LibreChat asks for. Baalda is a team second brain built on plain markdown files on each member's own disk: several people edit the same note in real time, access is set per folder and per file, it is open source under Apache-2.0, and the backend is free to self-host. So the two halves fit without either side bending. LibreChat brings the interface and the choice of model, Baalda brings a memory that is a folder of `.md` files rather than rows in somebody's database.

MCP servers reach a LibreChat user in two places, and the docs separate them. Configured servers appear "directly in the chat area when using traditional endpoints (OpenAI, Anthropic, Google, Bedrock, etc.)", and they also integrate "with LibreChat Agents" through the Agent Builder. Both paths reach the same vault with the same permissions.

## What does self-hosting LibreChat actually keep?

Worth being precise about this before writing any config, because "self-hosted" is doing two different jobs in most sentences about it.

LibreChat is MIT-licensed and genuinely self-hostable. Run it and the interface is yours, the deployment is yours, and the model traffic is yours to route. What that does not give you is a place where the team's knowledge becomes legible to the team. LibreChat's own MongoDB page says the database holds "conversation histories, user profiles, presets, API keys, and more", and gives the reason plainly: "MongoDB's ability to store nested data structures as JSON-like documents makes it an excellent choice for storing conversation histories."

That is the correct design for a chat app. It also means a conclusion lives as a message inside a conversation document belonging to one account. Nobody greps it, nobody corrects it, and a new hire cannot open it. You have moved the server without moving the ownership, which is the same trade that makes [self-hosting Obsidian notes](/blog/self-host-obsidian-notes) three jobs rather than one.

So the division of labour is: conversations stay in LibreChat, because that is what LibreChat is for, and anything the team is meant to keep gets written into a file on the way past.

## How do you connect LibreChat to Baalda over MCP?

**1. Mint a vault token.** In the Baalda desktop app, open Vault Settings → MCP and create a connection token. It is minted as `mcp_` followed by 24 random bytes, the plaintext is shown once, and the server stores only a sha256 of it.

**2. Add Baalda to `librechat.yaml`.** One entry, with the token supplied per user rather than baked in:

```yaml
mcpServers:
  baalda:
    type: streamable-http
    url: https://notes.example.com/api/mcp
    headers:
      Authorization: "Bearer {{BAALDA_TOKEN}}"
    customUserVars:
      BAALDA_TOKEN:
        title: "Baalda vault token"
        description: "Vault Settings → MCP in the Baalda app. Your own, not the team's."
        sensitive: true
    timeout: 60000
    serverInstructions: true
```

`url` is `http://localhost:3010/api/mcp` for a vault running on the same machine, or your own hostname once the backend is deployed. `timeout` defaults to 30000 milliseconds and `initTimeout` to 10000, both of which are worth raising on a first connection to a large vault. `serverInstructions` controls whether the server's own guidance is injected into an agent's context.

**3. Restart LibreChat.** The docs are explicit about when this is needed: "Any time you add or edit a server in `librechat.yaml`, you will need to restart LibreChat to initialize the connections. Servers added through the MCP Settings panel take effect without a restart."

## How does one chat server give each person their own notes?

This is the question that decides whether a shared LibreChat is usable on a team at all, and the two sides answer half each.

LibreChat's half is isolation and per-user credentials. Its docs state that "Each user gets their own isolated connection to MCP servers", and `customUserVars` is how a credential becomes personal: a variable each user fills in themselves, marked `sensitive: true` so the value is not read back, referenced in headers as `{{BAALDA_TOKEN}}`. LibreChat will also substitute its own user fields into headers, including `{{LIBRECHAT_USER_ID}}`, `{{LIBRECHAT_USER_EMAIL}}`, `{{LIBRECHAT_USER_USERNAME}}` and `{{LIBRECHAT_USER_ROLE}}`. Those are useful, but they are LibreChat asserting who the caller is, not the vault deciding what that caller may touch.

The other half has to come from the notes server. A Baalda token is a row naming one person in one organisation. Every call re-reads that person's role in the organisation before doing anything, so a revoked membership kills a live token on its next call rather than at the next sign-in. Naming a vault that belongs to a different organisation fails with "This vault is outside the scope of this token". Inside the vault, each read and write resolves against that person's own access, folder by folder and file by file, [the same resolution a human in the app gets](/blog/obsidian-per-file-permissions).

The practical result is one `librechat.yaml` entry, one shared chat server, and ten people whose assistants see exactly the ten different subsets of the vault those ten people see. The shortcut is to put a single shared token in `headers` as `${BAALDA_TOKEN}` from the server's environment. That works, and it gives every LibreChat user the notes of whoever minted it, so it is fine for one person and wrong for a team.

## What happens to the files people upload into a conversation?

A chat window is also where documents go. Somebody drops a PDF in, asks three questions about it, and now both the file and the thinking about it are scoped to that thread and that account. This is the half most MCP setups skip, and it is where a vault behaves differently.

Baalda exposes the files in a vault, not only the notes. `list_attachments` returns the spreadsheets, documents, PDFs and images stored in a vault, each with a `hasText` flag saying whether there is extracted text to read. `read_attachment_text` then returns that text, capped at 20,000 characters by default and 200,000 at the most, with a `truncated` flag rather than a silent cut. It returns the text and not the file, so an assistant gets the words out of a report and not the chart on page four.

Both are gated exactly the way the rest of the vault is. A file registered in the tree follows its folder's access rules, and an attachment dropped beside a note follows the notes that embed it, which means a folder share, a revoke or a read-only lock applies to files without the file tools needing to know those features exist. `search_notes` ranks file text alongside note text out of the same index, tags those hits `kind: "file"`, and runs against the same readable set, so "what did we agree in the Q3 deck" is one search rather than a hunt for whoever uploaded it.

The difference from the thread is only that the next person arrives too. A PDF in a conversation is reachable by one account in one thread. The same PDF in a vault folder is reachable by every member's assistant, each within their own permissions, and it is still a file on their disk when LibreChat is down.

## What should the assistant actually write back?

The useful writes are the dull ones. A decision and the reason for it, appended to the note that already holds the decisions. A summary filed into the project's folder rather than pasted into a reply. A draft a teammate opens in the desktop app and edits while the model is still working, because Baalda merges a write from MCP with a person typing instead of overwriting one with the other.

Starting read-only is reasonable. Baalda's catalogue runs from `list_vaults`, `list_folders`, `list_notes`, `read_note` and `search_notes` through `create_note`, `append_note`, `update_note` and `edit_note`, and every tool carries annotations saying whether it is read-only or destructive, which is what a client reads to decide when to ask you first. Turn on the additive writes before the editing ones, and keep `chatMenu` in mind if you would rather the server stayed available to saved agents without appearing in every chat picker.

## Where does this not help?

Four honest limits, and the first one is the big one.

- **Your chat history stays in MongoDB.** Baalda has no conversation store and no surface for searching old threads. If better recall over past chats is the thing you want, that is a LibreChat question and this does not answer it.
- **A hosted LibreChat needs a reachable notes server.** The MCP endpoint is a route on the Baalda server that holds the notes, so a vault that exists only on your laptop will not serve a LibreChat running in Docker on a VPS. That means [self-hosting the backend](/docs/deploy) or taking the managed Team plan.
- **Extracted text is not the document.** A file nobody has indexed yet answers with an empty string rather than an error, because extraction happens on the device that holds the file. Images, charts and scanned pages are out of scope either way.
- **No comment threads.** Discussion that happens in LibreChat messages stays in LibreChat. Baalda has notes, version history and per-file access, not conversations.

And if you are one person running LibreChat for yourself, pointing a filesystem MCP server at a folder of markdown is less work than any of this. The arrangement above earns its setup when more than one person needs the same notes, with different access, and an assistant that writes into them rather than about them. That is the case [an MCP server over a team's notes](/blog/mcp-notes-ai) exists for.

The fully owned stack is not the model and the server. It is the model, the server, and a readable copy of what the team concluded, sitting in a file with a path.

## FAQ

### Which MCP transport should LibreChat use for a team?

Streamable HTTP. LibreChat supports stdio, websocket, sse and streamable-http, and its documentation recommends only Streamable HTTP for production, because SSE maintains long-running connections while Streamable HTTP offers stateless options better suited to scalable, multi-user deployments. Baalda serves MCP at POST /api/mcp over Streamable HTTP.

### Do I have to restart LibreChat after adding an MCP server?

It depends where you add it. LibreChat's docs say that any time you add or edit a server in librechat.yaml you need to restart LibreChat to initialize the connections, while servers added through the MCP Settings panel take effect without a restart.

### Can an AI in LibreChat read a PDF stored in the vault?

It can read the extracted text. list_attachments returns the files in a vault with a hasText flag, and read_attachment_text returns that text, 20,000 characters by default and 200,000 at most, with a truncated flag. It hands over words rather than the file, so charts and scanned pages are out of scope, and a file nobody has indexed yet comes back as an empty string.

### Does LibreChat store my team's notes in MongoDB?

No. MongoDB holds what LibreChat is for, which its own docs describe as conversation histories, user profiles, presets, API keys and more. Notes reached over MCP stay where they live, which for Baalda is a .md file on each member's disk. Connecting the two does not copy your vault into LibreChat's database.

### Is a single shared token safe on a team LibreChat?

No. A shared token in the headers block gives every LibreChat user the notes of whoever minted it. Use customUserVars so each person supplies their own token, which is scoped to one person in one organisation and resolved against that person's folder and file access on every call.
