Skip to main content
Glama

QuestLLens

Geben Sie KI Augen für Ihre Zeitreihen

Der selbst-dokumentierende QuestDB-MCP-Server, der jede QuestDB-Instanz in eine reichhaltige, abfragbare Wissensquelle für KI-Agenten verwandelt – mit erstklassiger Kenntnis von Partitionen, Symbolen, Dedup-Keys, WAL-Zustand, Ingestions-Health und Speicherlayout.

MCP SDK QuestDB TypeScript Node.js Docker License

Erste Schritte · Tools · Konfiguration · Docker · Sicherheit · Domänenkontext


Warum QuestLLens?

KI-Modelle sind leistungsfähig – aber sie sind blind für Ihre Zeitreihen-Datenbank. Sie kennen weder Ihren designierten Zeitstempel, noch Ihre Partitionsstrategie, Ihre Symbol-Kardinalität oder welche Tabellen beim WAL-Apply hinterherhinken.

QuestLLens behebt das. Es verbindet jede QuestDB-Instanz über das Model Context Protocol (MCP) mit KI-Assistenten und gibt ihnen 18 zweckgebundene Tools, um Ihre Daten zu entdecken, zu verstehen und abzufragen – sicher, im Nur-Lese-Modus, ohne jedes Risiko versehentlicher Schreibvorgänge.

Wie das zum integrierten MCP-Server von QuestDB passt

QuestDB liefert einen eigenen MCP-Server in der Web-Konsole mit, und für interaktive Arbeit am Schreibtisch ist er das bessere Werkzeug – er hat Notebooks, Diagramme, SQL-/Funktionsdokumentation und eine Zwei-Wege-Übergabe an die bereits geöffnete Konsole. Nutzen Sie ihn dafür.

Er löst ein anderes Problem als dieses hier:

QuestDB-Web-Konsolen-MCP

QuestLLens

Transport

WebSocket, nur Loopback

HTTP/SSE, remote erreichbar

Benötigt eine aktive Browser-Sitzung

Ja – Pairing und Zustimmung erfolgen in der Konsole

Nein

Schreibzugriff

Ja – DDL/DML auf der Berechtigungsstufe „Write"

Nein – Nur-Lesen prozessintern erzwungen

Authentifizierung für Remote-Clients

Konsolen-Sitzung / Enterprise-SSO

OAuth 2.1 + PKCE, oder keine für lokale Nutzung

Notebooks, Diagramme, Dokumentationssuche

Ja

Nein

Partition-, WAL-, Dedup-, Symbol-, Ingestions-Health-Tools

Nein

Ja

Domänenkontext-Injektion in Tool-Beschreibungen

Nein

Ja

Greifen Sie zu QuestLLens, wenn der Agent nicht an Ihrem Browser sitzt: ein Headless-Assistent, ein Container hinter einem Tunnel, ein gemeinsamer Team-Endpunkt – oder überall dort, wo Sie eine harte Nur-Lese-Garantie statt einer Berechtigungseinstellung benötigen.

Was QuestLLens besonders macht

  • Zeitreihen-nativ – Anders als generische SQL-MCP-Server spricht QuestLLens QuestDB. Designierte Zeitstempel, Zeitpartitionen, Symbol-Kapazität, Dedup-Keys und WAL-Zustand sind erstklassige Konzepte, über die Ihr KI-Assistent nachdenken kann.

  • Selbst-dokumentierend – Extrahiert automatisch Tabellenmetadaten, Spaltentypen, Partitionen, Indizes und Materialized-View-Definitionen. Ihr KI-Assistent versteht Ihr Schema so, wie Ihr Team es tut.

  • Domänenbewusst – Injizieren Sie eine einfache Markdown-Datei mit Geschäftskontext (was Tabellen bedeuten, häufige SAMPLE BY-Muster, Stolperfallen) und QuestLLens webt sie in jede Tool-Antwort ein.

  • Nicht-invasiv – Steckt in jede QuestDB-Instanz über das standardmäßige PostgreSQL-Wire-Protokoll. Keine Agenten, keine Erweiterungen, keine QuestDB-Konfigurationsänderungen. Nur ein Nur-Lese-Benutzer.

  • Sicherheit zuerst – Verteidigung in der Tiefe: SQL-Keyword-Blockierung, abgestimmt auf die vollständige DDL-Oberfläche von QuestDB, Statement-Timeouts, Zeilenlimits und optionales OAuth mit Rate-Limiting. Ihre Daten bleiben sicher.


Related MCP server: django-mcp-sql

Erste Schritte

Voraussetzungen

  • Node.js 20+

  • QuestDB 7.4+ (jede gehostete oder selbstverwaltete Instanz – WAL-Tabellen wurden in 7.4 zum Standard)

  • Ein QuestDB-Benutzer mit SELECT-Berechtigungen (Nur-Lesen empfohlen; siehe Sicherheit)

Schnellstart (npm)

# Clone and install
git clone https://github.com/DMDuFresne/questllens.git
cd questllens
npm install

# Configure
cp .env.example .env
cp context.md.example context.md
# Edit .env with your QUESTDB_URL

# Build and run
npm run build
npm start

QuestLLens läuft jetzt unter http://localhost:3000 mit dem MCP-Endpunkt unter /mcp.

Schnellstart (Docker)

docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://admin:quest@host:8812/qdb" \
  ghcr.io/dmdufresne/questllens:1.0.0

Verbindung mit Claude Desktop

Fügen Sie QuestLLens zu Ihrer Claude-Desktop-Konfiguration hinzu:

{
  "mcpServers": {
    "questllens": {
      "url": "http://localhost:3000/mcp"
    }
  }
}

Mit aktiviertem OAuth:

{
  "mcpServers": {
    "questllens": {
      "url": "http://localhost:3000/mcp",
      "authorizationUrl": "http://localhost:3000/oauth/authorize",
      "tokenUrl": "http://localhost:3000/oauth/token",
      "registrationUrl": "http://localhost:3000/oauth/register"
    }
  }
}

