Blog

Erstellung eines ChatGPT-Konnektors mit Elasticsearch zur Abfrage von GitHub-Issues

Erfahren Sie, wie Sie einen benutzerdefinierten ChatGPT-Konnektor erstellen und einen Elasticsearch MCP-Server bereitstellen, der die hybride Suche verwendet, um interne GitHub Issues abzufragen.

Kürzlich hat OpenAI benutzerdefinierte Konnektoren für ChatGPT für Pro-, Business-, Enterprise- und Edu-Abos angekündigt. Sie ergänzen die vorkonfigurierten Konnektoren zum Abrufen von Daten auf Gmail, GitHub, Dropbox usw. Es ist möglich, mithilfe von MCP-Servern benutzerdefinierte Konnektoren zu erstellen.

Benutzerdefinierte Konnektoren ermöglichen es Ihnen, Ihre vorhandenen ChatGPT-Konnektoren mit zusätzlichen Datenquellen wie Elasticsearch zu kombinieren, um umfassende Antworten zu erhalten.

In diesem Artikel werden wir einen MCP-Server erstellen, der ChatGPT mit einem Elasticsearch-Index verbindet, der Informationen zu internen GitHub Issues und Pull Requests enthält. So können Abfragen in natürlicher Sprache mit Ihren Elasticsearch-Daten beantwortet werden.

Wir werden den MCP-Server mithilfe von FastMCP auf Google Colab mit ngrok bereitstellen, um eine öffentliche URL zu erhalten, mit der ChatGPT eine Verbindung herstellen kann. Dadurch entfällt die Notwendigkeit einer komplexen Infrastruktureinrichtung.

Einen umfassenden Überblick über MCP und sein Ökosystem finden Sie unter „Der aktuelle Stand von MCP“.

Voraussetzungen

Vor Beginn benötigen Sie Folgendes:

  • Elasticsearch-Cluster (8.X oder höher)

  • Elasticsearch-API-Schlüssel mit Lesezugriff auf Ihren Index

  • Google-Konto (für Google Colab)

  • Ngrok-Konto (das kostenlose Abo genügt)

  • ChatGPT-Konto mit Pro-, Enterprise-, Business- oder Edu-Plan

Die Anforderungen für ChatGPT MCP-Konnektoren verstehen

Für die ChatGPT MCP-Konnektoren müssen zwei Tools implementiert werden: search und fetch. Weitere Einzelheiten finden Sie in den OpenAI Dokumenten.

Suchtool

Gibt eine Liste relevanter Ergebnisse aus Ihrem Elasticsearch-Index auf der Grundlage einer Benutzerabfrage zurück.

Das erhält es:

  • Eine einzelne Zeichenfolge mit der Anfrage des Benutzers in natürlicher Sprache.

  • Beispiel: „Finde Probleme im Zusammenhang mit der Elasticsearch-Migration.“

Was es zurückgibt: 

  • Ein Objekt mit einem result-Schlüssel, der ein Array von result-Objekten enthält. Jedes Ergebnis umfasst:

    • id - Eindeutige Dokumentkennung

    • title - Titel eines Issues oder Pull Requests (PR)

    • url - Link zum Issue/PR

In unserer Implementierung:

return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}

Abrufwerkzeug

Ruft den vollständigen Inhalt eines bestimmten Dokuments ab.

Das erhält es:

  • Eine einzelne Zeichenfolge mit der Elasticsearch-Dokument-ID aus dem Suchergebnis

  • Beispiel: „Nenne mir die Details zum PR-578.“

Was es zurückgibt:

  • Ein vollständiges Dokumentobjekt mit:

    • id - Eindeutige Dokumentkennung

    • title - Titel eines Issues oder Pull Requests (PR)

    • text - Vollständige Beschreibung und Details des Problems/PR

    • url - Link zum Issue/PR

    • type - Dokumenttyp (Issue, Pull-Request)

    • status - Aktueller Status (offen, in Bearbeitung, abgeschlossen)

    • priority - Prioritätsstufe (niedrig, mittel, hoch, kritisch)

    • assignee - Person, der das Issue/der PR zugewiesen wurde

    • created_date - Erstellungsdatum

    • resolved_date - Wann das Issue gelöst wurde (falls zutreffend)

    • labels - Mit dem Dokument verbundene Tags

    • related_pr - Verwandte Pull-Request-ID

