【MCP】Caldav个人日程助手

前言

自己想要搭建一个MCP服务器帮助我管理日程。所以想自己编写一个。网上看到了一位B站UP主的视频按照他的想法编写了框架代码,然后又自己找资料、问AI来完成Caldav日历系统的操作。目前已经发布到服务器了挺好用的。(但就是有点费Token)。

我把所有踩过的坑和实际开发时的过程总结成了这篇文章希望对其他人有些帮助。

一、准备工作

  1. 安装UV

    Windows

    shell 复制代码
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

    (如果下载比较慢可以尝试开代理)

    Linux

    bash 复制代码
    curl -LsSf https://astral.sh/uv/install.sh | sh # 如果无法下载开代理
    pip install uv #服务器上用这个更好
  2. 初始化项目

    bash 复制代码
    uv init [项目名]
  3. 配置虚拟环境

    Windows

    shell 复制代码
    uv venv
    .venv\Scripts\activate

    Linux

    bash 复制代码
    uv venv
    source .venv/bin/activate
  4. 安装依赖

    shell 复制代码
    uv add "mcp[cli]"
  5. 在IDE中打开项目

    项目中的main.py用来编写MCP代码,项目中的pyproject.toml是项目描述文件,README.md文件顾名思义是读我文件。

  6. 安装mcp包

    bash 复制代码
    # 先进入虚拟环境
    pip install mcp
  7. 安装CalDav包

    bash 复制代码
    # 先进入虚拟环境
    pip install caldav

二、了解Caldav文件结构

vbnet 复制代码
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:20200516T060000Z-123401@example.com
DTSTAMP:20200516T060000Z
DTSTART:20200517T060000Z
DTEND:20200517T230000Z
RRULE:FREQ=YEARLY
SUMMARY:Do the needful
END:VEVENT
END:VCALENDAR

以下内容根据Radicale所使用的icalendar标准总结

VEVENT表示该文件是一个日程(事件)

VTODO表示该文件是一个待办

DTSTART表示开始时间

DTEND表示结束时间(注意,待办的结束时间是DUE不是DTEND

SUMMARY标题(不是备注信息

LOCATION日程地理位置(比如:人民公园。注意:如果日程没有指定地理位置,这个属性就不存在,因此在写程序时要判断一下

STATUS 待办事项的完成状态,完成了是COMPLETED

PRIORITY 待办事项的优先级,是一个整数

更多字段信息可以通过event.data来获取。

三、开始编写代码

我们可以在项目中创建一些其他模块文件。

先创建日程的封装类任务的封装类

entities/calendar_info.py

python 复制代码
class CalendarEventInfo:  
    """  
    日历日程信息类  
    用于存储和管理日历事件的相关信息,包括事件的基本属性和操作方法。    该类提供了事件信息的封装、访问和修改功能。    属性:  
        name(str):日程名  
        start_time(datetime):开始时间  
        end_time(datetime):结束时间  
        calendar_name(str):所属日历  
    返回值:  
        CalendarEventInfo实例对象  
    """    def __init__(self,calendar_name:str,name:str,start_time:datetime,end_time:datetime):  
        self.name=name  
        self.start_time=start_time  
        self.end_time=end_time  
        self.calendar_name=calendar_name  
    def to_dict(self):  
        return {  
            "calendar":self.calendar_name,  
            "name":self.name,  
            "start_time":self.start_time,  
            "end_time":self.end_time  
        }  
    @staticmethod  
    def from_dic(dic):  
        return CalendarEventInfo(dic["calendar_name"],dic["name"],dic["start_time"],dic["end_time"])  
    def to_LLM(self)->str: 
	    """
	    用于转化为大模型能理解的文字。
	    """ 
        return f"日历:{self.calendar_name},日程:{self.name}\n时间:{self.start_time}~{self.end_time}\n"  
class CalendarTodoInfo:  
    """  
    日历待办信息类  
    属性:  
        name(str): 待办名  
        start_time(str):开始时间  
        end_time(str):结束时间  
        calendar_name(str):所属日历的名字  
        status(str):任务状态  
        priority(int)=0:优先级  
    """    def __init__(self,calendar_name:str,name:str,start_time:datetime,end_time:datetime,priority:int=0):  
        self.name=name  
        self.start_time=start_time  
        self.end_time=end_time  
        self.calendar_name=calendar_name  
        self.status=""  
        self.priority=priority  
    def to_dict(self):  
        return {  
            "calendar":self.calendar_name,  
            "name":self.name,  
            "start_time":self.start_time,  
            "end_time":self.end_time,  
            "status":self.status,  
            "priority":self.priority  
        }  
  
    @staticmethod  
    def from_dic(dic):  
        return CalendarTodoInfo(dic["calendar_name"],dic["name"],dic["start_time"],dic["end_time"],dic["priority"])  
    def to_LLM(self)->str:  
        return f"日历:{self.calendar_name},待办:{self.name}\n时间:{self.start_time}~{self.end_time}\n状态:{self.status}\n优先级:{self.priority}\n"

编写MCP核心代码-获取全部日历和程序入口

main.py

python 复制代码
from mcp.server.fastmcp import FastMCP
from caldav.davclient import get_davclient  
from datetime import datetime
# 创建MCP客户端
fastMCP=FastMCP("Calendar")  
client=None
# 处理时间的函数
def to_datetime(strDatetime:str,format="%Y-%m-%dT%H:%M:%S"):  
    return datetime.strptime(strDatetime,format)
@mcp.tool("list_calendars")
async def list_calendars():  
	# 必须写多行注释,方便大模型理解工具的用途。
    """  
    获取所有日历
    :return: 日历名  
    """
    # 创建caldav客户端主体,日历所有操作都通过主体实现。
    principal=client.principal()
    calendars=principal.caldendars()
    calendar_str=""
    # 组织返回文本,大模型以return的表达式作为工具的结果输出。同时,不建议输出JSON字符串。
    for calendar in calendars:  
        calendar_str+=calendar.get_display_name()+"\n"  
    return f"找到了以下日历:\n{calendar_str}"
if __name__=="__main__":
    config_JSON=json.loads(open("config.json","r",encoding="utf-8").read())  
    client=get_davclient(username=config_JSON["calendar_username"],  
                         password=config_JSON["calendar_password"],  
                         url=config_JSON["calendar_url"])
    mcp.run(transport="stdio")

编写MCP核心代码-获取日程

先用calendar.date_search()搜索出指定日期范围内的日程。得到event列表。再用for遍历列表得到event对象,再通过event对象的icalendar_component属性来获取事件字段的值(DTSTART、SUMMARY这些。)

main.py

python 复制代码
# 其他部分省略
@fastMCP.tool("get_event")  
async def get_events(start_time:str,end_time:str):  
    """  
    获取指定日期的日程(日期的格式是:2025-09-29T10:00:00)  
    :param start_time: 指定的开始日期  
    :end_time: 指定的结束日期  
    :return: 日程信息  
    """
    principal=client.principal()  
    calendars=principal.calendars()  
    events_result=""""""  
    new_start_time=to_datetime(start_time)  
    new_end_time=to_datetime(end_time)  
    for calendar in calendars:  
        events = calendar.date_search(start=new_start_time, end=new_end_time)  
        print(events)  
        for event in events:  
            eventInfo=CalendarEventInfo(calendar.get_display_name(),event.icalendar_component["SUMMARY"],event.icalendar_component["DTSTART"].dt,event.icalendar_component["DTEND"].dt)
            events_result+=eventInfo.to_LLM()   
    return events_result

编写MCP核心代码-添加日程

main.py

python 复制代码
# 其他部分省略
@fastMCP.tool("create_event")  
async def creat_event(calendar_name:str,name:str,start_time:str,end_time:str,location:str=""):  
    """  
    创建日程(日期的格式是:2025-09-29T10:00:00)  
    :param calendar_name: 指定的日历名称(模糊查询),用户未指定时询问用户保存到哪个日历里  
    :param name: 日程名  
    :param start_time: 日程开始时间  
    :param end_time: 日程结束时间  
    :param location: 日程地理位置  
    :return: 完成状态  
    """    principal = client.principal()  
    calendars = principal.calendars()  
    new_start_time=to_datetime(start_time)  
    new_end_time=to_datetime(end_time)  
    calendar=None  
    for c in calendars:  
        if re.match(f"(.*){calendar_name}(.*)",c.get_display_name()):  
            calendar=c  
            break  
    if calendar is None:  
        return "未找到日历"  
    else: 
        event=calendar.save_event(summary=name,location=location,
                                  dtstart=new_start_time,dtend=new_end_time)  
        if event!=None:  
            return f"将日程{name}添加到日历{calendar.get_display_name()}成功"  
        else:  
            return "添加日程失败"

(待办事项的操作和日程操作的过程是一样的,只不过icalendar_component属性中的键有一些不一样。)

四、使用MCP服务器

  1. 打开CherryStudio

  2. 找到设置中的MCP

  3. 安装CherryStudio内部UV

    (我这里已经安装完了,所以是绿色的,未安装的话不是绿色的。)

  4. 配置服务器(点击添加下面的快速创建)

    命令那一栏写uv,其他的自己填写就行。

  5. 启动调试

四、部署

  1. "__main__"中的fastMCP.run(transport="stdio")中的stdio改成sse

  2. fastMCP.run()前添加fastMCP.settings.host="0.0.0.0"方便外网访问。

  3. 上传文件到服务器上

    1. 按照之前安装UV的步骤为服务器安装UV
    2. 将项目文件中的main.py、calendar_info.py(要保证目录结构)、pyproject.toml、config.json一起上传到服务器的项目目录中。
    3. 进入到项目中先创建一个虚拟环境uv venv
    4. 执行uv pip install .来安装整个项目的依赖环境
    5. 执行uv run main.py
    6. 如果提示缺少某个包,利用uv安装以一个就行`uv pip install xxx
  4. 修改Cherry Studio中的MCP服务器配置。将标准输出改成sse然后URL改成http://[ip地址]:[MCP端口,默认8000]/sse就可以了。

五、已知问题

  1. 所有工具的注释内容过多,实际并不需要这么多的描述。
  2. 消耗Token有点多。
相关推荐
l12345sy2 小时前
Day31_【 NLP _1.文本预处理 _(4)文本特征处理、文本数据增强】
人工智能·深度学习·自然语言处理
说私域2 小时前
开源AI智能名片链动2+1模式S2B2C商城小程序在公益课裂变法中的应用与影响研究
人工智能·小程序
0xCode 小新2 小时前
【C语言内存函数完全指南】:memcpy、memmove、memset、memcmp 的用法、区别与模拟实现(含代码示例)
linux·c语言·人工智能·深度学习·机器学习·容器·内存函数
Elastic 中国社区官方博客2 小时前
如何在 vscode 里配置 MCP 并连接到 Elasticsearch
大数据·人工智能·vscode·elasticsearch·搜索引擎·ai·mcp
三掌柜6663 小时前
2025三掌柜赠书活动第三十五期 AI辅助React Web应用开发实践:基于React 19和GitHub Copilot
前端·人工智能·react.js
机器之心3 小时前
强强联手!深度求索、寒武纪同步发布DeepSeek-V3.2模型架构和基于vLLM的模型适配源代码
人工智能·openai
机器之心3 小时前
Claude Sonnet 4.5来了!能连续编程30多小时、1.1万行代码
人工智能·openai
8K超高清3 小时前
汇世界迎全运 广州国际社区运动嘉年华举行,BOSMA博冠现场展示并分享与科技全运的故事
运维·服务器·网络·数据库·人工智能·科技
2401_841495643 小时前
【机器学习】朴素贝叶斯法
人工智能·python·数学·算法·机器学习·概率论·朴素贝叶斯法