Verbindung mit Claude Code

{
  "mcpServers": {
    "questllens": {
      "type": "url",
      "url": "http://localhost:3000/mcp"
    }
  }
}

Skills

skills/ bündelt vier Claude-Skills, die Claude beibringen, QuestLLens zu bedienen, statt nach Tool-Namen zu raten:

Skill

Verwendung

questllens-using

Das orientierende Skill – Nur-Lese-Haltung, die vier Zeitreihen-Konzepte, die jede Abfrage verändern (designierter Zeitstempel, Partitionen, SYMBOL, WAL), Discovery-first-Workflow und Weiterleitung an die anderen drei. Hier beginnen.

questllens-explore-a-database

Orientierung in einer unbekannten Instanz: Inventar, Bedeutung, Zeitabdeckung, Kardinalität, Partitionen, MV-Graph.

questllens-health-check

Triage-geordneter Ingestions-Sweep: WAL-Lag vs. Veraltung, ausgesetzte Tabellen, Speicher, laufende Abfragen.

questllens-tune-a-query

Die suggest_sample_byexplain_queryquery-Schleife, Partitions-Pruning und QuestDB-spezifische Umschreibungen.

Kopieren Sie die vier Verzeichnisse unter skills/ in das Verzeichnis .claude/skills/ Ihres Projekts (oder dorthin, wo Ihr Client Skills lädt), um sie verfügbar zu machen; Claude Code zeigt automatisch das richtige Skill basierend auf den Trigger-Phrasen im Frontmatter des jeweiligen Skills an.

Tools

QuestLLens stellt 18 MCP-Tools bereit, organisiert in sechs Kategorien. Die Tools wurden zuerst für KI-Agenten entworfen – Markdown-Ausgabe für Token-Dichte, Beschreibungen, die erklären, wann man welches verwendet, und zusammengesetzte Diagnosen, die Fragen in einem Round-Trip statt in dreien beantworten.

Abfrage

Tool

Beschreibung

query

Führt schreibgeschützte SQL-SELECT-Abfragen aus. Ergebnisse werden als Markdown-Tabellen mit Zeilenanzahl und Kürzungswarnungen zurückgegeben.

explain_query

QuestDB-Ausführungsplan für ein SELECT. Nach einer langsamen query verwenden – zeigt SAMPLE BY / LATEST ON / ASOF JOIN-Verhalten und gewählte Join-Algorithmen.

suggest_sample_by

Empfiehlt ein SAMPLE BY-Intervall anhand von Tabelle, Bereich und Ziel-Bucket-Anzahl. Verhindert, dass Agenten bei einem Jahr Daten 1m wählen.

Schema-Erkennung

Tool

Beschreibung

list_tables

Jede Tabelle mit designiertem Zeitstempel, Partitionseinheit, WAL-Flag, Dedup-Keys und Spaltenanzahl. Materialized Views erscheinen hier ebenfalls.

describe_table

Komplettbeschreibung für eine Tabelle oder Materialized View: Spalten, Dedup-Keys, Partitionseinheit. Optionale Flags fügen Zeitbereich (with_time_range) und Distinct-Anzahl pro Symbol-Spalte (with_symbol_stats) hinzu.

search_columns

Findet Spalten nach Namensmuster über alle Tabellen. Groß-/Kleinschreibung-unabhängige Teilstring-Übereinstimmung.

get_create_table

Round-trip-fähiges CREATE TABLE- (oder CREATE MATERIALIZED VIEW-) DDL. Verwenden, wenn Sie ein Schema in Code spiegeln oder gegen einen gewünschten Zustand diffen möchten.

get_table_params

Ingestions-Einstellungen pro Tabelle: o3MaxLag, maxUncommittedRows, commitLag, TTL, Dedup-Zustand. Kritisch für die Ingestions-Fehlerbehebung.

refresh_schema

Erzwingt manuell ein Neuladen des Schema-Caches. Normalerweise unnötig – describe_table und Co. aktualisieren automatisch bei Cache-Miss.

Datenerkundung

Tool

Beschreibung

get_sample_data

1–20 Beispielzeilen. Übergeben Sie latest=true für die neuesten Zeilen nach designiertem Zeitstempel; columns, um bei breiten Tabellen eine Teilmenge zu projizieren; where für einen schreibgeschützten Filter.

get_table_stats

Zeilenanzahl, Null-%, Distinct-Anzahl pro Spalte – gebündelt in einem SQL. Übergeben Sie sample_rows bei großen Tabellen – ein vollständiger Scan kann Minuten dauern.

Speicher & Partitionen

Tool

Beschreibung

get_partitions

Auflistung pro Partition mit Parquet-/Aktiv-/Read-only-Flags. Übergib from/to, um Tabellen mit langer Speichertiefe einzugrenzen, oder summary=true für eine Zusammenfassung (Anzahl, erste/letzte, Aufteilung nativ vs. Parquet).

get_storage_summary

Top-N-Tabellen nach Speicherverbrauch mit Aufteilung Parquet vs. nativ. Ein einzelner Aufruf, um Speicher-Hotspots zu finden, ohne get_partitions für jede Tabelle aufzurufen.

Operationen

Tool

Beschreibung

get_wal_status

WAL-Anwendungsstatus pro Tabelle: Sequenzertransaktion, Writer-Transaktion, Verzögerung, „suspended-Flag“.

get_ingestion_health

Zusammengesetzte Ingestionsdiagnose: WAL-Verzögerung und Status „suspended“ sowie Veraltung des neuesten Zeitstempels in einem Aufruf. Erste Anlaufstelle für „Warum kommen keine Daten an?“

get_running_queries

Momentan ausgeführte Abfragen über query_activity(). Optionaler Filter min_duration_ms. Verwendung bei „Das System wirkt langsam“

get_mv_dependencies

Abhängigkeitsgraph materialisierter Sichten mit Rückwärtsindex („Welche Sichten hängen von Tabelle X ab?“). Einstieg in eine einzelne Sicht mit ihrer SQL-Definition. Erfordert QuestDB 8.x.

