mcp 서버를 만들 때는 서버 구현보다 테스트 루프가 더 중요하다. Real Python의 강의는 MCP Python SDK로 명령줄 클라이언트를 만들어 stdio transport로 서버에 연결하고, 서버가 노출하는 tools, prompts, resources를 직접 확인하는 흐름을 다룬다.
무엇을 만드는가
목표는 특정 MCP 서버를 터미널에서 다음처럼 검사하는 작은 클라이언트다.
python mcp_client.py list-tools --server "python server.py"
python mcp_client.py list-prompts --server "python server.py"
python mcp_client.py get-resource --server "python server.py" --uri "..."
python mcp_client.py call-tool --server "python server.py" --name search --args '{"q":"test"}'핵심은 “LLM 앱 안에서 잘 되는지”를 보기 전에 MCP 프로토콜 레벨에서 서버 능력을 결정론적으로 확인하는 것이다.
테스트 클라이언트가 필요한 이유
MCP 서버는 보통 에디터, Claude Desktop, Codex, 사내 에이전트 같은 클라이언트 뒤에서 호출된다. 이 상태로만 테스트하면 실패 원인이 서버인지, 클라이언트 설정인지, 모델의 tool call 선택인지 분리하기 어렵다.
| 점검 항목 | 확인할 내용 |
|---|---|
| 연결 | stdio transport로 서버 프로세스가 정상 시작되는가 |
| 기능 목록 | list_tools(), list_prompts(), list_resources() 결과가 기대와 같은가 |
| 스키마 | tool 입력 파라미터가 클라이언트에 올바르게 노출되는가 |
| 실행 | tool 호출 결과와 오류가 구조화되어 돌아오는가 |
| 리소스 | URI 기반 resource fetch가 실제 데이터를 반환하는가 |
구현 흐름
MCP Python SDK의 ClientSession을 중심에 둔다.
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(server) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()이후 CLI 서브커맨드를 붙여 list_tools, list_prompts, list_resources, call_tool, get_prompt, read_resource를 각각 실행하게 만들면 된다.
실무 적용 팁
- 서버 개발 중에는 에디터 연동보다 먼저 이 클라이언트로 contract test를 만든다.
- tool schema 변경 시 snapshot을 남겨 클라이언트 호환성 깨짐을 빠르게 찾는다.
- 오류 반환도 성공 케이스만큼 테스트한다. 모델은 오류 메시지와
recoverable여부를 보고 다음 행동을 정한다. - CI에서는 실제 LLM 호출 없이 MCP 서버의 프로토콜 표면만 검증한다.
참고 자료
- Testing MCP Servers With a Python MCP Client — Real Python (2026-07-07)