이번 글에서는 회의 내용을 요약하는 MCP 서버를 개발하고, 이를 Notion MCP 서버와 연동하여 회의 내용을 Notion에 정리하는 과정을 다루겠습니다.
MCP란 무엇인가?
MCP (Model Context Protocol) 는 애플리케이션이 LLM에 컨텍스트를 제공하는 방법을 표준화하는 개방형 프로토콜입니다.
예를 들어 사용자가 “이 회의 내용을 요약해줘”라고 요청하면, LLM은 그 요청을 처리하기 위해 여러 단계를 거쳐야 합니다.
1. 음성 파일을 받아서 텍스트로 전사하고,
2. 전사된 텍스트를 요약한 뒤,
3. 요약 결과를 데이터베이스에 저장
이처럼 하나의 요청을 처리하려면 여러 기능이 순차적으로 실행되어야 하는데, MCP는 이러한 기능들을 ‘LLM이 호출할 수 있는 작업 단위’로 정의하고, 그 작업들을 표준화된 방식으로 연결할 수 있도록 해줍니다.
각 작업은 “회의 전사”, “요약”, “저장”처럼 구체적인 목적을 가진 독립적인 단위로 구성되며, LLM은 이들을 직접 호출하거나, 연속된 흐름 속에서 조합하여 사용할 수 있습니다.
결과적으로 MCP는 LLM이 단순한 대화 엔진을 넘어, 실제 업무를 자동으로 처리할 수 있는 실행 주체(에이전트)로 확장되도록 만들어주는 핵심 기술입니다.
MCP의 구성: Host, Client, Server
MCP는 세 가지 주요 구성 요소인 Host, Client, Server로 이루어져 있으며, 각 요소는 다음과 같은 역할을 담당합니다.
MCP Host
MCP Host는 LLM과 사용자 인터페이스를 통합하는 애플리케이션입니다. 예를 들어 Claude Desktop, Cursor 같은 도구들이 여기에 해당하며, 사용자의 요청을 받아 LLM을 통해 처리하고 결과를 반환하는 흐름의 중심이 됩니다.
MCP Client
MCP Client는 LLM과 MCP Server 사이의 브릿지(중개자) 역할을 합니다. LLM으로부터 작업 요청을 받아 적절한 MCP Server에 전달하고, 서버의 응답 결과를 다시 LLM으로 반환하는 역할을 수행합니다.
즉, LLM이 외부 기능을 직접 호출할 수 있게 만들어주는 연결 계층입니다.
MCP Server
MCP Server는 LLM이 호출할 수 있는 구체적인 작업(예: STT, 요약, 데이터 저장 등)을 실제로 수행합니다. 또한 외부 데이터베이스나 API, 파일 시스템 등에 접근하여 필요한 처리를 수행할 수 있도록 합니다.
개발자는 이 MCP Server에 기능 단위를 정의하고, HTTP API 등의 형태로 외부에 노출함으로써 LLM이 이를 자유롭게 사용할 수 있게 만듭니다.
공식 MCP GitHub 저장소에서는 다양한 기능을 수행하는 서버들이 오픈소스로 공개되어 있습니다. https://github.com/modelcontextprotocol/servers
구현

