이번 프로젝트에서는 Anthropic의 최신 통신 규격인 MCP(Model Context Protocol)를 활용하여,
내 컴퓨터의 파일을 스스로 읽고 요약하여 마크다운 보고서까지 작성하는 AI 에이전트를 구현했다.
전체 시스템 구조
사용자의 명령이 실제 하드웨어 자원을 제어하기까지 총 5개의 계층을 거치도록 설계했다.
- User Prompt: 사용자의 자연어 명령
- LangChain Agent: 전체 워크플로우를 관리하는 오케스트레이터
- Claude 3.5 Sonnet: 명령의 의도를 파악하고 적절한 도구를 선택하는 두뇌
- MCP (Client & Server): LLM과 로컬 리소스 사이의 표준화된 통신 규격
- OS / File System: 실제 데이터가 저장된 하드웨어 자원
에이전트 실행 흐름
에이전트는 ReAct(Reasoning + Acting) 프레임워크에 따라 다음과 같이 동작한다.
- Thinking: Claude가 현재 작업에 list_files와 read_text, write_md 도구가 필요함을 인식
- Action (1): list_files를 호출하여 폴더 내 파일들을 확인
- Action (2): 각 파일을 read_text로 읽어 내용을 파악하고 Claude가 내부적으로 요약
- Action (3): 요약된 텍스트를 write_md 도구에 전달하여 물리적인 파일로 저장
- Observation: 모든 작업이 완료되었음을 확인하고 사용자에게 최종 응답을 보냄
트러블슈팅
① API 모델 권한 문제 (404 Not Found)
anthropic.NotFoundError: Error code: 404 - model: claude-3-5-sonnet-latest
Anthropic 계정의 Tier(등급)나 API 라이브러리 버전에 따라 특정 모델 식별자를 인식하지 못하는 문제였다.
최신 모델인 20241022 버전에서, 안정적인 호출이 보장되는 claude-3-haiku-20240307 모델로 전환했다.
② 비동기(Async) 처리와 결과 출력 누락
작업은 완료되었다고 뜨는데 결과값이 빈 리스트([])로 출력
MCP 통신은 비동기로 이루어지는데, 프로그램이 최종 답변이 나오기 전 마지막 도구 반환값만 출력한다는 문제였다.
astream 방식을 도입하여 에이전트의 생각과 도구 실행을 실시간으로 화면에 출력하도록 코드를 수정했다.
③ AI 응답 데이터 구조 불일치 (KeyError: 'text') 문제
AI가 텍스트 답변 없이 곧바로 도구를 호출할 때, 응답 리스트에 'text' 키가 없어 프로그램이 종료되는 문제였다.
last_msg.content 내부를 순회하며 type == "text"인 블록만 골라내어 결합하는 방어적 로직을 추가하였다.
주요 코드 상세 설명 및 기술 분석
① 비동기 컨텍스트 매니저 (async with)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
표준 입력/출력 을 통해 서버와 실시간으로 대화한다.
async with를 사용하여 서버와의 연결과 세션이 작업 종료 후 자동으로 안전하게 닫히도록 관리한다.
리소스 누수(Memory Leak)를 방지하는 현대 파이썬의 핵심 패턴이다.
② astream 기반의 실시간 로그 출력
async for chunk in agent.astream({"messages": [("user", query)]}, stream_mode="values"):
if "messages" in chunk:
last_msg = chunk["messages"][-1]
# AI 생각 출력 및 도구 실행 결과 확인
ainvoke를 쓰면 모든 작업이 끝난 후의 최종 스냅샷만 보여준다.
이때 마지막 메시지가 AI의 대답이 아닌 도구 실행 결과일 경우 화면에 아무것도 안 뜨는([]) 문제가 있었다.
따라서 astream을 사용해 에이전트의 내부 상태 변화를 한 단계씩(Chunk) 받아온다.
last_msg.type == "ai"일 때는 AI가 무슨 도구를 쓸지 선택하는 내용을 보여준다.
last_msg.type == "tool"일 때는 실제 파일 시스템에서 일어난 일을 보여준다.
결과적으로 사용자는 에이전트가 수행하는 과정을 실시간으로 관찰할 수 있게 된다.
최종 실행 시나리오
시나리오 테스트를 위해 test_folder 폴더 안에 아래 두 파일을 새로 만들어 주었다.


[시나리오 1 테스트]
query = "'money.csv' 파일을 읽어서 지출 금액 평균을 내주고, 평소보다 너무 많이 쓴 '과소비' 항목이 있는지 찾아서 주의를 좀 줘."


[시나리오 2 테스트]
query = "폴더에 있는 'trip.txt' 파일을 읽고, 핵심 장소 2곳을 깔끔하게 요약해서 'tripPlan.md' 파일로 저장해줘."



마무리하며
왜 랭체인만으로는 부족할까?
기존 랭체인 방식은 새로운 도구가 추가될 때 마다 파이썬 코드를 직접 수정하고 에이전트 로직을 다시 짜야한다.
하지만 MCP(Model Context Protocol)를 도입하면, 도구를 제공하는 서버와 이를 사용하는 에이전트가 완전히 분리된다.
따라서 서버만 따로 업데이트하거나 다른 환경에서도 재사용할 수 있는 표준화된 확장성을 얻게 된다.
만약 이번 과제에서 랭체인만 사용했다면.
agent.py 안에 파일 읽기, CSV 분석, 마크다운 생성 로직을 @tool 데코레이터를 붙여서 다 작성했을 것이다.
즉, 도구가 늘어날수록 agent.py 코드는 길어지고 복잡해지게 된다,
MCP를 사용해 server.py에 도구들을 몰아넣고, 에이전트에서는 load_mcp_tools() 한 줄로 모든 기능을 가져온다.
에이전트와 서버가 분리되어 코드가 훨씬 간결하고 관리가 편하다는 것을 직접 느낄 수 있었다.
또한 CSV 분석 기능을 추가하거나 수정할 때, 에이전트의 메인 로직(agent.py)은 건드릴 필요가 없었다.
이를 통해 서버 쪽 기능만 업데이트하면 에이전트가 실행될 때 자동으로 새로운 기능을 인식하는 것을 알 수 있다.
대규모 시스템일수록 MCP 같은 표준 프로토콜이 유지보수에 유리하겠다는 생각이 들었다.