Leporello
MCP remoto para agendas de eventos de Ópera e Música Clássica
Documentação
Leporello
Agenda remoto de eventos de ópera e música clássica via MCP.
Também disponível como aplicativo web em leporello.app.
Locais Suportados
Falta o seu local favorito? PRs são bem-vindos! Leia CONTRIBUTING.md para instruções passo a passo sobre como adicionar um novo scraper de local.
| Local | Cidade |
|---|---|
| Staatsoper Stuttgart | Stuttgart |
| Stuttgarter Philharmoniker | Stuttgart |
| Wiener Staatsoper | Viena |
| Metropolitan Opera | Nova York |
| Oper Frankfurt | Frankfurt |
| San Francisco Opera | São Francisco |
| Gran Teatre del Liceu | Barcelona |
| Semperoper | Dresden |
| Opéra National de Paris | Paris |
| Carnegie Hall | Nova York |
| Teatro Real | Madri |
| Staatsoper Unter den Linden | Berlim |
| Sydney Opera House | Sydney |
| Philharmonie de Paris | Paris |
| Bayerische Staatsoper | Munique |
| Berliner Philharmonie | Berlim |
| Gewandhaus | Leipzig |
| Tonhalle-Orchester Zürich | Zurique |
| Lyric Opera of Chicago | Chicago |
| Palau de la Música Catalana | Barcelona |
| Royal Opera House | Londres |
| Teatro alla Scala | Milão |
| Auditorio Nacional de Música | Madri |
| Salzburger Festspiele | Salzburgo |
| Elbphilharmonie | Hamburgo |
| Opernhaus Zürich | Zurique |
| Concertgebouw | Amsterdã |
| Kölner Philharmonie | Colônia |
| Musikverein | Viena |
| Wiener Konzerthaus | Viena |
| Barbican Hall | Londres |
| Hamburgische Staatsoper | Hamburgo |
Como funciona
Scrapers específicos de cada local buscam páginas de agenda (HTML ou JSON) de casas de ópera e salas de concerto, analisam-nas com Cheerio e armazenam os eventos em um banco de dados SQLite local. Um agendador node-cron re-executa a raspagem de todos os locais diariamente às 03:00 UTC, substituindo os eventos de cada local pelos dados mais recentes. O servidor expõe os dados por meio de um endpoint Model Context Protocol (MCP), para que qualquer assistente de IA compatível com MCP possa consultar as próximas apresentações. Um frontend estático em Astro lê o mesmo banco de dados no momento da compilação e serve uma listagem de eventos filtrável em leporello.app.
Ferramentas MCP
| Ferramenta | Descrição |
|---|---|
list_countries | Todos os países com contagens de cidades/locais |
list_cities | Todas as cidades com locais, opcionalmente filtradas por país |
list_venues | Todos os locais, opcionalmente filtrados por país ou cidade |
list_events | Próximos eventos filtrados por país, cidade ou local |
Privacidade
A instância hospedada em leporello.app registra o uso agregado do MCP (nome da ferramenta, argumentos, tempo de resposta, User-Agent e um hash salgado rotativo diário do IP do cliente) para que eu possa ver o que está funcionando e o que não está. Sem IPs brutos, sem dados pessoais, sem tokens de autenticação.
Executar localmente
npm install
npm test # run scraper tests
npm run scrape # one-off scrape all venues
#or
npm run scrape -- wiener-staatsoper # scrape a single venue
npm run dev # start server on http://localhost:3000
# or
npm run dev:fresh # scrape, then serve
Raspagem diária às 03:00 UTC. Execute npm run scrape para popular o banco de dados no primeiro uso.
Docker
Dois contêineres: web (servidor HTTP + MCP + frontend estático) e scraper (busca dados de locais no SQLite). Eles compartilham um volume de dados. A pilha web é docker-compose.yml; o scraper é um job de execução única em docker-compose.scrape.yml sob seu próprio projeto compose (leporello-scrape) para que seu contêiner encerrado nunca seja contabilizado na verificação de saúde da pilha web. Agende-o com um job cron no host.
# Start the web server
docker compose up -d
# Run all scrapers (one-off, container stops when done)
docker compose -f docker-compose.scrape.yml run --rm scraper
# Scrape a single venue
docker compose -f docker-compose.scrape.yml run --rm scraper node dist/scrape.js wiener-staatsoper
# Rebuild the Astro frontend (e.g. after a manual scrape)
docker compose exec web node -e "
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
await promisify(execFile)('npm', ['run', 'build', '--prefix', 'web'], { cwd: '/app', timeout: 60000 });
console.log('done');
"
# Tail logs
docker compose logs -f web
O contêiner web reconstrói o frontend Astro a cada inicialização. Para executar scrapers em um agendamento, adicione um job cron no host que faça a raspagem e depois reinicie o web:
0 3 * * * cd /home/phil/docker/lep && docker compose -f docker-compose.scrape.yml build && docker compose -f docker-compose.scrape.yml run --rm scraper && docker compose restart web