Skip to main content
Glama
SarjuThakkar

Skylight MCP server

by SarjuThakkar

Skylight MCP server

Pebble Index 戒指可以通过语音向 Skylight 家庭日历添加活动。Pebble 的云端 agent 是 MCP 客户端;此服务器负责对 Skylight 的非官方 API 执行 HTTP 操作。Pebble 永远不会直接与 Skylight 通信,Skylight 也永远不会看到你的 Pebble 账户。

暴露的唯一工具是 create_event(只写——Pebble 不需要回读日历,去掉 list_events 也让工具暴露面没有歧义)。它可以自动标记默认家庭成员资料,并将 “me”/“myself” 理解为该成员,或按名字识别任何其他成员。

传输方式:Streamable HTTP。认证:Authorization 请求头中的静态 Bearer 令牌(Pebble 不支持自定义 MCP 服务器的 OAuth 登录流程,只支持固定请求头)。

可以复用到别人的 Pebble + Skylight 吗?

可以——代码中没有任何内容与特定个人绑定。每个账户专属值(Skylight 登录信息、frame id、时区、哪个家庭成员是默认成员、Bearer 令牌)都来自环境变量,家庭成员名称会实时针对 你的 Skylight 类别解析,而不是硬编码。任何拥有自己的 Skylight 账户、一个 Pebble Index,以及一个可以托管小型 Python HTTP 服务的地方的人,都可以运行自己的副本。参见下面的部署到 Railway——只需几条 CLI 命令,无需手动编辑代码。

Related MCP server: Google Calendar AutoAuth MCP Server

环境变量

变量

必填

描述

SKYLIGHT_EMAIL

你的 app.ourskylight.com 登录邮箱。

SKYLIGHT_PASSWORD

你的 app.ourskylight.com 登录密码。

SKYLIGHT_FRAME_ID

登录后 app.ourskylight.com/calendar/<id> 中的数字。

MCP_BEARER_TOKEN

Pebble 以 Authorization: Bearer <token> 形式发送的静态令牌。用 openssl rand -hex 32 生成。

SKYLIGHT_TIMEZONE

IANA 时区,无时区(naive)活动时间将按此时区解释。默认为 America/Chicago。这也是工具 docstring 告知 Pebble agent 的时区,因此会自动保持同步。

SKYLIGHT_DEFAULT_MEMBER

Skylight 家庭成员的个人资料名称(必须与日历上的类别标签匹配)。当省略 who 时使用,也是 “me”/“myself”/“i” 所解析到的成员。留空则不进行默认标记。

PORT

由 Railway/大多数主机自动设置。本地默认为 8000

本地设置

python3 -m venv .venv && source .venv/bin/activate   # needs Python 3.10+
pip install -r requirements.txt

cp .env.example .env   # then fill in real values
export $(grep -v '^#' .env | xargs)   # or use your own env loader
export MCP_BEARER_TOKEN=$(openssl rand -hex 32)

python skylight_mcp_server.py

服务器监听 http://0.0.0.0:8000(如果设置了 $PORT 则使用该端口),MCP 端点为 /mcp,健康检查位于 /healthz(无需认证)。

使用 MCP Inspector 测试

npx @modelcontextprotocol/inspector

在 Inspector 界面中:

  1. 传输方式:Streamable HTTP

  2. URL:http://localhost:8000/mcp

  3. 在 Authentication 下,添加请求头 Authorization: Bearer <your MCP_BEARER_TOKEN>

  4. 连接,然后使用测试标题调用 create_event,检查它是否落到了 Skylight 应用中的正确资料上。

部署到 Railway

选项 A:deploy.sh 辅助脚本

export SKYLIGHT_EMAIL=you@example.com
export SKYLIGHT_PASSWORD=...
export SKYLIGHT_FRAME_ID=1234567
export MCP_BEARER_TOKEN=$(openssl rand -hex 32)
# optional:
export SKYLIGHT_TIMEZONE=America/Chicago
export SKYLIGHT_DEFAULT_MEMBER=YourName

./deploy.sh

