网球及羽毛球场地预订服务

一个用于通过Auth0认证和Azure托管API预订网球及羽毛球场地的MCP服务器。

  • by brainboost
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 brainboost/mcp_twojtenis_pl

使用说明

TwojTenis MCP服务器

MCP(模型上下文协议)服务器,用于通过以下方式预订羽毛球场和网球场 app.twojtenis.pl身份验证使用Auth0-OIDC;每个预订呼叫都会使用 Bearer <jwt> 头球

特征

  • Auth0登录 --授权码+PKCE通过无头Chromium;返回JWT访问+刷新令牌。
  • 俱乐部目录 --列出俱乐部,获取详细信息,查看预订设置(最多提前几天,取消窗口)。
  • 日程 --公开预订+不包括任何日期,无需授权。
  • 预订 --列出/创建/取消;批量在一次服务器端调用中创建多个法庭。
  • 无状态 --服务器端没有存储会话;MCP呼叫者提供 access_token 每次通话。
  • 键入 -新API的Pydanticv2模型。

安装

先决条件

设置

git clone <repository-url>
cd twojtenis_pl
uv sync
uv pip install -e ".[browser-auth]"
uv run playwright install chromium

这个 browser-auth 额外安装Playwright;需要,因为此租户上禁用了Auth0 ROPC,登录通过通用登录表单进行。

配置

所有配置都是通过环境变量进行的——0.2.0中没有配置文件。

# Optional — overrides for dev/staging
TWOJTENIS_MAIN_API_URL=https://app-twojtenis-api-p-weu.azurewebsites.net
TWOJTENIS_REQUEST_TIMEOUT=30

# Auth0 — see "Auth0 Client ID" note below before filling these in
AUTH0_DOMAIN=twojtenis.eu.auth0.com
AUTH0_CLIENT_ID=<your-auth0-client-id>
AUTH0_AUDIENCE=https://api.twojetenis.pl   # extra 'e' is intentional
AUTH0_REDIRECT_URI=https://app.twojtenis.pl
AUTH0_SCOPE=openid profile email offline_access
AUTH0_BROWSER_HEADLESS=true
AUTH0_BROWSER_TIMEOUT=60
AUTH0_BROWSER_EXECUTABLE_PATH=  # set on AWS Lambda

Auth0客户端ID

AUTH0_CLIENT_ID 是OAuth 2.0客户端注册ID twojtenis.pl 应用程序 twojtenis.eu.auth0.com这是一个公共标识符(PKCE流——不涉及客户端机密)。

要查找值,请执行以下操作:

  1. 打开 Auth0仪表板 为了 twojtenis.eu.auth0.com 租户。
  2. 首选 应用程序 → 找到twojtenis.pl应用程序→ 复制 客户端ID.

如果您是twojtenis.pl用户(不是租户管理员),请向项目维护人员询问客户端ID,或在访问时从浏览器的网络流量中提取它 app.twojtenis.pl (它出现在Auth0中 /authorize 将URL重定向为 client_id=...).

用法

运行服务器

uv run -m twojtenis_mcp.server

可用工具(v0.2.0)

身份验证:

工具 参数 返回值
login_oauth email, password {success, access_token, refresh_token, expires_at, token_type, scope, id_token}
refresh_oauth_token refresh_token 形状与 login_oauth

预订——每个工具都需要 access_token 作为第一个参数:

工具 参数 返回值
get_all_clubs access_token [{id, name, address, openHours, priceMin, priceMax, ...}]
get_club_locations access_token, club_id, sport="" [{id, name, sport, short_name, tags, sort_number, type, has_light, ...}] --俱乐部的球场。 sport 推导出: tennis, badminton, padel, squash, table_tennis, fitness, bowling, football, multi,或 null.通行证 sport="badminton" 等等进行过滤。
get_club_schedule access_token, club_id, date {success, data: {club_id, date, availability: [{location_id, location_name, sport, slots: [{start, end, available}]}]}} --俱乐部开放时间内的30分钟时段,标记为“无预订/排除重叠”。
get_reservations access_token, from_date="", to_date="" 预订列表(默认窗口:今天..+90d)
get_reservation_details access_token, booking_id {success, reservation} 或 {success: False, message}
put_reservation access_token, club_id, location_id, location_name, date, start_time, end_time {success, reservation}
put_bulk_reservation access_token, club_id, court_bookings {success, reservations: [...]}
delete_reservation access_token, booking_id {success, message}
delete_all_reservations access_token {success, deleted_count, deleted_booking_ids, errors}

