Blog

Criar um conector do ChatGPT com o Elasticsearch para consultar problemas no GitHub

Saiba como criar um conector ChatGPT personalizado e implantar um servidor Elasticsearch MCP que usa a pesquisa híbrida para buscar problemas internos do GitHub.

Recentemente, a OpenAI anunciou o recurso de conectores personalizados para o ChatGPT nos planos Pro/Business/Empresarial e Edu. Além dos conectores prontos para uso para acessar dados no Gmail, GitHub, Dropbox etc. É possível criar conectores personalizados usando servidores MCP.

Os conectores personalizados permitem que você combine seus conectores ChatGPT existentes com fontes adicionais de dados, como o Elasticsearch, para obter respostas abrangentes.

Neste artigo, criaremos um servidor MCP que conecta o ChatGPT a um índice Elasticsearch contendo informações sobre problemas internos e solicitações de pull do GitHub. Isso permite que consultas em linguagem natural sejam respondidas usando os dados do seu Elasticsearch.

Implantaremos o servidor MCP usando o FastMCP no Google Colab com ngrok para obter um URL público ao qual o ChatGPT possa se conectar, eliminando a necessidade de uma configuração de infraestrutura complexa.

Para uma visão geral do MCP e seu ecossistema, consulte O Estado Atual do MCP.

Pré-requisitos

Antes de começar, você precisará de:

  • Cluster do Elasticsearch (8.X ou superior)

  • Chave de API do Elasticsearch com acesso de leitura ao seu índice

  • Conta do Google (para o Google Colab)

  • Conta Ngrok (versão gratuita funciona)

  • Conta do ChatGPT com plano Pro/Empresarial/Business ou Edu

Entendendo os requisitos do conector MCP do ChatGPT

Os conectores MCP do ChatGPT exigem a implementação de duas ferramentas: search e fetch. Para mais detalhes, consulte OpenAI Docs.

Ferramenta de busca

Retorna uma lista de resultados relevantes do seu índice Elasticsearch com base em uma consulta do usuário.

O que ele recebe:

  • Uma única string com a consulta de linguagem natural do usuário.

  • Exemplo: "Encontre problemas relacionados à migração do Elasticsearch."

O que ele retorna: 

  • Um objeto com uma chave result contendo um array de objetos de resultado. Cada resultado inclui:

    • id - Identificador único do documento

    • title - Título da issue ou do PR

    • url - Link para o problema/PR

Na nossa implementação:

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

Ferramenta de recuperação

Recupera o conteúdo completo de um documento específico.

O que ele recebe:

  • Uma única string com o ID do documento Elasticsearch do resultado de busca

  • Exemplo: "Me dê os detalhes do PR-578."

O que ele retorna:

  • Um objeto de documento completo com:

    • id - Identificador único do documento

    • title - Título da issue ou do PR

    • text - Complete a descrição e os detalhes do problema/PR

    • url - Link para o problema/PR

    • type - Tipo de documento (issue, pull_request)

    • status - Status atual (aberto, em_andamento, resolvido)

    • priority - Nível de prioridade (baixo, médio, alto, crítico)

    • assignee - Pessoa designada para o problema/PR

    • created_date - Quando foi criado

    • resolved_date - Quando foi resolvido (se aplicável)

    • labels - Tags associadas ao documento

    • related_pr - ID de pull request relacionado

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
}

Observação: este exemplo usa uma estrutura plana onde todos os campos estão no nível raiz. Os requisitos do OpenAI são flexíveis e também permitem objetos de metadados aninhados.

Questões do GitHub e conjunto de dados PRs

Para este tutorial, vamos usar um conjunto de dados interno do GitHub contendo problemas e solicitações de pull. Isso representa um cenário em que você deseja consultar dados privados e internos por meio do ChatGPT.

O conjunto de dados pode ser encontrado aqui. E atualizaremos o índice dos dados usando a bulk API.

Esse conjunto de dados inclui:

  • Problemas com descrições, status, prioridade e responsáveis

  • Solicitações de pull com alterações de código, revisões e informações de implantação

  • Relações entre problemas e PRs (por exemplo, PR-578 corrige o ISSUE-1889)

  • Rótulos, datas e outros metadados

Mapeamentos de índice

O índice usa os seguintes mapeamentos para permitir a pesquisa híbrida com o ELSER. A text_semantic é usada para busca semântica, enquanto outros campos permitem a busca por palavras-chave.

