cloudbeds-mcp-server
MCP server that exposes Cloudbeds occupancy, check-ins/check-outs, and reservations as natural language tools, with read-only access and secure credential storage.
README
cloudbeds-mcp-server
Servidor MCP que expõe ocupação, check-ins/check-outs e reservas do Cloudbeds como tools de linguagem natural, sem nenhum caminho de escrita na v1 — por decisão de design, não por limitação técnica.
A coisa em ação
Sem UI — é um servidor MCP (stdio). A imagem abaixo é a suíte de testes rodando de verdade; o restante das capturas reais (setup rejeitando uma key inválida contra a API ao vivo do Cloudbeds, tools nos dois estados do servidor) está na seção Prints.

Links
Repositório: https://github.com/ronaldoribeirosm/cloudbeds-mcp-server
Demo: sem demo pública — cada instalação precisa da API key e do
Property ID do Cloudbeds de um hotel real; não dá pra expor
isso num ambiente compartilhado sem vazar dado de hóspede
Docs/Swagger: não se aplica — não é uma API HTTP. A superfície de tools
(nomes, parâmetros, descrições) está documentada na tabela
abaixo e é a mesma coisa que o cliente MCP lista via
`list_tools()`
Vídeo: [ opcional — não gravado ]
O problema
Consultar ocupação, check-ins do dia ou o status de uma reserva no Cloudbeds hoje significa abrir o painel, navegar até a tela certa e ler manualmente — ou interromper quem está na recepção pra perguntar. Isso já tinha sido resolvido uma vez no automacao-recepcao, só que atrás de uma UI Streamlit: resolve pra quem abre aquela tela, não pra quem só quer perguntar "quantos check-ins tenho hoje?" de dentro do assistente de IA que já está usando.
A solução ingênua seria integrar a API do Cloudbeds direto em cada assistente de IA que alguém usa — um conector pro Cursor, outro pra qualquer coisa que apareça depois. Isso não escala: N ferramentas de IA × M integrações de dado vira N×M código de cola. MCP inverte essa conta — escreve-se um servidor, e qualquer cliente compatível com o protocolo consegue chamá-lo.
sequenceDiagram
participant Agente as Agente de IA
participant MCP as cloudbeds-mcp-server
participant CB as API Cloudbeds
Agente->>MCP: "quantos check-ins tenho hoje?"
MCP->>CB: GET getReservations (checkInFrom=checkInTo=hoje)
CB-->>MCP: lista de reservas
MCP-->>Agente: texto formatado, pronto pra responder
A solução (visão geral)
- Camada de tools (
src/cloudbeds_mcp/tools/) separa lógica pura (compute_*,format_*, sem I/O) da tool MCP em si (register_*_tools) — a mesma separação que oautomacao-recepcaojá fazia entre regra de negócio e interface Streamlit, só que aqui o "front-end" é o protocolo MCP. - Camada de auth (
src/cloudbeds_mcp/auth.py) valida a API key com uma chamada real ao Cloudbeds antes de gravar qualquer coisa, criptografa em disco, e nunca devolve a key pro modelo de IA depois de configurada. - Cliente HTTP (
src/cloudbeds_mcp/cloudbeds_client.py) reaproveita a lógica de retry/paginação e as descobertas de comportamento real da API já validadas no projeto irmão — sem herdar nenhum dos métodos de escrita. - Detalhes completos na seção Decisões técnicas abaixo.
Prints — o projeto rodando
Sem uma conta Cloudbeds real disponível, três coisas foram validadas de verdade, capturadas em screenshot (não é texto inventado — é a saída real dos comandos):
1. Suíte de testes e lint:

2. --setup com uma API key inválida — bateu na API real do Cloudbeds e voltou um 401 de verdade:

Isso confirma que o endpoint de validação (getHotels) existe e se comporta como assumido — o que faltava validar era só o caminho de sucesso (key válida), que segue sem teste ao vivo.
3. As tools nos dois estados do servidor (antes e depois do setup), capturado chamando list_tools() de verdade, mais a saída real de format_occupancy_snapshot()/format_movements() contra os dados de tests/test_occupancy.py:

