librechat-personal-files-mcp
Publishes user files as secure public links via Nginx X-Accel-Redirect, enforcing download headers and access controls.
Click on "Deploy 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., "@librechat-personal-files-mcpSave the meeting notes to my private files and publish a shareable link"
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.
librechat-personal-files-mcp
MCP server providing per-user personal file storage, persistent documentation, RAG indexing/retrieval, and opaque public link publishing for LibreChat agents.
Related fork: this server is designed to work with a fork of ForLegalAI/mcp-ms-office-documents that stores generated Office documents (
.docx/.xlsx/.pptx/.eml/.xml) in the user's private storage here via thePOST /binaryendpoint. See the fork's documentation for thePERSONAL_FILESupload strategy.
Features
Per-user storage: Private files under
/data/private/<userId>/with full CRUD, list, move operations.Binary file support:
write_binary_file/read_binary_file(base64) MCP tools plus aPOST /binarymultipart endpoint (service-token authenticated) so other services — e.g. the Office-documents fork — can store generated.docx/.xlsx/.pptxfiles per user.Documentation catalog:
save_documentation/update_documentationwrite todocs/and updatememory-index.json(schema v2).RAG integration:
index_documentsends content to rag_api viaPOST /embedwith canary verification;search_knowledgefor semantic search;remove_from_knowledgefor deletion.Public publishing:
publish_filecreates cryptographically random tokens (≥128 bits); files served via NginxX-Accel-RedirectwithContent-Disposition: attachment,nosniff,no-store.Strict security: Fail-closed on missing/invalid
X-User-Id; path traversal blocked; no absolute paths; no..segments; symlink escape detection; reserved segment protection; atomic index writes with cross-process locking.
Related MCP server: knowledge_mgmt
Architecture
┌──────────────┐ ┌────────────────────────────────┐ ┌─────────────┐
│ LibreChat │────▶│ librechat-personal-files-mcp │──▶ │ rag_api │
│ (Agent) │ MCP │ (stateless HTTP /mcp) │ │ (vector DB) │
└──────────────┘ └────────────────────────────────┘ └─────────────┘
│
│ GET /files/{token}
▼
┌──────────────┐
│ Nginx │
│ (X-Accel) │
└──────────────┘
│
▼
┌──────────────┐
│ /data/private│ (read-only bind)
└──────────────┘Requirements
A running LibreChat instance with
rag_apienabled.A
JWT_SECRETshared with LibreChat andrag_api(see below).Nginx as the reverse proxy in front of LibreChat (required for public links via
X-Accel-Redirect).
Installation
1. Get the Docker image
Pull the pre-built image from GHCR:
docker pull ghcr.io/martinriesel/librechat-personal-files-mcp:latestOr build it yourself:
git clone https://github.com/martinriesel/librechat-personal-files-mcp.git
cd librechat-personal-files-mcp
docker build -t librechat-personal-files-mcp .2. Prepare the host (run once, as root)
Create the data directory and a shared group. The container runs as UID 2000 / GID 2500; Nginx (which serves published files) needs read access.
cd /path/to/your/librechat
groupadd -g 2500 lcfiles 2>/dev/null || true
mkdir -p ./data/private/_system
chown -R 2000:2500 ./data/private
chmod 2771 ./data/private # group rwx + setgid; world executable so nginx can traverse
setfacl -R -m g:2500:rX,d:g:2500:rX ./data/private 2>/dev/null || \
echo "ACL tools absent; using 644/755 fallback"Ensure JWT_SECRET is set in your LibreChat .env (same value must be shared with rag_api):
grep -q '^JWT_SECRET=' .env || echo "JWT_SECRET=$(openssl rand -hex 32)" >> .env3. Add the service to your compose file
Add the blocks from docker-compose.snippet.yml to your docker-compose.override.yml:
A
librechat-personal-filesservice (image, environment, volumes, networks).An extra read-only volume
./data/private:/data/private:roon thenginxservice.
Set PUBLIC_BASE_URL to your own public URL, e.g. https://chat.example.com/files.
Then start it:
docker compose up -d librechat-personal-files nginx4. Configure the Nginx reverse proxy
Add these blocks to your existing Nginx server block (the same file LibreChat uses):
limit_req_zone $binary_remote_addr zone=pubfiles:10m rate=5r/s;
location ^~ /files/ {
limit_req zone=pubfiles burst=10 nodelay;
limit_req_status 429;
proxy_pass http://librechat-personal-files:8080/files/;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
}
location ^~ /_protected/ {
internal;
alias /data/private/;
}Reload Nginx:
docker exec <nginx-container> nginx -s reload5. Activate the MCP server in LibreChat
In the LibreChat Admin Panel → MCP Servers, add (or in your librechat.yaml):
mcpSettings:
allowedAddresses:
- 'librechat-personal-files:8080'
mcpServers:
personal-files:
type: streamable-http
url: http://librechat-personal-files:8080/mcp
timeout: 120000
chatMenu: false
headers:
X-User-ID: '{{LIBRECHAT_USER_ID}}'
X-User-Email: '{{LIBRECHAT_USER_EMAIL}}'Restart LibreChat. The personal-files server will be available to your agents.
6. Service-to-service binary uploads (POST /binary)
Other services can store files in a user's private storage without speaking
MCP. Set SERVICE_TOKEN on this server and have the service send a multipart
request:
POST http://librechat-personal-files:8080/binary
X-Service-Token: <SERVICE_TOKEN>
X-User-Id: <userId>
form: file=<bytes>, path=office/report.docxBoth SERVICE_TOKEN and a valid X-User-Id are required (fail-closed); the
path goes through the same path-safety checks as every other tool. On success
it returns {path, filename, mime_type, size_bytes, sha256}.
This is how the Office-documents fork
stores generated .docx/.xlsx/.pptx files per user (see its PERSONAL_FILES
strategy).
Environment Variables
Variable | Default | Description |
|
| Header carrying the user identity |
|
| Root directory for user data |
|
| Legacy shared area (read/write during migration) |
|
| rag_api endpoint |
| required | HS256 secret shared with LibreChat/rag_api (≥32 chars) |
|
| Base URL for public links |
|
| Max upload size for text files |
|
| Max upload size for binary files ( |
| (empty) | Shared token required by the |
|
| SQLite registry for public links |
MCP Tools
Storage
list_files(path="", recursive=false, pattern=null)— list files/dirsread_file(path)— read UTF-8 text; error for binary or >2 MBread_multiple_files(paths)— read several text files at oncewrite_file(path, content)— write text (UTF-8), creates parent dirswrite_binary_file(path, content_base64)— write a binary file (e.g. Office documents); uses the binary size limit, never indexesread_binary_file(path)— read any file as base64 (binary or text)update_file(path, content)— update existing fileedit_file(path, edits)— find/replace text (eacholdTextmust match exactly once)delete_file(path)— delete file or directorymove_file(src, dst)— move within user rootget_file_info(path)— metadata + docindex + publishing status
Documentation
save_documentation(filename, content, title?, description?, tags?, topics?)— saves todocs/, updates index, does not publish or indexupdate_documentation(filename, content, ...)— update existing docget_document_metadata(filename)— full index entry
RAG
search_knowledge(query, limit=8)— semantic search (owner-scoped via JWT)index_document(path)— embed + canary verify, updates index statusremove_from_knowledge(path)— delete from rag_api, clears indexget_index_status()— counts + rag_api health
Publishing
publish_file(path, expires_in_days?)— create/reuse public link, returns token + URLunpublish_file(path_or_token)— revoke link (file stays private)get_public_link(path)— active link for pathlist_public_links()— all links for user
Security Model
Identity:
X-User-Idheader injected by LibreChat ({{LIBRECHAT_USER_ID}}). Placeholder unresolved → empty string → fail-closed.Fail-closed: Missing/empty/invalid header → HTTP 403
{"error":"missing_user_identity"}or{"error":"invalid_user_identity"}.Path safety: All paths are relative; absolute paths and
..rejected; symlink escape detected viaPath.resolve()+ prefix check.Isolation: rag_api owner scope enforced by JWT
sub/id= userId.Public links: Opaque
secrets.token_urlsafe(16)tokens; no user/path in URL; revocation via410 Gone; lazy cleanup of expired entries.
Development
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
# Run tests
.venv/bin/pytest -q
# Lint
.venv/bin/ruff check .License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Document hosting and encrypted agent memory with multi-tenant persistence.
File uploads for AI agents. Upload, list, and manage files. No signup required.
Persistent file storage for AI agents via MCP and curl. Upload, download, and version files.
Securely search and manage workspace context files for AI agents and teams.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceEnables AI agents to process files locally — OCR images, extract text from PDFs and DOCX, and describe images using local vision models, all without sending data to external services.-
- AlicenseNot gradedqualityDmaintenanceEnables uploading, organizing, and semantically searching documents with support for various file types and embedding providers.6 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents with long-term memory and retrieval-augmented generation (RAG) capabilities, allowing them to recall past conversations, search local files, and learn user preferences.MIT
- AlicenseAqualityCmaintenanceProvides AI agents with local file-processing capabilities for token counting, RAG chunking, CSV/JSON conversion, QR generation, and more, while keeping documents private on the user's machine.71MIT