return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}

Hinweis: In diesem Beispiel wird eine flache Struktur verwendet, bei der sich alle Felder auf der Stammebene befinden. Die Anforderungen von OpenAI sind flexibel und unterstützen auch verschachtelte Metadatenobjekte.

Datensatz zu GitHub Issues und Pull Requests

Für dieses Tutorial verwenden wir einen internen GitHub Datensatz mit Issues und Pull Requests. Dies stellt ein Szenario dar, in dem Sie private, interne Daten über ChatGPT abfragen möchten.

Den Datensätze finden Sie hier. Wir werden außerdem den Index der Daten mit der Bulk-API aktualisieren.

Dieser Datensatz umfasst:

  • Issues mit Beschreibungen, Status, Priorität und Zuweisungen

  • Pull-Requests mit Codeänderungen, Bewertungen und Deployment-Informationen

  • Beziehungen zwischen Issues und PRs (z. B. PR-578 behebt ISSUE-1889)

  • Labels, Daten und andere Metadaten

Index-Mappings

Der Index verwendet die folgenden Mappings, um die hybride Suche mit ELSER zu unterstützen. Das text_semantic wird für die semantische Suche verwendet, während andere Felder die Schlüsselwortsuchen ermöglichen.

{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}

Den MCP-Server erstellen

Unser MCP-Server implementiert zwei Tools gemäß den OpenAI Spezifikationen und verwendet die hybride Suche, um den semantischen und den textbasierten Abgleich für bessere Ergebnisse zu kombinieren.

Suchtool

Nutzt hybride Suche mit RRF (Reciprocal Rank Fusion), die semantische Suche mit Textabgleich kombiniert:

@mcp.tool()
    async def search(query: str) -> Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")

Wichtige Punkte:

  • Hybride Suche mit RRF: Kombiniert semantische Suche (ELSER) und Text-Suche (BM25) für bessere Ergebnisse.

  • Multi-Match-Abfrage: Sucht über mehrere Felder hinweg mit Boosting (title^3, text^2, assignee^2). Das Caret-Symbol (^) multipliziert die Relevanzwerte und priorisiert dabei Treffer in Titeln gegenüber solchen im Inhalt.

  • Fuzzy-Matching: fuzziness: AUTO behebt Tippfehler und Rechtschreibfehler, indem es ungefähre Übereinstimmungen erlaubt.

  • RRF-Parameterabstimmung:

    • rank_window_size: 50 - Legt fest, wie viele Top-Ergebnisse von jedem Retriever (semantische und textbasierte) vor dem Zusammenführen berücksichtigt werden.

    • rank_constant: 60 - Dieser Wert bestimmt, wie viel Einfluss Dokumente in einzelnen Ergebnismengen auf das endgültige Ranking haben.

  • Gibt nur die erforderlichen Felder zurück: id, title, url gemäß OpenAI-Spezifikation, und vermeidet das unnötige Freilegen zusätzlicher Felder.

Abrufwerkzeug

Ruft Dokumentdetails anhand der Dokumenten-ID ab, sofern vorhanden:

@mcp.tool()
    async def fetch(id: str) -> Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")

Wichtige Punkte:

  • Suchen nach Dokumenten-ID-Feld: Verwendet eine Begriffsabfrage im benutzerdefinierten id-Feld.

  • Vollständige Rückgabe des Dokuments: Mit vollständigem text-Feld mit allen Inhalten

  • Flache Struktur: Alle Felder auf der Wurzelebene, entsprechend der Dokumentstruktur von Elasticsearch.

Bereitstellung auf Google Colab

Wir nutzen Google Colab, um unseren MCP-Server zu betreiben, und ngrok, um ihn öffentlich zugänglich zu machen, damit ChatGPT eine Verbindung herstellen kann.

Schritt 1: Öffnen Sie das Google Colab Notebook

Greifen Sie auf unser vorkonfiguriertes Notebook Elasticsearch MCP für ChatGPT zu.

Schritt 2: Konfigurieren Sie Ihre Anmeldeinformationen

Sie benötigen drei Informationen:

  • Elasticsearch URL: Ihre Elasticsearch-Cluster-URL.

  • Elasticsearch API-Schlüssel: API-Schlüssel mit Lesezugriff auf Ihren Index.

  • Ngrok-Auth-Token: Kostenloses Token von ngrok. Wir nutzen ngrok, um die MCP-URL im Internet sichtbar zu machen, damit ChatGPT sich damit verbinden kann.

