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 parakitinerary-extractor --context-date. Ajuda a resolver datas que não informam o ano ou são relativas a "hoje" (passe o cabeçalhoDate: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 dokitinerary-extractorincluí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 dokitinerary.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:
-
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.
-
As versões dos pacotes apt
libkitinerary-bin/libkitinerary-datafixadas noDockerfile— o Renovate não tem datasource nativo para Debian-apt, então estas são fixadas exatamente (nunca umapt-get installsimples sem versão) e anotadas para um regexcustomManagerscontra os releases do GitHub doKDE/kitinerarycomo sinal de atualização:# renovate: depName=KDE/kitinerary datasource=github-releases ARG KITINERARY_VERSION=24.12.3A 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 arquivotrixieno 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. -
A versão do SDK Python
mcp— fixada emrequirements.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.