User Tools

Site Tools


computing:librechat

This is an old revision of the document!



  • librechat
  • Jonathan Haack
  • Haack's Networking
  • webmaster@haacksnetworking.org

librechat


Introduction

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.

1. Prerequisites

sudo apt update && sudo apt upgrade -y
sudo apt install -y git build-essential curl gnupg python3-pip python3-venv

Node.js 24

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

MongoDB 8.0 (Debian 13 uses Bookworm repo)

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

PM2

sudo npm install -g pm2

2. Install LibreChat

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 once, then Ctrl+C
pm2 start npm --name librechat -- run backend
pm2 save
pm2 startup              # follow the printed command

3. Apache Reverse Proxy + HTTPS

Modules

sudo a2enmod proxy proxy_http proxy_wstunnel ssl rewrite headers socache_shmcb
sudo systemctl restart apache2

Certificate

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d chat.haacksnetworking.org

VirtualHost

<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

Firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable

4. librechat.yaml (DeepSeek + Web Search + MCP)

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

5. MCP Document-Tools Server (docx, xlsx, pdf, pptx)

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

6. Using the Tools

  • In a chat, open the tools / MCP menu.
  • Add or enable the document-tools MCP server.
  • Ask for a document. Available tools:
    • create_docx — Word
    • create_xlsx — Excel
    • create_pdf — PDF
    • create_pptx — PowerPoint

Example prompts:

  • “Create a Word document titled 'Solar System' with paragraphs and bullets about the planets. Give me the download link.”
  • “Make an Excel file named 'Employees' with headers Name, Department, Salary and three sample rows.”
  • “Create a PDF titled 'Monthly Report' with a heading and a few paragraphs.”
  • “Create a PowerPoint titled 'Project Update' with slides for Goals, Progress, and Next Steps.”

Files appear at https://chat.haacksnetworking.org/downloads/….

7. Admin and Registration

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.

8. Updates

cd /opt/LibreChat
pm2 stop librechat
git pull
npm run smart-reinstall
pm2 restart librechat

Quick Checklist

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

```

computing/librechat.1792262277.txt.gz · Last modified: by oemb1905