Server data from the Official MCP Registry
Guest check-in and SES.HOSPEDAJES status for short-term rental hosts. No guest PII exposed.
About
Guest check-in and SES.HOSPEDAJES status for short-term rental hosts. No guest PII exposed.
Remote endpoints: streamable-http: https://mcp.partelisto.es/mcp
Security Report
Valid MCP server (1 strong, 0 medium validity signals). No known CVEs in dependencies. Imported from the Official MCP Registry.
6 tools verified · Open access · No issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
How to Connect
Remote Plugin
No local installation needed. Your AI client connects to the remote endpoint directly.
Add this to your MCP configuration to connect:
{
"mcpServers": {
"io-github-targetgrps-partelisto-mcp": {
"url": "https://mcp.partelisto.es/mcp"
}
}
}Documentation
View on GitHubFrom the project's GitHub README.
Partelisto MCP
A remote MCP server that lets an AI client (Claude, ChatGPT, etc.) answer questions about a signed-in host's Partelisto data — arrivals, check-in status, SES.HOSPEDAJES submission state, plan usage — 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.
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 |
send_guest_checkin_link | partelisto:write | sendGuestLink mutation — emails the guest, rotates their link |
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
- Scope, checked in this service (
PartelistoTools.RequireScope): the bearer token's JWTscopeclaim must containpartelisto:readfor the five read tools,partelisto:writeadditionally forsend_guest_checkin_link. The token is validated (signature, issuer, expiry) against Keycloak by the standardAddJwtBearerhandler inProgram.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." - 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
OwnerAccesspolicy decides what data that specific user may see. A validpartelisto:writescope 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:
fullScopeAllowedwasfalseon thepartelisto-mcpclient (Keycloak's default for a client created via the Admin API). With it off, the issued token'srealm_access.rolescontained onlyoffline_access— none of the user's actual roles — regardless of what the user actually had. Fixed by setting it totrue(alreadytrueonpartelisto-spa; brings this client in line with that).- Missing the
oidc-usermodel-realm-role-mapperprotocol mapper (name "realm roles", claim nameroles) thatpartelisto-spahas directly on the client.TargetGrps.BuildingBlocks.Bootstrapper's JWT setup setsRoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"and never mapsrealm_access.rolesinto that claim type itself (confirmed by decompiling the installed NuGet package — itsOnTokenValidatedhandler only logs claims) — soRequireRole(...)policies likeOwnerAccessfail for any client missing this exact mapper, no matter what roles the user has or whatrealm_accesscontains. Copied verbatim frompartelisto-spa's mapper config ontopartelisto-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 /appwas 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 withKeycloak:Authority is not configured. Fixed by publishing to/outinstead.request.Schemereadhttpbehind the TLS-terminating ingress, so/.well-known/oauth-protected-resourcereported"resource": "http://mcp.partelisto.es". Fixed withUseForwardedHeaders.
Also hit and fixed as part of this rollout, outside this repo: cert-manager 1.18.2 in this cluster
couldn't issue any new TLS certificate (an upstream bug with ingress-nginx's strict path validation —
cert-manager#7791). Patched by adding
--feature-gates=ACMEHTTP01IngressPathTypeExact=false to the cert-manager Deployment's args in the
cert-manager namespace — the documented workaround, reverting to pre-1.18 behavior. This was blocking
certificate issuance cluster-wide, not just for this service.
Verified live over real HTTPS: /healthz, /.well-known/oauth-protected-resource (correct https://
resource and both scopes listed), and the MCP initialize handshake.
Listed in the official MCP registry
io.github.TargetGrps/partelisto-mcp is live and active at registry.modelcontextprotocol.io — verify
with curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=partelisto-mcp". Published by
.github/workflows/publish-mcp-registry.yml on every push to main that touches server.json, via
GitHub Actions OIDC — no login, no stored token, nobody approves anything by hand. The namespace has to
match the org's exact casing (TargetGrps, not targetgrps) or the registry's OIDC check 403s.
What's NOT done yet (manual follow-ups)
- Not submitted to the ChatGPT App directory. Unlike the MCP registry, there's no OIDC/CI path for
this — OpenAI's submission flow is a human review process behind a developer-account login: verify the
production
/mcpURL, verify the domain, provide reviewer credentials for the OAuth flow, write test cases, submit for a 5-10 business day review (submission guidelines). That needs someone who owns (or will own) the org's OpenAI developer account — logging into or creating one isn't something to automate. - Not actually connected from ChatGPT — the redirect URI is registered but nobody has completed ChatGPT's connector-add flow against this server yet, unlike Claude.ai (see the Keycloak section above, verified end to end).
- Only Claude.ai's and ChatGPT's redirect URIs are registered on the
partelisto-mcpKeycloak client. Add more as other clients (Copilot, etc.) actually get connected.
Local development
dotnet build # builds src/*.sln
dotnet test # 8 unit tests, ResponseShaper + GatewayQueries PII guard
docker compose build targetgrps-partelisto-mcp
docker compose up -d
curl http://localhost:5207/healthz
curl http://localhost:5207/.well-known/oauth-protected-resource
docker compose down
appsettings.Development.json points Gateway:BaseUrl at http://localhost:5201 — the api-gateway
port from the workspace compose stack (see the partelisto local-dev-startup notes for how to bring that
up). The Docker Compose file instead uses http://host.docker.internal:5201, since this service's own
container isn't on that stack's docker network.
Manually exercising a tool without going through a real MCP client
tools/list needs no token. tools/call needs an access token minted for the partelisto-mcp client
with partelisto:read/partelisto:write in its scope — a token from partelisto-spa (e.g. copied from
the SPA's dev tools) will not work, RequireScope rejects it. The client has consentRequired: true,
so it won't hand out a token via password grant (no browser to show consent to) unless that's toggled
off in the Keycloak admin console first — fine for one-off local testing, but flip it back afterward.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
MarkItDown
Freeby Microsoft · Content & Media
Convert files (PDF, Word, Excel, images, audio) to Markdown for LLM consumption