이번 프로젝트에서는 데이터베이스에 저장된 회의 내용을 불러와 요약한 뒤, 그 결과를 Notion에 자동으로 기록하는 시스템을 구성했습니다. 이를 위해 두 개의 MCP 서버를 사용합니다:
Summary MCP Server
Notion MCP Server
Summary MCP Server
FastMCP로 구현
데이터베이스에서 회의 원문을 불러옵니다.
LLM을 사용해 회의 내용을 요약하고 액션 아이템을 추출합니다.
FastMCP
는 MCP기반의 작업들을 FastAPI를 이용해 간단하게 실행할 수 있도록 도와주는 경량 서버 프레임워크입니다.@mcp.tool() 데코레이터
로 summarize()
함수를 MCP에서 호출 가능한 도구(tool)로 등록합니다. LLM이 "summarize"라는 이름으로 이 함수를 실행할 수 있게 됩니다.
LLM 앱과 로컬 통신이기 때문에 stdio
로 실행합니다.
from mcp.server.fastmcp import FastMCP
from tools.get_transcript import get_transcript
from tools.generate_summary import generate_summary
mcp = FastMCP("Meeting_Summary")
@mcp.tool()
def summarize(room_id: str) -> dict:
"""Summarizes the transcript of a meeting and returns the summary."""
transcript = get_transcript(room_id)
summary = generate_summary(transcript)
return summary
if __name__ == "__main__":
mcp.run(transport="stdio")
회의 내용은 get_transcript(room_id)
함수를 통해 데이터베이스에서 불러옵니다.
현재는 DB에 저장된 전사 데이터를 기반으로 회의 내용을 요약하지만, 오디오 파일로부터 직접 회의 내용을 가져오고 싶다면, 이 부분을 다음과 같은 흐름으로 대체할 수 있습니다.
audio → STT 모델 → 전사 텍스트 추출(transcribe)
즉, get_transcript()
함수 대신, 오디오 파일을 입력으로 받아 Whisper 등의 STT 모델을 이용해 텍스트를 추출하는 함수를 사용하면 됩니다.
이처럼 MCP 구조에서는 입력 소스(DB, 오디오 등)에 따라 기능을 유연하게 교체할 수 있다는 장점이 있습니다.
from db import get_db_connection
def get_transcript(room_id: str) -> list[dict]:
"""
특정 room_id에 해당하는 회의 내용을 시간순으로 반환합니다.
각 항목은 {speaker_id, text, timestamp} 형태입니다.
"""
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
SELECT speaker_id, text, timestamp
FROM transcripts
WHERE room_id = %s
ORDER BY timestamp ASC
""", (room_id,))
rows = cur.fetchall()
cur.close()
conn.close()
return [
{
"speaker_id": row[0],
"text": row[1],
"timestamp": row[2].isoformat()
}
for row in rows
]
LLM에 회의 내용 요약을 요청하는 함수입니다. 예제에서는 OpenAI의 gpt-4o-mini 모델을 사용하여 수행되지만, MCP 구조에서는 이 부분을 쉽게 다른 LLM으로 교체할 수 있습니다. 예를 들어, HuggingFace의 mistral, claude, gemini, 또는 사내에 배포한 전용 모델 등을 사용할 수 있으며, generate_summary()
함수 내부에서 LLM 객체만 바꿔주면 동일한 흐름으로 작동합니다.
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
prompt = ChatPromptTemplate.from_messages([
("system", "너는 회의 요약 도우미야."),
("user", """다음 회의 내용을 바탕으로:
1. 회의 시작/종료(마지막 시간기준)시간과 참석자 목록을 포함한 주요 논의 내용을 자세히 요약하고,
2. 회의에서 결정된 액션 아이템들을 항목으로 정리해줘.
{transcript_text}""")
])
chain = prompt | llm
def generate_summary(transcript: list[dict]) -> dict:
transcript_text = "\n".join(f"{t['timestamp']} - {t['speaker_id']}: {t['text']}" for t in transcript)
result = chain.invoke({"transcript_text": transcript_text})
return {"summary": result.content.strip()}
Notion MCP Server
Summary 서버로부터 전달받은 요약 결과를 지정된 Notion 페이지에 저장합니다.
makenotion/notion-mcp-server 오픈소스를 사용하여 구성했습니다.
이 서버는 MCP 형식에 맞춰 설계되어 있어, LLM이 직접 호출하여 Notion에 기록할 수 있습니다.
Notion 페이지에 접근하려면 다음 두 가지 정보가 필요합니다:
Integration Token (Notion API 토큰)
Page ID (내용을 기록할 대상 페이지 ID)
해당 정보는 위 공식 저장소의 README에서 설정 방법이 자세히 설명되어 있으니, 사용 전 참고하시면 됩니다.
MCP Host
앞서 정의한 두 MCP 서버(Summary MCP Server, Notion MCP Server)를 사용하여, 회의 내용을 요약하고 Notion에 저장하는 전체 워크플로우를 실행하는 Host 코드를 작성합니다.
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
import asyncio
import os
from contextlib import AsyncExitStack
from types import SimpleNamespace
load_dotenv()
notion_token = os.getenv("NOTION_TOKEN")
servers = [
{
"name": "meeting_summary_server",
"params": StdioServerParameters(
command="python",
args=["mcp_server.py"]
)
},
{
"name": "notionApi",
"params": SimpleNamespace(
command="npx",
args=["-y", "@notionhq/notion-mcp-server"],
cwd=None,
encoding="utf-8",
encoding_error_handler="replace",
env={
"OPENAPI_MCP_HEADERS": f'{{"Authorization": "Bearer {notion_token}", "Notion-Version": "2022-06-28"}}'
}
)
}
]
async def connect_to_server(server_config, stack: AsyncExitStack):
"""서버에 연결하고 MCP 세션 및 툴 로딩"""
name = server_config["name"]
params = server_config["params"]
read, write = await stack.enter_async_context(stdio_client(params))
session = await stack.enter_async_context(ClientSession(read, write))
await session.initialize()
tools = await load_mcp_tools(session)
return {
"name": name,
"tools": tools,
}
async def run_multi_server_agent():
async with AsyncExitStack() as stack:
connections = []
for server in servers:
conn = await connect_to_server(server, stack)
connections.append(conn)
all_tools = [tool for conn in connections for tool in conn["tools"]]
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(llm, all_tools)
room_id = "test-room"
page_id = os.getenv("NOTION_PAGE_ID")
return await agent.ainvoke({
"messages": [
("system",
f"당신은 회의 내용을 요약하고 액션 아이템을 추출한 후, Notion에 저장하는 도우미입니다.\n"
f"Notion에 저장할 때는 다음 지침을 따르세요:\n"
f"- 지정된 `page_id`를 부모로 사용합니다. (page_id: {page_id})\n"
f"- 새 페이지의 제목은 회의 날짜(YYYY년 MM월 DD일)로 설정합니다.\n"
f"- 본문은 다음과 같이 구성합니다:\n"
f" 1. 요약(Summary) 섹션: 회의 내용을 간단히 요약한 텍스트 블록\n"
f" 2. 액션 아이템(Action Items) 섹션: 각 액션 아이템을 bullet 블록으로 정리\n"
f"모든 정보를 JSON 형식으로 MCP Tool에 전달해 저장을 요청하세요."
),
("user",
f"{room_id} 회의 내용을 요약하고 액션 아이템을 추출해 주세요. 그리고 위 기준에 따라 Notion에 저장해 주세요.")
]
})
# 비동기 함수 실행
if __name__ == "__main__":
result = asyncio.run(run_multi_server_agent())
구성 설명
meeting_summary_server:
로컬에서 실행되는 Python 기반 MCP 서버로, 회의 내용을 요약하는 기능을 제공합니다.notionApi:
Notion 공식 MCP 서버를 npx를 통해 실행합니다.
이 서버는 Notion에 요약 결과를 저장하는 기능을 담당하며,
환경변수로 인증 토큰(NOTION_TOKEN)과 Notion API 버전을 함께 전달합니다.
이 서버들을 ClientSession
으로 연결한 뒤, load_mcp_tools()
로 MCP 도구 목록을 로드합니다.
또한, LLM이 요약 결과를 Notion에 저장할 때 따라야 할 포맷을 명확히 안내하기 위해 시스템 프롬프트에 지침을 명시해야 합니다.
테스트
테스트를 위해 데이터베이스에 다음과 같은 예시 회의 데이터를 삽입했습니다.
('test-room', 'alice', '프로젝트 일정 조정이 필요합니다.', '2025-07-01 10:00:00'),
('team-sync', 'david', '팀 전체 회의는 내일 오전 9시입니다.', '2025-07-01 10:00:05'),
('test-room', 'bob', '디자인 시안은 오늘 중으로 공유하겠습니다.', '2025-07-01 10:00:10'),
('team-sync', 'emma', 'SNS 캠페인은 다음 주부터 시작합니다.', '2025-07-01 10:00:15'),
('test-room', 'carol', 'QA는 다음 주까지 완료 예정입니다.', '2025-07-01 10:00:20');
테스트를 위해 아래 명령어로 클라이언트를 실행합니다:
python3 client.py
잠시 후 Notion에 요약된 회의 내용이 아래와 같이 기록된 것을 볼 수 있습니다.
마무리
이번 글에서는 MCP (Model Context Protocol) 기반으로 회의 내용을 요약하고, 그 결과를 자동으로 Notion에 기록하는 시스템을 구축해 보았습니다.
MCP 구조로 얻은 가장 큰 장점은 다음과 같습니다.
기능 단위를 Tool로 분리하고, LLM이 이를 조합해 사용할 수 있어 높은 유연성
각 MCP 서버는 독립적으로 실행되므로, 확장성과 테스트 용이성
다양한 입력 소스(STT, DB, API 등)에 맞춰 구성 변경이 간단
향후에는 STT 도구를 추가해 오디오 기반 회의 요약을 실시간으로 처리하거나, 요약 결과를 Slack, Google Docs 등 다른 협업 도구로 확장하는 것도 어렵지 않습니다.
전체 코드는 GitHub 저장소에서 확인하실 수 있습니다. https://github.com/hissinger/meeting_summary