Skip to content
Park Hyoin PARKHYO.IN
Go back

MCP 공부 #3 — Resources / Prompts 의 정체 + LangGraph 와 통합 (`MultiServerMCPClient` · `ainvoke`)

Edit page

MCP 공부 #1 (Inspector) / #2 (Claude Desktop) 에서는 Tools 만 다뤘다. 오늘은 나머지 두 구성요소 — Resources / Prompts + LangGraph 통합 까지.

Table of contents

Open Table of contents

MCP 의 세 구성요소 — 다시

요소정의
ToolsLLM 이 호출하는 함수
ResourcesLLM 이 읽을 수 있는 데이터 / 파일
Prompts미리 정의된 프롬프트 템플릿

이렇게만 봐서는 차이가 잘 안 와닿는다. 더 구체적인 비교가 필요하다.

Tools / Resources / Prompts 비교표 — 누가 호출(LLM/클라이언트/사용자) / 언제(LLM판단/컨텍스트로 미리/명시적 트리거) / 부작용(있음/없음/없음) / 비유(직원 시키기/참고 자료/양식·템플릿)

측면ToolsResourcesPrompts
누가 호출?LLM클라이언트 / 사용자사용자
언제?LLM 판단컨텍스트로 미리명시적 트리거
부작용?있을 수 있음없음 (읽기)없음 (텍스트)
비유직원 시키기참고 자료양식 / 템플릿

핵심 차이 (한 줄)

  • Resources = 백그라운드로 항상 제공 되는 컨텍스트 (읽기 전용)
  • Tools = 필요할 때 능동적으로 가져오는 데이터 (호출 비용·부작용 있음)
  • Prompts = 사용자가 명시적으로 트리거 하는 양식 / 템플릿

구현 — @mcp.resource() / @mcp.prompt()

Resources 는 @mcp.resource("...") 로 구현한다. URI 패턴으로 노출:

@mcp.resource("config://operating-rules")
def get_operating_rules() -> str:
    """공장 운영 규정 — 백그라운드 컨텍스트."""
    return "..."  # 텍스트 그대로 반환

Prompts 는 @mcp.prompt() 로 구현한다. 입력 데이터를 어떤 절차대로 처리할지, 출력을 어떤 형식으로 내놓을지 를 미리 명시할 수 있다. 시스템 프롬프트를 미리 적어둔 것과 비슷한 개념이다.

@mcp.prompt()
def daily_analysis_prompt(date: str) -> str:
    """일별 에너지 분석 프롬프트 — /daily_analysis 입력 시 사용."""
    return f"{date} 자료를 분석해 다음 형식으로 정리해줘..."

Inspector 로 확인

MCP Inspector — 'energy-management-v2' 서버 연결 완료, Resources / Prompts 탭이 활성화된 상태

Resources / Prompts 탭이 활성화됐다. 이전엔 Tools 만 있었는데 새 서버는 셋 다 갖췄다.

Resources 목록 — get_operating_rules / get_line_specifications / get_troubleshooting_guide

Prompts 목록 — daily_analysis_prompt (일별 에너지 분석 프롬프트, /daily_analysis 입력 시 사용) / alarm_response_prompt (알람 대응 프롬프트)

Claude Desktop 에서도 확인

#2 에서 등록한 config 에 새 서버 추가 후 재시작:

Claude Desktop — '오후예요, hyoin님' 인사 + 커넥터 메뉴에 energy-v2 활성화, 우측에 Daily analysis prompt / Alarm response prompt / Get operating rules / Get line specifications / Get troubleshooting guide 노출

채팅창 + 버튼 → 커넥터 → energy-v2 토글 켜면 우측에 등록된 Resources / Prompts 가 메뉴로 노출된다. 항목 누르면 프롬프트가 채팅에 복사 되거나 파라미터를 직접 입력 할 수 있는 폼이 나온다.

LangGraph 와 통합 — langchain-mcp-adapters

이제 만든 MCP 서버를 LangGraph 에서 호출한다.

pip install langchain-mcp-adapters

LangChain 에서 지원하는 라이브러리인데 — MCP 서버의 도구를 LangChain 의 @tool 형식으로 자동 변환 해준다. 직접 어댑터를 짤 필요가 없다.

셋업 흐름

MCP 클라이언트 셋업

LangGraph State + 노드 함수 정의 (asyncio)

도구 로드 → ainvoke

이전 LangGraph 시리즈 패턴 그대로인데 asyncio 가 추가됐다.

asyncio — 이전 회사 데이터로거 프로젝트가 반갑게 떠오름

asyncio 를 보니 이전 회사에서 강우량계 데이터로거 SW 짤 때 쓴 기억이 났다. 센서로부터 들어오는 값을 놓치지 않고 받기 위해 코루틴 + asyncio 로 받았었다. 그때 코루틴을 처음 배웠는데 다시 보니 반가웠다.

ainvoke 가 왜 필요한가

코드를 따라치다 ainvoke 라는 메서드를 발견했다. invoke 의 async 버전이다. LangChain 이 비동기 실행을 지원하는 이유가 궁금했는데, 답은 단순했다:

MCP 통신 자체가 비동기. stdio / SSE 둘 다 비동기 I/O 기반. 그래서 LangGraph 에서도 비동기 메서드인 ainvoke 를 써야 한다.

