"Life is Full of Possibilities" - Soul, 2020

IT (프론트엔드 외)

Notion AI 대신 무료로 페이지 요약하기 (feat. MCP, Claude)

m2ndy 2026. 1. 28. 23:17

 

 

 

 

Notion 교육 요금제로 Notion AI를 잘 쓰고 있었는데.. 2025년 12월부터 더 상위 요금제를 사용해야만 Notion AI 사용이 가능하더라구요.

 

 

 

 

제가 목표하는 기능은 다음과 같습니다.

 

1. AI는 페이지의 기능을 요약할 수 있어야 한다.

2. AI가 요약한 내용을 페이지 속성의 "요약" 텍스트 필드에 입력할 수 있어야 한다.

 

 

그래서 Notion 유료 요금제 대신, Claude MCP를 연동해 AI 기능을 사용할 수 있도록 구축했습니다.

사용한 Claude 요금제는 무료 플랜이라 비용이 전혀 들지 않습니다. (단, Claude는 특정 시간 동안에 사용할 수 있는 쿼터에 제한이 있어 할당량이 끝나면 쿼터가 채워질 때까지 기다려야 합니다..)

 

 

 

 

 

Notion과 Claude MCP 연동하기

 

 

1. Claude Desktop 설치

 

아래 링크에서 클로드 데스크탑 버전을 설치합니다.

https://claude.com/download

 

Download Claude | Claude

Download Claude for desktop and mobile. Access AI assistance natively on Mac, Windows, iOS, and Android across all your devices.

claude.com

 

 

 

 

2. Notion API Key 발급

 

아래 링크에서 Notion API Key를 발급받습니다.

https://www.notion.so/profile/integrations/internal

 

The AI workspace that works for you. | Notion

Build custom agents, search across all your apps, and automate busywork. The AI workspace where teams get more done, faster.

www.notion.com

 

 

API Key가 사용될 워크스페이스를 선택하고, 수행 가능한 역할을 체크합니다.

이 기능을 구현하기 위해서는 읽기, 수정, 생성 기능이 필요하므로 아래 항목들을 모두 체크합니다. (읽기 외에 업데이트 및 입력 권한까지 열어두는 것은 MCP가 잘못된 글을 편집할 수 있다는 사실을 반드시 유념한 뒤 진행합니다.)

 

 

이제 프라이빗 API 통합 시크릿 키가 발행되면 복사해두었다가 4번의 Claude MCP 설정에 사용하면 됩니다.

 

 

 

3. 사용할 기능에 대한 함수 만들기

 

Node.js와 Javascript를 이용해 함수를 만들었습니다.

먼저, 원하는 위치에 폴더를 만들고 해당 위치에서 터미널을 켭니다.

 

저는 {원하는 경로}/mcp/notion/ 폴더를 생성했습니다.

이제 이곳에서 터미널을 켠 다음, 다음 명령어를 입력해 npm을 초기화합니다.

npm init -y

 

 

package.json에 다음 내용을 입력합니다.

 

{
  "name": "notion",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.25.3",
    "@notionhq/client": "^5.8.0",
    "zod": "^4.3.6"
  }
}

 

 

mcp에 연결하기 위해 @modelcontextprotocol/sdk, @notionhq/client 라이브러리를 사용했습니다.

추가로 타입 안정성을 위해 zod 패키지도 추가하여 타입을 검증했습니다.

 

 

이어서 package.json 파일이 위치한 곳에 index.js 파일을 생성합니다.

이곳에서는 라이브러리들을 활용하여 MCP 서버에 연결합니다.

 

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { Client } from '@notionhq/client';
import { z } from 'zod';

// Notion API 클라이언트 초기화
const notion = new Client({ auth: process.env.NOTION_API_KEY });

const server = new Server(
  { name: 'my-notion-server', version: '1.0.0' },
  { capabilities: { tools: {} } },
);

// 1. 툴 정의 (검색, 읽기, 블록 추가, 속성 업데이트)
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'search_pages',
        description: 'Notion에서 페이지를 검색하여 ID와 제목을 반환합니다.',
        inputSchema: z.object({
          query: z.string().describe('검색할 키워드'),
        }),
      },
      {
        name: 'read_page_content',
        description: '페이지 ID를 받아 해당 페이지의 텍스트 내용을 읽어옵니다.',
        inputSchema: z.object({
          page_id: z.string(),
        }),
      },
      {
        name: 'append_summary_block',
        description:
          '페이지 맨 아래에 요약된 내용을 텍스트 블록으로 추가합니다.',
        inputSchema: z.object({
          page_id: z.string(),
          summary_text: z.string().describe('추가할 요약 내용'),
        }),
      },
      {
        name: 'update_summary_property',
        description:
          "페이지의 특정 속성에 텍스트를 업데이트합니다.",
        inputSchema: z.object({
          page_id: z.string(),
          summary_text: z.string(),
          property_name: z
            .string()
            .default('{속성 이름}')
        }),
      },
    ],
  };
});

