DEV Community

Cover image for I Built an AI Agent That Writes My Daily Standup in Notion Automatically
Pablo Ifrรกn
Pablo Ifrรกn

Posted on

I Built an AI Agent That Writes My Daily Standup in Notion Automatically

Notion MCP Challenge Submission ๐Ÿง 

This is a submission for the Notion MCP Challenge

What I Built

Notion of Progress is an AI-powered standup agent that runs every morning and handles your entire daily standup automatically.

Every day it:

  1. Reads your Notion task database to find what was completed yesterday and what's active today
  2. Generates a concise Yesterday / Today / Blockers summary using Claude
  3. Autonomously writes a beautifully formatted standup page back into your Notion workspace via the Notion MCP server
  4. Posts a summary to Discord so your team stays in the loop

No forms. No copy-pasting. Just open Notion and your standup is already there.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     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     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

The result in Notion looks like this โ€” every single day, automatically:

Block Content
๐Ÿ“Š 3 completed ยท 4 active ยท 1 blocker
โœ… Yesterday Bullet points with direct links to completed tasks
๐Ÿ”จ Today Bullet points with direct links to active tasks
๐Ÿšง Blockers Red callout if blockers exist, gray if none

Video Demo

Watch the demo


Show us the Code

GitHub: github.com/elpic/notion-of-progress

The project is built in TypeScript using a ports and adapters architecture the core domain has zero knowledge of Notion, Claude, or any external system.

src/
โ”œโ”€โ”€ core/
โ”‚   โ”œโ”€โ”€ domain/types.ts          โ† TaskSummary, StandupSummary
โ”‚   โ”œโ”€โ”€ ports/                   โ† pure interfaces, no dependencies
โ”‚   โ””โ”€โ”€ standup.ts               โ† StandupService orchestrator
โ””โ”€โ”€ adapters/
    โ”œโ”€โ”€ notion/
    โ”‚   โ”œโ”€โ”€ NotionTaskRepository.ts      โ† reads Task DB
    โ”‚   โ””โ”€โ”€ NotionStandupRepository.ts   โ† writes standup pages
    โ”œโ”€โ”€ claude/
    โ”‚   โ””โ”€โ”€ ClaudeSummaryGenerator.ts    โ† calls Claude API
    โ”œโ”€โ”€ mcp/
    โ”‚   โ””โ”€โ”€ McpStandupAgent.ts           โ† Claude Agent SDK + Notion MCP
    โ””โ”€โ”€ discord/
        โ””โ”€โ”€ DiscordNotifier.ts           โ† posts to Discord webhook
Enter fullscreen mode Exit fullscreen mode

The key file is McpStandupAgent.ts this is where the MCP magic happens:

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;
}
Enter fullscreen mode Exit fullscreen mode

Phase 3 is where a Claude Opus 4.6 agent takes over. Instead of hand-coded Notion API calls, Claude autonomously navigates the workspace via MCP tools deciding whether to create a new page or update an existing one, deleting stale blocks, and appending fresh content.

Run it with --verbose to watch Claude think in real time:

๐Ÿ”ง [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/...
Enter fullscreen mode Exit fullscreen mode

How I Used Notion MCP

The Notion MCP server (@notionhq/notion-mcp-server) is the core of this project. It's what makes the agent approach possible and it fundamentally changes how you think about building on top of Notion.

The traditional approach vs the MCP approach

Before MCP, building a Notion integration meant writing a lot of brittle glue code:

// 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 });
}
Enter fullscreen mode Exit fullscreen mode

With MCP, you describe what you want and Claude figures out how to do it:

// 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',
  },
})) { ... }
Enter fullscreen mode Exit fullscreen mode

Claude handles the idempotency logic, block management, and page structure things that would take dozens of lines of manual code.

What MCP unlocks

  • Idempotency for free Claude checks if today's page exists and updates it instead of creating duplicates, without you writing that logic
  • Resilient to Notion API changes Claude adapts its tool usage at runtime rather than breaking on schema changes
  • Readable agent output with --verbose, you see exactly what Claude is doing and why, making debugging trivial
  • Dry-run mode mise run standup -- --dry-run previews the summary without touching Notion, perfect for testing

Running the MCP server locally

One important detail: the @notionhq/notion-mcp-server requires OAuth for the hosted version, but you can run it locally via stdio with an internal integration token โ€” no OAuth needed:

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',
      }),
    },
  },
},
Enter fullscreen mode Exit fullscreen mode

This was the key insight that made the whole project work spawning the MCP server as a local subprocess with the internal token in the headers.

Get started in 5 minutes

git clone https://github.com/elpic/notion-of-progress
cd notion-of-progress
npm install
cp .env.example .env
# Add NOTION_API_KEY and ANTHROPIC_API_KEY to .env
npm run setup   # creates Notion databases automatically
mise run standup  # run your first standup
Enter fullscreen mode Exit fullscreen mode

Top comments (0)