So erhalten Sie Ihr ngrok-Token

  1. Registrieren Sie sich für ein kostenloses Konto bei ngrok

  2. Gehe zu Ihrem Dashboard

  3. Kopieren Sie Ihr Authentifizierungs-Token

Secrets zu Google Colab hinzufügen

Im Google Colab-Notizbuch:

  1. Klicken Sie auf das Schlüsselsymbol in der linken Seitenleiste, um Secrets zu öffnen.

  2. Fügen Sie diese drei Secrets hinzu:

ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token

3. Aktivieren Sie den Notebook-Zugriff für jedes Secret.

Hinzufügen von Geheimnissen zu Google Collab

Schritt 3: Führen Sie das Notebook aus

  1. Klicken Sie auf Runtime und dann auf alle ausführen, um alle Zellen auszuführen.

  2. Warten Sie, bis der Server startet (etwa 30 Sekunden)

  3. Suchen Sie nach der Ausgabe, die Ihre öffentliche ngrok-URL anzeigt.

4. Die Ausgabe sieht in etwa so aus:

Die Ausgabe der Ausführung eines Notebooks in Google Collab

Mit ChatGPT verbinden

Nun verbinden wir den MCP-Server mit Ihrem ChatGPT-Konto.

  1. Öffnen Sie ChatGPT und gehen Sie zu Einstellungen.

  2. Navigieren Sie zu Konnektoren. Wenn Sie ein Pro-Konto nutzen, müssen Sie den Entwicklermodus in den Konnektoren aktivieren.

Verbindung des MPC-Servers mit einem ChatGPT-Konto

Wenn Sie ChatGPT Enterprise oder Business verwenden, müssen Sie den Konnektor an Ihrem Workspace veröffentlichen.

3. Klicken Sie auf Erstellen.

Hinzufügen eines Konnektors zu ChatGPT

Hinweis: In Business-, Enterprise- und Edu-Workspaces können nur Workspace-Inhaber, Administratoren sowie Benutzer, bei denen die entsprechende Einstellung aktiviert ist (für Enterprise/Edu), benutzerdefinierte Konnektoren hinzufügen. Benutzer mit der regulären Mitgliederrolle sind nicht berechtigt, selbst benutzerdefinierte Konnektoren hinzuzufügen.

Sobald ein Konnektor von einem Benutzer mit der Inhaber- oder Admin-Rolle hinzugefügt und aktiviert wurde, kann er von allen Mitgliedern des Workspaces verwendet werden.

4. Geben Sie die erforderlichen Informationen ein sowie Ihre ngrok-URL, die mit /sse/ endet. Beachten Sie den „/“ nach „sse“. Ohne diesen funktioniert es nicht:

  • Name: Elasticsearch MCP

  • Beschreibung: Benutzerdefiniertes MCP zum Suchen und Abrufen von internen GitHub-Informationen.

Erstellen eines elastischen MCP-Steckers

5. Klicken Sie auf Erstellen, um das benutzerdefinierte MCP zu speichern.

Speichern des benutzerdefinierten MCP-Konnektors durch Klicken auf „Erstellen“.

Die Verbindung ist sofort hergestellt, wenn Ihr Server läuft. Keine zusätzliche Authentifizierung ist erforderlich, da der Elasticsearch-API-Schlüssel auf Ihrem Server konfiguriert ist.

Teste den MCP-Server

Bevor Sie Fragen stellen, müssen Sie auswählen, welchen Konnektor ChatGPT verwenden soll.

Auswahl des Konnektors, den ChatGPT verwenden soll

Aufforderung 1: Nach Problemen suchen

Anfrage: „Finde Probleme im Zusammenhang mit der Elasticsearch-Migration“ und bestätigen Sie die Aktionen des aufgerufenen Tools.

ChatGPT wird aufgefordert, „Probleme im Zusammenhang mit der Elasticsearch-Migration zu finden“, und die Aktionen des aufgerufenen Tools werden bestätigt.

ChatGPT wird das Tool search mit Ihrer Abfrage aufrufen. Sie können sehen, dass es nach verfügbaren Tools sucht, sich darauf vorbereitet, das Elasticsearch-Tool aufzurufen. Vor jeglichen Aktionen gegenüber dem Tool wird die Bestätigung des Benutzers eingeholt.