만약 invoke (동기) 로 호출하면 비동기 통신을 동기로 감싸는 어색한 패턴이 된다. LangChain 이 둘 다 제공하는 건 호출자가 자기 컨텍스트에 맞춰 선택 할 수 있게 하기 위해서다.

실행 결과

LangGraph + MCP 실행 — MCP 도구 2개 로드 → list_production_lines / get_energy_consumption(line_4, 12h) 자동 호출 → 라인 ID·용량 표 형식 답변

list_production_lines()get_energy_consumption(line_4, 12h) 순서로 자동 호출한다. 결과를 마크다운 표로 정리해서 응답한다.

MultiServerMCPClient 가 서버 그대로 불러오는 비결

흥미로운 부분 — MCP 서버 코드는 한 줄도 안 바뀌었는데 LangChain 에서 MultiServerMCPClient 로 그대로 가져온다.

가능한 이유 = MCP 표준을 지키면서 구현했기 때문. 서버는 stdio / 메시지 포맷 표준만 지키면 어떤 클라이언트든 가져다 쓸 수 있다.

이게 #1 에서 비교했던 “Tool Use vs MCP” 표“범용 표준” 항목이 실제로 어떻게 발현되는지를 직접 본 셈이다.

Resources 까지 통합 — 도메인 지식이 시스템 프롬프트로

Tools 만 불러오는 데 그치지 않고 Resources 도 가져와서 시스템 프롬프트에 적용한다.

async with mcp_client.session(...) as session:
    resources = await session.list_resources()
    context = ""
    for r in resources:
        content = await session.read_resource(r.uri)
        context += content.text + "\n"
    return context  # → 시스템 프롬프트에 주입

async with 로 리소스를 받아와서 context 로 묶고, 나머지는 평소대로 LLM 셋업 → 노드 정의 → 엣지 연결 → ainvoke 순으로 간다.

LangGraph + Resources + Tools — 초반에 3개 Resource 로드 (config://operating-rules 419자, config://line-specifications 315자, manual://troubleshooting-guide 385자) → 2개 Tool 로드 → 질문 'line_4 의 최근 24시간 전력 사용량 보고, 운영 규정 위반 여부 확인, 대응 방법' → get_energy_consumption 호출 후 도메인 지식 반영된 보고서 응답

초반에 3개 Resource (operating-rules 419자 + line-specifications 315자 + troubleshooting-guide 385자) 를 로드한 후 질문에 답한다.

Resources 있을 때 vs 없을 때

가장 큰 차이는 도메인 지식이 반영된 답변 품질.

같은 데이터를 받아도 “운영 규정 위반 여부 확인” / “대응 방법 제안” 같이 도메인 컨텍스트가 필요한 분석 이 가능하다. Resources 없으면 단순 데이터 요약에 그친다.

회고

MCP 의 세 구성요소를 다 만져봤고 LangGraph 와도 묶어봤다. 정리:

  1. Tools / Resources / Prompts 의 분업 — “능동 호출 / 배경 컨텍스트 / 사용자 트리거 양식”. 같은 정보라도 어떤 형식으로 노출하는 게 자연스러운지 설계 단계에서 결정해야 한다.
  2. MCP 통신은 비동기ainvoke 가 자연스러운 선택이다. 동기 코드만 쓰던 사람은 asyncio 한 번 짚고 가야 한다.
  3. MCP 표준 준수의 위력 — 서버 코드 변경 없이 Inspector / Claude Desktop / LangGraph 셋 다에서 그대로 동작한다. 도구를 한 번 만들면 클라이언트 종류에 독립적이다.
  4. Resources 가 도메인 지식 주입의 자연스러운 그릇 — 시스템 프롬프트에 박아두는 것보다 MCP Resource 로 분리하면 서버 / 클라이언트 / 다른 에이전트 모두 같은 컨텍스트를 공유한다.

더 공부해볼 것

1. MCP 서버 깊이 — 사이드 프로젝트 트랙

  • 단순 데모 말고 실제 사용 시나리오로 (예: 내 FEMS RAG 를 MCP 서버로 노출)
  • 인증 / 권한 / rate limit 같은 운영 이슈
  • Resource template (config://users/{user_id}) 같은 동적 URI

2. RAG 응답 / 에이전트 시스템의 Eval

3. langchain-mcp-adapters 의 내부

  • MCP 도구 → LangChain @tool 변환의 메커니즘
  • input schema / output schema 매핑 규칙
  • 에러 / 타임아웃 / 재시도 정책
  • 여러 MCP 서버를 한 번에 묶을 때의 도구 충돌 처리

4. asyncio + 코루틴 기본기 복습

  • async with / async for / gather / as_completed
  • 동기 코드 안에서 비동기 호출하는 패턴 (asyncio.run)
  • 이전 회사 데이터로거 (강우량계) 에 쓴 코루틴과 LLM 도구 호출의 공통점·차이

5. Resources vs RAG 의 경계

  • 작은 도메인 지식 → Resources 로 시스템 프롬프트 주입
  • 큰 코퍼스 → RAG 로 검색
  • 그 사이 회색지대 — 50KB 정도의 가이드라인은 어느 쪽이 맞나
  • 토큰 비용 vs 검색 latency 의 trade-off

6. Prompts 의 운영 패턴

  • 슬래시 커맨드 (/daily_analysis) 같은 사용자 트리거
  • 파라미터 유효성 / 기본값 / 다국어
  • Prompts 와 Resources 를 조합한 워크플로우 (Prompt 가 어떤 Resource 를 같이 가져올지 명시)

Edit page