Server 不是「AI 服务」
一个 MCP Server 可以一行模型代码都没有。它只是「能力封装」:接收结构化参数,调用系统 API,返回结果。日历 MCP 里没有任何 LLM 调用。
AI技术 · 2026-09-09
大模型再聪明,也碰不到你电脑上的日历。MCP 就是那座桥。这篇文章拿日历当教材,把协议是什么、两个角色怎么分工、一次工具调用背后发生了什么、以及怎么亲手搭一个跨 Windows / macOS 的日历 MCP——从原理到落地一次讲透。贯穿全文的核心追求只有一句话:一套 Server,多个 Agent 端复用。
你在豆包办公里打下「明天下午 3 点和客户开会,持续一小时」,模型回复「好的,已记下,别忘了添加到日历」。它记得住,但它没有手。
这不是模型不够聪明,而是架构上缺了关键一环:大语言模型只有文本进出的接口。它能看到你的话、能生成回答,但碰不到你系统里的任何东西——日历、文件、邮件、数据库,全都隔着权限与操作系统的墙。想让它真正替你「做事」,就必须给它一条可以安全操作本机资源的通道。
过去解决这个问题有三条老路,代价各异:为每个应用单独接 API(开发成本高、每家一套协议);用浏览器插件模拟点击(脆弱、易碎、维护噩梦);把能力内嵌进模型厂商(封闭、不可迁移)。MCP(Model Context Protocol,模型上下文协议)是第四条路:它定义一套开放的标准协议,让任何 AI 应用都能以同一种方式调用任何工具提供方的能力。这也是本文的主角——我选择用「日历」这个每个人都用得上的场景,把它拆开当教材。
MCP 由 Anthropic 在 2024 年 11 月开源,2025 年 3 月被捐赠给 Linux 基金会旗下的 Joint Development Foundation 托管,随后 OpenAI、Google、Microsoft 相继宣布兼容。不到两年,它已经成为「让大模型连接外部世界」的事实标准。
理解它最顺手的类比是 USB-C 接口:在 USB-C 之前,每台手机一个充电口、每个设备一种线;USB-C 之后,一根线通吃所有设备。MCP 之于 AI 工具,就是充电口之于电子设备。它规定好「插座」长什么样(协议规范),从此模型应用和工具服务商各自按标准生产,插上就能用,不用再互相定制。
在深入角色之前,先记住一个总览:一次完整的 MCP 交互涉及四个组件——大模型(负责理解与决策)、MCP Host(宿主应用,提供对话界面)、MCP Client(宿主内的协议处理器,负责会话)、MCP Server(工具提供方,真正动手)。
学习 MCP 最容易卡住的,就是「Client 和 Server 到底谁是干什么的」。这个词和 Web 里的 Client/Server 直觉一致,但细节完全不同——Web 里 Client 是浏览器(人直接操作),Server 是网站(存数据、出页面);而在 MCP 里,Client 不是用户,Server 不是模型。先把这层窗户纸捅破。
| 组件 | 它是什么 | 它做什么 | 它不做什么 |
|---|---|---|---|
| 大语言模型 | 云端或本地的推理引擎 | 理解自然语言、决定是否调用工具、生成参数 | 不直接碰任何系统资源 |
| 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 是车间。
一个 MCP Server 可以一行模型代码都没有。它只是「能力封装」:接收结构化参数,调用系统 API,返回结果。日历 MCP 里没有任何 LLM 调用。
Client 是替模型跑腿的协议代理。模型说「我想写日历」,Client 负责翻译成 tools/call 报文发给 Server,再把结果翻译回给模型。它没有自己的意志。
在 stdio 模式下,Client 是父进程,Server 是子进程。Host 配置里写 Server 的启动命令,连接时由 Client 把 Server 拉起来,进程退出连接即断开——生命周期完全跟随宿主。
MCP 是标准协议,你写的日历 Server 可以同时被豆包办公、WorkBuddy、DeepSeek Harness 加载。能力与对话彻底解耦,这也是 MCP 最大的价值。
MCP 定义了三种「能力」,分清它们对理解协议很重要:
| 原语 | 方向 | 谁触发 | 典型例子 |
|---|---|---|---|
| Tools(工具) | 模型 → 外部世界 | 模型自主决定调用 | createCalendarEvent 写日历、searchCode 查代码 |
| Resources(资源) | 外部世界 → 模型 | 应用/用户读取 | 暴露一个数据库 schema、一份配置文件内容 |
| Prompts(提示词模板) | 模板 → 模型 | 用户主动触发 | 「按公司格式写周报」的固定指令模板 |
日历 MCP 用到的是 Tools——这是 MCP 里最核心、也最像「Agent 能力」的一类。模型看到工具清单后,自己判断要不要调、怎么调。
MCP 的所有消息都是 JSON-RPC 2.0 格式:jsonrpc(版本)、method(方法名)、params(参数)、id(请求编号,用于配对响应)。一次完整交互分四个阶段:
initialize,声明自己支持的协议版本;Server 返回自己的能力声明(比如「我支持 tools」)与身份信息。这一步决定后续能不能聊。name、description、inputSchema。模型正是靠这个清单知道「有个写日历的工具,参数是标题和起止时间」。tools/call,Server 执行真实动作,返回结果。这一步才是真正「动手」的地方。| 传输 | 工作方式 | 适用场景 | 典型特征 |
|---|---|---|---|
| stdio | Client 以子进程方式启动 Server,消息走 stdin/stdout 管道 | 本机工具、需要访问本地资源 | 无端口、无网络、进程生命周期一致 |
| HTTP / SSE | Server 作为网络服务,Client 通过 HTTP 连接 | 远程服务、多客户端共享、云端部署 | 需要地址、鉴权、公网/内网可达 |
这也回答了一个很常见的困惑:「我的日历 MCP 需要公网 IP 吗?」永远不需要。只要 Server 跑在用户本机、用 stdio 连接,数据全程不出这台电脑。真正会联网的是 Host 里的模型——它接收对话文本、返回解析结果,但那和「碰日历」是两回事。
回到最初的问题:为什么不能直接让豆包办公「帮我写日历」?因为两段工作性质完全不同:
「明天下午 3 点和客户开会」→ 提取标题、解析出具体时间、处理「明天」这类相对表达。这是自然语言理解,模型天生擅长。
调用 macOS Calendar / Windows Outlook 的系统 API,创建事件。这是操作系统级操作,需要本机权限,云端模型永远无法直接执行。
MCP 的价值就是把这两段拆开、各归其位:模型负责解析,本机 MCP Server 负责执行。模型看到工具清单后,把解析出的结构化参数(标题、起止时间)塞进 tools/call;Server 收到后调用系统 API,把结果「已写入 ✅」返回给模型,模型再组织成自然语言回复用户。
顺带说明平台差异的真相:macOS 与 Windows 的系统日历 API 完全不同(前者走 osascript/EventKit,后者走 Outlook COM),所以底层适配器必须分平台实现;但这是「Server 内部的事」,对上层协议完全透明——这正是适配器模式的价值。
下面是完整搭建路线。代码片段只给关键骨架,重点在「每一步解决什么问题」——理解了为什么,代码是水到渠成的事。
process.platform === 'darwin' 走 macOS 分支,win32 走 Windows 分支。这一步决定后续实例化哪个适配器。
const isMac = process.platform === 'darwin';
const adapter: CalendarAdapter =
isMac ? new MacCalendarAdapter()
: new WindowsCalendarAdapter();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 };
}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 并解析返回
}
}@modelcontextprotocol/sdk)把三个能力注册成标准工具,带上 description 和 inputSchema——这两样是模型决定「何时调、传什么参」的依据,写得越清楚,模型调用越准。
server.tool(
'createCalendarEvent',
'按标题与起止时间创建日历事件(时间用 ISO 8601)',
{ title: 'string', start: 'string', end: 'string' },
async (args) => adapter.createEvent(args)
);stderr。这是新手踩得最多的坑,没有之一。calendar-mcp-server(mac)与 calendar-mcp-server.exe(win)。mcp.json,DeepSeek Harness 用 yaml patch——配置格式不同,但指向的是同一个 Server。
{
"mcpServers": {
"local-calendar": {
"command": "/path/to/calendar-mcp-server",
"args": []
}
}
}createCalendarEvent——投稿截止日写一条(可带提前几天的提醒),会议日再写一条。链接里的文字 → 模型结构化 → 本机日历落账,全程不需要手打任何一个日期。| 症状 | 原因 | 解法 |
|---|---|---|
| 客户端连接失败 / 协议解析错误 | console.log 污染了 stdout | 所有日志改走 stderr;用 MCP SDK 的调试模式 |
| mac 上第一次运行被系统拦截 | 未签名二进制触发 Gatekeeper | 系统设置 → 隐私与安全性 → 允许运行(实验期正常) |
| 创建事件时系统弹权限框 | mac 自动化权限 / Outlook COM 安全弹窗 | 正常系统管控,授权一次即可;正式发布前做签名 |
| 模型参数传错 / 工具不触发 | description 与 inputSchema 写得太模糊 | 把工具说明写成「给模型看的手册」,字段名用语义化英文 |
| 时间总是偏一天 / 时区错 | 本地时间与 ISO 转换丢了时区 | 统一用 ISO 8601 + 本地时区偏移,避免裸字符串 |
调试工具建议:用官方 MCP Inspector(可视化连接、查看 tools/list 结果、手动发 tools/call)——它把上面四个阶段的报文全部可视化,是排查问题的最佳起点。
如果想把 MCP 学到能独立开发任意工具,按这个顺序做三个实验,难度递进:
本文配套的完整 TS 日历 MCP Server 已打包发布,读者可以直接下载到自己的电脑上部署使用,不需要从零写代码。一套 Server 写好后,可同时挂载到 WorkBuddy、豆包办公、Claude Desktop 等任意支持 MCP 的 AI 客户端。
~/calendar-mcp)cd ~/calendar-mcp && chmod +x install.sh && ./install.sh,脚本自动检查 Node.js 环境、安装依赖、编译 TypeScript、验证 MCP 协议连通性local-calendar 配置(install.sh 完成后会打印完整配置,直接复制即可),完全退出并重启 AI 客户端,在 MCP 连接器列表中信任 local-calendar| 客户端 | 配置文件位置 | 传输类型 |
|---|---|---|
| WorkBuddy | ~/.workbuddy/mcp.json | stdio |
| 豆包办公(桌面端) | 设置 → MCP 连接器 | stdio |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json | stdio |
| DeepSeek Harness | yaml patch 配置 | stdio |
部署完成后,直接在 AI 对话框里说「帮我把明天下午3点的项目评审会写入日历,持续1小时,提前15分钟提醒」即可。进阶用法:把一条会议通知的微信公众号文章链接发给 AI,它会自动读取文章、提取投稿截止日与会议日、连续创建两条日历事件。完整使用说明、常见问题排查、卸载方法见项目内的 README.md。
加载中…