Skip to content

feat: add mcp support - #310

Merged
EvanTechDev merged 36 commits into
mainfrom
dev
Aug 1, 2026
Merged

feat: add mcp support#310
EvanTechDev merged 36 commits into
mainfrom
dev

Conversation

@EvanTechDev

@EvanTechDev EvanTechDev commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Description

Please provide a clear and concise description of what this pull request does.


Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to break)
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • 🔒 Security enhancement
  • ⚡ Performance improvement

Areas Affected

Check all that apply:

  • Calendar Component
  • Event Logic / Data Handling
  • User Interface / Styling
  • API Endpoints
  • Authentication / Authorization
  • Static Export / Routing
  • Project Documentation
  • Build / Deployment Config
  • Other (please specify):

Testing Done

List all types of testing you've completed:

  • Unit tests added or updated
  • Integration tests added or updated
  • Manual testing performed
  • Cross-browser testing (for UI changes)

Security Considerations

For changes involving authentication, data handling, or permissions:

  • No sensitive data is exposed
  • Authentication and permission checks are in place

Checklist

  • My code follows the project’s style guidelines
  • I have performed a self-review of my code
  • I have commented my code, especially in hard-to-understand areas
  • I have updated the documentation as needed
  • My changes do not produce new warnings or errors
  • I have added tests that prove my fix/feature works
  • All existing and new tests pass locally
  • Any dependent changes have been merged and deployed

Additional Notes

Include any additional context, TODOs, deployment notes, or follow-up tasks.


Screenshots/Recordings

Include screenshots or recordings if this PR changes UI.


By submitting this pull request, I confirm that my contribution is made under the terms of the project's license and coding guidelines.

Summary by Sourcery

添加基于 MCP 的 AI 集成,用于访问日历数据,并引入 OAuth 流程、API 密钥、服务器工具与缓存优化,同时改进日历 UI 行为。

New Features:

  • 引入 MCP 服务器和工具,通过 Model Context Protocol 访问事件、类别、倒计时、设置和个人资料。
  • 添加 OAuth 授权、令牌和设备码端点,以及 well-known 元数据,以支持外部 MCP 客户端。
  • 提供面向用户的 MCP 设置界面,用于管理启用状态、速率限制、API 密钥、已授权应用和审计日志。
  • 暴露 MCP HTTP 端点和中间件路由,使 AI 代理能够安全访问日历数据。

Bug Fixes:

  • 确保倒计时目标日期使用仅包含日期的字符串格式,而不是完整 ISO 时间戳,以避免日期偏差或时区问题。

Enhancements:

  • 添加基于 Redis 的会话缓存和按月事件查询缓存,以降低数据库负载并提升性能。
  • 调整月视图和年视图的事件弹出层,改用共享的 Popover 组件,改进定位、滚动行为和页面滚动锁定。
  • 更新代理路由,对应用和 OAuth 授权路径强制执行注册/登录重定向。
  • 从右侧边栏移除迷你日历面板,以简化侧边栏体验。
Original summary in English

Summary by Sourcery

Add MCP-based AI integration for calendar data with OAuth flows, API keys, server tooling, and caching improvements while refining calendar UI behavior.

New Features:

  • Introduce MCP server and tools for events, categories, countdowns, settings, and profile access via the Model Context Protocol.
  • Add OAuth authorization, token, and device-code endpoints plus well-known metadata to support external MCP clients.
  • Provide user-facing MCP settings UI for managing enablement, rate limits, API keys, authorized apps, and audit logs.
  • Expose an MCP HTTP endpoint and middleware routing so AI agents can securely access calendar data.

Bug Fixes:

  • Ensure countdown target dates use a date-only string format instead of full ISO timestamps to avoid off-by-one or timezone issues.

Enhancements:

  • Add Redis-backed caching for sessions and per-month event queries to reduce database load and improve performance.
  • Refine month and year view event popovers to use the shared Popover component, improve positioning, scrolling, and body scroll locking.
  • Update proxy routing to enforce sign-up/sign-in redirects for app and OAuth authorize paths.
  • Remove the mini calendar sheet from the right sidebar for a simpler sidebar experience.

…ogging

