我构建了一个 AI 代理,自动在 Notion 中撰写我的每日站会

发布: (2026年3月16日 GMT+8 11:06)
6 分钟阅读
原文: Dev.to

Source: Dev.to

它每天的工作

  1. 获取任务 – 读取你的 Notion 任务数据库,查找昨天完成的任务以及今天仍在进行的任务。
  2. 生成摘要 – 使用 Claude 创建简洁的 Yesterday / Today / Blockers(昨天/今天/阻碍)摘要。
  3. 写入页面 – 通过 Notion MCP 服务器,自动将精美排版的站立会议页面写回你的 Notion 工作区。
  4. 通知 – 将摘要发布到 Discord,让团队保持同步。

无需表单。无需复制粘贴。只需打开 Notion,你的站立会议就已经在那里。

架构图

┌─────────────────────────────────────────────────────────────┐
│                     Notion of Progress                      │
│                                                             │
│  1. Fetch Tasks      2. Generate Summary   3. Write Page    │
│  ─────────────       ───────────────── ────────────────   │
│  Notion Task DB  →   Claude API        →   Notion MCP   →│
│  (typed client)      (Sonnet 4.6)          (Opus 4.6   │
│                                             Agent SDK)│
│                                                             │
│                                         4. Notify  ↓    │
│                                         Discord Webhook   │
└─────────────────────────────────────────────────────────────┘

Example result in Notion

Block
Content
📊3 已完成 · 4 正在进行 · 1 阻碍
Yesterday带有直接链接的已完成任务的项目符号
🔨 Today带有直接链接的正在进行任务的项目符号
🚧 Blockers如果存在阻碍则使用红色提示,否则使用灰色提示

Repository

GitHub:

该项目使用 TypeScript 构建,采用 ports‑and‑adapters 架构;核心领域对 Notion、Claude 或任何外部系统一无所知。

src/
├── core/
│   ├── domain/types.ts          ← TaskSummary, StandupSummary
│   ├── ports/                   ← 纯接口,无依赖
│   └── standup.ts               ← StandupService 编排器
└── adapters/
    ├── notion/
    │   ├── NotionTaskRepository.ts      ← 读取任务数据库
    │   └── NotionStandupRepository.ts   ← 写入站会页面
    ├── claude/
    │   └── ClaudeSummaryGenerator.ts    ← 调用 Claude API
    ├── mcp/
    │   └── McpStandupAgent.ts           ← Claude Agent SDK + Notion MCP
    └── discord/
        └── DiscordNotifier.ts           ← 推送至 Discord webhook

关键文件 – McpStandupAgent.ts

export async function runMcpStandupAgent({ verbose = false, dryRun = false } = {}) {
  // Phase 1: fetch tasks via typed Notion client (reliable)
  const { completed, active } = await taskRepo.fetchTasks();

  // Phase 2: generate summary with Claude
  const summary = await summarizer.generateSummary(completed, active);

  // Phase 3: write the page autonomously via Notion MCP
  const url = await writeStandupViaMcp(summary, completed, active, verbose);

  // Phase 4: notify Discord
  await notifyDiscord(summary, url, todayFormatted());

  return url;
}

Phase 3 是 Claude Opus 4.6 代理接管的地方。它不再使用手写的 Notion API 调用,而是通过 MCP 工具自主在工作区中导航,决定是创建新页面还是更新已有页面,删除陈旧块并追加新内容。

使用 --verbose 运行可实时观察 Claude 的思考过程:

🔧 [MCP] [NOTION API] Post Search
💭 [Claude] A page already exists for today. I'll update it instead of creating a new one.
🔧 [MCP] [NOTION API] Get Block Children
🔧 [MCP] [NOTION API] Patch Page
💭 [Claude] Properties updated. Now deleting 14 old blocks...
🔧 [MCP] [NOTION API] Delete A Block
🔧 [MCP] [NOTION API] Delete A Block
...
🔧 [MCP] [NOTION API] Patch Block Children
💭 [Claude] Done! Here's the standup page: https://notion.so/...

为什么 MCP 很重要

传统方法(手写胶水代码)

// Traditional: you write every API call by hand
const existing = await notion.databases.query({ database_id, filter });
if (existing.results.length > 0) {
  const blocks = await notion.blocks.children.list({ block_id });
  await Promise.all(
    blocks.results.map(b => notion.blocks.delete({ block_id: b.id }))
  );
  await notion.pages.update({ page_id, properties });
  await notion.blocks.children.append({ block_id, children });
} else {
  await notion.pages.create({ parent, properties, children });
}

MCP 方法(Claude 驱动)

// MCP approach: Claude navigates the Notion API autonomously
for await (const message of query({
  prompt: `Write today's standup page in the Standup Log DB.
           Check if a page exists for ${todayISO()} — update it if so, create it if not.
           Use callout blocks with these sections: Yesterday, Today, Blockers.`,
  options: {
    mcpServers: {
      notion: {
        command: 'npx',
        args: ['-y', '@notionhq/notion-mcp-server'],
        env: {
          OPENAPI_MCP_HEADERS: JSON.stringify({
            Authorization: `Bearer ${NOTION_API_KEY}`,
            'Notion-Version': '2022-06-28',
          }),
        },
      },
    },
    allowedTools: ['mcp__notion__*'],
    permissionMode: 'acceptEdits',
  },
})) { /* … */ }

好处

功能传统方式MCP
幂等性需要手动检查Claude 自动检查今天的页面是否存在并自动更新
对 API 变更的弹性模式变化会导致崩溃Claude 在运行时自行适配工具使用
可读性 / 调试大量样板代码冗余模式会准确显示 Claude 正在做什么以及原因
干运行 / 冗余需要自定义日志内置 --dryRun--verbose 参数

快速入门

# 克隆仓库
git clone https://github.com/elpic/notion-of-progress
cd notion-of-progress

# 安装依赖
npm install

# 设置环境变量
cp .env.example .env
# 编辑 .env 并添加 NOTION_API_KEY 和 ANTHROPIC_API_KEY

# 自动创建 Notion 数据库
npm run setup

运行 Stand‑up

mise run standup

干运行模式

mise run standup -- --dry-run

--dry-run 在不触碰 Notion 的情况下预览摘要——非常适合测试。

MCP 服务器配置

@notionhq/notion-mcp-server 通常在托管版本中需要 OAuth,但你可以通过 stdio 使用内部集成令牌在本地运行——无需 OAuth。

mcpServers: {
  notion: {
    command: 'npx',
    args: ['-y', '@notionhq/notion-mcp-server'],
    env: {
      OPENAPI_MCP_HEADERS: JSON.stringify({
        Authorization: `Bearer ${config.notion.apiKey}`,
        'Notion-Version': '2022-06-28',
      }),
    },
  },
},

关键洞察: 将 MCP 服务器作为本地子进程启动,并在请求头中携带内部令牌,使整个项目能够无缝运行。

0 浏览
Back to Blog

相关文章

阅读更多 »