日期格式: YYYY-MM-DD 或遗产 DD.MM.YYYY (两者均接受输入)。 时间格式: HH:MM 或 HH:MM:SS. ID(俱乐部、地点、预订、球员)是UUID。

批量预订

court_bookings 是一个字典列表:

[
  {"location_id": "3931aabd-...", "location_name": "Badminton 2",
   "date": "2026-05-11", "start_time": "16:00", "end_time": "17:00"},
  {"location_id": "3931aabd-...", "location_name": "Badminton 2",
   "date": "2026-05-11", "start_time": "17:00", "end_time": "18:00"}
]

服务器制作了一个 calculate-price 按项目调用,然后单个 POST /bookings 所有条目。

Auth0身份验证

login_oauth 通过无头Chromium驱动Auth0通用登录,并将生成的授权码交换为JWT令牌。通过刷新 refresh_oauth_token (纯HTTP,无浏览器)。

如果 playwright 未安装, login_oauth 回报 {success: false, code: "OAUTH_PLAYWRIGHT_REQUIRED"}.

对于AWS Lambda,设置 AUTH0_BROWSER_EXECUTABLE_PATH=/opt/chromium/chromium 并使用 Sparticz/铬 层。

OAuth错误代码: OAUTH_INVALID_CREDENTIALS, OAUTH_PLAYWRIGHT_REQUIRED, OAUTH_BROWSER_TIMEOUT, OAUTH_NETWORK_ERROR, OAUTH_UNEXPECTED.

MCP检验员测试

npx @modelcontextprotocol/inspector uv run -m twojtenis_mcp.server

打开控制台中打印的URL(例如。 http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...).

示例调用:

{
  "tool": "get_club_schedule",
  "arguments": {
    "club_id": "958662f0-0bd2-4fdc-8bef-bb2d69761adb",
    "date": "2026-05-11"
  }
}

VSCode中的调试

  1. 添加 .vscode/launch.json:
{
  "configurations": [{
    "name": "Attach to Running MCP Server",
    "type": "debugpy",
    "request": "attach",
    "connect": {"host": "localhost", "port": 5678},
    "pathMappings": [{"localRoot": "${workspaceFolder}", "remoteRoot": "."}]
  }]
}
  1. 与一起跑步 --debug 并将调试器连接到端口5678:
npx @modelcontextprotocol/inspector uv run python -m twojtenis_mcp.server --debug -Xfrozen_modules=off

从0.1.x迁移

重大变化:

  • session_id (php会话)→ access_token (Auth0 JWT)在每个工具上。
  • 俱乐部ID是UUID(例如。 958662f0-0bd2-4fdc-8bef-bb2d69761adb);传统字符串id和数字 num 都走了。
  • 法院处理 location_id (UUID)+ location_name 而不是数字 court_number.
  • sport_id 已删除(新的API不是sport-scoped-通过筛选客户端 location_id).
  • get_all_sports 远离的。
  • 已删除环境变量: TWOJTENIS_EMAIL, TWOJTENIS_PASSWORD, TWOJTENIS_BASE_URL, TWOJTENIS_RETRY_ATTEMPTS, TWOJTENIS_RETRY_DELAY, TWOJTENIS_CONFIG_PATH, TWOJTENIS_CLUBS_FILE.

发展

项目结构

src/twojtenis_mcp/
├── __init__.py
├── server.py           # FastMCP entrypoint + @mcp.tool() definitions
├── config.py           # Env-driven configuration
├── client.py           # ApiClient — async httpx, Bearer auth, JSON only
├── tech_group.py       # Per-club regional API URL resolver (cached)
├── locations.py        # Court UUID + name resolver
├── models.py           # Pydantic v2 models for the new API
├── utils.py            # Date conversion, auth0 sub URL encoding
├── jwt_utils.py        # JWT decode helpers (sub, expiry)
├── oauth_browser.py    # Playwright-driven Auth0 login flow
├── oauth_client.py     # PKCE + token exchange
└── endpoints/
    ├── clubs.py
    ├── reservations.py
    ├── schedules.py
    └── oauth.py

测试

uv run pytest tests/

Real-API集成测试(test_real_login_returns_jwt_with_correct_audience)自动跳过,除非 TWOJTENIS_EMAIL 和 TWOJTENIS_PASSWORD 设置。

棉绒

uvx ruff check src/

错误处理

所有预订工具包装 ApiErrorException 并返回 {success: false, code, message, details} 失败。

代码: AUTHENTICATION_REQUIRED, FORBIDDEN, HTTP_ERROR, REQUEST_FAILED, VALIDATION_ERROR, NO_TECH_GROUP, PRICE_CALCULATION_FAILED, BOOKING_FAILED.

许可证

麻省理工学院