{
  "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"
      }
    }
  }
}

Construa o servidor MCP

Nosso servidor MCP implementa duas ferramentas seguindo as especificações da OpenAI, usando busca híbrida para combinar correspondência semântica e de texto para obter melhores resultados.

Ferramenta de busca

Utiliza busca híbrida com RRF (Reciprocal Rank Fusion), combinando buscar semântica com correspondência de texto:

@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)}")

Pontos principais:

  • Busca híbrida com RRF: combina busca semântica (ELSER) e busca por texto (BM25) para melhores resultados.

  • Consulta multi-correspondência: busca em múltiplos campos com aumento de relevância (title^3, text^2, assignee^2). O símbolo de caret (^) multiplica as pontuações de relevância, priorizando as correspondências nos títulos em detrimento do conteúdo.

  • Correspondência inexata: fuzziness: AUTO lida com erros de digitação e ortografia, permitindo correspondências aproximadas.

  • Ajuste dos parâmetros do RRF:

    • rank_window_size: 50 - Especifica quantos resultados principais de cada recuperador (semântico e textual) são considerados antes da mesclagem.

    • rank_constant: 60 - Esse valor determina quanta influência os documentos em conjuntos de resultados individuais têm sobre o resultado final classificado.

  • Retorna somente os campos obrigatórios: id, title, url de acordo com a especificação da OpenAI e evita a exposição desnecessária de campos adicionais.

Ferramenta de recuperação

Recupera detalhes do documento pelo ID do documento, quando existe:

@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)}")

Pontos principais:

  • Buscar por campo de ID do documento: Utiliza consulta de termo no campo personalizado id

  • Retorna o documento completo: inclui o campo text completo com todo o conteúdo

  • Estrutura plana: Todos os campos no nível da raiz, correspondendo à estrutura de documentos do Elasticsearch.

Implantar no Google Colab

Usaremos o Google Colab para executar nosso servidor MCP e o ngrok para expô-lo publicamente, permitindo que o ChatGPT se conecte a ele.

Etapa 1: Abra o notebook do Google Colab

Acesse nosso notebook pré-configurado Elasticsearch MCP para ChatGPT.

Etapa 2: Configure suas credenciais

Você precisará de três informações:

  • URL do Elasticsearch: seu URL do cluster do Elasticsearch.

  • Chave da API do Elasticsearch: Chave da API com permissão de leitura do seu índice.

  • Token de autenticação Ngrok: token grátis do ngrok. Vamos usar o ngrok para expor a URL do MCP à internet para que o ChatGPT possa se conectar a ela.

Obter seu token ngrok

  1. Cadastre-se para uma conta gratuita em ngrok

  2. Acesse seu painel do ngrok

  3. Copie seu token de autenticação.

Adicionando segredos ao Google Colab

No notebook do Google Colab:

  1. Clique no ícone de chave na barra lateral esquerda para abrir Secrets.

  2. Adicione estes três segredos:

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

3. Habilitar o acesso ao notebook para cada segredo

Adicionar segredos ao Google Collab

Passo 3: Execute o notebook

  1. Clique em Runtime e depois em Executar tudo para executar todas as células

  2. Aguarde o servidor iniciar (cerca de 30 segundos)

  3. Procure a saída mostrando seu URL público do ngrok

4. A saída exibirá algo como:

A saída da execução de um notebook no Google Collab

Conectar-se ao ChatGPT

Agora vamos conectar o servidor MCP à sua conta do ChatGPT.

  1. Abra o ChatGPT e vá para Configurações.

  2. Navegue até Conectores. Se você estiver usando uma conta Pro, precisará ativar o modo de desenvolvedor nos conectores.

Conectando o servidor MPC a uma conta do ChatGPT

Se você está usando o ChatGPT em empresas ou negócios, precisa disponibilizar o conector para seu local de trabalho.

3. Clique em Criar.

Adicionando um conector ao ChatGPT

Observação: nos espaços de trabalho Business, Empresarial e Edu, somente os proprietários, administradores e usuários com a respectiva configuração ativada (para Empresarial/Edu) podem adicionar conectores personalizados. Usuários com a função de membro padrão não têm permissão para adicionar conectores personalizados.

Após um conector ser adicionado e habilitado por um proprietário ou usuário administrador, ele fica disponível para todos os membros do espaço de trabalho.

