mcpField
P
Partelisto

Partelisto

远程服务
partelisto.es地图与出行06 周前更新

关于 Partelisto

Partelisto 是面向西班牙度假租赁运营者的远程 MCP 服务,提供预订、入住登记、SES.HOSPEDAJES 状态和用量信息。工具支持分权限的读写操作,且不会返回客人个人数据。

主要功能

  1. 01列出房源和预订
  2. 02查看入住表单及 SES.HOSPEDAJES 状态
  3. 03查看用量和待处理事项
  4. 04重新发送入住登记链接
  5. 05在写入权限下创建预订
  6. 06排除客人个人数据

适用场景

  1. 01查找入住表单未完成的到店客人
  2. 02检查 SES.HOSPEDAJES 提交失败的预订
  3. 03查看账户用量和套餐限额
  4. 04创建预订或重新发送入住登记链接

MCP 相关

通过 HTTP 和 Keycloak OAuth bearer token 将 MCP 客户端连接到 Partelisto。服务提供读取工具,以及需单独授权的预订和入住链接变更操作。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://mcp.partelisto.es/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

8
参数类型必填说明
checkInstring是入住日期,yyyy-MM-dd。
checkOutstring是退房日期,yyyy-MM-dd。
guestNamestring|null否房客姓名,可选。
guestEmailstring|null否房客电子邮件,可选。
guestPhonestring|null否房客电话,可选。
propertyIdstring是房产 ID,来自 list_properties。
templateIdstring|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_propertiespartelisto:readproperties
list_bookingspartelisto:readbookingsPage(仅支持 skip/take——尚不支持 filter,见下文)
get_guest_form_statuspartelisto:readsubmissionStatus
list_ses_statusespartelisto:readsesSubmissionStatuses
get_usage_summarypartelisto:readpartelistoUsageInfo
get_attention_requiredpartelisto:readbookingsPage + sesSubmissionStatuses,在客户端组合——不引入新查询。扫描最近的 50 笔预订;筛出即将入住或正在入住且房客表单未填写完整的预订,以及任何 SES 提交失败的预订。
send_guest_checkin_linkpartelisto:writesendGuestLink 变更——向房客发送电子邮件,并轮换其链接
create_bookingpartelisto:writecreateBooking 变更。如果房源恰好有一个启用中的模板,则通过 templates(propertyId) 自动解析 templateId;否则会要求调用方选择一个模板。不会发送入住登记链接——如需发送,请另外调用 send_guest_checkin_link。

这些工具都不会选择或返回房客的电子邮箱、电话、护照/DNI、出生日期、国籍或证件内容——请参见 GatewayQueries(所选字段)和 ResponseShaper(映射到 DTO 的字段)。作为第二道防线,如果查询扩展到选择疑似 PII 的字段,GatewayQueriesTests 会导致构建失败。

list_bookings 尚未开放 BookingsQuery.BookingFilter(propertyId/日期范围/状态),因为它的 GraphQL 输入类型名称由 HotChocolate 的 mutation-conventions 生成,不值得盲目猜测——待能够直接检查 gateway 架构后再添加。

授权 — 两个独立层

  1. 范围,由此服务检查(PartelistoTools.RequireScope):承载令牌的 JWT scope 声明必须包含 partelisto:read,才能调用五个读取工具;调用 send_guest_checkin_link 时还必须包含 partelisto:write。Program.cs 中的标准 AddJwtBearer 处理程序会通过 Keycloak 验证令牌(签名、颁发者、过期时间)——此服务会真正验证 JWT,不会信任未经验证的声明。这样 OAuth 同意页面就能将“读取我的数据”与“代表我发送电子邮件”分开提供。
  2. 所有权/租户,由 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 的提交流程需要通过开发者账户登录并由人工审核:验证生产环境的 /mcp URL、验证域名、提供用于 OAuth 流程的审核者凭据、编写测试用例,然后提交并等待 5-10 个工作日审核 (提交指南)。这需要由拥有(或将要拥有)组织 OpenAI 开发者账户的人处理 — 登录或创建账户不是可以自动化的操作。
  • 尚未实际连接 ChatGPT — 虽然已注册重定向 URI,但还没有人通过 ChatGPT 的连接器添加流程连接到此服务;而 Claude.ai 已完成端到端验证(见上文 Keycloak 部分)。
  • partelisto-mcp Keycloak 客户端目前只注册了 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 管理控制台中关闭该设置 — 临时本地测试可以这样做,但之后请恢复。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。