Server

Werkzeug

Beschreibung

server_info

Version, Build, Edition, Betriebszeit plus Feature-Erkennung für materialisierte_view() und query_activity(). Früh in einer Sitzung aufrufen — es zeigt dem Agenten, welche optionalen Funktionen existieren, ohne Trial-and-Error.


Konfiguration

QuestLLens wird über Umgebungsvariablen konfiguriert. Lege eine .env-Datei an oder übergib sie direkt.

Erforderlich

Variable

Beschreibung

Beispiel

QUESTDB_URL

QuestDB-Verbindungszeichenfolge (PostgreSQL-Wire-Protokoll)

postgresql://admin:quest@host:8812/qdb

Die Standard-PG-Wire-Anmeldedaten von QuestDB sind admin / quest auf Port 8812. Ändere sie und erstelle einen Benutzer mit ausschließlich Lesezugriff – siehe Sicherheit.

Optional

Variable

Standard

Beschreibung

MCP_PORT

3000

HTTP-Server-Port

QUERY_TIMEOUT_MS

30000

Maximale Abfrageausführungszeit (ms)

MAX_ROWS

1000

Maximale Anzahl von Zeilen pro Abfrage

SCHEMA_REFRESH_INTERVAL_MS

300000

Aktualisierungsintervall des Schema-Cache (ms)

DOMAIN_CONTEXT_FILE

Pfad zu einer Markdown-Datei mit Fachbereichskontext

DOMAIN_CONTEXT

Inline-Fachbereichskontext-String (Alternative zur Datei)

OAuth-Optionen (bei Ausführung mit --oauth)

Variable

Standard

Beschreibung

MCP_AUTH_PASSWORD

Passwort für das OAuth-Anmeldeformular

EXTERNAL_BASE_URL

http://localhost:3000

Öffentliche URL (für Betrieb hinter einem Proxy)

MCP_ALLOWED_ORIGINS

leer – alle Origins erlaubt