// 2. 툴 실행
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    // [검색]
    if (name === 'search_pages') {
      const response = await notion.search({
        query: args.query,
        filter: { property: 'object', value: 'page' },
        page_size: 5,
      });
      const results = response.results.map((page) => ({
        id: page.id,
        title:
          page.properties.Name?.title[0]?.plain_text ||
          page.properties.title?.title[0]?.plain_text ||
          '제목 없음',
        url: page.url,
      }));
      return {
        content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
      };
    }

    // [읽기]
    if (name === 'read_page_content') {
      const blocks = await notion.blocks.children.list({
        block_id: args.page_id,
      });
      const textContent = blocks.results
        .map((block) => {
          const type = block.type;
          if (block[type].rich_text) {
            return block[type].rich_text.map((t) => t.plain_text).join('');
          }
          return '';
        })
        .filter((text) => text.length > 0)
        .join('\n');

      return { content: [{ type: 'text', text: textContent || '내용 없음' }] };
    }

    // [쓰기 - 블록 추가]
    if (name === 'append_summary_block') {
      await notion.blocks.children.append({
        block_id: args.page_id,
        children: [
          {
            object: 'block',
            type: 'heading_3',
            heading_3: { rich_text: [{ text: { content: 'AI 요약' } }] },
          },
          {
            object: 'block',
            type: 'paragraph',
            paragraph: {
              rich_text: [{ text: { content: args.summary_text } }],
            },
          },
        ],
      });
      return {
        content: [{ type: 'text', text: '성공: 요약 블록이 추가되었습니다.' }],
      };
    }

    // [쓰기 - 속성 업데이트]
    if (name === 'update_summary_property') {
      await notion.pages.update({
        page_id: args.page_id,
        properties: {
          [args.property_name]: {
            rich_text: [{ text: { content: args.summary_text } }],
          },
        },
      });
      return {
        content: [
          { type: 'text', text: '성공: 요약 속성이 업데이트되었습니다.' },
        ],
      };
    }
  } catch (error) {
    return {
      content: [{ type: 'text', text: `에러 발생: ${error.message}` }],
      isError: true,
    };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

 

 

 


라이브러리와 코드를 반영하기 위해 다시 npm 의존성을 업데이트합니다.

npm install

 

 

 

 

4. Claude MCP 설정

 

윈도우 사용자라면 C:\Users\{사용자이름}\AppData\Roaming\Claude 경로에,

Mac 사용자라면 ~/Library/Application Support/Claude 경로에

claude_desktop_config.json 파일을 만들어 MCP 설정을 합니다.

 

앞서 만들어둔 index.js가 위치한 파일 위치를 args에 추가하고 발급 받은 Notion API Key도 추가합니다.

{
  "mcpServers": {
    "notion": {
      "command": "node",
      "args": [
        "{index.js가 위치한 파일 위치 - ex. C:/Users/user/mcp/notion/index.js}"
      ],
      "env": {
        "NOTION_API_KEY": "{발급받은 NOTION API KEY}"
      }
    }
  }
}

 

 

 

 

5. Claude Desktop 실행

 

Claude를 완전히 종료한 뒤 재시작하여 실행합니다. (이때 백그라운드에서 동작하고 있는 Claude까지 모두 종료한 뒤 재실행을 해야 합니다!)

 

 

 

Claude에게 원하는 목적을 달성하기 위한 프롬프트를 입력합니다. 구체적일수록 좋습니다.

특히, "어느 위치"의 "어떤 속성"인지 정확한 위치를 알려주어야 mcp가 더 잘 동작합니다.

 

예시로, 저는 다음과 같이 작성했습니다.

 

 

 

 

 

MCP가 작성했던 함수들을 실행하기 위해서는 사용자의 승인이 필요한데요, MCP가 요청하는 권한을 살펴본 뒤 의도에 맞게 동작하고 있으면 엔터를 눌러 요청을 허가합니다.

 

 

 

 

Next.js 리다이렉트에 대해 작성해놓은 글을 임시로 붙여넣고, 요약을 요청해보았을 때 결과는 다음과 같았습니다.

 

 

 

 

 

 

Notion에도 잘 반영된 것을 확인할 수 있습니다.