KItenerary MCP

Um wrapper leve para extrair deterministicamente informações de viagem de um documento.

Documentação

kitinerary-mcp

Um servidor MCP que encapsula o CLI kitinerary-extractor do KDE: forneça um e-mail de confirmação de viagem, um bilhete em PDF ou um arquivo .pkpass da Apple/Google Wallet, e ele retorna todo o JSON-LD estruturado de schema.org Reservation que o arquivo contém — de forma determinística, sem LLM no processo.

A maioria dos e-mails reais de confirmação de companhias aéreas/hotéis já incorpora esse JSON-LD (é a mesma marcação que o Gmail analisa para seus próprios cartões de viagem "inteligentes"). O kitinerary-extractor lê isso diretamente, junto com bilhetes em PDF estruturados e passes de Wallet que a extração por texto simples/regex não consegue acessar. Este servidor é um shim fino e sem estado em torno desse binário, para que qualquer cliente MCP possa chamá-lo.

O que isto é (e não é)

  • É: uma única ferramenta, extract_booking, que recebe um arquivo e retorna JSON-LD bruto (ou nada, se o extrator não encontrar nada). Sem chamadas de rede de saída, sem segredos, sem estado — cada chamada é um subprocesso isolado contra um arquivo temporário, limpo imediatamente depois.
  • Não é: um mapeador de tipos de reserva. Ele não traduz o JSON-LD para o esquema de reserva de nenhum aplicativo específico — isso é um limite deliberado, para que permaneça reutilizável em qualquer consumidor que o chame, em vez de acoplado ao modelo de dados de um único chamador.

Como é usado

Este servidor alimenta o estágio de extração determinística de um substituto auto-hospedado do TripIt: encaminhe um e-mail de confirmação de reserva, um workflow do n8n chama extract_booking nele, e um resultado correspondente é mapeado e gravado no Trek como uma viagem — sem LLM no caso comum, com fallback para LLM apenas para e-mails em que este servidor não encontra nada. Veja tripit_replacement.md no repositório desse pipeline para a arquitetura completa, a tabela de mapeamento JSON-LD-para-reserva que a saída deste servidor alimenta, e os problemas encontrados na integração (alguns campos de data/hora retornam como objetos {"@type":"QDateTime",...} em vez de strings simples, tratamento de múltiplos trechos/múltiplos passageiros, e mais).

Esse pipeline é um consumidor, não uma dependência que este servidor tem dele — nada neste repositório está acoplado a ele, e qualquer cliente MCP pode usar extract_booking da mesma forma. Se você construir algo em cima deste servidor, abrir um PR para listá-lo aqui é bem-vindo.

Interface da ferramenta

extract_booking(file_base64: str, filename: str, context_date: str | None = None) -> {
  items: object[],    # the raw JSON-LD array kitinerary-extractor emitted (possibly empty)
  warnings: string[], # e.g. "unsupported file type", "no reservation data found in file"
}
  • file_base64 — os bytes brutos do arquivo, codificados em base64 (os argumentos de ferramentas MCP são JSON, então não há transporte binário/multipart nesta camada).
  • filename — usado para inferir o formato a partir da extensão. Aceitos: .eml, .pdf, .pkpass, .html, .txt. Qualquer outra coisa é rejeitada com um aviso, não passada silenciosamente.
  • context_date — data/hora ISO opcional, encaminhada para kitinerary-extractor --context-date. Ajuda a resolver datas que não informam o ano ou são relativas a "hoje" (passe o cabeçalho Date: do próprio e-mail, se tiver).
  • Nenhuma correspondência não é um erro — você recebe items: [] com um aviso. Erros de ferramenta são reservados para falhas reais: o binário está ausente, o processo travou ou o arquivo excede o limite de tamanho.
  • Limites: 10 MB de tamanho de arquivo decodificado, timeout de extração de 60s. Ambos falham de forma limpa (um aviso ou um erro de ferramenta) em vez de travar ou derrubar o chamador.

Executando

docker run --rm -i ghcr.io/mrwulf/kitinerary-mcp:v0.1.0

Ele fala MCP via stdio. Conecte-o à configuração de servidor stdio de qualquer cliente MCP, por exemplo, o claude_desktop_config.json do Claude Desktop:

{
  "mcpServers": {
    "kitinerary": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "ghcr.io/mrwulf/kitinerary-mcp:v0.1.0"]
    }
  }
}

Ou aponte um MCPServer do ToolHive para a mesma imagem com transport: stdio — sem segredos, sem volumes necessários.

Sempre fixe uma tag exata; latest é publicado junto com cada release por conveniência, mas não é feito para ser usado em deploy.