Anfrage zum Aufruf des Tools:

{
  "query": "Elasticsearch migration issues"
}

Reaktion des Tools:

{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}

ChatGPT verarbeitet die Ergebnisse und präsentiert sie in einem natürlichen, gesprächsorientierten Format.

Wie ChatGPT die Ergebnisse der Tool-Aufrufanfrage und der Tool-Aufrufantwort verarbeitet

Hinter den Kulissen

Prompt: „Finde Probleme im Zusammenhang mit der Elasticsearch-Migration.“

1. ChatGPT-Anrufe search(“Elasticsearch migration”)

2. Elasticsearch führt eine hybride Suche durch.

  • Die semantische Suche versteht Konzepte wie „Upgrade“ und „Versionskompatibilität“.

  • Die Textsuche findet exakte Treffer für „Elasticsearch“ und „Migration“.

  • RRF kombiniert und bewertet Ergebnisse beider Ansätze

3. Sie gibt die 10 am besten mit id, title, übereinstimmenden Ergebnisse zurück url

4. ChatGPT identifiziert „ISSUE-1712: Migration von Elasticsearch 7.x auf 8.x“ als relevantestes Ergebnis.

Prompt 2: Nenne mir die vollständigen Details

Anfrage: „Nenne mir die Details zu ISSUE-1889“

ChatGPT erkennt, dass Sie detaillierte Informationen zu einem bestimmten Problem wünschen, und ruft das entsprechende Tool auf, um sich vor der Durchführung von Aktionen mit dem Benutzer abzustimmen.

ChatGPT erkennt, dass Sie detaillierte Informationen zu einem bestimmten Problem wünschen, ruft das Tool fetch auf holt beim Benutzer die Bestätigung ein, bevor Maßnahmen gegen das Tool ergriffen werden.

Anfrage zum Aufruf des Tools:

{
  "id": "ISSUE-1889"
}

Reaktion des Tools:

{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}

ChatGPT fasst die Informationen zusammen und präsentiert sie übersichtlich.

Wie ChatGPT die Informationen synthetisiert und präsentiert
Wie ChatGPT die Informationen präsentiert

Hinter den Kulissen

Prompt: „Nenne mir die Details von ISSUE-1889“

  1. ChatGPT-Anrufe fetch(“ISSUE-1889”)

  2. Elasticsearch ruft das vollständige Dokument ab

  3. Gibt ein vollständiges Dokument mit allen Feldern auf der Stammebene zurück

  4. ChatGPT synthetisiert die Informationen und antwortet mit korrekten Zitaten.

Fazit

In diesem Artikel haben wir einen maßgeschneiderten MCP-Server erstellt, der ChatGPT mit Elasticsearch über spezielle Such- und Abruf--MCP-Tools verbindet und so natürliche Sprachanfragen zu privaten Daten ermöglicht.

Dieses MCP-Muster funktioniert für jeden Elasticsearch-Index, jede Dokumentation, jedes Produkt, jedes Log oder andere Daten, die Sie über natürliche Sprache abfragen möchten.

Zugehörige Inhalte

Agent Builder, jenseits der Chatbox: Einführung von Augmented Infrastructure

Alexander Wert

Verwaltung von agentischem Gedächtnis mit Elasticsearch

Someshwaran Mohankumar

Die Verwendung der Elasticsearch Inference API zusammen mit Hugging Face-Modellen

Jeffrey Rengifo

Entwicklung eines KI-Agenten für die Personalabteilung mit Elastic Agent Builder und GPT-OSS

Tomás Murúa

Erstellung eines Elasticsearch MCP-Servers mit TypeScript

Jeffrey Rengifo

Sind Sie bereit, hochmoderne Sucherlebnisse zu schaffen?

Eine ausreichend fortgeschrittene Suche kann nicht durch die Bemühungen einer einzelnen Person erreicht werden. Elasticsearch wird von Datenwissenschaftlern, ML-Ops-Experten, Ingenieuren und vielen anderen unterstützt, die genauso leidenschaftlich an der Suche interessiert sind wie Sie. Lasst uns in Kontakt treten und zusammenarbeiten, um das magische Sucherlebnis zu schaffen, das Ihnen die gewünschten Ergebnisse liefert.

Probieren Sie es selbst aus