如果缺少 Railway CLI,它会安装 Railway CLI,提示你登录(浏览器 OAuth——这一步无法脚本化),在首次运行时创建项目,设置所有环境变量,部署并打印公共 URL。编辑 skylight_mcp_server.py 后,随时重新运行它即可推送新构建——项目链接存储在 ~/.railway/config.json 中,以本目录为键,而不是在仓库中,因此 git 中不会出现任何 Railway 专属内容。

选项 B:手动操作

railway login                                  # browser OAuth
railway init --name skylight-mcp               # first time only
railway variable set SKYLIGHT_EMAIL=you@example.com --service skylight-mcp --skip-deploys
railway variable set SKYLIGHT_PASSWORD=... --service skylight-mcp --skip-deploys
railway variable set SKYLIGHT_FRAME_ID=1234567 --service skylight-mcp --skip-deploys
railway variable set MCP_BEARER_TOKEN=$(openssl rand -hex 32) --service skylight-mcp
railway up -c -y --service skylight-mcp        # builds the Dockerfile, deploys
railway domain --service skylight-mcp          # public HTTPS URL, real cert

之后重新部署(无论是你在代码更改后,还是任何已经运行过一次设置的人)只需:

railway up -c -y --service skylight-mcp

重新部署的全部内容就是这些——除了本仓库已有的 Dockerfile 外,不需要任何配置文件,也没有 CI 流水线。railway logs --service skylight-mcp 可以实时跟踪日志,这很有用,因为每次调用 create_event 都会记录其参数(参见故障排除)。

将 Pebble 的 MCP 客户端配置指向 https://<your-railway-domain>/mcp,并使用设置时生成的 Bearer 令牌。

配置 Pebble 应用

在 Pebble 应用的 MCP 服务器设置中:

  • 名称:任意,但不能有空格或特殊字符——参见故障排除SkylightCalendarskylight-calendar 都可以。

  • URLhttps://<your-railway-domain>/mcp

  • 传输方式Streamable(下拉菜单中就是 “SSE/Streamable”——选择 Streamable,而不是 SSE)

  • AuthorizationBearer <your MCP_BEARER_TOKEN>——完整字符串,包括 Bearer 前缀

自定义 MCP 工具只在 Pebble 的双击录音模式下运行(单击使用 Pebble 自己的内置操作)。请确保此服务器分配给了你的双击操作所使用的那个 sandbox 组。

家庭成员标记的工作原理

家庭成员完全不需要在此服务器中配置——create_event 会对你的真实 Skylight 账户调用 GET /frames/{id}/categories(每个进程在内存中缓存),并将 who 参数与这些标签进行不区分大小写的匹配。“me”/“myself”/“i” 解析为 SKYLIGHT_DEFAULT_MEMBER。如果精确匹配失败,它会回退到对相同标签的模糊匹配(difflib),因为 Pebble 的语音转文字可能会弄错不太常见的名字(例如 “Metree” 或 “May Tree” 仍都能解析为 “Maitree”——已实测验证)。仍然没有匹配到任何内容的名称不会阻止活动创建——活动会在没有个人资料标签的情况下创建,确认信息也会说明这一点,因此听错是可见的,而不是悄悄出错。

可以尝试的示例短语

每个短语都测试工具的不同部分——说出一个短语后(双击戒指),在 Skylight 中检查日期/时间、全天 vs. 定时活动,以及哪些个人资料被标记:

  • “明天下午 2 点添加一个牙医预约。” 定时活动,默认为 SKYLIGHT_DEFAULT_MEMBER 的个人资料,无地点。

  • “把下周一设为休假。” 没有说出时间 → 会创建为全天活动(根据不带时间的日期自动检测——你不需要说“全天”也能生效)。

  • “添加一个 9 月 2 日到 9 月 3 日去芝加哥的行程。” 多天全天活动——包含这两天(首尾都算)。

  • “添加 Maitree 下周二上午 10 点的理发预约。” 显式 who——标记该成员的个人资料,而不是默认成员。

  • “为 Maitree 和我添加周五晚上 7 点的家庭电影之夜。” 多人标记——活动会同时标记到两者。

  • “添加一个下周三下午 3 点在 Dr. Smith 诊所的牙医预约。” 测试 location 字段是否能被正确捕获。