Stack
| Camada | Tecnologia |
|---|---|
| Protocolo | MCP (mcp SDK oficial, MCPServer, transporte stdio) |
| HTTP | requests, com retry/backoff e paginação própria |
| Criptografia da credencial | cryptography (Fernet), chave e payload fora do repositório |
| Testes | pytest + responses (mock de HTTP) |
| Lint | ruff |
| CI | GitHub Actions |
Como rodar localmente
git clone https://github.com/ronaldoribeirosm/cloudbeds-mcp-server
cd cloudbeds-mcp-server
pip install -e ".[dev]"
cloudbeds-mcp-server --setup
O --setup pede a API key e o Property ID do Cloudbeds (Configurações > API no painel), valida com uma chamada real e salva criptografado em ~/.cloudbeds-mcp/credentials.enc. Depois disso, aponte um cliente MCP pro binário cloudbeds-mcp-server (exemplo de config em mcp_client_config.example.json).
Rodar os testes:
pytest -v
ruff check .
Decisões técnicas
Por que nenhuma tool de escrita existe na v1
O dilema: dava pra implementar putGuest/postReservationNote reaproveitando o cliente do automacao-recepcao — a API já suporta. A escolha foi não implementar nenhum método de escrita no cliente desta v1 — não é uma flag de segurança que dá pra contornar, é a própria classe CloudbedsClient deste projeto que não tem o método. Um agente de IA com acesso só-leitura pode, na pior das hipóteses, vazar um dado que já tinha permissão de ver; nunca alterar uma reserva ou um preço sozinho sem confirmação humana. Escrita fica pra v2, atrás de um design de confirmação explícita que ainda não existe.
Por que a API key nunca "aparece" de volta
O dilema: o jeito mais simples de guardar uma credencial de API é variável de ambiente ou arquivo de texto plano — mas isso é publicável, e qualquer pessoa que rode este servidor está potencialmente compartilhando a key com o histórico de conversa de um agente de IA. A escolha: configure_cloudbeds_api_key recebe a key só pra validar (uma chamada real de getHotels) e gravar; a função devolve só o nome do hotel conectado, nunca a key — nem em sucesso, nem no texto de erro de falha. O arquivo fica criptografado com Fernet fora da pasta do projeto (~/.cloudbeds-mcp/), com permissão restrita ao dono. Isso está coberto por teste: test_credentials_file_is_encrypted_on_disk falha se o valor da key aparecer em texto puro no arquivo salvo em disco.
Por que a busca por nome varre reservas em vez de filtrar no servidor
Descoberta herdada do automacao-recepcao (confirmada contra a API real do Cloudbeds em 2026-07-22, não retestada aqui por falta de credencial): getReservations ignora filtro por nome, e getGuestList só filtra de verdade no servidor por e-mail ou telefone exato — nunca por nome parcial. A alternativa seria buscar todas as páginas de reservas da propriedade inteira, o que não escala pra contas com anos de histórico. A solução adotada limita a varredura a uma janela de 12 meses passados + 3 meses futuros e filtra por substring em memória — resolve o caso de uso real ("achar o hóspede que acabou de ligar") sem paginar a conta inteira, ao custo de não achar hóspedes fora dessa janela (documentado como limitação conhecida, não escondido).
Por que get_occupancy_today detalha reserva por reserva
getReservations (lista) não traz quarto nem datas por quarto — só getReservation (individual) traz o array assigned, que é o que permite saber se um quarto específico está ocupado hoje. Isso também foi uma descoberta real do projeto irmão, não uma suposição de documentação. Por isso a tool primeiro filtra candidatos "baratos" pela lista (reservas não canceladas cujo check-out é depois de hoje) e só depois detalha cada uma — evita bater getReservation pra cada reserva histórica da propriedade.
Testes — o que é coberto e por quê
| Arquivo | Cobre | Por quê |
|---|---|---|
test_auth.py |
Setup, validação, criptografia, roundtrip e limpeza de credenciais | É a parte mais sensível do projeto — precisa provar que a key nunca fica em texto puro em disco e que falha de validação não grava nada |
test_cloudbeds_client.py |
Paginação, erro HTTP, erro de nível de API, achatamento de getRooms, extração de hóspede principal |
Cobre os comportamentos reais da API (paginação por total, não por count) com HTTP mockado via responses, sem depender de rede |
test_occupancy.py |
Cálculo de ocupação (incluindo o limite check-in ≤ dia < check-out), check-ins/check-outs do dia, formatação de texto | Lógica pura de negócio — testável sem tocar o Cloudbeds |
test_guests.py |
Busca por substring case-insensitive, ordenação por mais recente, truncamento de lista longa | Garante que a limitação conhecida (busca por nome em memória) pelo menos se comporta de forma previsível |
test_reservations.py |
Formatação de status de reserva com e sem quarto atribuído | Cobre o caminho feliz e o caso "reserva sem quarto ainda" |
Resultado real: 31/31 testes passando. ruff check . limpo.
O que eu faria diferente
- Só o caminho de erro foi testado contra a API real — não tenho uma key válida disponível agora. Rodar
--setupcom uma key inválida confirmou quegetHotelsé um endpoint real e rejeita corretamente (401), mas o formato exato da resposta de sucesso (opropertyNamequevalidate_and_saveespera encontrar emhotels[0]) segue assumido a partir do guia original, não confirmado ao vivo. Primeira coisa a fazer antes de publicar: rodar--setupcontra um hotel de teste de verdade e corrigir o que a API real devolver diferente do assumido. get_guest_by_nameescala mal em contas com muito histórico — varrer 15 meses de reservas por substring funciona pra um hotel pequeno/médio, mas numa conta com dezenas de milhares de reservas isso fica lento. Combinar comgetGuestListquando o usuário já tiver e-mail ou telefone (mais rápido, filtro real no servidor) resolveria a maioria dos casos.- Sem cache — cada chamada de tool bate a API do zero, mesmo pra dados que não mudam a cada segundo (lista de quartos, por exemplo). Um cache curto (TTL de alguns minutos) em
get_roomsreduziria bastante chamada repetida. - v2 do guia original não entrou nesta v1 —
get_occupancy_forecast,get_guest_stay_history,search_available_roomse o resourcedaily_reportficaram de fora por escopo, não por dificuldade técnica; a base de cliente/auth já suporta.
Licença
MIT.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。