TwojTenis MCP服务器
MCP(模型上下文协议)服务器,用于通过以下方式预订羽毛球场和网球场 app.twojtenis.pl身份验证使用Auth0-OIDC;每个预订呼叫都会使用 Bearer 头球
特性
- Auth0登录 --授权码+PKCE通过无头Chromium;返回JWT访问+刷新令牌。
- 俱乐部目录 --列出俱乐部,获取详细信息,查看预订设置(最多提前几天,取消窗口)。
- 日程 --公开预订+不包括任何日期,无需授权。
- 预订 --列出/创建/取消;批量在一次服务器端调用中创建多个法庭。
- 无状态 --服务器端没有存储会话;MCP呼叫者提供
access_token每次通话。 - 键入 -新API的Pydanticv2模型。
安装
先决条件
- Python 3.11+
- 紫外线
设置
git clone
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=
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 LambdaAuth0客户端ID
AUTH0_CLIENT_ID 是OAuth 2.0客户端注册ID twojtenis.pl 应用在 twojtenis.eu.auth0.com这是一个公共标识符(PKCE流——不涉及客户端机密)。
要查找值,请执行以下操作:
- 打开 Auth0仪表板 为了
twojtenis.eu.auth0.com租户。 - 首选 应用程序 → 找到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": {
"access_token": "",
"club_id": "958662f0-0bd2-4fdc-8bef-bb2d69761adb",
"date": "2026-05-11"
}
}VSCode中的调试
- 添加
.vscode/launch.json:
{
"configurations": [{
"name": "Attach to Running MCP Server",
"type": "debugpy",
"request": "attach",
"connect": {"host": "localhost", "port": 5678},
"pathMappings": [{"localRoot": "${workspaceFolder}", "remoteRoot": "."}]
}]
}- 与一起跑步
--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.
许可证
麻省理工学院
