This is an old revision of the document!
librechat
This is a tutorial for Debian users to setup Librechat and attach a custom API to it. I chose to use Deepinfra for cost and speed reasons, but/and used the open weight DeepSeek v4 Flash model via the Deepinfra API. I also built an MCP server to create docx, xlsx, pptx, and pdf files using python under the hood. Lastly, I used the built-in and free keenable service so that the chat could scrape websites. I've tested some common tasks that from other fully proprietary offerings and so far, so good. I prefer apache reverse proxies over nginx so I also included the vhost config I'm using, which includes the download directory for the MCP server and tooling. Librechat has two installation methods: npm or docker and I chose npm. Service management and systemd unit building is handled by pm2, an npm-based service manager that automatically cuts your systemd unit.
sudo apt update && sudo apt upgrade -y sudo apt install -y git build-essential curl gnupg python3-pip python3-venv
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt install -y nodejs sudo npm install -g npm@11 node -v && npm -v
wget -qO - https://www.mongodb.org/static/pgp/server-8.0.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/8.0 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list sudo apt update sudo apt install -y mongodb-org sudo systemctl enable --now mongod
sudo npm install -g pm2
cd /opt sudo git clone https://github.com/LibreChat-AI/LibreChat.git sudo chown -R $USER:$USER LibreChat cd LibreChat cp .env.example .env nano .env
Essential .env values:
MONGO_URI=mongodb://127.0.0.1:27017/LibreChat JWT_SECRET=your_64_hex_secret JWT_REFRESH_SECRET=your_64_hex_secret CREDS_KEY=your_64_hex_secret CREDS_IV=your_32_hex_iv DOMAIN_CLIENT=https://chat.haacksnetworking.org DOMAIN_SERVER=https://chat.haacksnetworking.org TRUST_PROXY=1 HOST=127.0.0.1 DEEPINFRA_API_KEY=sk-di-your_key_here
Generate secrets with openssl rand -hex 32 and openssl rand -hex 16 for the IV.
npm run reinstall npm run backend #test, debug, then use pm2 instead pm2 start npm --name librechat -- run backend pm2 save pm2 startup
sudo a2enmod proxy proxy_http proxy_wstunnel ssl rewrite headers socache_shmcb sudo systemctl restart apache2
sudo apt install -y certbot python3-certbot-apache sudo certbot --apache -d chat.haacksnetworking.org
<VirtualHost *:443>
ServerName chat.haacksnetworking.org
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/chat.haacksnetworking.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/chat.haacksnetworking.org/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
ProxyPreserveHost On
ProxyRequests Off
# Exclude downloads from proxy (must come before ProxyPass /)
ProxyPass /downloads !
Alias /downloads /opt/LibreChat/generated_docs
<Directory /opt/LibreChat/generated_docs>
Options -Indexes
Require all granted
</Directory>
ProxyPass / http://127.0.0.1:3080/
ProxyPassReverse / http://127.0.0.1:3080/
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:3080/$1" [P,L]
ProxyTimeout 300
Timeout 300
LimitRequestBody 26214400
ErrorLog ${APACHE_LOG_DIR}/librechat-error.log
CustomLog ${APACHE_LOG_DIR}/librechat-access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName chat.haacksnetworking.org
Redirect permanent / https://chat.haacksnetworking.org/
</VirtualHost>
sudo a2ensite librechat sudo apache2ctl configtest sudo systemctl reload apache2
sudo ufw allow OpenSSH sudo ufw allow 'Apache Full' sudo ufw enable
Create or edit /opt/LibreChat/librechat.yaml:
version: 1.3.17
cache: true
endpoints:
custom:
- name: "DeepInfra DeepSeek"
apiKey: "${DEEPINFRA_API_KEY}"
baseURL: "https://api.deepinfra.com/v1/openai"
models:
default: ["deepseek-ai/DeepSeek-V4-Flash"]
fetch: false
titleConvo: true
titleModel: "deepseek-ai/DeepSeek-V4-Flash"
modelDisplayLabel: "DeepSeek V4 Flash"
webSearch:
searchProvider: "keenable"
scraperProvider: "keenable"
rerankerType: "none"
mcpServers:
document-tools:
type: stdio
command: /opt/mcp-docs/venv/bin/python
args:
- /opt/mcp-docs/server.py
timeout: 60000
cd /opt mkdir -p /opt/mcp-docs cd /opt/mcp-docs python3 -m venv venv source venv/bin/activate pip install mcp python-docx openpyxl pandas reportlab python-pptx
Create /opt/mcp-docs/server.py:
#!/usr/bin/env python3
import json
import os
from mcp.server import MCPServer
from docx import Document
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from pptx import Presentation
OUTPUT_DIR = "/opt/LibreChat/generated_docs"
os.makedirs(OUTPUT_DIR, exist_ok=True)
mcp = MCPServer("document-tools")
@mcp.tool()
def create_docx(title: str, content: list = None, body: str = None) -> str:
"""Create a Word (.docx) document.
Prefer content as a list of objects: [{"type":"heading|paragraph|bullet", "text":"..."}].
If that fails, pass body as a plain multi-line string.
"""
doc = Document()
doc.add_heading(title, 0)
items = content or []
if not items and body:
for line in body.splitlines():
line = line.strip()
if not line:
continue
if line.startswith("#"):
doc.add_heading(line.lstrip("# ").strip(), level=1)
elif line.startswith("- ") or line.startswith("* "):
doc.add_paragraph(line[2:].strip(), style="List Bullet")
else:
doc.add_paragraph(line)
else:
for item in items:
if isinstance(item, str):
doc.add_paragraph(item)
continue
if not isinstance(item, dict):
continue
t = (item.get("type") or "paragraph").lower()
text = item.get("text") or item.get("content") or ""
if not text:
continue
if t == "heading":
doc.add_heading(text, level=1)
elif t == "bullet":
doc.add_paragraph(text, style="List Bullet")
else:
doc.add_paragraph(text)
filename = f"{title.replace(' ', '_')}.docx"
path = os.path.join(OUTPUT_DIR, filename)
doc.save(path)
return json.dumps({
"success": True,
"file": filename,
"download": f"https://chat.haacksnetworking.org/downloads/{filename}"
})
@mcp.tool()
def create_xlsx(name: str, headers: list, rows: list) -> str:
"""Create an Excel (.xlsx) spreadsheet."""
wb = Workbook()
ws = wb.active
ws.title = name[:31]
for col, h in enumerate(headers, 1):
cell = ws.cell(1, col, h)
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
for r_idx, row in enumerate(rows, 2):
for c_idx, val in enumerate(row, 1):
ws.cell(r_idx, c_idx, val)
filename = f"{name.replace(' ', '_')}.xlsx"
path = os.path.join(OUTPUT_DIR, filename)
wb.save(path)
return json.dumps({
"success": True,
"file": filename,
"download": f"https://chat.haacksnetworking.org/downloads/{filename}"
})
@mcp.tool()
def create_pdf(title: str, content: list = None, body: str = None) -> str:
"""Create a PDF document. content is a list of dicts with type (heading/paragraph/bullet) and text, or pass body as plain text."""
filename = f"{title.replace(' ', '_')}.pdf"
path = os.path.join(OUTPUT_DIR, filename)
doc = SimpleDocTemplate(path, pagesize=letter)
styles = getSampleStyleSheet()
story = []
story.append(Paragraph(title, styles["Title"]))
story.append(Spacer(1, 12))
items = content or []
if not items and body:
for line in body.splitlines():
line = line.strip()
if not line:
continue
if line.startswith("#"):
story.append(Paragraph(line.lstrip("# ").strip(), styles["Heading2"]))
elif line.startswith("- ") or line.startswith("* "):
story.append(Paragraph(f"• {line[2:].strip()}", styles["Normal"]))
else:
story.append(Paragraph(line, styles["Normal"]))
story.append(Spacer(1, 6))
else:
for item in items:
if isinstance(item, str):
story.append(Paragraph(item, styles["Normal"]))
story.append(Spacer(1, 6))
continue
if not isinstance(item, dict):
continue
t = (item.get("type") or "paragraph").lower()
text = item.get("text") or item.get("content") or ""
if not text:
continue
if t == "heading":
story.append(Paragraph(text, styles["Heading2"]))
elif t == "bullet":
story.append(Paragraph(f"• {text}", styles["Normal"]))
else:
story.append(Paragraph(text, styles["Normal"]))
story.append(Spacer(1, 6))
doc.build(story)
return json.dumps({
"success": True,
"file": filename,
"download": f"https://chat.haacksnetworking.org/downloads/{filename}"
})
@mcp.tool()
def create_pptx(title: str, slides: list = None) -> str:
"""Create a PowerPoint (.pptx). slides is a list of dicts with title and content (list of bullet strings)."""
prs = Presentation()
title_slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(title_slide_layout)
slide.shapes.title.text = title
if len(slide.placeholders) > 1:
try:
slide.placeholders[1].text = "Generated by LibreChat"
except Exception:
pass
bullet_layout = prs.slide_layouts[1]
for s in (slides or []):
slide = prs.slides.add_slide(bullet_layout)
slide.shapes.title.text = s.get("title", "Slide")
body = slide.placeholders[1]
tf = body.text_frame
tf.text = ""
for bullet in s.get("content", []):
p = tf.add_paragraph()
p.text = str(bullet)
p.level = 0
filename = f"{title.replace(' ', '_')}.pptx"
path = os.path.join(OUTPUT_DIR, filename)
prs.save(path)
return json.dumps({
"success": True,
"file": filename,
"download": f"https://chat.haacksnetworking.org/downloads/{filename}"
})
if __name__ == "__main__":
mcp.run(transport="stdio")
chmod +x /opt/mcp-docs/server.py mkdir -p /opt/LibreChat/generated_docs deactivate
Restart LibreChat:
pm2 restart librechat
First account is admin. Verify:
mongosh --quiet --eval 'db = db.getSiblingDB("LibreChat"); db.users.find({}, {email:1, role:1}).pretty()'
To disable public registration, add to .env:
ALLOW_REGISTRATION=false
Then create users with npm run create-user.
cd /opt/LibreChat pm2 stop librechat git pull npm run smart-reinstall pm2 restart librechat
1. Node 24 + MongoDB (Bookworm repo) 2. Clone → ''.env'' (secrets, domains, HOST, DeepInfra key) → ''npm run reinstall'' 3. PM2 4. Apache modules + Certbot + VirtualHost (with ''ProxyPass /downloads !'' + Alias) 5. ''librechat.yaml'' (DeepSeek + webSearch + mcpServers) 6. MCP server in ''/opt/mcp-docs'' with docx / xlsx / pdf / pptx tools 7. Restart LibreChat and enable the document-tools MCP server in chat
This is the full working configuration.
— oemb1905 2026/10/17 18:31
```