MCP 전화 tool 설계 — create_call / control_call / send_message 패턴 정리
MCP 서버에서 전화 기능을 노출할 때 어떤 tool 을 어떻게 쪼개야 LLM 이 제대로 쓰는지. ClawOps MCP / SDK 기반 실전 patterns.
MCP 전화 tool 설계 — create_call / control_call / send_message 패턴 정리
MCP 서버에 전화 기능을 노출할 때 tool 을 어떻게 쪼개느냐로 LLM 사용성이 갈린다. 너무 잘게 쪼개면 LLM 이 순서 헷갈리고, 너무 묶으면 통제 못 함.
좋은 소식: ClawOps 는 이 tool 셋을 원격 HTTP MCP 서버로 이미 제공한다. 직접 MCP 서버를 짤 필요 없이 URL 한 줄(https://api.claw-ops.com/mcp, 인증은 OAuth)만 등록하면 아래 tool 들이 그대로 노출된다. 이 글은 (1) ClawOps MCP 가 실제로 어떤 tool 을 어떤 모델로 노출하는지, (2) 직접 MCP 서버를 짤 때 같은 패턴을 어떻게 따라야 하는지를 정리한다.
ClawOps MCP 가 노출하는 전화 관련 tool
| tool | 역할 | 호출 시점 |
|---|---|---|
create_call | 전화 발신 (To/From/Url/AI) | 발신이 필요한 모든 시나리오 |
control_call | 진행 중 통화 제어 (현재 hangup) | 통화 종료 |
send_message | SMS/LMS/MMS 발송 | 문자 안내·링크 전달 |
get_call_summary | 끝난 통화 요약 조회 | 결과 보고 |
get_transcript | 통화 전사 조회 | 상세 확인 |
핵심: 발신은 항상 create_call 하나다. "알림성 단방향"과 "대화형"을 별도 tool 로 쪼개지 않는다. 차이는 tool 이 아니라 어떤 모드로 거느냐에서 갈린다.
Url을 지정하면 → 그 URL 이 반환하는 VoiceML 시나리오대로 통화 진행AgentId를 지정하면 → 콘솔에서 만든 매니지드 에이전트가 통화를 처리.CallContext로 이번 통화만의 지시를 덧붙일 수 있다CallFlowId를 지정하면 → 콜 플로우(결정적 ARS)가 통화를 진행.Variables로 시작 변수를 넘긴다- 셋 다 생략하면 →
From번호에 연결된 Agent SDK(실시간 대화) 모드로 통화 진행
세 필드는 서로 배타적이다.
1. 직접 MCP 서버를 짤 때 — create_call 래핑
ClawOps MCP 를 그대로 쓰지 않고, 자기 도메인 로직을 끼워 직접 MCP 서버를 만들 수도 있다. 이때도 발신 tool 은 하나로 유지하고 내부에서 ClawOps SDK client.calls.create 를 호출한다.
DEFAULT_FROM = "07012345678" # 발급받은 070 번호
@server.tool()
def create_call(to: str, scenario_url: str | None = None) -> dict:
"""전화 발신.
- scenario_url 지정: 해당 VoiceML 시나리오대로 통화 진행
- scenario_url 생략: From 번호에 연결된 Agent(실시간 대화) 모드
"""
call = client.calls.create(
to=to,
from_=DEFAULT_FROM,
url=scenario_url, # None 이면 Agent 모드
)
return {"call_id": call.call_id, "status": call.status}
LLM 이 헷갈리지 않게: "대화형이냐 알림이냐"를 tool 이름이 아니라 scenario_url 유무로 표현한다고 description 에 명시. 시스템 프롬프트 작성을 LLM 한테 떠넘기지 말 것 — 시나리오는 scenario_url 이 가리키는 서버에서 고정한다.
매니지드 에이전트로 발신
콘솔에서 만든 AI 에이전트에게 통화를 맡길 수도 있다. agent_id 는 url 과 동시에 쓸 수 없다.
@server.tool()
def create_ai_call(to: str, instruction: str) -> dict:
"""AI 가 instruction 대로 응대하는 통화. url 과 동시 사용 불가."""
call = client.calls.create(
to=to,
from_=DEFAULT_FROM,
agent_id=DEFAULT_AGENT_ID,
call_context={"instruction": instruction},
)
return {"call_id": call.call_id}
에이전트의 말투·지식·전환 대상은 콘솔에 설정해 두고, 이번 통화에서만 다르게 할 내용을 call_context 로 넘기는 구조다. 같은 에이전트로 동시에 거는 다른 통화에는 영향이 없다.
2. control_call — 통화 종료
진행 중 통화 제어는 control_call 이다. 현재 지원하는 동작은 Status="completed"(hangup). idempotent 하다.
@server.tool()
def control_call(call_id: str) -> dict:
"""진행 중 통화 종료 (Status='completed')."""
client.calls.update(call_id, status="completed")
return {"call_id": call_id, "status": "completed"}
참고: DTMF 전송이나 통화 전환(transfer) 같은 동작은 별도 MCP tool 로 제공되지 않는다. 통화 안에서의 흐름 제어(목적 달성 후 종료 등)는 Agent 모드의 system prompt / VoiceML 시나리오에서 처리하고, 외부에서의 강제 종료만
control_call로 한다.
3. send_message — 문자 안내
통화 중/후에 링크·주소·기사 연락처 등을 문자로 보내는 패턴.
@server.tool()
def send_message(to: str, body: str) -> dict:
"""SMS/LMS/MMS 발송. 본문 길이에 따라 자동 분류."""
msg = client.messages.create(to=to, from_=DEFAULT_FROM, body=body)
return {"message_id": msg.message_id}
4. get_call_summary / get_transcript — 결과 조회
끝난 통화의 요약·전사는 별도 tool 로 조회한다. 둘 다 비동기 처리라 status 가 completed 가 될 때까지 폴링할 수 있다.
@server.tool()
def get_call_summary(call_id: str) -> dict:
"""통화 요약 조회. 아직 처리 중이면 status='pending'."""
s = client.calls.get_summary(call_id)
return {"status": s.status, "summary": getattr(s, "result_json", None)}
@server.tool()
def get_transcript(call_id: str) -> dict:
"""통화 전사 조회."""
t = client.calls.get_transcript(call_id)
return {
"status": t.status,
"segments": [
{"speaker": seg.speaker, "text": seg.text}
for seg in (t.segments or [])
],
}
5. 안 권장 — 묶지 말 것
make_call_and_get_summary(발신+대기+결과 묶기) — 통화 시간 변동이 큰데 동기 호출이라 timeout 위험. 발신(create_call)과 결과 조회(get_call_summary)는 분리.- 알림용/대화용 발신 tool 을 따로 만들기 — 발신은
create_call하나, 모드는Url/AI로 표현. start_recording/stop_recording별도 tool — ClawOps 는 기본 자동 녹음이라 불필요.
6. tool description 작성 팁 (LLM 한테 잘 쓰이게)
- 한 문장 안에 언제 쓰는지 명시 ("발신용", "통화 종료용")
- 파라미터 한국어 예시 ("010-1234-5678 형식")
- 부작용 명시 ("통화 종료 즉시 호출")
- 다른 tool 과의 관계 명시 ("결과는 get_call_summary 로 조회")
LLM 은 description 만 보고 어떤 tool 을 쓸지 결정. 모호하면 wrong tool 선택.
7. 작동 원리
ClawOps MCP 를 그대로 쓰는 경우:
LLM (Claude Code / Cursor 등)
↓ MCP tool call (HTTP / OAuth)
api.claw-ops.com/mcp (원격 MCP 서버)
↓
한국 070 → 대상 휴대폰
직접 MCP 서버를 짜는 경우:
LLM
↓ MCP tool call
당신의 MCP 서버 (create_call / control_call ...)
↓ ClawOps SDK (client.calls.create ...)
api.claw-ops.com
↓
한국 070 → 대상 휴대폰