- Add @modelcontextprotocol/sdk dependency
- Create MCP database tables (api_keys, tokens, device_codes, audit_logs, settings)
- Implement API Key and OAuth token verification
- Add scope-based authorization checks
- Set up audit logging for MCP operations
- Add MCP settings management
- Add .well-known/oauth-authorization-server metadata endpoint
- Implement device code request endpoint (/api/oauth/device)
- Implement token exchange/polling endpoint (/api/oauth/token)
- Create OAuth authorize UI page with user consent flow
- Add authorization confirmation API endpoint
- Create McpServer with 16 tools across 5 categories
- Implement event CRUD tools with search and pagination
- Add category, countdown, settings, and profile tools
- Create auth-aware MCP handler with rate limit check
- Wire /api/mcp endpoint to handle iJSON-RPC requests
- Integrate scope-based authorization in every tool
- API Key CRUD (list, create, revoke, update scopes)
- MCP settings toggle (enable/disable, rate limit)
- OAuth authorized apps list and revoke
- Audit log retrieval with pagination
- All routes use existing session-based auth
- Create MCPSettings component with tabbed interface
- Implement Overview tab with MCP toggle and endpoint info
- Add API Key management (create, list, revoke, edit scopes)
- Add OAuth authorized apps viewer with revoke capability
- Add audit log viewer with pagination
- Integrate MCP section into main settings page
- Implement in-memory rate limiter per user (configurable RPM)
- Integrate rate limit check into MCP request handler
- Add audit logging for both success and failure MCP requests
- Return 429 with retry_after when rate limit exceeded
- Remove unused getClientId function
- Clean up catch blocks without error variable usage
- Remove unused router import in OAuth authorize page
- Add Bot icon instead of unused user profile image
- Fix unnecessary spread fallback in settings-tools
- Transport cannot be reused in stateless JSON mode
- Create new transport + server per request
- Add allowedOrigins: ['*'] for cross-origin MCP clients
…ompatibility

- Add mcp_auth_requests table for PKCE auth request storage
- Rewrite /oauth/authorize to handle both device_code and auth_code flows
- Implement PKCE code challenge verification in token endpoint
- Update .well-known to advertise authorization_code grant
- Use Node.js crypto for PKCE S256 challenge generation
…dit and rate limiter

- Fix listCategories/listCountdowns/create/update returning encrypted fields
- Add pagination to listCountdowns matching spec (50/page)
- Remove duplicated HEX_TO_BG color maps, pass through hex colors directly
- Extract respond/respondError/respondMessage helpers, eliminate 16x try-catch boilerplate
- Remove dead hasScope/requireScope in types.ts
- Rename getFullUserInfo -> getUserNameAndEmail
- Fix API key prefix display to spec (zc_ + last 4 chars)
- Add per-tool action audit logging and OAuth discovery fields in 401
- Replace in-memory rate limiter Map with DB-backed query
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
one-calendar Ready Ready Preview, v0 Aug 1, 2026 10:57am

@sourcery-ai

sourcery-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Reviewer's Guide

为日历应用添加完整的 MCP(Model Context Protocol,模型上下文协议)支持,包括 OAuth/设备码流程、API Key、审计/速率限制基础设施、基于 Redis 的会话与事件缓存,以及用于管理 MCP 设置和凭据的 UI,同时重构部分日历视图/弹出层并收紧鉴权重定向。

新增 MCP 表的实体关系图

erDiagram
  user {
    text id PK
  }

  mcpApiKeys {
    text id PK
    text userId FK
    text name
    text keyHash
    text keyPrefix
    jsonb scopes
    boolean isActive
  }

  mcpTokens {
    text id PK
    text userId FK
    text tokenHash
    jsonb scopes
    text clientId
    text clientName
    timestamptz expiresAt
    boolean isRevoked
  }

  mcpDeviceCodes {
    text id PK
    text userId FK
    text deviceCode
    text userCode
    text clientId
    jsonb scopes
    text status
  }

  mcpAuditLogs {
    text id PK
    text userId FK
    text authType
    text keyId
    text action
    boolean success
    timestamptz createdAt
  }

  mcpSettings {
    text userId PK,FK
    boolean enabled
    int rateLimitRpm
  }

  mcpAuthRequests {
    text id PK
    text userId FK
    text clientId
    text redirectUri
    jsonb scopes
    text authorizationCode
    timestamptz codeExpiresAt
    text status
  }

  user ||--o{ mcpApiKeys : has
  user ||--o{ mcpTokens : has
  user ||--o{ mcpDeviceCodes : has
  user ||--o{ mcpAuditLogs : has
  user ||--|| mcpSettings : has
  user ||--o{ mcpAuthRequests : has
Loading

File-Level Changes

Change Details Files
将月份/年份视图和日历搜索中的自定义 popover 和 ScrollArea 实现替换为 @zntr/ui popover 和原生 overflow 滚动,并进行一些小的 UX 调整(锁定 body 滚动、锚点定位、点击行为)。
  • 年份视图中的日期弹出层现在使用 Popover/PopoverAnchor/PopoverContent,锚点为固定定位,在 Popover open-change 时关闭,通过 useEffect 锁定 body 滚动,并在触发 onEventClick 之前先关闭弹出层。
  • 月份视图中“剩余事件”弹出层同样迁移到 Popover 组件,锚定到单元格而不仅仅是按钮,锁定 body 滚动,且重新点击触发按钮时不再执行打开/关闭切换。
  • 主日历视图中的搜索结果列表用带 CSS overflow-y-auto 和 min-height/max-height 约束的 div 替换了 ScrollArea,以获得更简单的滚动行为。
apps/calendar/components/app/views/year-view.tsx
apps/calendar/components/app/views/month-view.tsx
apps/calendar/components/app/calendar.tsx
为 MCP 相关的 API Key、OAuth token/授权请求、设备码、审计日志和按用户配置的 MCP 设置引入数据库 schema 和关系。
  • 新增 mcp_api_keys、mcp_tokens、mcp_device_codes、mcp_audit_logs、mcp_settings 和 mcp_auth_requests 表,包含合适的列、索引以及指向 user 的外键。
  • 将 MCP 相关表接入 userRelations,并为每个 MCP 表定义专用关系辅助方法。
  • 添加初始 drizzle migration SQL,用于创建 MCP 表/索引以及外键。
apps/calendar/lib/drizzle/schema.ts
apps/calendar/drizzle/0001_create_mcp_tables.sql
apps/calendar/drizzle/meta/_journal.json
实现 MCP 服务器、工具处理器和基于 HTTP 传输的端点,并接入鉴权辅助函数、速率限制、审计日志和按用户配置的 MCP 设置。
  • 创建 createServer(),使用 @modelcontextprotocol/sdk 和 zod 注册 events、categories、countdowns、settings 和 profile 的 MCP 工具,配置作用域权限和受限枚举值。
  • 添加 event/category/countdown/settings/profile 工具模块,这些模块基于 drizzle 表进行操作,对敏感字段执行加解密,并在需要时返回分页数据。
  • 实现 handleMcpRequest:提取 MCP 鉴权信息(API key 或 OAuth),强制检查 enabled 标志和按用户的 RPM 速率限制,为每个请求记录审计日志,并通过 WebStandardStreamableHTTPServerTransport 同时处理 GET(工具清单/元数据)和 POST(工具调用)。
  • 新增 MCP 鉴权辅助函数(verifyApiKey/verifyOAuthToken、密钥生成、token 哈希、设备码/用户码生成、API key CRUD、isMcpEnabled、ALL_SCOPES),以及共享类型和 McpAuthError。
  • 引入 MCP 审计辅助函数、设置持久化辅助函数以及基于最近审计记录计数的速率限制器,用于执行按用户 RPM 控制。
  • 在 /api/mcp 暴露 MCP HTTP 端点,通过 GET/POST 转发到 handleMcpRequest。
apps/calendar/lib/mcp/server.ts
apps/calendar/lib/mcp/event-tools.ts
apps/calendar/lib/mcp/category-tools.ts
apps/calendar/lib/mcp/countdown-tools.ts
apps/calendar/lib/mcp/settings-tools.ts
apps/calendar/lib/mcp/profile-tools.ts
apps/calendar/lib/mcp/auth.ts
apps/calendar/lib/mcp/auth-helpers.ts
apps/calendar/lib/mcp/types.ts
apps/calendar/lib/mcp/audit.ts
apps/calendar/lib/mcp/settings.ts
apps/calendar/lib/mcp/rate-limiter.ts
apps/calendar/app/api/mcp/route.ts
为 MCP 客户端添加 OAuth 2.0 / 设备码流程相关的端点和授权 UI,并提供 well-known 元数据发现。
  • 实现 /oauth/authorize 页面,使用 Suspense 包裹的 AuthorizeForm:读取查询参数、展示当前用户、渲染 scopes/客户端信息,并向 /api/oauth/authorize 提交(支持设备码和授权码两种流程),处理成功/错误状态,并根据 redirect_uri 携带 code+state 重定向回客户端。
  • 添加 /api/oauth/authorize 的 POST 处理器:对设备码流程,通过更新 mcpDeviceCodes 的 status/userId 来完成授权;对授权码 PKCE 流程,校验 client_id/redirect_uri,生成 authorization_code 并插入 mcpAuthRequests 行。
  • 实现 /api/oauth/token POST 处理器:支持 device_code 授权(哈希并校验设备码、状态和过期时间)和 authorization_code 授权(校验授权请求、PKCE verifier/challenge、标记授权码已使用),然后在 mcpTokens 中发放访问/刷新 token,并返回包含用户信息的 OAuth token 响应。
  • 添加 /api/oauth/device POST 端点,用于签发新的 device_code/user_code 对,将哈希后的设备码连同 scopes 和过期时间存入 mcpDeviceCodes,并返回标准的设备授权响应载荷。
  • 在 /.well-known/oauth-authorization-server 暴露路由,返回 issuer、授权/令牌/设备端点、支持的授权类型、scopes 和 PKCE 方法,以供发现。
  • 更新代理中间件:未登录用户访问 /app 时重定向到 /sign-up,访问 /oauth/authorize 时重定向到 /sign-in,并扩展 matcher 以包含 /oauth/authorize。
apps/calendar/app/oauth/authorize/page.tsx
apps/calendar/app/api/oauth/authorize/route.ts
apps/calendar/app/api/oauth/token/route.ts
apps/calendar/app/api/oauth/device/route.ts
apps/calendar/app/.well-known/oauth-authorization-server/route.ts
apps/calendar/proxy.ts
添加面向用户的 MCP 设置 UI(API Key、已授权应用、审计日志、开关、端点信息),并集成到个人资料设置中。
  • 创建 MCPSettings 组件,包含 Tabs(概览、API Keys、OAuth 应用、审计日志)以及子组件:用于启用 MCP、展示带复制按钮的端点 URL、快速开始说明,以及用于创建/撤销/更新 API Key 与 scopes 的控件(对话框、徽章)。
  • 在 MCPSettings 中集成 MCP API:请求 /api/mcp/settings/api-keys/authorized-apps/audit-logs,处理审计日志分页,支持撤销 OAuth 应用和 API Key,并展示最近使用时间和 scope 徽章。
  • 在个人资料 Settings 页面中挂载 MCPSettings,将其置于 ImportExport/BuildInfo 上方的带边框区域内。
  • 从 RightSidebar 中移除 MiniCalendar 按钮/抽屉,以简化侧边栏并去除未使用的 UI。
apps/calendar/components/app/settings/mcp/mcp-settings.tsx
apps/calendar/components/app/profile/settings.tsx
apps/calendar/components/app/sidebar/right-sidebar.tsx
apps/calendar/components/app/sidebar/mini-calendar-sheet.tsx
引入基于 Redis 的会话和事件查询缓存,并将缓存失效挂接到事件 CRUD 中,同时调整数据库查询的日期范围。
  • 添加通用 Redis 客户端工具(getRedis, withRedis)以及用于会话和按用户按月事件缓存的 key 辅助方法,包括计算受影响月份和 fullMonthRange 的工具函数。
  • 实现基于会话 token 的会话缓存 get/set,使用 JSON 序列化并带日期 reviver,TTL 依据会话过期时间(最大 10 分钟),并集成到 getServerSession 中,使用 better-auth 的 getSessionCookie;优先尝试从缓存读取,若没有则调用 auth.api.getSession 并缓存成功响应。
  • 实现事件缓存的 get/set/invalidate:按年-月分组存储事件,使用 JSON 保存到 Redis,并带 TTL;通过 mget 取回多个月份缓存,并按请求的日期范围过滤;同时将数据库结果按月份分组以便缓存。
  • 更新 /api/events GET:在提供 startDate/endDate 时优先查缓存,否则将查询范围扩展为 fullMonthRange(start,end),将分组结果按月缓存,并在返回前对解密后的事件按请求日期范围进行后置过滤;同时更新 POST/DELETE,在基于旧/新事件日期范围进行更新/创建/删除时调用 invalidateEventCache。
  • 在 apps/calendar/package.json 中新增 @modelcontextprotocol/sdk 和 ioredis 依赖,并在 .env 示例和 pnpm-lock 中添加 Redis/MCP 配置相关变更。
apps/calendar/lib/cache/client.ts
apps/calendar/lib/cache/keys.ts
apps/calendar/lib/cache/session.ts
apps/calendar/lib/cache/events.ts
apps/calendar/lib/auth/server.ts
apps/calendar/app/api/events/route.ts
apps/calendar/package.json
apps/calendar/.env.example
pnpm-lock.yaml
.gitignore
针对 MCP/一致性,对倒计时目标日期的序列化做了一个小的功能性调整。
  • CountdownTool 的 createCountdown 调用现在将 targetDate 作为仅日期字符串(toDateString)传递,而不是 ISO 时间戳,以与预期的 schema/表示方式保持一致。
apps/calendar/components/app/sidebar/countdown.tsx

Tips and commands

Interacting with Sourcery

  • 触发新的评审: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub Issue: 在评审评论下回复请求 Sourcery 创建 issue。你也可以直接回复 @sourcery-ai issue,从该评论生成一个 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题中任意位置写上 @sourcery-ai 即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 描述正文中任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 来在任意时刻(重新)生成摘要。
  • 生成 reviewer's guide: 在 Pull Request 中评论 @sourcery-ai guide,即可在任意时刻(重新)生成 reviewer's guide。
  • 解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,可一次性将所有 Sourcery 评论标记为已解决。适用于你已处理所有评论且不希望继续看到它们的场景。
  • 忽略所有 Sourcery 评审: 在 Pull Request 中评论 @sourcery-ai dismiss,以忽略所有现有 Sourcery 评审。若你希望重新开始一次新的评审流程,尤其有用——别忘了再评论 @sourcery-ai review 来触发新评审!

Customizing Your Experience

打开你的 dashboard 以:

  • 启用或禁用评审功能,例如 Sourcery 自动生成的 Pull Request 摘要、reviewer's guide 等。
  • 修改评审语言。
  • 添加、移除或编辑自定义评审说明。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Adds full MCP (Model Context Protocol) support for the calendar app, including OAuth/device-code flows, API keys, audit/rate-limit infrastructure, Redis-backed caching for sessions and events, and UI to manage MCP settings and credentials, while refactoring some calendar views/popovers and tightening auth redirects.

Entity-relationship diagram for new MCP tables

erDiagram
  user {
    text id PK
  }

  mcpApiKeys {
    text id PK
    text userId FK
    text name
    text keyHash
    text keyPrefix
    jsonb scopes
    boolean isActive
  }

  mcpTokens {
    text id PK
    text userId FK
    text tokenHash
    jsonb scopes
    text clientId
    text clientName
    timestamptz expiresAt
    boolean isRevoked
  }

  mcpDeviceCodes {
    text id PK
    text userId FK
    text deviceCode
    text userCode
    text clientId
    jsonb scopes
    text status
  }

  mcpAuditLogs {
    text id PK
    text userId FK
    text authType
    text keyId
    text action
    boolean success
    timestamptz createdAt
  }

  mcpSettings {
    text userId PK,FK
    boolean enabled
    int rateLimitRpm
  }

  mcpAuthRequests {
    text id PK
    text userId FK
    text clientId
    text redirectUri
    jsonb scopes
    text authorizationCode
    timestamptz codeExpiresAt
    text status
  }

  user ||--o{ mcpApiKeys : has
  user ||--o{ mcpTokens : has
  user ||--o{ mcpDeviceCodes : has
  user ||--o{ mcpAuditLogs : has
  user ||--|| mcpSettings : has
  user ||--o{ mcpAuthRequests : has
Loading

File-Level Changes

Change Details Files
Replace custom popover and ScrollArea implementations in month/year views and calendar search with @zntr/ui popover and native overflow scrolling, plus minor UX tweaks (body scroll lock, anchor positioning, click behavior).
  • Year view day popover now uses Popover/PopoverAnchor/PopoverContent with fixed-position anchor, closes on Popover open-change, locks body scroll via useEffect, and closes before firing onEventClick.
  • Month view remaining-events popover similarly migrated to Popover components, anchors to the table cell instead of just the button, locks body scroll, and no longer toggles open/closed when re-clicking the trigger.
  • Search results list in the main calendar view replaces ScrollArea with a div using CSS overflow-y-auto and min-height/max-height constraints for simpler scrolling behavior.
apps/calendar/components/app/views/year-view.tsx
apps/calendar/components/app/views/month-view.tsx
apps/calendar/components/app/calendar.tsx
Introduce MCP-related database schema and relations for API keys, OAuth tokens/auth requests, device codes, audit logs, and per-user MCP settings.
  • Add mcp_api_keys, mcp_tokens, mcp_device_codes, mcp_audit_logs, mcp_settings, and mcp_auth_requests tables with appropriate columns, indexes, and foreign keys to user.
  • Wire MCP tables into userRelations and define dedicated relation helpers for each MCP table.
  • Add initial drizzle migration SQL to create MCP tables/indexes and foreign keys.
apps/calendar/lib/drizzle/schema.ts
apps/calendar/drizzle/0001_create_mcp_tables.sql
apps/calendar/drizzle/meta/_journal.json
Implement MCP server, tool handlers, and HTTP transport endpoint backed by auth helpers, rate limiting, audit logging, and per-user MCP settings.
  • Create createServer() that registers MCP tools for events, categories, countdowns, settings, and profile using @modelcontextprotocol/sdk and zod, with scoped permissions and allowed value enums.
  • Add event/category/countdown/settings/profile tool modules that operate against drizzle tables, respecting encryption/decryption for sensitive fields and returning paginated data where relevant.
  • Implement handleMcpRequest that extracts MCP auth (API key or OAuth), enforces enabled flag and per-user RPM rate limit, logs audit entries for each request, and serves both GET (tool manifest/metadata) and POST (tool calls) via WebStandardStreamableHTTPServerTransport.
  • Add MCP auth helpers (verifyApiKey/verifyOAuthToken, key generation, token hashing, device/user code generation, API key CRUD, isMcpEnabled, ALL_SCOPES) plus shared types and McpAuthError.
  • Introduce MCP audit helpers, settings persistence helpers, and rate-limiter that count recent audit entries to enforce per-user RPM.
  • Expose MCP HTTP endpoint at /api/mcp with GET/POST delegating to handleMcpRequest.
apps/calendar/lib/mcp/server.ts
apps/calendar/lib/mcp/event-tools.ts
apps/calendar/lib/mcp/category-tools.ts
apps/calendar/lib/mcp/countdown-tools.ts
apps/calendar/lib/mcp/settings-tools.ts
apps/calendar/lib/mcp/profile-tools.ts
apps/calendar/lib/mcp/auth.ts
apps/calendar/lib/mcp/auth-helpers.ts
apps/calendar/lib/mcp/types.ts
apps/calendar/lib/mcp/audit.ts
apps/calendar/lib/mcp/settings.ts
apps/calendar/lib/mcp/rate-limiter.ts
apps/calendar/app/api/mcp/route.ts
Add OAuth 2.0 / device-code flow endpoints and authorization UI for MCP clients, plus well-known metadata discovery.
  • Implement /oauth/authorize page with a Suspense-wrapped AuthorizeForm that reads query params, shows the current user, renders scopes/client info, and posts to /api/oauth/authorize for both device-code and auth-code flows, handling success/error states and redirecting back to client redirect_uri with code+state.
  • Add /api/oauth/authorize POST handler that approves device-code flows by updating mcpDeviceCodes status/userId and auth-code PKCE flows by validating client_id/redirect_uri, generating an authorization_code, and inserting mcpAuthRequests rows.
  • Implement /api/oauth/token POST handler that supports device_code grant (hash & validate device code, status, expiry) and authorization_code grant (validate auth request, PKCE verifier/challenge, mark code used) then issues access/refresh tokens in mcpTokens and returns OAuth token response including user info.
  • Add /api/oauth/device POST endpoint that issues new device_code/user_code pairs, stores hashed device codes in mcpDeviceCodes with scopes and expiry, and returns standard device-authorization payload.
  • Expose /.well-known/oauth-authorization-server route that returns issuer, authorization/token/device endpoints, supported grant types, scopes, and PKCE methods for discovery.
  • Update proxy middleware to redirect unauthenticated users hitting /app to /sign-up and /oauth/authorize to /sign-in, and extend matcher to include /oauth/authorize.
apps/calendar/app/oauth/authorize/page.tsx
apps/calendar/app/api/oauth/authorize/route.ts
apps/calendar/app/api/oauth/token/route.ts
apps/calendar/app/api/oauth/device/route.ts
apps/calendar/app/.well-known/oauth-authorization-server/route.ts
apps/calendar/proxy.ts
Add user-facing MCP settings UI (API keys, authorized apps, audit logs, on/off toggle, endpoint info) and integrate into profile settings.
  • Create MCPSettings component with Tabs (overview, API keys, OAuth apps, audit logs) plus subcomponents for enabling MCP, showing endpoint URL with copy button, quick-start instructions, and controls for creating/revoking/updating API keys and scopes with dialogs and badges.
  • Add MCP API integration in MCPSettings: fetch /api/mcp/settings/api-keys/authorized-apps/audit-logs, handle pagination for audit logs, allow revoking OAuth apps and API keys, and display last-used timestamps and scope badges.
  • Mount MCPSettings inside profile Settings page in a bordered section above ImportExport/BuildInfo.
  • Remove MiniCalendar button/sheet from RightSidebar to simplify sidebar and avoid unused UI.
apps/calendar/components/app/settings/mcp/mcp-settings.tsx
apps/calendar/components/app/profile/settings.tsx
apps/calendar/components/app/sidebar/right-sidebar.tsx
apps/calendar/components/app/sidebar/mini-calendar-sheet.tsx
Introduce Redis-backed caching for sessions and event queries, with cache invalidation hooked into event CRUD and adjusted DB date ranges.
  • Add generic Redis client utilities (getRedis, withRedis) and cache key helpers for sessions and per-user per-month event caches, including utilities to compute affected months and fullMonthRange.
  • Implement session cache get/set keyed by session token, using JSON serialization with date reviver and TTL based on session expiry (max 10 minutes), and integrate into getServerSession using better-auth getSessionCookie; attempt cache lookup before calling auth.api.getSession and cache successful responses.
  • Implement event cache get/set/invalidate that groups events per year-month, stores them as JSON in Redis with TTL, retrieves multi-month caches via mget and filters by requested date range, and groups DB results by month for caching.
  • Update /api/events GET to first check cache when startDate/endDate are provided, otherwise expand query range to fullMonthRange(start,end), cache grouped results per month, and then post-filter decrypted events to the requested date range when returning; also update POST/DELETE to call invalidateEventCache around update/create/delete based on old/new event date ranges.
  • Add new dependencies @modelcontextprotocol/sdk and ioredis in apps/calendar/package.json and add .env example and pnpm-lock changes for Redis/MCP config.
apps/calendar/lib/cache/client.ts
apps/calendar/lib/cache/keys.ts
apps/calendar/lib/cache/session.ts
apps/calendar/lib/cache/events.ts
apps/calendar/lib/auth/server.ts
apps/calendar/app/api/events/route.ts
apps/calendar/package.json
apps/calendar/.env.example
pnpm-lock.yaml
.gitignore
Minor functional tweak to countdown target date serialization for MCP/consistency.
  • CountdownTool createCountdown call now passes targetDate as a date-only string (toDateString) instead of ISO timestamp to align with expected schema/representation.
apps/calendar/components/app/sidebar/countdown.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并留下了一些总体反馈:

  • 在 events 的 GET 处理器中,你在最终的内存过滤里重新计算了 new Date(startDate) / new Date(endDate);在过滤之前只计算一次并复用这些值,会更简洁也稍微更高效。
  • lib/cache/client.ts 中的 Redis 客户端假定 process.env.REDIS_URL 一定已定义(使用了 ! 断言);建议增加防御性检查或回退行为,这样在 Redis 未配置时应用不会在启动阶段直接抛错。
提供给 AI Agents 的提示
Please address the comments from this code review:

## Overall Comments
- 在 events 的 GET 处理器中,你在最终的内存过滤里重新计算了 `new Date(startDate)` / `new Date(endDate)`;在过滤之前只计算一次并复用这些值,会更简洁也稍微更高效。
- `lib/cache/client.ts` 中的 Redis 客户端假定 `process.env.REDIS_URL` 一定已定义(使用了 `!` 断言);建议增加防御性检查或回退行为,这样在 Redis 未配置时应用不会在启动阶段直接抛错。

## Individual Comments

### Comment 1
<location path="apps/calendar/lib/cache/events.ts" line_range="9-11" />
<code_context>
+
+export type CachedEvent = typeof calendarEvents.$inferSelect
+
+function parseCachedEvents(json: string): CachedEvent[] {
+  return JSON.parse(json, (_key, value) => {
+    if (
+      typeof value === 'string' &&
+      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 在 JSON 解析期间进行日期复原,可能会意外地把任意 ISO 风格的字符串字段转换成 Date。

这个 reviver 会应用到任何匹配 `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/` 的字符串,而不考虑属性名,因此任何新的 ISO 风格字符串字段(例如 payload、ID、日志)都会被悄悄转成 `Date`。建议通过检查 `key` 来限制复原范围(只转换已知的日期字段,如 `startDate` / `endDate` / `createdAt` / `updatedAt`),或者利用 `calendarEvents.$inferSelect` 的类型结构,显式控制哪些字段应当被视为日期。

Suggested implementation:

```typescript
export type CachedEvent = typeof calendarEvents.$inferSelect

const DATE_FIELD_KEYS = new Set<string>([
  'startDate',
  'endDate',
  'createdAt',
  'updatedAt',
])

function parseCachedEvents(json: string): CachedEvent[] {
  return JSON.parse(json, (key, value) => {
    if (
      typeof value === 'string' &&
      DATE_FIELD_KEYS.has(key) &&
      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
    ) {
      return new Date(value)
    }
    return value
  }) as CachedEvent[]
}

```

- 如果 `CachedEvent` 还有其他类似日期的字段(例如 `deletedAt``reminderAt`),请把它们的属性名加入 `DATE_FIELD_KEYS`,以确保这些字段会被复原为 `Date` 实例。
- 如果实际的字段名不同(例如 `startsAt` / `endsAt`),请调整 `DATE_FIELD_KEYS` 中的条目,使其与 `calendarEvents.$inferSelect` 的结构保持一致。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈改进后续的代码评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In the events GET handler, you recompute new Date(startDate)/new Date(endDate) inside the final in-memory filter; it would be cleaner and slightly more efficient to compute these once before filtering and reuse them.
  • The Redis client in lib/cache/client.ts assumes process.env.REDIS_URL is always defined (! assertion); consider adding a defensive check or fallback behavior so the app doesn’t throw at startup when Redis is not configured.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the events GET handler, you recompute `new Date(startDate)`/`new Date(endDate)` inside the final in-memory filter; it would be cleaner and slightly more efficient to compute these once before filtering and reuse them.
- The Redis client in `lib/cache/client.ts` assumes `process.env.REDIS_URL` is always defined (`!` assertion); consider adding a defensive check or fallback behavior so the app doesn’t throw at startup when Redis is not configured.

## Individual Comments

### Comment 1
<location path="apps/calendar/lib/cache/events.ts" line_range="9-11" />
<code_context>
+
+export type CachedEvent = typeof calendarEvents.$inferSelect
+
+function parseCachedEvents(json: string): CachedEvent[] {
+  return JSON.parse(json, (_key, value) => {
+    if (
+      typeof value === 'string' &&
+      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Date revival during JSON parse can accidentally convert any ISO-like string field to Date.

This reviver applies to any string matching `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/`, regardless of the property, so any new ISO‑like string field (e.g. payloads, IDs, logs) will be silently turned into a `Date`. Consider restricting revival by inspecting the `key` (only converting known date fields like `startDate`/`endDate`/`createdAt`/`updatedAt`) or using the typed shape of `calendarEvents.$inferSelect` to explicitly control which fields are treated as dates.

Suggested implementation:

```typescript
export type CachedEvent = typeof calendarEvents.$inferSelect

const DATE_FIELD_KEYS = new Set<string>([
  'startDate',
  'endDate',
  'createdAt',
  'updatedAt',
])

function parseCachedEvents(json: string): CachedEvent[] {
  return JSON.parse(json, (key, value) => {
    if (
      typeof value === 'string' &&
      DATE_FIELD_KEYS.has(key) &&
      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
    ) {
      return new Date(value)
    }
    return value
  }) as CachedEvent[]
}

```

- If `CachedEvent` has other date-like fields (e.g. `deletedAt`, `reminderAt`), add their property names to `DATE_FIELD_KEYS` to ensure they are revived as `Date` instances.
- If the actual field names differ (for example `startsAt`/`endsAt`), adjust the entries in `DATE_FIELD_KEYS` to match the schema of `calendarEvents.$inferSelect`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +9 to +11
function parseCachedEvents(json: string): CachedEvent[] {
return JSON.parse(json, (_key, value) => {
if (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): 在 JSON 解析期间进行日期复原,可能会意外地把任意 ISO 风格的字符串字段转换成 Date。

这个 reviver 会应用到任何匹配 /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ 的字符串,而不考虑属性名,因此任何新的 ISO 风格字符串字段(例如 payload、ID、日志)都会被悄悄转成 Date。建议通过检查 key 来限制复原范围(只转换已知的日期字段,如 startDate / endDate / createdAt / updatedAt),或者利用 calendarEvents.$inferSelect 的类型结构,显式控制哪些字段应当被视为日期。

Suggested implementation:

export type CachedEvent = typeof calendarEvents.$inferSelect

const DATE_FIELD_KEYS = new Set<string>([
  'startDate',
  'endDate',
  'createdAt',
  'updatedAt',
])

function parseCachedEvents(json: string): CachedEvent[] {
  return JSON.parse(json, (key, value) => {
    if (
      typeof value === 'string' &&
      DATE_FIELD_KEYS.has(key) &&
      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
    ) {
      return new Date(value)
    }
    return value
  }) as CachedEvent[]
}
  • 如果 CachedEvent 还有其他类似日期的字段(例如 deletedAtreminderAt),请把它们的属性名加入 DATE_FIELD_KEYS,以确保这些字段会被复原为 Date 实例。
  • 如果实际的字段名不同(例如 startsAt / endsAt),请调整 DATE_FIELD_KEYS 中的条目,使其与 calendarEvents.$inferSelect 的结构保持一致。
Original comment in English

suggestion (bug_risk): Date revival during JSON parse can accidentally convert any ISO-like string field to Date.

This reviver applies to any string matching /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/, regardless of the property, so any new ISO‑like string field (e.g. payloads, IDs, logs) will be silently turned into a Date. Consider restricting revival by inspecting the key (only converting known date fields like startDate/endDate/createdAt/updatedAt) or using the typed shape of calendarEvents.$inferSelect to explicitly control which fields are treated as dates.

Suggested implementation:

export type CachedEvent = typeof calendarEvents.$inferSelect

const DATE_FIELD_KEYS = new Set<string>([
  'startDate',
  'endDate',
  'createdAt',
  'updatedAt',
])

function parseCachedEvents(json: string): CachedEvent[] {
  return JSON.parse(json, (key, value) => {
    if (
      typeof value === 'string' &&
      DATE_FIELD_KEYS.has(key) &&
      /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)
    ) {
      return new Date(value)
    }
    return value
  }) as CachedEvent[]
}
  • If CachedEvent has other date-like fields (e.g. deletedAt, reminderAt), add their property names to DATE_FIELD_KEYS to ensure they are revived as Date instances.
  • If the actual field names differ (for example startsAt/endsAt), adjust the entries in DATE_FIELD_KEYS to match the schema of calendarEvents.$inferSelect.

Implement RFC 7591 registration endpoint so OAuth clients like opencode
can register dynamically. Add mcp_oauth_clients table, refresh_token
grant support, and oauth-protected-resource metadata for scopes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant