AI技术 · 2026-09-09

通过日历 MCP 学会 MCP

MCP Client 与 MCP Server 一次讲清,并一步步搭建一套 Server、多 Agent 复用的跨平台日历 MCP

大模型再聪明,也碰不到你电脑上的日历。MCP 就是那座桥。这篇文章拿日历当教材,把协议是什么、两个角色怎么分工、一次工具调用背后发生了什么、以及怎么亲手搭一个跨 Windows / macOS 的日历 MCP——从原理到落地一次讲透。贯穿全文的核心追求只有一句话:一套 Server,多个 Agent 端复用。

你在豆包办公里打下「明天下午 3 点和客户开会,持续一小时」,模型回复「好的,已记下,别忘了添加到日历」。它记得住,但它没有手。

这不是模型不够聪明,而是架构上缺了关键一环:大语言模型只有文本进出的接口。它能看到你的话、能生成回答,但碰不到你系统里的任何东西——日历、文件、邮件、数据库,全都隔着权限与操作系统的墙。想让它真正替你「做事」,就必须给它一条可以安全操作本机资源的通道。

过去解决这个问题有三条老路,代价各异:为每个应用单独接 API(开发成本高、每家一套协议);用浏览器插件模拟点击(脆弱、易碎、维护噩梦);把能力内嵌进模型厂商(封闭、不可迁移)。MCP(Model Context Protocol,模型上下文协议)是第四条路:它定义一套开放的标准协议,让任何 AI 应用都能以同一种方式调用任何工具提供方的能力。这也是本文的主角——我选择用「日历」这个每个人都用得上的场景,把它拆开当教材。

一、MCP 是什么:AI 世界的 USB-C

MCP 由 Anthropic 在 2024 年 11 月开源,2025 年 3 月被捐赠给 Linux 基金会旗下的 Joint Development Foundation 托管,随后 OpenAI、Google、Microsoft 相继宣布兼容。不到两年,它已经成为「让大模型连接外部世界」的事实标准。

理解它最顺手的类比是 USB-C 接口:在 USB-C 之前,每台手机一个充电口、每个设备一种线;USB-C 之后,一根线通吃所有设备。MCP 之于 AI 工具,就是充电口之于电子设备。它规定好「插座」长什么样(协议规范),从此模型应用和工具服务商各自按标准生产,插上就能用,不用再互相定制。

别被术语吓到。MCP 不神秘:它本质上是「JSON-RPC 2.0 消息规范 + 一组约定的能力原语」。你写的服务跑起来、暴露几个工具,模型应用连上来就能调用——原理和 Web API 很像,只是这套协议专门为 AI 场景设计。

在深入角色之前,先记住一个总览:一次完整的 MCP 交互涉及四个组件——大模型(负责理解与决策)、MCP Host(宿主应用,提供对话界面)、MCP Client(宿主内的协议处理器,负责会话)、MCP Server(工具提供方,真正动手)。

MCP 全景:大语言模型、MCP Host 内含 Client、MCP Server、本机系统资源的四层结构
图 1 · MCP 全景。模型只负责「理解与决定」,真正碰本机资源的是挂在本机的 MCP Server;Client 是夹在中间的协议会话层。

二、两个角色必须分清:MCP Client 与 MCP Server

学习 MCP 最容易卡住的,就是「Client 和 Server 到底谁是干什么的」。这个词和 Web 里的 Client/Server 直觉一致,但细节完全不同——Web 里 Client 是浏览器(人直接操作),Server 是网站(存数据、出页面);而在 MCP 里,Client 不是用户,Server 不是模型。先把这层窗户纸捅破。

2.1 职责对照:一张表讲清

组件它是什么它做什么它不做什么
大语言模型云端或本地的推理引擎理解自然语言、决定是否调用工具、生成参数不直接碰任何系统资源
MCP Host宿主应用(豆包办公、WorkBuddy、Claude Desktop、DeepSeek Harness、自研 Agent)提供对话框/界面,管理多个 Client 会话不直接与 Server 逐条收发协议报文
MCP Client宿主应用内的协议处理器(1 个连接 = 1 个 Client)负责与 Server 建立连接、握手协商、发现工具、发起调用、处理返回不提供界面,不承载模型推理
MCP Server工具提供方,运行在本机或远端暴露 Tools / Resources / Prompts,执行真实动作(写日历、读文件)不主动联网、不主动对话、不含模型推理逻辑

大多数人日常感知到的是「Host」和「Server」:Host 是你看到的那个 App,Server 是你写的那个程序。而 Client 藏在 Host 内部——当用户说「调用日历工具」,是 Host 里的 Client 去启动 Server 进程、发消息、收结果。Host 是容器,Client 是管道工,Server 是车间。

2.2 最容易搞混的三件事

Server 不是「AI 服务」

一个 MCP Server 可以一行模型代码都没有。它只是「能力封装」:接收结构化参数,调用系统 API,返回结果。日历 MCP 里没有任何 LLM 调用。

Client 不是「用户」

Client 是替模型跑腿的协议代理。模型说「我想写日历」,Client 负责翻译成 tools/call 报文发给 Server,再把结果翻译回给模型。它没有自己的意志。

谁启动谁:Client 拉起 Server

在 stdio 模式下,Client 是父进程,Server 是子进程。Host 配置里写 Server 的启动命令,连接时由 Client 把 Server 拉起来,进程退出连接即断开——生命周期完全跟随宿主。

同一个 Server 可被多个 Client 挂载

MCP 是标准协议,你写的日历 Server 可以同时被豆包办公、WorkBuddy、DeepSeek Harness 加载。能力与对话彻底解耦,这也是 MCP 最大的价值。

2.3 三种能力原语:Tools / Resources / Prompts

MCP 定义了三种「能力」,分清它们对理解协议很重要:

原语方向谁触发典型例子
Tools(工具)模型 → 外部世界模型自主决定调用createCalendarEvent 写日历、searchCode 查代码
Resources(资源)外部世界 → 模型应用/用户读取暴露一个数据库 schema、一份配置文件内容
Prompts(提示词模板)模板 → 模型用户主动触发「按公司格式写周报」的固定指令模板

日历 MCP 用到的是 Tools——这是 MCP 里最核心、也最像「Agent 能力」的一类。模型看到工具清单后,自己判断要不要调、怎么调。

三、一次调用发生了什么:协议与传输

MCP 的所有消息都是 JSON-RPC 2.0 格式:jsonrpc(版本)、method(方法名)、params(参数)、id(请求编号,用于配对响应)。一次完整交互分四个阶段:

  1. 握手(initialize):Client 先发 initialize,声明自己支持的协议版本;Server 返回自己的能力声明(比如「我支持 tools」)与身份信息。这一步决定后续能不能聊。
  2. 就绪通知(notifications/initialized):Client 通知 Server「握手完成,可以正式开始」,这是一条不需要响应的通知。
  3. 发现工具(tools/list):Client 问「你提供哪些工具?」Server 返回每个工具的 name、description、inputSchema。模型正是靠这个清单知道「有个写日历的工具,参数是标题和起止时间」。
  4. 调用工具(tools/call):模型构造参数,Client 发送 tools/call,Server 执行真实动作,返回结果。这一步才是真正「动手」的地方。
MCP 工具调用的四阶段消息序列:initialize、notifications/initialized、tools/list、tools/call
图 2 · 一次 MCP 工具调用的消息序列。只有最后一步 tools/call 真正执行动作,前面都是「自我介绍」。

3.1 传输方式:stdio 与 HTTP

传输工作方式适用场景典型特征
stdioClient 以子进程方式启动 Server,消息走 stdin/stdout 管道本机工具、需要访问本地资源无端口、无网络、进程生命周期一致
HTTP / SSEServer 作为网络服务,Client 通过 HTTP 连接远程服务、多客户端共享、云端部署需要地址、鉴权、公网/内网可达
为什么本机 MCP 默认用 stdio?三点:其一,安全——不监听任何端口,外网根本扫不到,也不需要公网 IP 和域名;其二,生命周期——Server 随 Client 启停,不会留下僵尸进程;其三,权限——Server 以本机用户身份运行,天然能调用系统日历 API。读本地资源这件事,根本不该走网络。

这也回答了一个很常见的困惑:「我的日历 MCP 需要公网 IP 吗?」永远不需要。只要 Server 跑在用户本机、用 stdio 连接,数据全程不出这台电脑。真正会联网的是 Host 里的模型——它接收对话文本、返回解析结果,但那和「碰日历」是两回事。

四、为什么日历必须做成 MCP

回到最初的问题:为什么不能直接让豆包办公「帮我写日历」?因为两段工作性质完全不同:

第一段:理解一句话

「明天下午 3 点和客户开会」→ 提取标题、解析出具体时间、处理「明天」这类相对表达。这是自然语言理解,模型天生擅长。

第二段:写入系统日历

调用 macOS Calendar / Windows Outlook 的系统 API,创建事件。这是操作系统级操作,需要本机权限,云端模型永远无法直接执行。

MCP 的价值就是把这两段拆开、各归其位:模型负责解析,本机 MCP Server 负责执行。模型看到工具清单后,把解析出的结构化参数(标题、起止时间)塞进 tools/call;Server 收到后调用系统 API,把结果「已写入 ✅」返回给模型,模型再组织成自然语言回复用户。

一个新手常犯的设计错误:把 LLM 塞进 Server。有人会想「让 Server 自己解析『明天下午三点』」。这违背了职责分离:Server 一旦依赖模型,就变成联网服务、有了网络依赖和鉴权负担,不再「纯本地、纯工具」。正确做法是让模型做解析、Server 只做执行——Server 的输入输出都是结构化数据,不是自然语言。

顺带说明平台差异的真相:macOS 与 Windows 的系统日历 API 完全不同(前者走 osascript/EventKit,后者走 Outlook COM),所以底层适配器必须分平台实现;但这是「Server 内部的事」,对上层协议完全透明——这正是适配器模式的价值。

五、一步步搭建:TS 版日历 MCP

下面是完整搭建路线。代码片段只给关键骨架,重点在「每一步解决什么问题」——理解了为什么,代码是水到渠成的事。

  1. 初始化工程并做平台探测。用 TypeScript 建一个普通 Node 项目,入口处先判断操作系统:process.platform === 'darwin' 走 macOS 分支,win32 走 Windows 分支。这一步决定后续实例化哪个适配器。
    const isMac = process.platform === 'darwin';
    const adapter: CalendarAdapter =
      isMac ? new MacCalendarAdapter()
            : new WindowsCalendarAdapter();
  2. 定义统一接口 CalendarAdapter。这是整个设计的核心抽象:上层(MCP 工具注册)只依赖接口,不关心底下是 mac 还是 win。固定三个能力:探测可用日历、列出账户、创建事件。
    interface CalendarAdapter {
      detectCalendar(): { type: string; accounts: string[] };
      listCalendars(): string[];
      createEvent(input: {
        title: string;
        start: string;  // ISO 8601
        end: string;
        location?: string;
        reminder?: number; // 分钟
      }): { ok: boolean; id?: string; error?: string };
    }
  3. 实现两个适配器。mac 版用 osascript 驱动 Apple Calendar;Windows 版用 COM 驱动 Outlook。两者都返回统一的 createEvent 结果。这是唯一「平台相关」的代码,被接口完全隔离。
    // Mac 适配器内部示意(实际通过 osascript 子进程调用)
    class MacCalendarAdapter implements CalendarAdapter {
      async createEvent(input) {
        const script = `tell application "Calendar"
          make new event at end with properties {summary:"${input.title}",
            start date:${isoToAppleDate(input.start)}}
        end tell`;
        // spawn osascript 并解析返回
      }
    }
  4. 注册 MCP 工具。用 MCP SDK(TypeScript 版 @modelcontextprotocol/sdk)把三个能力注册成标准工具,带上 description 和 inputSchema——这两样是模型决定「何时调、传什么参」的依据,写得越清楚,模型调用越准。
    server.tool(
      'createCalendarEvent',
      '按标题与起止时间创建日历事件(时间用 ISO 8601)',
      { title: 'string', start: 'string', end: 'string' },
      async (args) => adapter.createEvent(args)
    );
  5. 守住 stdio 纪律。stdio 模式下,stdout 只能输出 MCP 协议 JSON,任何 console.log 都会污染管道导致客户端解析失败。调试日志一律走 stderr。这是新手踩得最多的坑,没有之一。
  6. 打包成 mac / win 两个二进制。源码只有一套,但打包产物必须分平台——mac 二进制不能在 Windows 跑,反之亦然。用 pkg / nexe 等工具,同一份代码分别产出 calendar-mcp-server(mac)与 calendar-mcp-server.exe(win)。
  7. 挂载到任意 MCP 客户端。把二进制路径写进客户端的 MCP 配置。豆包办公 / WorkBuddy 用 mcp.json,DeepSeek Harness 用 yaml patch——配置格式不同,但指向的是同一个 Server。
    {
      "mcpServers": {
        "local-calendar": {
          "command": "/path/to/calendar-mcp-server",
          "args": []
        }
      }
    }
跨平台日历 MCP 架构:三个客户端复用同一个 TS MCP Server,内部按平台分支到 Mac/Windows 适配器
图 3 · 一套 Server 被三个客户端复用。切换客户端只改配置文件,Server 一行代码不用动。
时间解析交给谁?「明天下午 3 点」这类表达由模型解析成具体 ISO 时间;Server 只接受结构化时间。所以歧义追问(「您说的下周四是 9 月 18 日吗?」)是 Host 里模型的对话能力,不是 Server 的职责——又一次职责分离。
进阶场景:扔一条链接进去。这套日历 MCP 接好后,把一条会议通知的微信公众号文章链接直接丢给 Agent(豆包办公 / WorkBuddy 自带读取网页内容的能力):模型会从文章里提取会议名称、会议日期与投稿截止日,然后调用两次 createCalendarEvent——投稿截止日写一条(可带提前几天的提醒),会议日再写一条。链接里的文字 → 模型结构化 → 本机日历落账,全程不需要手打任何一个日期。

六、调试与常见坑

症状原因解法
客户端连接失败 / 协议解析错误console.log 污染了 stdout所有日志改走 stderr;用 MCP SDK 的调试模式
mac 上第一次运行被系统拦截未签名二进制触发 Gatekeeper系统设置 → 隐私与安全性 → 允许运行(实验期正常)
创建事件时系统弹权限框mac 自动化权限 / Outlook COM 安全弹窗正常系统管控,授权一次即可;正式发布前做签名
模型参数传错 / 工具不触发description 与 inputSchema 写得太模糊把工具说明写成「给模型看的手册」,字段名用语义化英文
时间总是偏一天 / 时区错本地时间与 ISO 转换丢了时区统一用 ISO 8601 + 本地时区偏移,避免裸字符串

调试工具建议:用官方 MCP Inspector(可视化连接、查看 tools/list 结果、手动发 tools/call)——它把上面四个阶段的报文全部可视化,是排查问题的最佳起点。

七、学习路线与建议实验

如果想把 MCP 学到能独立开发任意工具,按这个顺序做三个实验,难度递进:

  1. Echo Server:写一个返回入参原样的工具,跑通「握手 → 发现 → 调用」全链路,熟悉 SDK 与 Inspector。
  2. 文件工具:做一个「列出目录 / 读取文件」的工具,掌握 stdio 纪律与参数校验——此时你已经能封装任何只读能力。
  3. 日历 / 系统工具:回到本文的日历案例,加入平台适配层。完成后你就掌握了「模型决定、本地执行」的完整范式,可以举一反三到邮件、浏览器、数据库、硬件设备。
一句话总结全文:MCP 把「AI 的能力」做成了标准插座——Host 提供对话,Client 负责协议会话,Server 提供真实能力,模型负责理解和决策。你只需要写好 Server,它就能被任意支持 MCP 的应用复用;日历只是第一个例子,同样的模式可以复制到任何本机能力。

八、下载与部署:读者可直接使用

本文配套的完整 TS 日历 MCP Server 已打包发布,读者可以直接下载到自己的电脑上部署使用,不需要从零写代码。一套 Server 写好后,可同时挂载到 WorkBuddy、豆包办公、Claude Desktop 等任意支持 MCP 的 AI 客户端。

下载地址:calendar-mcp.zip(约 45KB,含完整 TypeScript 源码 + macOS 一键安装脚本 + README 部署文档 + MCP 连通性测试脚本)

部署三步(macOS)

  1. 下载解压:下载 zip 后解压到任意目录(推荐 ~/calendar-mcp)
  2. 一键安装:终端执行 cd ~/calendar-mcp && chmod +x install.sh && ./install.sh,脚本自动检查 Node.js 环境、安装依赖、编译 TypeScript、验证 MCP 协议连通性
  3. 配置到 AI 客户端:编辑 mcp.json 添加 local-calendar 配置(install.sh 完成后会打印完整配置,直接复制即可),完全退出并重启 AI 客户端,在 MCP 连接器列表中信任 local-calendar

支持的 AI 客户端

客户端配置文件位置传输类型
WorkBuddy~/.workbuddy/mcp.jsonstdio
豆包办公(桌面端)设置 → MCP 连接器stdio
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonstdio
DeepSeek Harnessyaml patch 配置stdio
系统要求:当前版本完整支持 macOS(Apple Calendar,通过 osascript 原生调用);Windows 适配器接口已预留,可参考项目内 README 扩展。需要 Node.js 18+(install.sh 会自动检查并提示安装方式)。首次使用时 macOS 会弹出权限请求「XX 想要控制日历」,点击允许即可。

部署完成后,直接在 AI 对话框里说「帮我把明天下午3点的项目评审会写入日历,持续1小时,提前15分钟提醒」即可。进阶用法:把一条会议通知的微信公众号文章链接发给 AI,它会自动读取文章、提取投稿截止日与会议日、连续创建两条日历事件。完整使用说明、常见问题排查、卸载方法见项目内的 README.md。

参考资料

  1. Model Context Protocol 官方文档与规范 — modelcontextprotocol.io
  2. Anthropic:Introducing the Model Context Protocol — anthropic.com/news/model-context-protocol
  3. MCP TypeScript SDK(官方,本教程实现所依据) — github.com/modelcontextprotocol/typescript-sdk
  4. MCP 官方快速上手(Quickstart for TypeScript) — modelcontextprotocol.io/quickstart
  5. MCP 加入 Linux 基金会联合开发基金会托管公告 — linuxfoundation.org

评论

加载中…

0 / 500