Tags e strings de versão

Cada release publica três tags:

  • vX.Y.Z — a versão deste servidor, ex.: v0.1.0. Fixe esta no uso normal.
  • vX.Y.Z-kitineraryA.B.C — o mesmo build, com o release do kitinerary-extractor incluído dobrado na tag (ex.: v0.1.0-kitinerary24.12.3), para que você saiba qual release upstream do KDE um determinado build carrega sem puxá-lo ou ler o Dockerfile. Fixe esta em vez disso se sua própria compatibilidade depender de um comportamento específico do kitinerary.
  • latest — conveniência apenas, não para fixar.

Os mesmos dois números de versão também estão na própria imagem, para que você não precise confiar apenas na tag: como rótulos OCI (org.opencontainers.image.version, io.github.mrwulf.kitinerary-mcp.kitinerary-version) legíveis via docker inspect, e no campo version do próprio MCP do servidor em execução (0.1.0+kitinerary.24.12.3, forma de metadados de build semver) que qualquer cliente MCP pode ler após conectar.

Desenvolvimento

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt pytest

# Fast unit tests (mock the CLI subprocess, no image build needed)
.venv/bin/pytest tests/test_server.py -v

# Real end-to-end tests against the actual kitinerary-extractor binary
docker build -t kitinerary-mcp:test .
IMAGE_TAG=kitinerary-mcp:test .venv/bin/pytest tests/test_integration.py -v

Pegada de dependências

libkitinerary-bin puxa uma cadeia genuína de KDE Frameworks 6 + Qt 6 (sim, incluindo libqt6gui6/libqt6qml6, mesmo para este uso CLI headless) — não há pacote mais enxuto upstream, então espere uma imagem de várias centenas de MB. Este é um tradeoff de usar a implementação KDE de referência em vez de reimplementar a lógica de extração do zero, o que significaria re-derivar e manter parsers para cada dialeto JSON-LD de companhia aérea/hotel/ferrovia nós mesmos.

Licença

O código próprio deste repositório (server.py e arquivos de suporte) é licenciado sob MIT. A imagem construída inclui adicionalmente a biblioteca kitinerary do KDE, que é LGPL-2.0-or-later (confirmado a partir de /usr/share/doc/libkitinerary-bin/copyright no pacote Debian trixie, versão 24.12.3-1). Este servidor apenas invoca kitinerary-extractor como subprocesso — ele não faz link contra libkitinerary — então usar esta imagem não impõe obrigações LGPL ao seu próprio código de aplicação; as obrigações que se aplicam (disponibilidade de fonte para a própria biblioteca, etc.) já são satisfeitas pela distribuição de pacotes do próprio Debian.

Mantendo isto atualizado conforme o kitinerary cresce

Duas coisas com versões independentes precisam de rastreamento, e o Renovate as trata de forma diferente:

  1. A tag publicada da própria imagem — uma referência de contêiner normal; qualquer ferramenta de consumidor downstream (Renovate, Flux, etc.) a rastreia exatamente como qualquer outra imagem fixada.

  2. As versões dos pacotes apt libkitinerary-bin/libkitinerary-data fixadas no Dockerfile — o Renovate não tem datasource nativo para Debian-apt, então estas são fixadas exatamente (nunca um apt-get install simples sem versão) e anotadas para um regex customManagers contra os releases do GitHub do KDE/kitinerary como sinal de atualização:

    # renovate: depName=KDE/kitinerary datasource=github-releases
    ARG KITINERARY_VERSION=24.12.3
    

    A string de versão do Debian rastreia de perto os releases upstream do KDE Gear, mas acrescenta seu próprio sufixo de revisão (-1, -2, ...) que um bump do Renovate não pode verificar se ainda resolve no arquivo trixie no dia do build. Se não resolver, o build de CI simplesmente falha de forma ruidosa — um modo de falha aceito e visível, em vez de tentar automatizar totalmente em torno da cadência de pacotes do Debian.

  3. A versão do SDK Python mcp — fixada em requirements.txt; qualquer bot de dependências ciente de pip (Renovate, Dependabot) rastreia isso nativamente.

Teste de aceitação

As fixtures em tests/fixtures/ e tests/test_integration.py cobrem o teste de fumaça que este servidor deve passar antes de qualquer release: uma confirmação de hotel sintética com JSON-LD LodgingReservation incorporado extrai corretamente, um e-mail de marketing simples sem dados estruturados retorna um resultado vazio com um aviso (não um erro), e um arquivo superdimensionado é rejeitado de forma limpa.