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.
Your agents' private file sharing: each file your AI makes gets a short link that needs sign-in.
File uploads for AI agents. Upload, list, and manage files. No signup required.
Share documents privately from an AI agent. The agent gets a link; it cannot read the file.
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.25 npmMIT
- AlicenseAqualityDmaintenanceProvides 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
- AlicenseBqualityAmaintenanceEnables file-based knowledge management with ranked keyword and semantic hybrid search, allowing AI agents to learn from documents and recall relevant knowledge as a persistent memory tool.3031 npmAGPL 3.0