MCP server for Spanish vacation-rental guest check-in and SES.HOSPEDAJES. Connect Claude, ChatGPT and AI agents to bookings, guest-form status and SES submissions.
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
- !No standard license detected
git clone https://github.com/TargetGrps/partelisto-mcpResumen de MCP Servers
# Partelisto MCP
SES.HOSPEDAJES and guest check-in for Spanish vacation rentals, exposed to AI agents.
A remote [MCP](https://modelcontextprotocol.io) server that lets Claude, ChatGPT, or any MCP-compatible
AI agent answer operational questions about a signed-in host's Spanish accommodation — which arrivals
still have an incomplete guest form, which bookings failed SES.HOSPEDAJES (police registration)
submission, how close the account is to its plan limit — and, with a separately granted permission,
resend a guest's check-in link. It adds no business logic of its own: every tool is a thin wrapper over
one query or mutation that already exists on the api-gateway, gated exactly the way the web app is.
**Tools:** `list_properties` · `list_bookings` · `get_guest_form_status` · `list_ses_statuses` ·
`get_usage_summary` · `get_attention_required` · `send_guest_checkin_link` · `create_booking` (the last
two need the extra `partelisto:write` scope — see the [Tools](#tools-v1) table below for what each one
wraps).
**Example prompts:** "What needs my attention today?" · "Which of today's arrivals still have an
incomplete guest form?" · "Show me bookings where SES.HOSPEDAJES submission failed." · "Create a booking
for Casa Sol, 12–15 September, guest Ana García." · "Resend the check-in link for booking X."
No guest PII (email, phone, passport/DNI, date of birth, nationality, document content) is ever
selected or returned by any tool — see [Tools (v1)](#tools-v1) below.
```
Claude / ChatGPT / Copilot
│ MCP over HTTP, Bearer token from Keycloak OAuth
▼
partelisto-mcp (this service)
│ same GraphQL call the SPA would make, same Bearer token forwarded as-is
▼
api-gateway → backoffice / booking / guestdocs
```
## Why it deviates from the usual service-structure template
Every other TargetGrps service owns data (MongoDB, tenancy middleware, `ApiServiceBootstrapper`). This
one doesn't — it has no Domain layer and no database. It's a client of the gateway, not a peer of it.
The project layout keeps `Application` (DTOs, the fixed GraphQL documents, and the pure
response-shaping/redaction logic) and `Infrastructure` (the gateway HTTP client) for the same testability
reasons the template exists, but skips Mongo/multitenancy bootstrap because there's nothing to bootstrap.
## Tools (v1)
| Tool | Scope | Wraps |
|---|---|---|
| `list_properties` | `partelisto:read` | `properties` |
| `list_bookings` | `partelisto:read` | `bookingsPage` (skip/take only — no `filter` yet, see below) |
| `get_guest_form_status` | `partelisto:read` | `submissionStatus` |
| `list_ses_statuses` | `partelisto:read` | `sesSubmissionStatuses` |
| `get_usage_summary` | `partelisto:read` | `partelistoUsageInfo` |
| `get_attention_required` | `partelisto:read` | `bookingsPage` + `sesSubmissionStatuses`, composed client-side — no new query. Scans the 50 most recent bookings; imminent/current stays with an incomplete guest form, plus any failed SES submission. |
| `send_guest_checkin_link` | `partelisto:write` | `sendGuestLink` mutation — emails the guest, rotates their link |
| `create_booking` | `partelisto:write` | `createBooking` mutation. Auto-resolves `templateId` via `templates(propertyId)` when the property has exactly one active template; otherwise asks the caller to pick one. Does not send the check-in link — call `send_guest_checkin_link` separately for that. |
None of these ever select or return guest email, phone, passport/DNI, date of birth, nationality, or
document content — see `GatewayQueries` (what's selected) and `ResponseShaper` (what's mapped into the
DTO). `GatewayQueriesTests` fails the build if a query is ever widened to select a field that looks like
PII, as a second line of defense.
`list_bookings` doesn't yet expose `BookingsQuery.BookingFilter` (propertyId/date range/status) because
its GraphQL input type name is generated by HotChocolate's mutation-conventions and wasn't worth
guessing blind — add it once the gateway schema can be introspected against directly.
## Authorization — two independent layers
1. **Scope**, checked in this service (`PartelistoTools.RequireScope`): the bearer token's JWT `scope`
claim must contain `partelisto:read` for the five read tools, `partelisto:write` additionally for
`send_guest_checkin_link`. The token is validated (signature, issuer, expiry) against Keycloak by the
standard `AddJwtBearer` handler in `Program.cs` — this service does real JWT verification, it does not
trust an unverified claim. This is what lets an OAuth consent screen offer "read my data" separately
from "send email on my behalf."
2. **Ownership/tenant**, enforced by the api-gateway on every call, same as for the web app: the raw
bearer token is forwarded unchanged, and the gateway's `OwnerAccess` policy decides what data that
specific user may see. A valid `partelisto:write` scope does not by itself grant access to any
particular booking — the gateway still checks the caller owns it.
RFC 9728 protected-resource metadata is published at `/.well-known/oauth-protected-resource`, pointing
`authorization_servers` at Keycloak's realm and listing both scopes, so a spec-compliant MCP client can
discover how to obtain a token without a human pasting one in.
## Keycloak setup (done)
The `partelisto` realm has a client `partelisto-mcp` (uuid `f5a1cb7f-d6f9-474c-818a-183584dbec30`):
public client, `standardFlowEnabled` (authorization_code + PKCE), `consentRequired: true`,
`directAccessGrantsEnabled: true`. Two optional client scopes are assigned and shown on the consent
screen: `partelisto:read` and `partelisto:write` (both `display.on.consent.screen: true`). Two access
token audience mappers are attached to the client — one adding `partelisto-mcp` (so this service accepts
the token), one adding `api-gateway` (so the same token, forwarded unchanged, is also accepted by the
gateway; the first mapper alone replaces the audience rather than extending it, which silently broke the
gateway hop — worth remembering if another audience mapper gets added here later).
Registered redirect URIs: `https://claude.ai/api/mcp/auth_callback` and
`https://chatgpt.com/connector_platform_oauth_redirect`. Add more (Claude Code's local callback, etc.)
as each client actually gets connected — Keycloak needs the exact URI before that client's OAuth flow
will complete.
Two more fixes were needed, found only by testing against a real signed-in Claude.ai session (browser,
not curl) with a real (non-e2e) Partelisto account:
- **`fullScopeAllowed` was `false` on the `partelisto-mcp` client** (Keycloak's default for a
client created via the Admin API). With it off, the issued token's `realm_access.roles` contained only
`offline_access` — none of the user's actual roles — regardless of what the user actually had. Fixed
by setting it to `true` (already `true` on `partelisto-spa`; brings this client in line with that).
- **Missing the `oidc-usermodel-realm-role-mapper` protocol mapper** (name "realm roles", claim name
`roles`) that `partelisto-spa` has directly on the client. `TargetGrps.BuildingBlocks.Bootstrapper`'s
JWT setup sets `RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"` and
never maps `realm_access.roles` into that claim type itself (confirmed by decompiling the installed
NuGet package — its `OnTokenValidated` handler only logs claims) — so `RequireRole(...)` policies like
`OwnerAccess` fail for *any* client missing this exact mapper, no matter what roles the user has or
what `realm_access` contains. Copied verbatim from `partelisto-spa`'s mapper config onto `partelisto-mcp`.
**Verified end to end with a real signed-in Claude.ai session**, not just curl: added the custom
connector, completed the full browser OAuth + consent flow (both scopes shown and granted separately,
confirming the two-scope design renders correctly), had `list_properties` fail twice with the two bugs
above, fixed both live, reconnected, and got a real answer back from backoffice through the whole chain
(gateway → backoffice → GraphQL → this service → Claude). This is now the most-verified path in the
whole project — the only thing left unverified is a ChatGPT connection specifically.
## Deployed
Live at `https://mcp.partelisto.es` — `k8s/deployment.yaml` applied directly (`kubectl apply -f k8s/`,
not Helm; see that file's header comment for why), image `ghcr.io/targetgrps/partelisto-mcp`, namespace
`targetgrps-microservices`. CI (`.github/workflows/build-publish.yml`) builds, tests, and pushes on
every push to `main`. Bump the `image:` tag in `k8s/deployment.yaml` and re-apply for future releases.
CI is self-contained — it does **not** call `targetgrps/reusable-workflows` the way every sibling
service's `build-publish.yml` does. That repo is private, and this one is deliberately public (see
"Made public" below); a public repository cannot call a reusable workflow in a private one at all —
GitHub rejects it at dispatch time ("workflow was not found"), independent of that repo's access-level
setting. The reusable workflow's other features (npm/nuget client publish, a Mongo image, Slack notify)
don't apply to this service anyway, so a small inline workflow was the right call, not a workaround.
Also needed `GH_TOKEN_TARGETGRPS` (not `secrets.GITHUB_TOKEN`) to log in to GHCR — the package was first
pushed with a personal token during initial rollout, so this repo's own Actions identity was never on
its "Manage Actions access" list (a GHCR setting with no REST API to fix remotely).
Two bugs found and fixed only by actually deploying, not by local `docker run`/`docker compose`:
- `dotnet publish --no-build -o /app` was publishing into the same directory the source tree already
occupied, which silently drops Content items (`appsettings.json`). The image had no config at all and
crashed on startup with `Keycloak:Authority is not configured`. Fixed by publishing to `/out`Lo que la gente pregunta sobre partelisto-mcp
¿Qué es TargetGrps/partelisto-mcp?
+
TargetGrps/partelisto-mcp es mcp servers para el ecosistema de Claude AI. MCP server for Spanish vacation-rental guest check-in and SES.HOSPEDAJES. Connect Claude, ChatGPT and AI agents to bookings, guest-form status and SES submissions. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-02.
¿Cómo se instala partelisto-mcp?
+
Puedes instalar partelisto-mcp clonando el repositorio (https://github.com/TargetGrps/partelisto-mcp) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar TargetGrps/partelisto-mcp?
+
Nuestro agente de seguridad ha analizado TargetGrps/partelisto-mcp y le ha asignado un Trust Score de 70/100 (tier: OK). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene TargetGrps/partelisto-mcp?
+
TargetGrps/partelisto-mcp es mantenido por TargetGrps. La última actividad registrada en GitHub es del 2026-09-02, con 0 issues abiertos.
¿Hay alternativas a partelisto-mcp?
+
Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.
Despliega partelisto-mcp en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/targetgrps-partelisto-mcp)<a href="https://claudewave.com/repo/targetgrps-partelisto-mcp"><img src="https://claudewave.com/api/badge/targetgrps-partelisto-mcp" alt="Featured on ClaudeWave: TargetGrps/partelisto-mcp" width="320" height="64" /></a>Más MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
The fastest path to AI-powered full stack observability, even for lean teams.
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!