已验证与假设

Skylight API 是非官方的、通过逆向工程得到的。此服务器的认证流程和载荷结构已于 2026-08-26/27 在真实账户上进行了实测(确切登录步骤见 skylight_mcp_server.py 中的模块 docstring)。以下重要发现与从 2025 年 12 月 OpenAPI 抓取中得出的初始假设相矛盾:

  • 旧的 POST /api/sessions(邮箱/密码 → Basic 认证)登录方式已停用——现在返回 401 “This version of Skylight is no longer supported.” 实际流程是 4 步 OAuth2 授权码交换(此流程成功不需要 PKCE,不过也有 PKCE 变体存在于现实中)。

  • 每次 API 调用都必须携带 skylight-api-version: 2026-05-01

  • GET .../calendar_events 上的 date_max 是一个排他性上界。

  • 全天事件的 ends_at 也是排他性的——单天全天事件需要将 ends_at 设为次日午夜(或者等效地,等于 starts_at,同样可行),N 天跨度则需要将 ends_at 在最后一个包含日之后再多填一天。create_event 在内部处理此填充,因此它自己的 end 参数对调用者来说仍然是包含的。

  • 家庭成员通过创建载荷中的 category_ids(一个数组)进行标记;类别 id 来自 GET .../categories,并按进程缓存。

如果 Skylight 再次更改其 API,这些是最可能出问题的地方:_login()(OAuth 步骤)和 create_event 中的 calendar_events 载荷结构。

故障排除

Pebble 提示 “invalid tool call, action failed”,且日历上没有新增活动。 先查看 railway logs --service skylight-mcp——每次 create_event 调用都会记录其原始参数,Skylight API 失败也会被捕获并记录。这个项目已经遇到过两种情况:

  • Pebble 应用配置中的 MCP 服务器名称包含空格或特殊字符。 已在 Pebble 论坛 上确认:服务器 Name 字段中的空格会导致 agent 构造错误的复合工具名称,调用会静默地永远无法到达服务器(你会在日志中看到 ListToolsRequest,但没有 CallToolRequest)。请将其重命名为仅包含字母数字和连字符。

  • 可选工具参数被声明为可空类型(str | None)。 一些严格的函数调用验证器会在发送请求之前就拒绝包含 anyOf: [string, null] 的 JSON schema。此服务器改用普通的 str = ""(空字符串 = “未提供”),正是为了避免这个问题。

日期/时间解析失败。 create_event 会捕获此错误并返回描述性错误字符串(在 Pebble 显示工具结果的任何位置可见),而不是崩溃,并明确指出无法解析的内容。

活动落在了错误的时间(例如凌晨 2 点)。 几乎可以肯定是 naive 时间与 UTC 混淆。create_event 对 naive 时间的处理始终假定为 SKYLIGHT_TIMEZONE,绝不假定 UTC——如果你遇到这种情况,请检查 Pebble 是否误发了未做时区转换的 UTC 时间(查看日志中记录的原始 start 参数)。

验证 Bearer 校验

# No token -> 401
curl -i https://<your-railway-domain>/healthz    # should be 200, no auth needed
curl -i https://<your-railway-domain>/mcp         # should be 401

# With token -> reaches the MCP layer
curl -i https://<your-railway-domain>/mcp \
  -H "Authorization: Bearer <your MCP_BEARER_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Transforms macOS calendar management into a conversational experience using natural language, allowing users to create, manage, and update calendar events seamlessly through an MCP-compatible client.
    327
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to manage Google Calendar through natural language interactions with features like creating, updating, and deleting events, searching calendars, and supporting natural language date/time inputs.
    27
    2
    MIT
  • F
    license
    Not graded
    quality
    F
    maintenance
    Enables programmatic management of Google Calendar events through natural language interactions, supporting creation, reading, updating, and deletion of events with features for recurring events, attendees, and reminders.
    2

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SarjuThakkar/skylight-mcp-pebble'

If you have feedback or need assistance with the MCP directory API, please join our Discord server