MCP 클라이언트를 코드에서 평가하기
MCP 클라이언트를 코드에서 평가하기
여러분 코드에서 완전히 구동하면서 MCP 클라이언트가 툴을 잘 고르는지 채점해 봐요.
출처: 문서
본문
개요
MCP 클라이언트는 MCP 서버에 연결해 그 툴 중 무엇을 호출할지 결정하는 에이전트의 일부예요. MCP 구성에서 무언가 잘못되면, 보통 서버 문제가 아니에요. 잘못된 툴을 고르거나, 잘못된 인자를 넘기거나, 눈앞에 있었던 툴을 무시한 클라이언트 문제예요.
이 가이드는 배포 전에 그걸 코드에서 잡는 방법을 보여줘요. 클라이언트가 말하는 MCP 서버를 묘사하고, 실행 중에 만든 툴 호출을 기록한 뒤, deepeval이 채점하게 해요. 결과는 테스트 런으로 Confident AI에 올라가요.
필요한 것:
- Python에서 호출할 수 있고 최소 하나의 MCP 서버에 연결된 MCP 클라이언트
- 평가할 메트릭 리스트 — 예컨대 MCP Use
- 테스트 런 업로드를 위한
CONFIDENT_API_KEY
MCP 클라이언트가 이미 배포되어 HTTP로 닿을 수 있다면 코드가 필요 없을 수도 있어요. 코드 없이 실행하기로 건너뛰세요.
어떻게 동작하나요
핵심 아이디어: 클라이언트가 닿을 수 있는 MCP 서버를 evaluate()에 넘기면, 기록된 툴 호출 중 어느 것이 MCP 호출이고 어느 것이 평범한 로컬 함수인지 알아서 구분해요.
- MCP 서버에 무엇을 노출하는지 물어서
deepeval이 툴 표면을 알게 해요 - 클라이언트를 실행하고 그가 만든 모든 툴 호출을 기록해요
- 입력, 출력, 그 툴 호출들로 테스트 케이스를 만드세요
- 서버를
evaluate()에 넘기면 각 툴 호출을MCP또는FUNCTION으로 태그하고 런을 업로드해요
sequenceDiagram
participant Your Code
participant MCP Server
participant MCP Client
participant deepeval
participant Confident AI
Your Code->>MCP Server: list_tools()
MCP Server-->>Your Code: Available tools
Your Code->>MCP Client: Invoke with input
loop For each tool the client picks
MCP Client->>MCP Server: call_tool(name, args)
MCP Server-->>MCP Client: Tool result
end
MCP Client-->>Your Code: actual_output + tools called
Your Code->>Your Code: Build test case (input + actual_output + tools_called)
Your Code->>deepeval: evaluate(test_cases, metrics, mcp_servers)
deepeval->>deepeval: Tag each tool call MCP or FUNCTION
deepeval->>Confident AI: Upload test run
Confident AI-->>Your Code: Testing report link
그 태깅 단계가 이해할 만한 부분이에요. 에이전트는 MCP 툴과 평범한 로컬 함수를 섞어 호출하고, 모두 같은 tools_called 리스트에 들어가요. deepeval은 각 호출을 이름으로 MCP 서버가 광고하는 툴과 매칭하므로, MCP 툴 호출은 다른 것들과 뭉뚱그려지지 않고 Confident AI에서 별도 라벨로 표시돼요.
MCP 서버 묘사하기
이 방법은 여러분이 서버를 소유하느냐, 남의 서버를 호출하느냐에 따라 달라져요.
서버를 소유한 경우
공식 MCP Python SDK로 서버를 만들었다면 그 객체를 그대로 넘기세요. deepeval이 툴, 리소스, 프롬프트를 대신 읽어요.
SDK의 서버 클래스도 MCPServer라고 불러서, 가독성을 위해 둘 중 하나만 import해요:
from mcp.server import MCPServer
server = MCPServer(name="GitHub")
@server.tool()
def search_issues(query: str) -> str:
...
@server.tool()
def create_issue(title: str, body: str) -> str:
...
이것뿐이에요. 이 단계에서는 deepeval 타입이 필요 없어요.
남의 서버를 호출하는 경우
서드파티 서버(GitHub, Slack, Google Drive, 여러분이 작성하지 않은 무엇이든)라면 ClientSession으로 연결하고 프리미티브를 deepeval의 MCPServer에 넘겨요:
from mcp import ClientSession
from deepeval.test_case import MCPServer
session = ClientSession(...)
await session.initialize()
tool_list = await session.list_tools()
mcp_server = MCPServer(
server_name="GitHub",
transport="streamable-http",
available_tools=tool_list.tools,
)
available_tools는 응답에서 .tools를 떼어낸 값이에요 — MCP 스펙에서 온 그대로의 Tool 객체 리스트죠. 클라이언트가 쓴다면 available_resources와 available_prompts도 같은 방식으로 넘길 수 있어요.
두 형태 모두
mcp_servers가 받아들여지는 어디서든 동작하고, 한 리스트에 섞을 수 있어요. 에이전트가 서버 세 대에 말한다면 세 대를 모두 넘기세요.
클라이언트가 호출한 것 기록하기
클라이언트가 요청을 처리하면서 각 툴 호출을 ToolCall로 캡처해요. 이름, 인자, 결과가 메트릭이 추론하는 대상이에요:
from deepeval.test_case import ToolCall
tools_called = []
result = await session.call_tool(tool_name, tool_args)
tools_called.append(
ToolCall(
name=tool_name,
input_parameters=tool_args,
output=result.content,
)
)
어느 것이 MCP 호출인지 표시할까 걱정하지 마세요. 다음 단계에서 자동으로 처리돼요. 로컬 함수 호출도 같은 리스트에 기록해 두면 알아서 정리해 줍니다.
그다음 평소처럼 테스트 케이스를 만들어요:
from deepeval.test_case import LLMTestCase
test_case = LLMTestCase(
input="Find the open bug about rate limiting and file a follow-up",
actual_output=agent_response,
tools_called=tools_called,
)
평가 실행하기
서버를 evaluate()에 한 번 넘기면 런의 모든 테스트 케이스가 그걸 받아요:
from deepeval import evaluate
from deepeval.metrics import MCPUseMetric
evaluate(
test_cases=[test_case],
metrics=[MCPUseMetric()],
mcp_servers=[server],
)
MCPUseMetric이 두 가지를 채점해요: 클라이언트가 사용 가능한 프리미티브를 합리적으로 사용했는지, 올바른 인자를 넘겼는지. 둘 다 툴 표면을 알아야 하는데, 그게 바로 mcp_servers가 제공하는 것이에요.
MCPUseMetric은 테스트 케이스에mcp_servers를 필요로 해요.evaluate()에 넘기면 런 전체를 커버하므로, 만드는 모든 테스트 케이스에 그 필드를 설정할 필요가 없어요. 자기만의mcp_servers를 정의한 테스트 케이스는 그걸 유지하고 런 수준 것을 무시해요.
런 끝에 출력되는 리포트 링크를 열어 보세요. MCP 툴 호출은 MCP로, 에이전트가 로컬에서 한 것은 평범한 함수 호출로 라벨링되므로, 클라이언트가 올바른 표면에 손을 뻗었는지 한눈에 알 수 있어요.
다중 턴 MCP 클라이언트
대화를 유지하는 챗봇이나 에이전트라면 툴 호출을 발생한 턴에 놓고 MultiTurnMCPUseMetric을 사용해요:
from deepeval import evaluate
from deepeval.metrics import MultiTurnMCPUseMetric
from deepeval.test_case import ConversationalTestCase, Turn, ToolCall
test_case = ConversationalTestCase(
turns=[
Turn(role="user", content="Any open bugs about rate limiting?"),
Turn(
role="assistant",
content="Found one, issue #42.",
tools_called=[ToolCall(name="search_issues", input_parameters={"query": "rate limiting"})],
),
Turn(role="user", content="File a follow-up for it"),
Turn(
role="assistant",
content="Filed issue #43.",
tools_called=[ToolCall(name="create_issue", input_parameters={"title": "Follow-up to #42"})],
),
],
)
evaluate(
test_cases=[test_case],
metrics=[MultiTurnMCPUseMetric()],
mcp_servers=[server],
)
태깅은 턴 단위로 동작해서, 다섯 턴 대화에서 클라이언트가 MCP 툴에만 두 번 손을 뻗었다면 정확히 그 두 턴을 보여줘요.
올바른 툴에 손을 뻗는 것과 일을 끝내는 것은 달라요. 클라이언트가 실제로 사용자 요청을 완료했는지가 중요하다면 use 메트릭과 함께
MCPTaskCompletionMetric을 추가하세요.
두 번째 옵션: AI Connection으로 코드 없이
지금까지는 평가를 여러분 코드에서 구동하고 싶다고 가정했어요. 그럴 필요는 없어요.
MCP 클라이언트가 배포되어 닿을 수 있다면 AI Connection으로 Confident AI가 가리키게 하고, 프로젝트 설정에서 MCP 서버를 등록한 뒤, 플랫폼에서 데이터셋을 상대로 전부 실행할 수 있어요. Confident AI가 클라이언트를 호출하고, 출력과 툴 호출을 캡처하고, metric collection으로 런을 채점해요. 테스트 케이스 구성도, evaluate() 호출도, Python에서 뭘 엮을 필요도 없어요.
그 경로는 그 자체로 충분히 설명할 가치가 있어서 별도로 분리해 두었어요:
- MCP 서버 평가하기가 MCP용 전체 AI Connection 설정을 다뤄요 — 트레이싱과 프로덕션 평가를 포함해요
- 평가 실행하기가 데이터셋 고르기부터 리포트 읽기까지 일반적인 무코드 흐름을 다뤄요
작업 방식에 맞는 것을 고르세요. 코드 구동 방식은 더 세밀한 제어가 가능하고 CI에 자연스럽게 들어맞아요. 무코드 방식은 앱을 건드리지 않고 리포트를 얻어요.
다음 단계
MCP 서버 평가하기
이 가이드의 무코드 대응편이에요. AI Connection을 설정하고, 서버를 등록하고, 플랫폼에서 평가하세요.
CI/CD에서 유닛 테스트
이걸 배포 전 게이트로 바꿔서 나쁜 툴 선택 회귀가 절대 프로덕션에 도달하지 않게 하세요.