4. Insira as informações necessárias e sua URL ngrok que termina em /sse/. Repare no "/" após "sse". Não vai funcionar sem ele:

  • Nome: Elasticsearch MCP

  • Descrição: MCP personalizado para pesquisar e recuperar informações internas do GitHub.

Criar um Conector MCP Elastic

5. Pressione Criar para salvar o MCP personalizado.

Salvar o conector MCP personalizado clicando em criar

A conexão será instantânea se seu servidor estiver em execução. Não é necessária autenticação adicional, pois a chave da API do Elasticsearch está configurada no seu servidor.

Teste o servidor MCP

Antes de fazer perguntas, você precisa selecionar qual conector o ChatGPT deve usar.

Selecionar qual conector o ChatGPT deve usar

Prompt 1: Buscar por problemas

Pergunte: "Encontre problemas relacionados à migração do Elasticsearch" e confirme a chamada da ferramenta de ações.

Peça ao ChatGPT "Encontre problemas relacionados à migração do Elasticsearch" e confirme a chamada da ferramenta de ações.

O ChatGPT chamará a ferramenta search com sua consulta. Você pode ver que ele está procurando as ferramentas disponíveis, se preparando para chamar a ferramenta Elasticsearch e confirma com o usuário antes de tomar qualquer medida em relação à ferramenta.

Solicitação de chamada de ferramenta:

{
  "query": "Elasticsearch migration issues"
}

Resposta da ferramenta:

{
  "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
  ]
}

O ChatGPT processa os resultados e os apresenta em um formato natural e conversacional.

Como o ChatGPT processa os resultados da solicitação de chamada da ferramenta e da resposta à chamada da ferramenta

Nos bastidores

Prompt: "Encontrar problemas relacionados à migração do Elasticsearch"

1. Chamadas do ChatGPT search(“Elasticsearch migration”)

2. O Elasticsearch realiza uma busca híbrida

  • A busca semântica compreende conceitos como "atualização" e "compatibilidade de versões".

  • A busca de texto encontra correspondências exatas para "Elasticsearch" e "migração".

  • O RRF combina e classifica os resultados de ambas as abordagens

3. Retorna os 10 melhores eventos de correspondência com id, title, url

4. O ChatGPT identifica "ISSUE-1712: migrar do Elasticsearch 7.x para o 8.x" como o resultado mais relevante

Prompt 2: Obter todos os detalhes

Perguntar: "Informe detalhes sobre o ISSUE-1889"

O ChatGPT reconhece que você quer informações detalhadas sobre um problema específico, aciona a ferramenta fetch e confirma com o usuário antes de tomar qualquer medida em relação à ferramenta.

O ChatGPT reconhece que você quer informações detalhadas sobre um problema específico e aciona a ferramenta fetch, confirmando com o usuário antes de tomar qualquer medida em relação à ferramenta.

Solicitação de chamada de ferramenta:

{
  "id": "ISSUE-1889"
}

Resposta da ferramenta:

{
  "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"
}

O ChatGPT sintetiza as informações e as apresenta claramente.

Como o ChatGPT sintetiza as informações e as apresenta
Como o ChatGPT apresenta as informações

Nos bastidores

Prompt: “Informe mais detalhes sobre ISSUE-1889”

  1. Chamadas do ChatGPT fetch(“ISSUE-1889”)

  2. O Elasticsearch recupera o documento completo

  3. Retorna um documento completo com todos os campos no nível raiz

  4. O ChatGPT sintetiza as informações e responde com citações adequadas.

Conclusão

Neste artigo, criamos um servidor MCP personalizado que conecta o ChatGPT ao Elasticsearch usando ferramentas MCP dedicadas de busca e recuperação, permitindo consultas em linguagem natural sobre dados privados.

Este padrão MCP funciona para qualquer índice Elasticsearch, documentação, produtos, log ou quaisquer outros dados que você queira consultar por meio de linguagem natural.

Conteúdo relacionado

Construtor de agentes além da caixa de bate-papo: apresentando a infraestrutura ampliada

Alexander Wert

Gerenciando a memória agentiva com o Elasticsearch

Someshwaran Mohankumar

Usando a API de Inferência Elasticsearch junto com modelos de Hugging Face

Jeffrey Rengifo

Criando um agente de IA para RH com Elastic Agent Builder e GPT-OSS

Tomás Murúa

Criando um servidor MCP do Elasticsearch com TypeScript

Jeffrey Rengifo

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)