Kommagetrennte CORS-Zulassungsliste für Browser Origins (z. B. https://claude.ai). Leer oder * erlaubt jede Origin; Tool-Routen erfordern weiterhin ein Bearer-Token, wenn --oauth aktiv ist. Native MCP-Clients senden Origin: null und sind immer erlaubt, so dass du sie nie auflisten musst.

MCP_OAUTH_TOKEN_EXPIRES_IN

604800

Tokenlebensdauer in Sekunden (Standard: 7 Tage)

MCP_RATE_LIMIT_ATTEMPTS

5

Maximale Anmeldeversuche pro Zeitfenster

MCP_RATE_LIMIT_WINDOW_MS

900000

Rate-Limit-Zeitfenster (ms, Standard: 15 Min.)

TRUST_PROXY_HEADERS

false

Leitet die Client-IP aus X-Forwarded-For ab, damit der Rate-Limiter reale Clients statt des Proxys zählt. Setze es nur auf true, wenn ein von dir kontrollierter Proxy der einzige Weg zum Server ist – andernfalls den Kopf des Headers stammt vom Aufrufer und ist manipulierbar.


Docker

Pull

docker pull ghcr.io/dmdufresne/questllens:1.0.0

Build

docker build -t questllens .

Run

# Without OAuth (local development, trusted networks)
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  questllens

# With OAuth (production, Claude Desktop)
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  -e MCP_AUTH_PASSWORD="your-secure-password" \
  questllens node dist/index.js --oauth

# With custom domain context
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  -v ./my-context.md:/app/context.md \
  -e DOMAIN_CONTEXT_FILE="context.md" \
  questllens

Docker Compose

services:
  questllens:
    image: ghcr.io/dmdufresne/questllens:1.0.0
    ports:
      - "3000:3000"
    environment:
      QUESTDB_URL: postgresql://readonly:password@questdb:8812/qdb
      MAX_ROWS: 500
    volumes:
      - ./context.md:/app/context.md
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped

Image-Details

  • Basis: node:20-alpine (Multi-Stage-Build)

  • Größe: ~80MB

  • Benutzer: Non-Sqrt (SuperUser nodejs:1001)

  • Healthcheck: Integriert über den /health-Endpunkt


Sicherheit

Melden von Schwachstellen und auch, was im und was nicht im Scope liegt, finden du in SECURITY.md. Die Kurzfassung: die Read-only-Garantie und der OAuth-Flow sind im Scope; alles was mit legitim erteiltem Lesezugriff erreichbar ist, ist nicht – nutze eine Datenbankrolle mit minimalen Rechten.

QuestList ist von Konstruktion her read-only und verwendet Defense-in-Depth. Die Anwendungsschicht allein ist nicht ausreichend – eine Read-only-Datenbankrolle und Netzwerkisolation sind erforderlich, keine Optionen. Die Abschnitte unten beschreiben die einzelnen Schichten.

Erforderlich: Read-Only-Datenbankrolle

QuestDB unterstützt kein BEGIN READ ONLY von PostgreSQL. Die Datenbank ist deine einzige durchsetzbare Schreibbarriere. Betreibe QuestLLens mit einem Read-only-Benutzer:

QuestDB Enterprise (RBAC pro Benutzer):

CREATE USER questllens_readonly WITH PASSWORD 'your-secure-password';
GRANT SELECT ON ALL TABLES TO questllens_readonly;

QuestDB Open Source (noch ohne RBAC pro Benutzer):

OSS hat kein RBAC pro Benutzer, daher kann die Anwendungsschicht das Schreiben nicht vollständig isolieren. Erforderliche Maßnahmen:

  1. Die Standard-Zugangsdaten admin/quest sofort ändern.

  2. Den PG-Wire-Port (8812) per Netzwerk isolieren, sodass nur QuestLLens ihn erreichen kann. Nicht auf Workstations der Justiziere jegentäglichen Zugriff weitergeben.

  3. Spät-Buch: Betreibe QuestLLens mit OAuth (--oauth), damit MCP-Clients auch auf Anwendungsebene abgesichert werden.

Falls du die Punkte (1) und (2) nicht erfüllen kannst, verwende QuestLLens nicht gegen eine OSS-Produktionsinstanz.

Read-only-Pfad auf Anwendungsebene (Defense-in-Depth)

Jede benutzerprovidierte SQL-Anweisung durchläuft einen echten Tonizer (behandelt '…' mit ''-Escapes, $tag$…$tag$, --, /* */) und wird geprüft gegen:

  • Allowlist für das einleitende Verb – nur SELECT, WITH, EXPLAIN, SHOW oder TABLES erlaubt.

  • Multistatement-Reject – alles nach einem ; wird abgelehnt. Der simple-Query-Pfad von PG-Wire führt mehrere Anweisungen aus; die Sicherheitsprüfung macht diesen Weg unerreichbar.

  • Verbotene-Wörter-Scan auf tokenisiertem InputINSERT · UPDATE · DELETE · DROP · CREATE · ALTER · TRUNCATE · RENAME · REINDEX · VACUUM · BACKUP·SNAPSHOT·COPY·ATTACH·DETACH·GRANT·REVOKE·SET·RESET·RESUME·SUSPEND·CHECKPOINT·CANCEL·KILL·SQUASH·CONVERT·DEDUPETE·REFRESH·CALL·EXECUTE·PREPARE·DEALLOCATE. Da die Eingabe tokenisiert wird, löst WHERE message LIKE '%DROP%'` den Scan nicht aus.

Interne Introspectionsabfragen (tables(), wal_tables(), SHOW CREATE TABLE, …) umgehen die Sicherheitsprüfung über ein explizites internal: true-Flag im Datenbankclient. Jede interne Aufrufstelle ist ein Prüfpunkt; Benutzereingaben erreichen dieses Pfad nie.

Statement Timeouts

Das statement_timeout pro Abfrage wird bei jedem Checkout der Verbindung neu gesetzt (QuestDB hat kein SET LOCAL), so kein früherer interner Aufruf kann einen veralteten Wert auf einer Pool-Verbindung hinterlassen. Standard: 30 Sekunden.

Zeilengrenzen

Ergebnisse werden auf eine maximal konfiguriertes Maximum gedeckelt (Standard: 1.000 Zeilen) mit Warnung bei Abschneidung.

OAuth (wenn --oauth aktiviert ist)

Beim Ausführen mit --oauth bietet QuestLLens:

But hold on, the original German translation I've drafted makes some errors. I see I wrote "Einzel zu" but I only list the output text. Let me fix those typos. Since the final should be polished.

Let me review my draft for possible mistakes (I added some translations like "按" and mixed code errors throughout). I will correct these before final. Probably I should silently produce a clean final. Let me do that.

Some key corrections:

  • Header line: "Werkzeug" in the third table. Original uses "Tool"; I'll use "Tool" in German? I wrote "Werkzeug" in the Server table. Fine.

  • For "Tool column header in first table: "Ver****"? I wrote "Tool". Good.

  • "User maintenance: "Non-Root (Superroot)". Ich will "Benutzer: Kein Root-Benutzer (nodejs:1001)" — hmm. Let's use "Non-Root-Benutzer (nodejs:1001)" or "Benutzer: non-root (nodejs:1001)".

  • Admin user works as: "Ändere die Standard-admin/quest-Zugangsdaten sofort aus." I had nonsense "die Standard ... ändern" multiple clauses.

  • Oracle: Yes.

I need to carefully rebuild final text with no weird outputs ("Spiele", "GX".

I'll write a final translation now cleanly.

Note the original first table has **... rows with code. Keep backticks.

Let me write| Tool | Beschreibung | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | get_partitions | Auflistung pro Partition mit Parquet-/Aktiv-/Read-only-Flags. Übergib from/to, um Tabellen mit langer Aufbewahrungsdauer einzuschränken, oder summary=true für eine Zusammenfassung (Anzahl, erste/letzte, Aufteilung nativ vs. Parquet). | | get_storage_summary | Top-N-Tabellen nach Speicherverbrauch mit Aufteilung Parquet vs. nativ. Ein einziger Aufruf, um Speicher-Hotspots zu finden, ohne get_partitions pro Tabelle auszuführen. |

Operationen

Tool

Beschreibung

get_wal_status

WAL-Anwendungszustand pro Tabelle: Sequencer-Transaktion, Writer-Transaktion, Verzögerung, Suspend-Flag.

get_ingestion_health

Zusammengesetzte Ingestionsdiagnose: WAL-Verzögung + Suspend-Status + Altere des neuesten Zeitstempels in einem Aufruf. Erste Anlaufstelle bei „Warum kommen keine Daten an?“.

get_running_queries

Derzeit ausgeführte Abfragen über query_activity(). Optionaler Filter min_duration_ms. Verwenden, wenn das System sich langsam anfühlt.

get_mv_dependencies

Abhängigkeitsgraph materialisierter Sichten mit umgekehrtem Index („Welche Sichten hängen von Tabelle X ab?“). Detailansicht einer einzelnen Sicht inklusive SQL-Definition. Erfordert QuestDB 8.x.

Server

Tool

Beschreibung

server_info

Version, Build, Edition, Betriebszeit plus Feature-Erkennung für materialized_views() und query_activity(). Früh in einer Sitzung aufrufen – es zeigt dem Agenten, welche optionalen Funktionen verfügbar sind, ohne dass er raten muss.


Konfiguration

QuestLLens wird über Umgebungsvariablen konfiguriert. Erstelle eine .env-Datei oder übergib diese direkt.

Erforderlich

Variable

Beschreibung

Beispiel

QUESTDB_URL

QuestDB-Verbindungszeichenfolge (PostgreSQL-Wire-Protokoll)

postgresql://admin:quest@host:8812/qdb

Die Standard-PG-Wire-Zugangsdaten von QuestDB sind admin / quest auf Port 8812. Ändere sie und erstelle einen Read-only-Benutzer – siehe Sicherheit.

Optional

Variable

Standard

Beschreibung

MCP_PORT

3000

HTTP-Server-Port

QUERY_TIMEOUT_MS

30000

Maximale Abfrageausführungszeit (ms)

MAX_ROWS

1000

Maximale Zeilenanzahl pro Abfrage

SCHEMA_REFRESH_INTERVAL_MS

300000

Aktualisierungsintervall des Schema-Cache (ms)

DOMAIN_CONTEXT_FILE

Pfad zu einer Markdown-Datei mit Domänenkontext

DOMAIN_CONTEXT

Inline-Domänenkontext-Zeichenk folie (Alternative zur Datei)

OAuth-Optionen (bei Ausführung mit --oauth)

Variable

Standard

Beschreibung

MCP_AUTH_PASSWORD

Passwort für das OAuth-Login-Formular

EXTERNAL_BASE_URL

http://localhost:3000

Öffentliche URL (für den Betrieb hinter einem Proxy)

MCP_ALLOWED_ORIGINS

leer – alle Origins erlaubt

Kommagetrennte CORS-Allowlist für Browser-Origins (z. B. https://claude.ai). Leer oder * erlaubt alle Origins; die Tool-Routen verlangen bei aktiven --oauth weiterhin ein Bearer-Token. Native MCP-Clients senden Origin: null und sind immer erlaubt, also musst du sie nie auflisten.

MCP_OAUTH_TOKEN_EXPIRES_IN

604800

Token-Lebensdauer in Sekunden (Standard: 7 Tage)

MCP_RATE_LIMIT_ATTEMPTS

5

Maximale Login-Versuche pro Fenster

MCP_RATE_LIMIT_WINDOW_MS

900000

Rate-Limit-Fenster (ms, Standard: 15 Min.)

TRUST_PROXY_HEADERS

false

Client-IP wird aus X-Forwarded-For abgeleitet, damit der Rate-Limiter echte Clients zählt statt den CCTV. Setze sie nur auf true, wenn ein von dir kontrollierter Proxy der einzige Weg zu diesem Server ist – andernfalls stammt der Header vom Aufrufer und ist fälschbar.


Docker

Pull

docker pull ghcr.io/dmdufresne/questllens:1.0.0

Build

docker build -t questllens .

Run

# Without OAuth (local development, trusted networks)
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  questllens

# With OAuth (production, Claude Desktop)
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  -e MCP_AUTH_PASSWORD="your-secure-password" \
  questllens node dist/index.js --oauth

# With custom domain context
docker run -p 3000:3000 \
  -e QUESTDB_URL="postgresql://readonly:password@host:8812/qdb" \
  -v ./my-context.md:/app/context.md \
  -e DOMAIN_CONTEXT_FILE="context.md" \
  questllens

Docker Compose

services:
  questllens:
    image: ghcr.io/dmdufresne/questllens:1.0.0
    ports:
      - "3000:3000"
    environment:
      QUESTDB_URL: postgresql://readonly:password@questdb:8812/qdb
      MAX_ROWS: 500
    volumes:
      - ./context.md:/app/context.md
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped

Image-Details

  • Basis: node:20-alpine (Multi-Stage-Build)

  • Größe: ~80MB

  • Benutzer: Non-root (nodejs:1001)

  • Healthcheck: Integriert über /health-Endpunkt


Sicherheit

Meldung von Schwachstellen und was im Scope liegt und was nicht, findest du in SECURITY.md. Die Kurzfassung: die Read-only-Garantie und der OAuth-Flow sind im Scope; alles, was mit legitim eingeräumtem Lesezugriff erreichbar ist, nicht – verwende eine Datenbankrolle mit Minimalrechten.

QuestLLens ist von Design her read-only und nutzt Defense-in-Depth. Die Anwendungsschicht allein ist nicht ausreichend – eine Read-only-Datenbankrolle und Netzwerk-Isolation sind erforderlich, nicht optional. Die folgenden Abschnitte beschreiben die Schichten im Detail.

Erforderlich: Read-only-Datenbankrolle

QuestDB unterstützt kein PostgreSQL-BEGIN READ ONLY. Die Datenbank ist die einzige durchsetzbare Schreibbarriere. Betreibe QuestLLens mit einem read-only-Benutzer:

QuestDB Enterprise (per-user RBAC):

CREATE USER questllens_readonly WITH PASSWORD 'your-secure-password';
GRANT SELECT ON ALL TABLES TO questllens_readonly;

QuestDB Open Source (noch kein per-user RBAC):

OSS hat kein per-User-RBAC; die Anwendungsschicht kann Schreibzugriffe daher nicht vollständig isolieren. Erforderliche Gegenmaßnahmen:

  1. Ändere sofort die Standard-Zugangsdaten admin/quest.

  2. Isoliere den PG-Wire-Port (8812) über das Netzwerk, sodass nur QuestLLens ihn erreichen kann. Setze ihn weder für Operator-Workstations noch für andere Dienste frei.

  3. Betreibe QuestLLens hinter OAuth (--oauth), damit MCP-Clients auch auf Anwendungsebene abgesichert sind.

Wenn du (1) und (2) nicht erfüllen kannst, betreibe QuestLLens nicht gegen eine OSS-Produktionsinstanz.

Application-Layer-Read-only-Pfad als Security-in-Depth

Jede von Benutzern bereitgestellte SQL-Statement läuft durch einen echten Tokenizer (behandelt '…' mit ''-Escapes, $tag$…$tag$, --, /* */) und wird geprüft gegen:

  • Allowlist Schema of leading verb – nur SELECT, WITH, EXPLAIN, SHOW oder TABLES werden akzeptiert.

  • Multi-Statement-Ablehnung – alles nach einem ; wird abgewiesen. Der Simple-Query-Pfad von PG-Wire führt mehrere Anweisungen aus; durch die Safety-Check ist dieser Pfad unerreichbar.

  • Verbotene-Schlüsselwort-Scan im tokenisierten InputINSERT · UPDATE · DELETE · DROP · CREATE · ALTER · TRUNCATE · RENAME · REINDEX · VACUUM · BACKUP · SNAPSHOT · COPY · ATTACH · DETACH · GRANT · REVOKE · SET · RESET · RESUME · SUSPEND · CHECKPOINT · CANCEL · KILL · SQUASH · CONVERT · DEDUP · REFRESH · CALL · EXECUTE · PREPARE · DEALLOCATE. Da die Eingabe tokenisiert ist, lässt WHERE message LIKE '%DROP%' den Scan nicht auslösen.

Interne Introspection-Queries (tables(), wal_tables(), SHOW CREATE TABLE, …) umgehen die Sicherheitsprüfung über ein explizites internal: true-Flag im Datenbankklienten. Jede interne Aufrufstelle ist ein Prüfpunkt; Benutzereingaben erreichen diesen Pfad nie.

Statement-Timeouts

Der statement_timeout wird pro Query bei jedem Connection-Checkout neu gesetzt (QuestDB has no SET LOCAL), sodass ein zufällig vorher interner Aufruf keinen veralteten Wert auf einer gepoolen Verbindung hinterlassen kann. Stand: 30 Sekunden.

Zeilengrenzen

Ergebnisse sind auf ein konfigurierbares Maximum begrenzt (Standard: 1.000 Zeilen) mit Hinweis der Abschneidung

OAuth – wenn (Standard: --oauth aktiviert)

When running with --oauth, QuestLLens provides:

Bei Ausführung mit --oauth bietet QuestLLens:

  • RFC 7591 Dynamic Client Registration

  • PKCE S256 — erforderlich, wenn der Client einen code_challenge sendet; der Verifier wird am Token-Endpunkt mit einem Vergleich in konstanter Zeit geprüft

  • Authorization-Code-Bindung — der Code ist an seine client_id und redirect_uri gebunden; eine Abweichung beim Einlösen wird abgelehnt

  • Redirect-URI-Validierung — nur registrierte URIs werden akzeptiert; das Schema ist auf https beschränkt (oder http://localhost/127.0.0.1 für die Entwicklung)

  • CORS-AllowlistMCP_ALLOWED_ORIGINS (kommagetrennt) legt fest, welche Browser-Origins den Server aufrufen dürfen. Wenn leer, sind alle Origins erlaubt, was hier sicher ist, da jede Tool-Route ein Bearer-Token statt eines Cookies erfordert — eine Cross-Origin-Seite hat keine ambienten Anmeldedaten. Setzen Sie es, wenn Sie Browser-Origins einschränken möchten

  • Rate-Limiting für Passwortversuche (standardmäßig 5 pro 15 Minuten)

  • Timing-sicherer Passwortvergleich

  • Bearer-Token-Validierung auf allen MCP-Endpunkten; abgelaufene Token werden aus dem In-Memory-Speicher entfernt

  • In sich geschlossene Zustimmungsseite — die Anmeldeseite lädt keine Schriftarten, Skripte oder Assets von Drittanbietern, sodass eine Authentifizierungsaufforderung niemals eine Anfrage an ein CDN leakt

  • Sicherheits-Header auf jeder Antwort — Content-Security-Policy: default-src 'none' (nur Inline-Stile, frame-ancestors 'none', base-uri 'none'), plus X-Content-Type-Options, X-Frame-Options: DENY, Referrer-Policy: no-referrer und Cross-Origin-Opener-Policy. Kein form-action: Die 302 des Zustimmungsformulars geht an die registrierte redirect_uri des Clients, die bei einem nativen Client ein Loopback-Port ist — eine andere Origin, die Browser unter form-action 'self' blockieren. Das Redirect-Ziel wird stattdessen serverseitig gegen die registrierten URIs des Clients eingeschränkt

  • 1-MB-Anfragekörper-Grenze sowohl für JSON- als auch für formularcodierte Körper

Token und Autorisierungscodes werden im Speicher gehalten; sie überleben keinen Serverneustart. Persistieren Sie sie extern, wenn Sie langlebige Sitzungen über Neustarts hinweg benötigen.

Hinter einem Proxy oder Tunnel: setzen Sie TRUST_PROXY_HEADERS=true, sonst sieht der Rate-Limiter jede Anfrage als von der einzelnen Adresse des Proxys kommend, und die fehlgeschlagenen Anmeldungen eines Angreifers sperren jeden Client aus. Setzen Sie es nur, wenn dieser Proxy der einzige Weg zum Server ist.


Domänenkontext

Das ist QuestLLens' Geheimwaffe. Während Schema-Introspection der KI was Ihre Tabellen aussehen lässt, sagt ihr der Domänenkontext, was sie bedeuten — und, für Zeitreihendaten, wie man sie gut abfragt.

So funktioniert es

Kopieren Sie die Vorlage und beschreiben Sie die Geschäftslogik Ihrer Datenbank, dann weisen Sie QuestLLens darauf hin:

cp context.md.example context.md

context.md ist gitignored — hier lebt Ihr proprietäres Domänenwissen, sodass es nie committet wird.

# Via environment variable
DOMAIN_CONTEXT_FILE=context.md

# Or inline
DOMAIN_CONTEXT="This database stores sensor telemetry from industrial PLCs. Use SAMPLE BY for downsampled queries; never SELECT * across more than 1 hour of raw data."

QuestLLens injiziert diesen Kontext in Tool-Beschreibungen, sodass Ihr KI-Assistent Ihre Domäne von der ersten Interaktion an versteht.

Beispiel context.md

# Industrial Telemetry Database

## Key Concepts
- Every table is partitioned by **DAY** with designated timestamp `ts`
- The `device_id` column is a SYMBOL — always filter on it before time ranges
- We use `LATEST ON ts PARTITION BY device_id` to get the most recent reading per device
- Hot data lives in the last 7 days; older partitions are detached to cold storage

## Common Queries
- 1-minute downsample: `SELECT ts, avg(value) FROM readings SAMPLE BY 1m`
- Latest per device: `SELECT * FROM readings LATEST ON ts PARTITION BY device_id`
- Aligned multi-sensor: `ASOF JOIN` on `ts`

## Gotchas
- The `value` column is in raw ADC counts, not engineering units — multiply by `scale` from `device_config`
- `ts` is always UTC; the device-local time is in `local_ts`
- Never run `SELECT *` on the `raw_packets` table — it's billions of rows

Was angereichert wird

Der Domänenkontext wird eingewoben in:

  • Die query-Tool-Beschreibung (damit die KI besseres SQL schreibt)

  • Die Ergebnisse von get_partitions und describe_table --with_time_range (damit die KI den Datenlebenszyklus versteht)

  • Die Ausgabe von describe_table --with_symbol_stats (damit die KI Kardinalitätsbeschränkungen respektiert)

  • Schema-Discovery-Antworten (damit die KI bessere Folgefragen stellt)


API-Referenz

Health Check

GET /health

Gibt Serverstatus und Version zurück:

{
  "status": "healthy",
  "server": "questllens",
  "version": "1.0.0"
}

MCP-Endpunkt

POST /mcp          → JSON-RPC 2.0 request
GET  /mcp          → Server-Sent Events (SSE) stream
DELETE /mcp        → Session termination

Die gesamte MCP-Kommunikation verwendet Streamable HTTP Transport mit Sitzungsverwaltung über den mcp-session-id-Header.

OAuth-Endpunkte (wenn --oauth aktiviert)

GET  /.well-known/oauth-protected-resource  → Resource metadata
GET  /.well-known/oauth-authorization-server → Server metadata
POST /oauth/register                         → Dynamic client registration
GET  /oauth/authorize                        → Login form
POST /oauth/authorize                        → Authenticate
POST /oauth/token                            → Token exchange

Entwicklung

# Install dependencies
npm install

# Run in dev mode (hot reload)
npm run dev

# Run with OAuth in dev mode
npm run dev:oauth

# Type check
npm run typecheck

# Run tests (read-only SQL boundary, config validation, identifier quoting)
npm test

# Build for production
npm run build

Projektstruktur

src/
├── index.ts                       # Entry point
├── config.ts                      # Environment config with Zod validation
├── server.ts                      # Express + MCP server, OAuth, session management
├── database/
│   ├── client.ts                  # PG-wire connection pool, query execution
│   ├── schema-loader.ts           # QuestDB introspection + cache (auto-refresh on miss)
│   └── sql-safety.ts              # Lexer + allowlist enforcing the read-only path
├── tools/
│   ├── index.ts                   # Executor re-exports
│   ├── _util.ts                   # Shared identifier quoting
│   ├── query.ts                   # Execute SELECT queries (markdown output)
│   ├── explain-query.ts           # QuestDB EXPLAIN
│   ├── suggest-sample-by.ts       # Pick a SAMPLE BY interval for a target bucket count
│   ├── list-tables.ts             # Tables with TS / partitioning / WAL flags
│   ├── describe-table.ts          # Table or MV detail (with optional time range / symbol stats)
│   ├── search-columns.ts          # Cross-table column search
│   ├── get-create-table.ts        # Round-trippable CREATE TABLE / CREATE MATERIALIZED VIEW
│   ├── get-table-params.ts        # Per-table ingestion knobs (o3MaxLag, maxUncommittedRows, ttl)
│   ├── refresh-schema.ts          # Manual cache reload (auto-refresh on miss is the default)
│   ├── get-partitions.ts          # Partition list with from/to filter and summary mode
│   ├── get-storage-summary.ts     # Top-N tables by disk (parquet vs native)
│   ├── get-sample-data.ts         # Sample rows with optional columns/where projection
│   ├── get-table-stats.ts         # Per-column null % + distinct (single batched SQL)
│   ├── get-wal-status.ts          # WAL apply state, lag, suspended tables
│   ├── get-ingestion-health.ts    # Composite WAL lag + latest-row staleness diagnostic
│   ├── get-running-queries.ts     # query_activity() wrapper
│   ├── get-mv-dependencies.ts     # Materialized view graph (forward + reverse)
│   └── server-info.ts             # Version, build, feature detection
├── descriptions/
│   ├── generator.ts               # Dynamic description builder
│   └── static.ts                  # Static description blocks
├── types/
│   └── index.ts                   # TypeScript interfaces
└── ...

tests/
├── sql-safety.test.ts             # Read-only boundary: verbs, literals, injection shapes
├── config.test.ts                 # Env parsing, limits, domain-context loading
└── identifiers.test.ts            # quoteIdent breakout attempts

skills/                            # Claude skills — copy into .claude/skills/
├── questllens-using/
├── questllens-explore-a-database/
├── questllens-health-check/
└── questllens-tune-a-query/

CI führt Typecheck, Tests und den Build auf Node 20 und 22 aus, baut dann das Image und prüft, ob die Read-only-Grenze weiterhin gegen einen Live-QuestDB-Container gilt (siehe .github/workflows/ci.yml).


Anwendungsfälle

Anwendungsfall

Wie QuestLLens hilft

KI-gestützte Zeitreihenanalyse

Lassen Sie Claude SAMPLE BY-, LATEST ON- und ASOF JOIN-Abfragen gegen Ihre Live-Daten schreiben — sicher im Read-only-Modus. Verwenden Sie zuerst suggest_sample_by, damit der Agent eine sinnvolle Bucket-Größe wählt.

Kapazitätsplanung

Kombinieren Sie get_storage_summary, get_partitions --summary und describe_table --with_symbol_stats, um heiße Partitionen, zu kleine Symbolkapazitäten und plattenlastige Tabellen in einem Durchgang zu identifizieren.

Onboarding in Zeitreihen

Weisen Sie eine KI mit Domänenkontext auf QuestDB hin und lassen Sie sie erklären: „Was bedeutet der designierte Zeitstempel für diese Tabelle?" oder „Warum ist diese Abfrage langsam?". server_info sagt dem Agenten, welche Funktionen verfügbar sind.

Abfrageoptimierung

Verwenden Sie explain_query plus describe_table --with_symbol_stats, um fehlende Indizes, Symbole mit geringer Kapazität und ineffiziente Zeitprädikate aufzudecken. Verwenden Sie get_running_queries, wenn „das System sich langsam anfühlt".

Ingestion-Debugging

get_ingestion_health ist ein Ein-Aufruf-Komposit aus WAL-Lag, angehaltenem Zustand und Aktualität der letzten Zeile. Kombinieren Sie es mit get_table_params (o3MaxLag, maxUncommittedRows), um stotternde Schreibvorgänge zu diagnostizieren.

Datenaufbewahrungsprüfung

Verwenden Sie get_partitions (mit from/to-Filtern) und describe_table --with_time_range, um zu bestätigen, dass Aufbewahrungsrichtlinien funktionieren und dass getrennte/Parquet-Partitionen dem erwarteten Zeitplan entsprechen.

Schema-Portabilität

get_create_table gibt round-trippable DDL zurück — nützlich zum Spiegeln von Schemas im Code, zum Vergleichen mit dem gewünschten Zustand oder zum Bootstrappen einer Schwesterumgebung.


Kompatibilität

QuestLLens funktioniert mit jedem MCP-kompatiblen Client:

  • Claude Desktop (mit oder ohne OAuth)

  • Claude Code (CLI)

  • Cursor / Windsurf / VS Code (über MCP-Erweiterungen)

  • Benutzerdefinierte MCP-Clients (jeder Client, der die MCP-Spezifikation implementiert)

Und mit jeder QuestDB-Bereitstellung:

  • QuestDB Open Source 7.4+

  • QuestDB Enterprise (empfohlen — ermöglicht benutzerbasiertes RBAC)

  • QuestDB Cloud

  • Selbstverwaltetes Docker, Kubernetes oder Bare-Metal

get_mv_dependencies und der materialisierte-View-Zweig von describe_table / get_create_table erfordern QuestDB 8.x. get_running_queries erfordert eine QuestDB-Version, die query_activity() bereitstellt. Führen Sie server_info aus, um zu sehen, was die verbundene Instanz unterstützt. Alle anderen Tools sind mit 7.4+ kompatibel.


Fehlerbehebung

„Verbindung abgelehnt" auf Port 8812

QuestLLens verbindet sich über das PostgreSQL-Wire-Protokoll auf Port 8812, nicht über die HTTP-API auf 9000. Stellen Sie sicher, dass der PG-Wire-Listener aktiviert ist (pg.enabled=true in server.conf) und erreichbar ist.

„Zugriff verweigert" bei Schema-Introspection

QuestLLens verwendet die Systemfunktionen von QuestDB (tables(), table_columns(), wal_tables(), table_partitions(), materialized_views()). Auf QuestDB OSS sind diese für jeden authentifizierten Benutzer verfügbar. Auf QuestDB Enterprise stellen Sie sicher, dass Ihrer Rolle die erforderlichen Leseberechtigungen erteilt wurden:

GRANT SELECT ON ALL TABLES TO questllens_readonly;

get_mv_dependencies gibt leer zurück

Materialisierte Views erfordern QuestDB 8.x. Wenn Sie auf 7.x sind, gibt dieses Tool ein leeres Ergebnis mit einem Hinweis zurück — aktualisieren Sie auf 8.0+, um MVs zu verwenden.

get_wal_status zeigt „WAL nicht aktiviert"

WAL-Tabellen wurden in QuestDB 7.4 zum Standard. Tabellen, die auf älteren Versionen erstellt wurden, können weiterhin Nicht-WAL sein; sie erscheinen in list_tables mit wal_enabled = false und werden nicht in get_wal_status aufgenommen.

Schemaänderungen werden nicht übernommen

QuestLLens cached Schema-Metadaten. Warten Sie entweder auf den nächsten Aktualisierungszyklus (Standard: 5 Minuten) oder rufen Sie refresh_schema auf, um den MCP-Cache sofort zu aktualisieren.

OAuth-Anmeldung schlägt fehl

Überprüfen Sie, ob MCP_AUTH_PASSWORD gesetzt ist und der Rate-Limiter nicht ausgelöst hat (standardmäßig 5 Versuche pro 15 Minuten). Überprüfen Sie die Server-Logs für Details.


Lizenz

Apache-2.0. Frei zu verwenden, zu modifizieren und mit Namensnennung zu verbreiten; enthält eine ausdrückliche Patentgewährung. Siehe LICENSE für die Bedingungen und NOTICE für Lizenzen von Drittanbietern, den QuestDB-Marken-Haftungsausschluss und die Abelara-Marken-Asset-Ausnahme — die Logos und Markenkunstwerke sind nicht durch Apache-2.0 abgedeckt.

Bereitgestellt „wie besehen" ohne jegliche Garantie — Nutzung auf eigenes Risiko.

Erstellt von Abelara

QuestLLens ist Teil des Abelara-Toolkits für industrielle KI und Edge-Computing, neben PgLLens für PostgreSQL.

Fehler melden · Funktion anfragen · Mehr erfahren

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides a read-only PostgreSQL SQL surface for LLM agents via MCP, with defense-in-depth security layers for safe database queries.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides read-only access to databases for MCP-compatible AI tools, allowing schema exploration and SELECT queries without exposing credentials or risking data changes.
    51
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides read-only access to PostgreSQL databases via MCP, enforcing least-privilege roles, row-level security, masked views, and SQL AST guardrails to prevent data leakage and unauthorized operations, enabling AI agents to safely query sensitive production data.
    MIT

View all related MCP servers

Related MCP Connectors

  • Read-only MCP access to sessions, funnels, campaigns, errors, live visitors, and anomalies.

  • Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.

  • Read-only Dant3 MCP for public rooms, agents, jobs and provisional machine onboarding.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DMDuFresne/questllens'

If you have feedback or need assistance with the MCP directory API, please join our Discord server