关于 Partelisto
Partelisto 是面向西班牙度假租赁运营者的远程 MCP 服务,提供预订、入住登记、SES.HOSPEDAJES 状态和用量信息。工具支持分权限的读写操作,且不会返回客人个人数据。
主要功能
- 01列出房源和预订
- 02查看入住表单及 SES.HOSPEDAJES 状态
- 03查看用量和待处理事项
- 04重新发送入住登记链接
- 05在写入权限下创建预订
- 06排除客人个人数据
适用场景
- 01查找入住表单未完成的到店客人
- 02检查 SES.HOSPEDAJES 提交失败的预订
- 03查看账户用量和套餐限额
- 04创建预订或重新发送入住登记链接
MCP 相关
通过 HTTP 和 Keycloak OAuth bearer token 将 MCP 客户端连接到 Partelisto。服务提供读取工具,以及需单独授权的预订和入住链接变更操作。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://mcp.partelisto.es/mcpclaude mcp add --transport http targetgrps-partelisto https://mcp.partelisto.es/mcp{"mcpServers":{"targetgrps-partelisto":{"type":"http","url":"https://mcp.partelisto.es/mcp"}}}在 Cursor 中打开{"servers":{"targetgrps-partelisto":{"type":"http","url":"https://mcp.partelisto.es/mcp"}}}在 VS Code 中打开{"mcpServers":{"targetgrps-partelisto":{"type":"http","url":"https://mcp.partelisto.es/mcp"}}}{"mcpServers":{"targetgrps-partelisto":{"serverUrl":"https://mcp.partelisto.es/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
8| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| checkIn | string | 是 | 入住日期,yyyy-MM-dd。 |
| checkOut | string | 是 | 退房日期,yyyy-MM-dd。 |
| guestName | string|null | 否 | 房客姓名,可选。 |
| guestEmail | string|null | 否 | 房客电子邮件,可选。 |
| guestPhone | string|null | 否 | 房客电话,可选。 |
| propertyId | string | 是 | 房产 ID,来自 list_properties。 |
| templateId | string|null | 否 | 入住模板 ID。如果该房产恰好有一个启用(未归档)的模板,则可省略,系统会自动选择;否则必填。 |
详细介绍
Partelisto MCP
面向西班牙度假租赁房源的 SES.HOSPEDAJES 和房客入住登记,并向 AI Agent 开放。
一个远程 MCP 服务,让 Claude、ChatGPT 或任何兼容 MCP 的 AI Agent 能够回答有关已登录房东的西班牙住宿房源的运营问题——哪些即将入住的房客尚未填写完整的房客表单、哪些预订的 SES.HOSPEDAJES(警方登记)提交失败,以及账户距离套餐限额还有多远;此外,在单独授予权限后,还可以重新发送房客的入住登记链接。它不添加任何自身的业务逻辑:每个工具都只是对 api-gateway 上现有某个查询或变更的轻量封装,并且访问控制与 Web 应用完全一致。
工具: list_properties · list_bookings · get_guest_form_status · list_ses_statuses ·
get_usage_summary · get_attention_required · send_guest_checkin_link · create_booking(最后两个工具需要额外的 partelisto:write 范围——每个工具封装的内容请参见下方的 工具 表格)。
示例提示词:“今天有哪些事项需要我关注?”·“今天入住的房客中,哪些还没有填写完整的房客表单?”·“显示 SES.HOSPEDAJES 提交失败的预订。”·“为房客 Ana García 创建一笔 9 月 12 日至 15 日入住 Casa Sol 的预订。”·“重新发送预订 X 的入住登记链接。”
任何工具都不会选择或返回房客个人身份信息(PII),包括电子邮箱、电话、护照/DNI、出生日期、国籍和证件内容——请参见下方的工具(v1)。
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
为何偏离常见的服务结构模板
其他每个 TargetGrps 服务都拥有数据(MongoDB、租户中间件、ApiServiceBootstrapper)。但这个服务不是——它没有 Domain 层,也没有数据库。它是 gateway 的客户端,而不是与 gateway 对等的服务。项目布局保留了 Application(DTO、固定的 GraphQL 文档,以及纯响应整形/脱敏逻辑)和 Infrastructure(gateway HTTP 客户端),原因与模板存在的原因相同,都是为了便于测试;但它跳过了 Mongo/多租户引导,因为没有任何内容需要引导。
工具(v1)
| 工具 | 范围 | 封装内容 |
|---|---|---|
list_properties | partelisto:read | properties |
list_bookings | partelisto:read | bookingsPage(仅支持 skip/take——尚不支持 filter,见下文) |
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,在客户端组合——不引入新查询。扫描最近的 50 笔预订;筛出即将入住或正在入住且房客表单未填写完整的预订,以及任何 SES 提交失败的预订。 |
send_guest_checkin_link | partelisto:write | sendGuestLink 变更——向房客发送电子邮件,并轮换其链接 |
create_booking | partelisto:write | createBooking 变更。如果房源恰好有一个启用中的模板,则通过 templates(propertyId) 自动解析 templateId;否则会要求调用方选择一个模板。不会发送入住登记链接——如需发送,请另外调用 send_guest_checkin_link。 |
这些工具都不会选择或返回房客的电子邮箱、电话、护照/DNI、出生日期、国籍或证件内容——请参见 GatewayQueries(所选字段)和 ResponseShaper(映射到 DTO 的字段)。作为第二道防线,如果查询扩展到选择疑似 PII 的字段,GatewayQueriesTests 会导致构建失败。
list_bookings 尚未开放 BookingsQuery.BookingFilter(propertyId/日期范围/状态),因为它的 GraphQL 输入类型名称由 HotChocolate 的 mutation-conventions 生成,不值得盲目猜测——待能够直接检查 gateway 架构后再添加。
授权 — 两个独立层
- 范围,由此服务检查(
PartelistoTools.RequireScope):承载令牌的 JWTscope声明必须包含partelisto:read,才能调用五个读取工具;调用send_guest_checkin_link时还必须包含partelisto:write。Program.cs中的标准AddJwtBearer处理程序会通过 Keycloak 验证令牌(签名、颁发者、过期时间)——此服务会真正验证 JWT,不会信任未经验证的声明。这样 OAuth 同意页面就能将“读取我的数据”与“代表我发送电子邮件”分开提供。 - 所有权/租户,由 api-gateway 在每次调用时强制执行,与 Web 应用的规则相同:承载令牌原样转发,网关的
OwnerAccess策略决定特定用户可以查看哪些数据。有效的partelisto:write范围本身并不授予对任何特定预订的访问权限——网关仍会检查调用者是否拥有该预订。
RFC 9728 受保护资源元数据发布在 /.well-known/oauth-protected-resource,其中 authorization_servers 指向 Keycloak 的 realm,并列出两个范围,因此符合规范的 MCP 客户端无需人工粘贴令牌,即可发现如何获取令牌。
Keycloak 设置(已完成)
partelisto 领域中有一个客户端 partelisto-mcp(uuid f5a1cb7f-d6f9-474c-818a-183584dbec30):
公共客户端,启用了 standardFlowEnabled(authorization_code + PKCE),consentRequired: true,
directAccessGrantsEnabled: true。分配了两个可选客户端范围,并会显示在授权页面上:partelisto:read 和
partelisto:write(两者的 display.on.consent.screen: true)。客户端上配置了两个访问令牌受众映射器——一个添加
partelisto-mcp(以便此服务接受该令牌),另一个添加 api-gateway(以便网关也能接受同一个未经修改转发的令牌;
第一个映射器单独使用时会替换受众,而不是扩展受众,因此网关跳转会在无提示的情况下失效——以后若在此处添加其他受众映射器,
请记住这一点)。
已注册的重定向 URI:https://claude.ai/api/mcp/auth_callback 和
https://chatgpt.com/connector_platform_oauth_redirect。随着各客户端实际接入,再添加更多 URI(例如
Claude Code 的本地回调等)——在该客户端的 OAuth 流程完成前,Keycloak 必须先配置好精确的 URI。
还需要修复另外两处问题,这些问题只有在使用真实的 Partelisto 账户,通过已登录的 Claude.ai 会话(浏览器, 而非 curl)测试后才发现:
partelisto-mcp客户端上的fullScopeAllowed为false(通过 Admin API 创建的客户端在 Keycloak 中默认如此)。关闭此项后,签发令牌的realm_access.roles只包含offline_access,不包含用户实际拥有的任何角色。无论用户实际拥有哪些角色,结果都一样。已通过将其设为true修复(partelisto-spa上已为true;现在此客户端也与之保持一致)。- 缺少
oidc-usermodel-realm-role-mapper协议映射器(名称为“realm roles”,声明名称为roles),而partelisto-spa直接在客户端上配置了该映射器。TargetGrps.BuildingBlocks.Bootstrapper的 JWT 设置将RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",但不会自行将realm_access.roles映射到该声明类型(通过反编译已安装的 NuGet 包确认——其OnTokenValidated处理程序只会记录声明)。 因此,对于缺少此确切映射器的任何客户端,OwnerAccess之类的RequireRole(...)策略都会失败,无论用户拥有哪些角色, 也无论realm_access中包含什么。已将partelisto-spa的映射器配置原样复制到partelisto-mcp。
已使用真实的已登录 Claude.ai 会话完成端到端验证,并非只用 curl 测试:添加自定义连接器,完成完整的浏览器 OAuth + 授权流程(两个范围分别显示并分别授予,确认双范围设计的呈现正确),发现 list_properties 因上述两个问题连续失败两次,在线修复了这两个问题,重新连接后,成功通过整条链路(网关 → backoffice → GraphQL → 此服务 → Claude)从 backoffice 获得真实答复。这是整个项目中验证最充分的路径——目前唯一尚未验证的是 ChatGPT 连接。
已部署
已在 https://mcp.partelisto.es 上线——直接应用了 k8s/deployment.yaml(使用 kubectl apply -f k8s/,未使用 Helm;原因见该文件顶部注释),镜像为 ghcr.io/targetgrps/partelisto-mcp,命名空间为
targetgrps-microservices。CI(.github/workflows/build-publish.yml)会在每次推送到
main 时构建、测试并推送。今后发布时,更新 k8s/deployment.yaml 中的 image: 标签并重新应用即可。
CI 完全独立运行——它不会像每个同类服务的 build-publish.yml 那样调用 targetgrps/reusable-workflows。该仓库是私有的,而本仓库有意设为公开(见下文“设为公开”);公开仓库根本无法调用私有仓库中的可复用工作流——GitHub 会在触发时拒绝(“workflow was not found”),无论该仓库的访问级别设置如何。可复用工作流的其他功能(npm/nuget 客户端发布、Mongo 镜像、Slack 通知)也不适用于此服务,因此编写一个小型内联工作流是正确的做法,而非权宜之计。
此外,登录 GHCR 需要使用 GH_TOKEN_TARGETGRPS(而不是 secrets.GITHUB_TOKEN)——初次上线时,软件包是用个人令牌首次推送的,因此此仓库自己的 Actions 身份从未列入其“Manage Actions access”列表(这是 GHCR 的一项设置,没有 REST API 可远程修改)。
通过实际部署才发现并修复了两个问题,本地运行 docker run/docker compose 并不能发现:
dotnet publish --no-build -o /app会将内容发布到源代码树已占用的同一目录中,导致 Content 项(appsettings.json)被静默丢弃。镜像中完全没有配置,因此启动时因Keycloak:Authority is not configured而崩溃。已改为发布到/out。- 在终止 TLS 的入口后面,
request.Scheme读取到的是http,因此/.well-known/oauth-protected-resource返回的"resource": "http://mcp.partelisto.es"。已通过UseForwardedHeaders修复。
本次上线期间还遇到并在本仓库之外修复了另一问题:此集群中的 cert-manager 1.18.2 无法签发任何新的 TLS 证书(这是 ingress-nginx 严格路径验证中的上游问题 —
cert-manager#7791)。修复方法是在 cert-manager 命名空间中,为 cert-manager Deployment 的参数添加
--feature-gates=ACMEHTTP01IngressPathTypeExact=false——这是文档中记载的解决方法,可恢复到 1.18 之前的行为。此问题阻止了整个集群的证书签发,并非仅影响此服务。
已通过真实 HTTPS 验证线上服务:/healthz、/.well-known/oauth-protected-resource(https:// 资源地址正确,且列出了两个范围),以及 MCP initialize 握手。
已列入 MCP 官方注册表
io.github.TargetGrps/partelisto-mcp 已在 registry.modelcontextprotocol.io 上线,状态为 active——可通过
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=partelisto-mcp" 验证。每次推送到 main 且修改了 server.json 时,.github/workflows/publish-mcp-registry.yml 都会通过
GitHub Actions OIDC 发布——无需登录、无需存储令牌,也无需任何人手动审批。命名空间必须与组织名称大小写完全一致(TargetGrps,而不是 targetgrps),否则注册表的 OIDC 检查会返回 403。
尚未完成的事项(需手动跟进)
- 尚未提交到 ChatGPT 应用目录。 与 MCP 注册表不同,这里没有 OIDC/CI 流程 — OpenAI 的提交流程需要通过开发者账户登录并由人工审核:验证生产环境的
/mcpURL、验证域名、提供用于 OAuth 流程的审核者凭据、编写测试用例,然后提交并等待 5-10 个工作日审核 (提交指南)。这需要由拥有(或将要拥有)组织 OpenAI 开发者账户的人处理 — 登录或创建账户不是可以自动化的操作。 - 尚未实际连接 ChatGPT — 虽然已注册重定向 URI,但还没有人通过 ChatGPT 的连接器添加流程连接到此服务;而 Claude.ai 已完成端到端验证(见上文 Keycloak 部分)。
partelisto-mcpKeycloak 客户端目前只注册了 Claude.ai 和 ChatGPT 的重定向 URI。随着其他客户端(Copilot 等)实际接入,请添加更多 URI。
本地开发
dotnet build # builds src/*.sln
dotnet test # 8 unit tests, ResponseShaper + GatewayQueries PII guard
dotnet run --project src/TargetGrps.Partelisto.Mcp.Api -- --stdio # MCP over stdin/stdout (Glama introspection)
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 将 Gateway:BaseUrl 指向 http://localhost:5201 — 这是工作区 compose 堆栈中的 api-gateway 端口(如何启动该堆栈,请参阅 partelisto 本地开发启动说明)。Docker Compose 文件则使用 http://host.docker.internal:5201,因为此服务自己的容器不在该堆栈的 Docker 网络中。
无需通过真实 MCP 客户端手动调用工具
tools/list 不需要令牌。tools/call 需要一个为 partelisto-mcp 客户端签发、且作用域中包含 partelisto:read/partelisto:write 的访问令牌 — 来自 partelisto-spa 的令牌(例如从 SPA 的开发工具中复制)无法使用,RequireScope 会拒绝它。此客户端设置了 consentRequired: true,因此不会通过密码授权模式发放令牌(因为没有浏览器显示同意页面),除非先在 Keycloak 管理控制台中关闭该设置 — 临时本地测试可以这样做,但之后请恢复。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。