librechat-search-mcp
by krahnikblis
README.md
# librechat-search-mcp
## This project allows for "all-message memory" by extending LibreChat's `Search messages` capability to MCP tools for proactive or on-demand use.
## Summary
A restricted, **LibreChat-specific MCP server** based on the generic [meilisearch-mcp](https://github.com/meilisearch/meilisearch-mcp) for searching the message history that LibreChat has indexed, intended to augment the native `memory` feature/capability while saving costs and context loss when also disabling the optional LibreChat `memory.agent`. It uses **Streamable HTTP** hosted in a dedicated container in the LibreChat Docker Compose network (i.e., **NOT implemented for non-Docker** hosting/deployment setups!), derives the effective user from LibreChat's per-request `User-Id` header, and applies the user filter server-side. Additional post-search filters are used to help constrain results before returning to the agent. It is intentionally designed to constrain these capabilities to the scopes of users and admins who already have them, and makes conscious note of potential risks where privacy is a concern, while being designed primarily for scenarios in which "no expectation of privacy" is explicitly communicated to users.
## Motivation
The [LibreChat built-in memory feature](https://www.librechat.ai/docs/features/memory) is *fine*, for notepad-style contextual memory, but it does has some costs and side effects:
- smaller models (e.g., `Gemma-4-12B`) interpret the memory as important prompt context in fresh chats, causing strange carry-over from other conversations
- memories are fully overwritten, not appended/combined, by the default LibreChat automated memory agent, causing intentionally-created memories to be lost
- the automated memory agent runs on *every* user message turn, doubling input token costs (yes, you probably use a cheaper model for your memory agent...)
- **cache-write costs - my trigger to finally build this project**: every time the memory agent adjusts a memory, the entire conversation cache is rewritten (when using OpenAI's caching system), because the memories are inserted amidst the first objects in the thread history.
- I had some GPT-5.6-Terra conversations cost me up to $3 each, and upon inspection of my usage, discovered it was the unnecessarily repeated **cache writes** which were the biggest contributing factor.
Meanwhile, I also wanted my agents to be more aware of overall history, like how ChatGPT can do. Seeing as how I can simply start typing into the LibreChat `Search messages` box, I was like, "why can't the agent be [given this capability](https://github.com/danny-avila/LibreChat/discussions/12533)?" Much richer information is available from full messages than from cheap-agent-summarized memories, and with some recursive searching, could connect a lot of dots from a lot of conversations. My personal implemenation is already proving this out; my at-work deployment within my group is yet to be seen...
## MCP tools
The server exposes LibreChat-specific versions of read-only tools (and discards the write tools) of the upstream [meilisearch-mcp](https://github.com/meilisearch/meilisearch-mcp) project this is based on.
<details>
<summary>Tool list:</summary>
- `search_messages`: proactively search the caller's indexed messages for continuity; optionally narrow by known conversation ID, sender, and result limit. Returns message ID, conversation ID, sender, and text.
- `search_conversations`: proactively find the caller's indexed conversations by title; returns conversation ID, title, and tags for follow-up message searches.
- `admin_search_messages`: explicitly requested, authorized search of a specified user's messages; returns the target user plus the documented message fields.
- `admin_search_conversations`: explicitly requested, authorized search of a specified user's conversations by title; returns the target user plus conversation ID, title, and tags.
- `health-check`: read the configured Meilisearch availability status.
- `get-version`: read Meilisearch version information.
- `get-stats`: read database-wide Meilisearch statistics.
- `get-health-status`: read structured health and index status.
- `get-system-info`: read Meilisearch system information.
- `get-index-metrics`: read metrics for one known index (`indexUid` required).
- NOT YET IMPLEMENTED - returns error in default LibreChat setups. The returned `fieldDistribution` could be useful in a future topical graph construction for networked message searches, but in LibreChat's latest version of Meili this feature is experimental and must be enabled/created.
</details>
### User scoping
Normal searches are request- and caller-scoped by the service and accept no target user or raw filter. Administrative searches require service authorization and normally a target user; if authorization fails, do not retry or probe. The current implementation reports a generic search failure. All tools are read-only and return only the fields documented above.
### Request-scoped tool behavior
What wasn't apparent to me from the start was how request-scoped (i.e., using message or conversation IDs in the MCP header) alter the UX. The MCP server is visible for selection overall, but the tools it offers are not - meaning you can't deselect the admin tools for a user-oriented agent (but there is some dynamism in how this server presents tools based on the user scope). Ideally we could configure server connection headers separately from tool use headers. I'm following the various MCP bugs/PRs in LibreChat to better understand what adjustments I can make to improve customizability.
### Known issues
<details>
<summary>This server runs mostly as intended, and the following items are on my radar:</summary>
#### Within the current project:
- [ ] Logs are too basic for troubleshooting (add request parameters and response metrics; optionally dump errors to json)
- [ ] Tool failure errors are non-descriptive (initially intentional for privacy preservation; agent needs more context for self-correction)
- [ ] `conversationId`-filtered use of `search_messages` intermittently fails? (could be connection/threading/async related; TODO: add convo as filterable field)
- [ ] `get-index-metrics` requires experimental feature enablement (TODO: test and explore this)
- [ ] Documentation and code contain legacy/dev artifacts and inconsistencies
- [ ] User ID for targeted admin searches is tedious to acquire (TODO: explore PeoplePicker API to map to handle or name+initial)
#### Integration/activation effects:
- [ ] Tool selection not availalbe (due to request-scoped headers)
- [ ] Rapid-fire tool use in a sub-agent generates failure responses (threading and async functions need a closer look)
- [ ] Repeated search/discussion of historical topics dilutes those topics in future searches
- [ ] Conversation grouping is cumbersome and potentially token-expensive (TODO: add a function or feature to return conversation-grouped stats over message searches)
- [ ] Complex search scenarios are costly (TODO: server-side grouping, keyword/topic graph, multi-search union/intersect/exclude ops, dedup, ordering)
</details>
## Security model
This tool is set up with the baseline assumption that the LibreChat deployment is single-tenant, single-policy, with no implemenation of security-specific models or providers!
Tools have basic user gating, and **potentially vast admin scope**:
- Normal tools: `search_messages` and `search_conversations` always search only the caller's user. The model cannot provide `user` or a raw Meilisearch filter.
- Admin tools: `admin_search_messages` and `admin_search_conversations` require the caller's `User-Id` to exactly match an entry in `MEILI_MCP_ADMINS`. A target `user` is required unless `MEILI_MCP_ADMIN_SCOPE_ALL_USERS=true`.
- The MCP service receives a restricted, read-only Meilisearch key in `MEILI_MCP_KEY`; never use or pass the Meilisearch master key.
- Results are schema-limited. Normal results contain only `messageId`, `conversationId`, `sender`, and `text`; conversation results contain only `conversationId`, `title`, and `tags`. Admin results additionally include the selected `user`.
- Missing, empty, or malformed identity fails closed. Authorization failures are intentionally generic and secrets are never returned.
Retrieved text is exactly whatever LibreChat indexed. Depending on the indexer and deployment, that may include assistant output, reasoning-like text, or tool traces. This MCP server cannot recover content LibreChat did not index; treat search results as potentially sensitive audit data.
**SEE [SECURITY.md](./SECURITY.md) FOR FURTHER CAUTIONS AND CAVEATS!!!**
### Stewardship
I already notify my corporate user base of the capability for IT sec-ops and me to view all data/history (**"no expectation of privacy"**) - my assumption is you would do the same if applicable to your deployment, or you would not implement this in a scenario where it could violate any policies. This tool merely makes it easier for me to do what I already can (vs the MongoDB tools or Meili CLI), while also exposing histories from one provider/model/agent to another (in my deployments, this is not a concern - *yet* - because I haven't implemented any private models for secure content).
## LibreChat setup
Copy or merge these `example` files into the LibreChat Compose project and input your specific keys/IDs/configs:
- `librechat-search-mcp/librechat.yaml.example`: merge `mcpSettings` and `mcpServers` into the existing `librechat.yaml` file. Note MCP header parameter mappings like `{{LIBRECHAT_USER_ID}}` must be configured exactly as given; LibreChat replaces them per request and **these are part of the security model and filtering functionality**.
- **Recommended** if you agree with my motivations above for your deployment: in `librechat.yaml`, disable the memory agent until/unless future LibreChat versions alter how its behavior impacts token costs and cross-conversation topic pollution
- **Note**: recent LibreChat changes have altered memory tool behavior to specific opt-in in the `endpoints.agents.capabilities` setting - add "memory" there if you want the agent to be able to write memories on demand.
- `librechat-search-mcp/docker-compose.override.yml.example`: merge the `librechat-search-mcp` service into the existing Compose project. It is internal-only: it has `expose: 8000`, no `ports`, and joins the Compose default network.
- `librechat-search-mcp/.env.example`: add these keys into your main LibreChat `.env`, and replace only the marked placeholders. I recommend adding it just below the existing `Search` section.
The MCP URL is `http://librechat-search-mcp:8000/mcp`. The MCP-visible server name remains `chat-search` for client comprehension. If LibreChat blocks private MCP destinations, retain the `allowedAddresses`/`allowedDomains` entries from `librechat.yaml.example`. CAUTION: a non-empty domain whitelist may affect other MCP servers, so merge intentionally.
`MEILI_HOST_PORT=7700` is the host/Compose bridge variable used by the surrounding LibreChat deployment. The MCP container itself must use the Compose service URL `http://meilisearch:${MEILI_HOST_PORT}` (the example override constructs that value); LibreChat's existing `MEILI_HOST=http://0.0.0.0:7700` is a separate host-facing setting and should not be replaced casually.
### Find your (admin) user ID for explicit enablement
`MEILI_MCP_ADMINS` user IDs can be gleaned from any of the following methods (in order of typical ease of execution):
- in the UI in your browser:
<details>
<summary>--> Browser steps to get/inspect relevant info</summary>
1. open the inspector/dev tools (ctrl+shift+i) and select the Network tab;
2. from the Chat History conversation list, select a prior conversation (you might need to select an older one not currently in browser cache);
3. the first API call recorded in the Networking tab loads the conversation header data in its `Response` sub-tab, which includes:
- `user` - **this value is *your* ID** (and this field is used in filtering searches within the server, or as the `target-user` parameter for the `admin_` tools) - **copy this** (and ask other admins/grantees you choose to supply the same to you) into the `.env` file as the `MEILI_MCP_ADMINS` [delimit a list with commas only];
- `conversationId` - this is the same ID used in parameters for the `search_` MCP tools, and it is noteworthy that this is the same as in the conversation's URL: `http://localhost:3080/c/{conversationId}`;
4. noteworthy about this project's functionality - the 3rd API call contains this conversation's messages list, similar in structure to how Meilisearch indexes them; this project uses the following keys:
- `conversationId` - used by this project to exclude search results from the current chat by default, or for targeted filtering of messages
- `sender` - "User" or agent display name
- `text` - the content of the message
- *future research considerations for desired functionality*:
- `endpoint` (or to a more targeted extent, `model`) could be used as a filter in conjunction with a `MEILI_MCP_CONSTRAIN_ENDPOINT=true` setting (if private models/agents are separated from public provider ones) to disallow cross-endpoint message search access
- `parentMessageId` is already integrated as a result filter during tool use via LibreChat's `{{LIBRECHAT_BODY_PARENTMESSAGEID}}` dynamic variable in the header of MCP transport; I plan to reexamine search indexes for this as a potential method for targeted before/after buffers from search hit messages, ordering results deterministically, and graphing message chains
- `content` contains tool call thoughts/reasoning (I don't recall seeing this in the indexed content, probably with good reason - a user searching message history likely expects to see hits where the title or message text match, not file/agentic internals)
- `attachments` contains tool call results (and potentiall RAG and/or uploaded file content) - similar to `content`, I don't expect this is indexed
- `createdAt` could be used as a deterministic temporal ordering or even filtering key (Meili already returns hits in apparent temporal order but this could be influenced by relevance scoring as well) - this may also be reachable (albeit a slightly different value) via Meili index document metadata (timestamp the index was created/updated), but any time a reindex action is performed it could lose all value.
</details>
- LibreChat container logs (`docker compose logs api`) immediately after you (as an admin user) perform an action from the LibreChat UI (CAUTION: not advisable if you have many concurrent users to conflate log results, as this does NOT display user names)
- examining the `users` table in Mongo Express (if you have [set this up separately](https://www.librechat.ai/blog/2023-11-30_mongoexpress), it's slighly easier to use its web UI)
- examining the `users` table in the MongoDB (following is the same for bash or PowerShell; run from the LibreChat directory or where your `docker-compose.yml` is located):
<details>
<summary>--> MongoDB users table terminal commands</summary>
```bash
# open a shell terminal within the MongoDB container - this assumes the default LibreChat service name `mongodb`:
docker compose exec mongodb sh
# open a database shell terminal within the container's shell:
mongosh
# switch to the database used by LibreChat (see all with `show databases`):
use LibreChat
# display target user by `role` attribute == "ADMIN" (LibreChat also stores `email` and `username` which may be present/null depending on registration method):
db.users.find({role: "ADMIN"}).forEach(printjson)
# Alternatively, display the entire users collection (be careful with this if you have many users):
db.users.find().forEach(printjson)
# the hash string in the first key of returned JSONs is the `user` ID - assuming you've found yourself/chosen admins, grab just this hash value from the `ObjectId` construct:
# {
# _id: ObjectId('derp7bfe19e9268da678derp'), #### <- in this dummy example, derp7bfe19e9268da678derp is my user ID to add to MEILI_MCP_ADMINS ####
# name: 'krahnik blis',
# username: 'krahnik',
# email: 'krahnik@emailmenot.derp',
# ...
# quit the mongosh terminal
quit
# exit the mongodb container shell terminal
exit
```
</details>
### Generate a read-only permissioned key for the MCP server
**Do NOT use the LibreChat `MEILI_MASTER_KEY` as your `MEILI_MCP_KEY`!**
The repository includes cross-platform helpers to generate and validate the API key in one command.
They do this by accessing the `meilisearch` container (so it must be running), generating a permissioned key,
printing the key by default or optionally writing to a local file, and never editing LibreChat's primary `.env`.
The output file method is optional and is refused if it already
exists unless `--force`/`-Force` is supplied; generated `*.local.env` files are
git-ignored and receive restrictive permissions where the platform supports them.
Run **from the LibreChat directory** after you have `git clone`d this repo into it:
bash:
```bash
# ensure the script is executable:
chmod +x librechat-search-mcp/scripts/generate-restricted-key.sh
# run the script in terminal mode:
./librechat-search-mcp/scripts/generate-restricted-key.sh
# OR, run it in file-output mode:
./librechat-search-mcp/scripts/generate-restricted-key.sh --output .librechat-search-mcp.local.env
```
PowerShell:
```powershell
# run the script in terminal mode:
powershell.exe -ExecutionPolicy Bypass -File .\librechat-search-mcp\scripts\generate-restricted-key.ps1
# OR, run it in file-output mode:
powershell.exe -ExecutionPolicy Bypass -File .\librechat-search-mcp\scripts\generate-restricted-key.ps1 -Output .librechat-search-mcp.local.env
```
The output of the script contains validation tests for endpoint permissions and a dummy delete probe to ensure read-only permissions;
the read-only permissioned key is printed at the end, or written to the file of your choice.
<details>
<summary>Example script output</summary>
The script contains checks and messaging I used in debugging, which I've left in place for my own future sanity, and so can you:
```text
[info] Working directory: /path/to/LibreChat
[info] Environment file: .env
[info] Meilisearch container: meilisearch
[info] Messages index: messages
[info] Conversations index: convos
[warning] MEILI_HOST used 0.0.0.0; using loopback for in-container requests.
[info] MEILI_HOST from .env: http://0.0.0.0:7700
[info] API URL used inside chat-meilisearch: http://127.0.0.1:7700
[info] MEILI_MASTER_KEY length: 32
[info] MEILI_MASTER_KEY SHA-256: d3907119a65e489d0202derp0ac65216a44derpb43bd8be71b7dderpb158ac67
[info] Testing Meilisearch connectivity from inside the container.
PASS /health -> HTTP 200
[info] Testing the MEILI_MASTER_KEY read from .env.
PASS .env master key accepted by /version
[info] Key contract payload: {"description":"LibreChat MCP search and read-only diagnostics","actions":["search","stats.get","metrics.get","indexes.get","settings.get","version"],"indexes":["messages","convos"],"expiresAt":null}
[info] Creating restricted key in chat-meilisearch.
[info] Restricted key created successfully.
[info] Generated key length: 64
[info] Validating read-only key contract.
PASS /health -> HTTP 200
PASS /version -> HTTP 200
PASS /stats -> HTTP 200
FAIL /metrics -> HTTP 400
{"message":"Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625","code":"feature_not_enabled","type":"invalid_request","link":"https://docs.meilisearch.com/errors#feature_not_enabled"}
PASS /indexes -> HTTP 200
PASS /indexes/messages/settings -> HTTP 200
PASS /indexes/convos/settings -> HTTP 200
[info] Testing that document deletion is rejected.
PASS DELETE /indexes/messages/documents/__mcp_read_only_probe__ -> HTTP 403
Restricted key was created, but one or more validation checks failed:
- /metrics returned HTTP 400
The key will still be returned below. Do not deploy it until the failures are understood.
394ederp18e6299f7fddderpbb485b77be7bb1d0906b29ade8derpebaf65de43
```
^ in this dummy example, `394ederp18e6299f7fddderpbb485b77be7bb1d0906b29ade8derpebaf65de43` is the key to use as `MEILI_MCP_KEY` in LibreChat's `.env` file.
</details>
#### Expected `FAIL` message
Presently, the `/metrics` endpoint is expected to fail due to this being an experimental feature in the Meilisearch version used by LibreChat;
I'm researching potential utility and will update this repo with enablement instructions/scripting, should anything fruitful result.
This means the `get_index_metrics` tool will return the error shown in the example script output above, until/unless you enable the feature yourself.
<details>
<summary>Manual key creation steps</summary>
The following is a manual method to perform what is contained within the `generate-restricted-key` scripts,
in case you have customized your implementation or encounter errors (or see the scripts for all their thorough glory):
Create one dedicated, read-only key for this service. Its exact action contract is:
- `search`
- `stats.get`
- `metrics.get`
- `indexes.get`
- `settings.get`
- `version`
Scope the key to the two configured indexes (`messages` and `convos`, or your configured names). The global health endpoint is checked separately and does not require a write-capable role. Set `expiresAt` to `null` only when a non-expiring operational key is intentional, and store the returned key only in `MEILI_MCP_KEY`.
Create the key from a terminal in the `chat-meilisearch`(docker exec)/`meilisearch`(docker compose exec) container, NOT from the MCP container. The master key below is a placeholder in command history and must not be pasted into prompts, logs, or this repository:
```bash
curl -fsS -X POST "http://127.0.0.1:7700/keys" \
-H "Authorization: Bearer $MEILI_MASTER_KEY" \
-H "Content-Type: application/json" \
--data '{"description":"LibreChat MCP search and read-only diagnostics","actions":["search","stats.get","metrics.get","indexes.get","settings.get","version"],"indexes":["messages","convos"],"expiresAt":null}'
```
Do not add `documents.*`, `indexes.create`, `indexes.delete`, `settings.*` write actions, `keys.*`, `tasks.cancel`, or `*`. Never set `MEILI_MCP_KEY` equal to `MEILI_MASTER_KEY`.
Verify the contract before deploying the key with the following read-only probes. They must all return HTTP 200 (the JSON is intentionally discarded):
```bash
auth=(-H "Authorization: Bearer $MEILI_MCP_KEY" -H "Accept: application/json")
for path in /health /version /stats /metrics /indexes /indexes/messages/settings /indexes/convos/settings; do
code=$(curl -sS -o /dev/null -w '%{http_code}' "${auth[@]}" "http://127.0.0.1:7700/$path")
test "$code" = 200 || { printf 'unexpected %s: HTTP %s\n' "$path" "$code" >&2; exit 1; }
done
```
Then verify that a harmless delete probe is rejected. Use a sentinel document ID that is not present; do not replace this with a real document ID:
```bash
code=$(curl -sS -o /dev/null -w '%{http_code}' -X DELETE \
"${auth[@]}" "http://127.0.0.1:7700/indexes/messages/documents/__mcp_read_only_probe__")
case "$code" in 401|403) ;; *) printf 'write permission was not rejected: HTTP %s\n' "$code" >&2; exit 1;; esac
```
The same key is used by the MCP search and diagnostics paths. If any read-only probe returns `401`/`403`, fix the key's action contract or index scope; do not substitute the master key or broaden permissions until writes are rejected.
</details>
#### Overabundance of keys and their deletion
CAUTION: running the above script/commands multiple times will create multiple orphan keys within Meilisearch. I recommend don't. But if you did,
<details>
<summary>here be dragons:</summary>
- Either set an environment variable within the container with your master key, or replace the following instances of `$MEILI_MASTER_KEY` with your real key
- From a terminal within the primary Meilisearch container, first list all keys: `curl -sS -H "Authorization: Bearer $MEILI_MASTER_KEY" "http://127.0.0.1:7700/keys"`
- Locate the keys labeled by this project's creation script, they will have the `description` "LibreChat MCP search and read-only diagnostics"
- Choose whichever are not the key you intend to keep, and get their `uid`s
- For each key to delete, run (replace `KEY_UID` with your real value): `curl -sS -X DELETE -H "Authorization: Bearer $MEILI_MASTER_KEY" "http://127.0.0.1:7700/keys/KEY_UID"`
</details>
### Full setup command sequence
Run the following commands one-by-one interactively (obvs Windows users can skip the cat/nano crap and use notepad/IDE):
```bash
cd LibreChat
# this creates the folder librechat-search-mcp WITHIN the LibreChat Compose scope:
git clone https://github.com/krahnikblis/librechat-search-mcp.git
# assuming the baseline LibreChat Compose services are already running, this creates & tests the restricted API key to set manually into the LibreChat .env MEILI_MCP_KEY:
# see above/README page for details and/or PowerShell equivalent commands
# either write to a local file:
./librechat-search-mcp/scripts/generate-restricted-key.sh --output .librechat-search-mcp.local.env
# OR print to the terminal:
./librechat-search-mcp/scripts/generate-restricted-key.sh
# print the example to copy as template:
cat librechat-search-mcp/.env.example
# copy or merge the example MEILI_MCP_ variables, including the key generated in the prior step into .env:
nano .env
# print the example to copy as template:
cat librechat-search-mcp/docker-compose.override.yml.example
# copy or merge the example configurations from the example into docker-compose.override.yml
nano docker-compose.override.yml
# print the example to copy as template:
cat librechat-search-mcp/librechat.yaml.example
# copy or merge the MCP [and optional agent capabilities and memory agent changes] configurations into librechat.yaml:
nano librechat.yaml
# validate config:
docker compose config
# stop existing services to recreate the LibreChat container with the MCP settings:
docker compose down
# build the image:
docker compose build librechat-search-mcp
# start all Compose services together:
docker compose up -d
# check logs for the new service:
docker compose logs --tail=100 librechat-search-mcp
```
If all went well, this MCP will be available in your LibreChat UI!
## Environment and index contract
<details>
<summary>dotenv variables</summary>
Required:
```dotenv
MEILI_MCP_KEY=<restricted-search-key>
MEILI_MCP_ADMINS=<admin,list>
```
Important values in `.env`:
```dotenv
MEILI_HOST_PORT=7700
MEILI_MCP_PORT=8000
MEILI_MCP_MESSAGES_INDEX=messages
MEILI_MCP_CONVOS_INDEX=convos
MEILI_MCP_DEFAULT_LIMIT=5
MEILI_MCP_MAX_LIMIT=25
MEILI_MCP_ADMIN_SCOPE_ALL_USERS=false
MEILI_MCP_LOG_HOST_DIR=<local/log/path>
```
Both indexes must contain a filterable `user` attribute (already existing by LibreChat design).
The messages index created by LibreChat does not have a filterable `conversationId`;
this and other parameters for MCP search tools are handled server-side before returning to the caller.
</details>
### Log mount and persistence
The container writes structured JSON-lines logs to `/var/log/librechat-search-mcp`.
The Compose example bind-mounts that directory to `${MEILI_MCP_LOG_HOST_DIR}`;
its default is `./librechat-search-mcp/logs` relative to the parent LibreChat
Compose project. Log files are named `librechat-search-mcp-YYYY-MM-DD.log`, so
recreating the container does not remove existing host-side logs. Keep this
host directory private and back it up or rotate it according to your deployment
policy. To use the parent project's conventional log directory instead, set
`MEILI_MCP_LOG_HOST_DIR=./logs` in the local `.env`; do not commit that populated
file or generated logs.
<details>
<summary>index contract test</summary>
Run the read-only contract check from the `librechat-search-mcp` container:
bash/PowerShell:
```bash
# The image's WORKDIR is /app and Compose injects MEILI_HOST plus the
# restricted MEILI_MCP_KEY into the service.
# the script was copied into the container as part of image build
docker compose exec -T -w /app librechat-search-mcp python scripts/check_index_contract.py
```
The script is copied into the image at `/app/scripts/check_index_contract.py`.
It checks health, index names, primary keys, filterability, and sortable
attributes without mutating settings or printing the key. Exit status `0` and
JSON `"status": "pass"` mean the contract passed; a nonzero exit status means
the reported health, index, settings, or required-filter check failed. It is a
diagnostic, not an MCP readiness probe. `/health` only confirms that the MCP
process is alive.
</details>
## Caller prompts and expected behavior
Start a fresh LibreChat conversation after configuration and containers are running
(e.g., in UI's `MCP` sidebar, `Agent Builder`, and/or the chat box's `MCP Servers` drop-down)
so the model discovers the current tool list.
Tool descriptions are written to encourage agentic use proactively for the standard search tools,
while the `admin_` variants are given "user-invoked first use" instructions. I'll probably tune
these instructions in future updates based on my experience - I've already seen some interesting
proactive usage which seemed unnecessary or was too broad...
NOTE: Small local models may need the tool named explicitly in prompts.
<details>
<summary>Example test prompts</summary>
### Normal search - explicit instruction:
```text
Use search_conversations with query "deployment" and limit 3. Return only conversationId, title, and tags.
```
```text
Use search_messages with query "deployment" and limit 3. Return only messageId, conversationId, sender, and text.
```
### Intended proactive use of normal search:
```text
Hey remember that time we went wild designing a giant robotic grackle? I have some ideas about how to combine it with the ornithopter we discussed last week...
```
The agent should proactively run a search with query resembling "grackle ornithopter", and you should see matching messages from multiple conversations.
### Intended search agent design:
This tool alone comes with generic setup and tool descriptions, but the real fun will be in building a dedicated agent and/or an informative SKILL.md for shaping memory-like behavior. Because the results of these tools are full messages, token costs could still be substantial, so a sub-agent built on a cheaper model but given instructions to recursively trace topics across interconnected conversations and then return a detailed symmary/synthesis is probably how I'll deploy this to my team in their default agent.
### Targeted admin audit (only from an allowlisted account):
On my to-do list is to research the LibreChat `PeoplePicker` API and see how/if it's exposed within the Compose network - ideally an admin could name a user by handle or first name, and if PeoplePicker is enabled, the agent could do a lookup for the internal ID (or perhaps bypass the agent and perform lookup targeted filtering/erroring within the server like "2 'Sally's found: did you mean Sally X. or Sally Y.?", *without exposing email or full name to the agent*)...
```text
Use admin_search_messages for target user "<target-user-id>" with query "deployment" and limit 3.
```
### Intended admin scope (debugging, prompt/conversation optimization, collective attention)
```text
What are our team members saying overall about our company's brand presense in the FIFA World Cup?
```
```text
Let's review <target-user-id>'s conversation <conversationId> - what initial prompt and context would have elicited the final answer more directly?
```
### Boundary checks:
```text
Try to search another user's messages by supplying a user argument and a raw filter. Do not bypass the tool schema; report whether the request was rejected and do not return cross-user results.
```
```text
Find messages before and after this hit using createdAt, reconstruct surrounding messages through MongoDB, and sort by timestamp.
```
The latter is **unsupported**: this project does not add timestamp ordering, before/after retrieval, MongoDB/API lookup, surrounding-message reconstruction, or arbitrary sorting. Sender and conversation filters are bounded post-filters, so fewer than the requested limit is valid and original Meilisearch hit order is retained.
</details>
## Verification
<details>
<summary>Tests</summary>
Local checks (from within this project's folder under LibreChat):
```bash
python -m pytest tests/test_monitoring.py tests/test_m2_contract.py tests/test_m2_authorization.py tests/test_m1_search.py tests/test_server.py -q
python -m compileall -q src scripts tests
git diff --check
```
Deployment checks still require a running LibreChat/Meilisearch stack: verify fresh-session tool discovery, the `User-Id` header, restricted-key permissions, index contract, no host port publication, and concurrent requests from two users. Do not treat local unit tests as proof of live caller behavior.
</details>
## Deployment and troubleshooting
- See [`docs/deployment.md`](docs/deployment.md) for the supported Docker Compose boundary, safe configuration order, service identities, and the `container_name` tradeoff.
- See [`docs/troubleshooting.md`](docs/troubleshooting.md) for common Compose, key-generation, networking, MCP discovery, index, and logging failures.
- See [`SECURITY.md`](SECURITY.md) before enabling the service. It covers indexed content, AI-provider exposure, identity and admin scope, logs/retention, restricted keys, trust boundaries, and what this project does not guarantee.
## Development
- See [`docs/tool-descriptions.md`](docs/tool-descriptions.md) for the agent-facing tool contract.
- See [`ATTRIBUTION.md`](ATTRIBUTION.md) for upstream provenance and licensing.
- Development, planning, audit, and internal decision records are intentionally maintained outside the production repository in `workspace/project-context/librechat-search-mcp/development-records/`.
I mean, if anyone *wants* to contribute stuff, open a discussion or issue or whatevs; I could see about adding a **Contributing** section and learn how PRs work... but really this is just my own hobby thing which I know will have great value to me at work too, and the easiest conduit from hurr to thurr is through publishing on GitHub. I.e., I like to make stuff and solve problems, but I offer no commitment to pay attention to anyone else's issues, and will probably prioritize what is either awesome feature enablement or close any gaps.This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues