凯尔·戴夫
NixOS与MCP集成的声明性CalDAV/CardDAV同步。
特性
- 声明性配置 -没有手动配置文件;Nix中的所有内容
- 谷歌日历和联系人 -一流的OAuth支持
- CalDAV/CardDAV -适用于任何标准提供商(Fastmail、Nextcloud等)
- HTTP/ICS订阅 -只读日历订阅源(假期、运动等)
- MCP服务器 -AI代理可以读取/创建日历事件和搜索联系人
- 独立或集成 -独立使用或作为 石堆
快速开始
1.添加到薄片中
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager";
cairn-dav.url = "github:kcalvelli/cairn-dav";
};
outputs = { self, nixpkgs, home-manager, cairn-dav, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
cairn-dav.nixosModules.default
home-manager.nixosModules.home-manager
./configuration.nix
];
};
};
}2.配置NixOS模块
# configuration.nix
{ inputs, ... }:
{
# Import cairn-dav NixOS module (provides services.pim.* options)
imports = [ inputs.cairn-dav.nixosModules.default ];
services.pim.calendar = {
enable = true;
defaultCalendar = "Family";
accounts = {
google = {
type = "google";
tokenFile = "/home/youruser/.vdirsyncer/google_token.json";
clientId = "your-client-id.apps.googleusercontent.com";
clientSecretFile = "/run/agenix/google-client-secret"; # Or any secrets manager
};
};
sync = {
frequency = "5m";
conflictResolution = "remote"; # Google wins on conflict
};
};
}3.配置家庭管理器模块
# In your home-manager config
{ inputs, ... }:
{
imports = [ inputs.cairn-dav.homeModules.default ];
# The home module reads services.pim.* from NixOS config
# and generates vdirsyncer, khal, and khard configurations
}4.初始设置
重建系统后:
# Discover and authorize calendars (opens browser for OAuth)
vdirsyncer discover
# Initial sync
vdirsyncer sync
# Verify with khal
khal list配置参考
谷歌日历(双向同步)
services.pim.calendar = {
enable = true;
defaultCalendar = "Family"; # Default for new events
accounts.google = {
type = "google";
tokenFile = "/home/user/.vdirsyncer/google_token.json"; # Must be writable for token refresh
clientId = "123456789-abc.apps.googleusercontent.com";
clientSecretFile = "/run/agenix/google-client-secret";
localPath = "~/.calendars/"; # Optional: custom path (default: ~/.calendars/google/)
readOnly = false; # Two-way sync (default)
};
sync = {
frequency = "5m"; # Sync interval
conflictResolution = "remote"; # "remote" or "local" wins on conflict
};
};CalDAV(Fastmail、Nextcloud等)
services.pim.calendar.accounts.fastmail = {
type = "caldav";
url = "https://caldav.fastmail.com/dav/calendars/user/me@fastmail.com/";
username = "me@fastmail.com";
passwordFile = "/run/agenix/fastmail-password";
};HTTP/ICS订阅(只读)
services.pim.calendar.accounts.orthodox = {
type = "http";
icsUrl = "https://orthocal.info/api/gregorian/ical/";
localPath = "~/.calendars-external/orthodox/"; # Keep separate from writable calendars
readOnly = true;
};
services.pim.calendar.accounts.holidays = {
type = "http";
icsUrl = "https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics";
readOnly = true;
};谷歌联系人
services.pim.contacts = {
enable = true;
accounts.google = {
type = "google";
tokenFile = "/home/user/.vdirsyncer/google_contacts_token.json"; # Separate from calendar!
clientId = "123456789-abc.apps.googleusercontent.com"; # Same as calendar
clientSecretFile = "/run/agenix/google-client-secret"; # Same as calendar
};
};CardDAV(Fastmail、Nextcloud等)
services.pim.contacts.accounts.fastmail = {
type = "carddav";
url = "https://carddav.fastmail.com/dav/addressbooks/user/me@fastmail.com/";
username = "me@fastmail.com";
passwordFile = "/run/agenix/fastmail-password";
};Google OAuth设置(详细)
Google日历和联系人需要OAuth凭据。以下是完整的设置过程:
第一步:创建谷歌云项目
- 首选 谷歌云控制台
- 创建一个新项目(例如“vdirsyncer”)
- 注意 项目编号 (稍后需要验证您是否参与了正确的项目)
步骤2:启用所需的API
重要: 您需要\*DAV API,而不是常规的日历/联系人API!
- 首选 API和服务 → 图书馆
- 搜索并启用: API加州大学戴维斯分校 (用于日历)
- 搜索并启用: CardDAV API (用于联系人)
不要使用“Google Calendar API”或“Google People API”-这是vdirsyncer不使用的不同API。
步骤3:配置OAuth同意屏幕
- 首选 API和服务 → OAuth 授权界面
- 选择 外部 (如果使用谷歌工作区,则为内部)
- 填写必填字段:
- 应用程序名称:“vdirsyncer”(或任何名称) - 用户支持电子邮件:您的电子邮件 - 开发人员联系人:您的电子邮件
- 跳过作用域(vdirsyncer自动请求它们)
- 将您的Google帐户添加为 测试用户
步骤4:创建OAuth凭据
- 首选 API和服务 → 凭证
- 点击 创建凭据 → OAuth客户端ID
- 应用程序类型: 桌面应用程序
- 名称:“vdirsyncer”(或任何名称)
- 点击 创建
- 注意你的 客户端ID 和 客户端密钥
步骤5:安全存储凭据
使用agenix(推荐):
# Store the client secret
echo "your-client-secret" | agenix -e secrets/google-client-secret.age
# In your NixOS config
age.secrets.google-client-secret = {
file = ./secrets/google-client-secret.age;
owner = "youruser";
mode = "0400";
};步骤6:初始授权
NixOS重建后:
# This will open your browser for OAuth authorization
vdirsyncer discover
# For calendar: authorize access to Google Calendar
# For contacts: authorize access to Google Contacts (separate authorization)
# After authorizing, token files are created automatically
# First sync
vdirsyncer sync重要说明
- 单独的令牌文件:日历和联系人需要单独的令牌文件,因为它们使用不同的API作用域
- 令牌位置:令牌文件必须是可写的(用于刷新令牌)。不要把它们放进/nix/store
- 项目一致性:确保您的客户端ID来自启用CalDAV/CardDAV API的同一项目
- 测试用户:如果您的OAuth应用程序处于“测试”模式,则必须将您的Google帐户添加为测试用户
CLI使用情况
日历(khal)
# List upcoming events
khal list
# List events in date range
khal list today 7d
khal list 2025-01-25 2025-02-01
# Create new event
khal new 2025-01-25 14:00 15:00 "Meeting with Bob"
khal new 2025-01-25 "All day event" # All-day event
khal new tomorrow 10:00 11:00 "Morning standup" -a Family # Specify calendar
# Interactive calendar UI
ikhal
# Show all calendars
khal printcalendars
# Search events
khal search "meeting"联系人(khard)
# List all contacts
khard list
# Search contacts
khard list "calvelli"
khard list "john"
# Show contact details
khard show "John Smith"
# Edit contact (opens $EDITOR)
khard edit "John Smith"
# Create new contact (opens $EDITOR)
khard new
# Export contact as vCard
khard export "John Smith"同步(vdirsyncer)
# Sync all calendars and contacts
vdirsyncer sync
# Sync specific pair
vdirsyncer sync cal_google
vdirsyncer sync contacts_google
# Discover new calendars/address books
vdirsyncer discover
# Sync metadata (calendar colors, names)
vdirsyncer metasync
# Debug mode
vdirsyncer -v DEBUG sync系统化服务
cairndav为自动同步创建了用户级systemd服务:
# Check sync timer status
systemctl --user status vdirsyncer-sync.timer
# List all vdirsyncer timers
systemctl --user list-timers | grep vdirsyncer
# Manually trigger sync
systemctl --user start vdirsyncer-sync.service
# View sync logs
journalctl --user -u vdirsyncer-sync.service -f创建的服务:
vdirsyncer-sync.service/.timer-主同步(默认每5分钟一次)vdirsyncer-metasync.service/.timer-元数据同步(每日)vdirsyncer-discover.service-初始收集发现
从手动配置迁移
如果您从手动配置的vdirsyncer安装迁移:
1.备份现有配置
cp ~/.config/vdirsyncer/config ~/.config/vdirsyncer/config.bak
cp -r ~/.calendars ~/.calendars.bak
cp -r ~/.contacts ~/.contacts.bak2.注意你现有的路径
检查您当前的配置:
- 令牌文件位置
- 本地日历/联系人路径
- 日历名称
3.配置具有匹配路径的cairn-dav
使用 localPath 匹配现有路径的选项:
services.pim.calendar.accounts.google = {
type = "google";
tokenFile = "/home/user/.vdirsyncer/google_token.json"; # Existing token
localPath = "~/.calendars/"; # Match existing path
# ...
};4.重建和验证
# Rebuild system
sudo nixos-rebuild switch
# The new config will use your existing token and data
khal list
khard list故障排除
vdirsyncer发现期间的“403禁止”
原因:启用了错误的API或错误的谷歌云项目
修复:
- 验证您是否已启用 API加州大学戴维斯分校 和 CardDAV API (不是日历API或人员API)
- 验证您的客户端ID是否来自启用API的同一项目
- 删除令牌文件并重新授权:
rm ~/.vdirsyncer/google_token.json
vdirsyncer discoverkhal显示“未找到日历”
原因:vdirsyncer尚未发现集合
修复:
vdirsyncer discover
vdirsyncer synckhard显示“未找到联系人”
原因:Google Contacts创建了一个旧配置可能不期望的“默认”子目录
修复:确保您使用的是自动处理此问题的最新cairn dav
同步定时器未运行
原因:需要启用家庭管理员服务
修复:
systemctl --user daemon-reload
systemctl --user enable --now vdirsyncer-sync.timer令牌刷新错误
原因:令牌文件不可写或已损坏
修复:
# Remove old token
rm ~/.vdirsyncer/google_token.json
# Re-authorize
vdirsyncer discoverMCP服务器
mcp-dav mcp服务器为AI代理提供了直接访问您同步的日历和联系人数据的权限。当 services.pim.mcp.enable = true (默认) mcp-dav 二进制文件已安装。
可用工具
日历工具
| 工具 | 说明 |
|---|---|
list_events | 按日期范围列出事件(默认值:今天到+30天) |
search_events | 按摘要/描述/位置中的文本搜索事件 |
create_event | 创建新的日历事件(运行 vdirsyncer sync 推到远程后) |
get_free_busy | 安排繁忙的时间段 |
联系人工具
| 工具 | 说明 |
|---|---|
list_contacts | 列出通讯簿中的所有联系人 |
search_contacts | 按姓名、电子邮件、电话或组织搜索联系人 |
get_contact | 按UID或姓名获取详细联系信息 |
使用Claude Code注册
将mcp-dav添加到您的Claude Code mcp配置中(~/.mcp.json 或 ~/.claude.json):
{
"mcpServers": {
"mcp-dav": {
"command": "mcp-dav",
"env": {
"MCP_DAV_CALENDARS": "~/.calendars",
"MCP_DAV_CONTACTS": "~/.contacts"
}
}
}
}或者使用Nix商店的完整路径:
# Find the path
which mcp-dav
# Output: /nix/store/...-mcp-dav-0.1.0/bin/mcp-dav使用mcp-cli进行测试
# List available tools
mcp-cli mcp-dav -d
# List events for next week
mcp-cli call mcp-dav list_events '{"start_date": "2025-01-24", "end_date": "2025-01-31"}'
# Search contacts
mcp-cli call mcp-dav search_contacts '{"query": "John"}'
# Create an event
mcp-cli call mcp-dav create_event '{
"summary": "Team Meeting",
"start": "2025-01-25T10:00:00",
"end": "2025-01-25T11:00:00",
"calendar": "Family",
"location": "Conference Room"
}'
# Then sync: vdirsyncer sync
# Check availability
mcp-cli call mcp-dav get_free_busy '{"start_date": "2025-01-24", "end_date": "2025-01-26"}'环境变量
| 变量 | 默认值 | 描述 |
|---|---|---|
MCP_DAV_CALENDARS | ~/.calendars | vdirsyncer日历存储路径 |
MCP_DAV_CONTACTS | ~/.contacts | vdirsyncer联系人存储路径 |
工具架构
list_events
{
"start_date": "2025-01-24", // Optional, defaults to today
"end_date": "2025-01-31", // Optional, defaults to +30 days
"calendar": "Family" // Optional, filter by calendar name
}search_events
{
"query": "meeting", // Required, case-insensitive search
"calendar": "Work", // Optional, filter by calendar
"limit": 50 // Optional, max results (default: 50)
}create_event
{
"summary": "Event Title", // Required
"start": "2025-01-25T10:00:00", // Required, ISO8601
"end": "2025-01-25T11:00:00", // Required, ISO8601
"calendar": "Family", // Required, calendar name
"location": "123 Main St", // Optional
"description": "Event details", // Optional
"all_day": false // Optional, default false
}get_free_busy
{
"start_date": "2025-01-24", // Required
"end_date": "2025-01-26", // Required
"calendars": ["Work", "Family"] // Optional, filter calendars
}list_contacts
{
"addressbook": "google", // Optional, filter by addressbook
"limit": 100 // Optional, max results (default: 100)
}search_contacts
{
"query": "john", // Required, searches name/email/phone/org
"addressbook": "google", // Optional
"limit": 50 // Optional, default: 50
}get_contact
{
"uid": "abc123", // Optional, exact UID match
"name": "John" // Optional, partial name match
}选项参考
服务.pim.calendar
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
enable | bool | false | 启用日历同步 |
defaultCalendar | string | null | 新事件的默认日历 |
accounts..type | enum | - | “谷歌”、“caldav”或“http” |
accounts..tokenFile | string | null | OAuth令牌文件路径(谷歌) |
accounts..clientId | string | null | OAuth客户端ID(谷歌) |
accounts..clientSecretFile | path | null | OAuth客户端机密文件(谷歌) |
accounts..url | string | null | CalDAV服务器URL |
accounts..username | string | null | CalDAV用户名 |
accounts..passwordFile | path | null | CalDAV密码文件 |
accounts..icsUrl | string | null | HTTP ICS URL(只读) |
accounts..localPath | string | null | 自定义本地存储路径 |
accounts..readOnly | bool | false | 禁用本地→远程同步 |
sync.frequency | 字符串 | “5m” | 同步间隔 |
sync.conflictResolution | enum | “remote” | “远程”或“本地”获胜 |
services.pim.联系人
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
enable | bool | false | 启用联系人同步 |
accounts..type | enum | - | “谷歌”或“carddav” |
accounts..tokenFile | string | null | OAuth令牌文件路径 |
accounts..clientId | string | null | OAuth客户端ID |
accounts..clientSecretFile | path | null | OAuth客户端机密文件 |
accounts..url | string | null | CardDAV服务器URL |
accounts..username | string | null | CardDAV用户名 |
accounts..passwordFile | path | null | CardDAV密码文件 |
accounts..localPath | string | null | 自定义本地存储路径 |
服务.pim.mcp
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
enable | bool | true | 启用MCP服务器以访问AI |
相关项目
- 石堆 -模块化NixOS分布
- 凯恩邮件 -NixOS的人工智能电子邮件
- Vdirsyncer -底层同步引擎
- 卡尔 -CLI日历应用程序
- khard -CLI联系人应用程序
许可证
麻省理工学院
