MCP 음성 서버 직접 만들기 — Stdio + HTTP 양쪽 지원, 코드 50줄
MCP 음성 서버 직접 만들기 — Stdio + HTTP 양쪽 지원, 코드 50줄
call-me-mcp (npm 패키지) 가 표준 구현이지만, 직접 만들면 ① 통화 단가 cap·발신 prefix 화이트리스트 등 custom 정책 박기, ② Slack 알림·DB 로깅 같은 후크 추가, ③ 회사 내부 admin 만 쓰게 자체 auth 적용이 쉬워진다.
0. 사전 준비
- Python 3.10+ 또는 Node 20+
- ClawOps API 키
- MCP SDK (
pip install mcp또는npm install @modelcontextprotocol/sdk)
1. Python — Stdio MCP 서버 (50줄)
mcp_server.py:
import os
from mcp.server.fastmcp import FastMCP
from clawops import ClawOps
server = FastMCP("clawops-voice")
client = ClawOps(api_key=os.environ["CLAWOPS_API_KEY"])
ALLOWED_PREFIXES = ("010", "02", "031", "032", "033", "041", "042", "043",
"044", "051", "052", "053", "054", "055", "061", "062",
"063", "064", "070")
def normalize_korean(num: str) -> str:
cleaned = num.replace("-", "").replace(" ", "").replace("+82", "0")
if not cleaned.startswith("0"):
cleaned = "0" + cleaned
return cleaned
@server.tool()
def make_call(to: str, message: str, from_number: str = "") -> dict:
"""한국 번호로 전화 걸고 message 를 AI 가 read out. 짧은 단방향 알림용."""
to = normalize_korean(to)
if not any(to.startswith(p) for p in ALLOWED_PREFIXES):
return {"error": f"unsupported prefix: {to}"}
sender = from_number or client.numbers.list().items[0].phone_number
call = client.calls.create(
to=to, from_=sender,
agent_config={
"provider": "openai-realtime",
"language": "ko-KR",
"system_prompt": f"다음 메시지를 자연스러운 한국어로 전달하고 끊으세요: '{message}'",
},
)
return {"call_id": call.id, "status": call.status}
@server.tool()
def make_call_with_prompt(to: str, system_prompt: str, from_number: str = "") -> dict:
"""대화형 통화. system_prompt 대로 AI 가 응대. 결과는 get_call_summary 로 조회."""
to = normalize_korean(to)
if not any(to.startswith(p) for p in ALLOWED_PREFIXES):
return {"error": f"unsupported prefix: {to}"}
sender = from_number or client.numbers.list().items[0].phone_number
call = client.calls.create(
to=to, from_=sender,
agent_config={
"provider": "openai-realtime",
"language": "ko-KR",
"system_prompt": system_prompt,
},
)
return {"call_id": call.id, "status": call.status}
@server.tool()
def send_sms(to: str, body: str) -> dict:
"""한국 SMS/LMS 발송. body 길이로 SMS/LMS 자동 분기."""
to = normalize_korean(to)
msg = client.messages.create(to=to, body=body)
return {"message_id": msg.id, "type": msg.type}
@server.tool()
def get_call_summary(call_id: str) -> dict:
"""통화 종료 후 요약·전사 조회."""
call = client.calls.get(call_id)
return {
"status": call.status,
"duration_seconds": call.duration_seconds,
"summary": getattr(call, "summary", None),
"transcript_url": getattr(call, "transcript_url", None),
}
if __name__ == "__main__":
server.run()
Claude Desktop 에 등록
{
"mcpServers": {
"clawops-voice": {
"command": "python",
"args": ["/path/to/mcp_server.py"],
"env": { "CLAWOPS_API_KEY": "ck_live_..." }
}
}
}
2. HTTP 변형 (Cursor background / ChatGPT / 원격 에이전트용)
같은 tool 정의를 HTTP transport 로:
from mcp.server.fastmcp import FastMCP
# ... tool 정의 동일 ...
if __name__ == "__main__":
server.run(transport="http", host="0.0.0.0", port=8080,
auth_token=os.environ["MCP_AUTH_TOKEN"])
Vercel/Fly/Render 에 배포 후 endpoint url + auth_token 으로 원격 에이전트가 호출.
3. 정책 후크 추가 — 일 cap, 야간 차단
import sqlite3, datetime
from zoneinfo import ZoneInfo
DB = sqlite3.connect("/data/mcp-policy.sqlite")
DB.execute("CREATE TABLE IF NOT EXISTS calls (ts TEXT)")
def policy_check(to: str) -> str | None:
now = datetime.datetime.now(ZoneInfo("Asia/Seoul"))
# 야간 발신 금지 (21시-08시)
if now.hour >= 21 or now.hour < 8:
return "야간 발신 금지 (KST 21:00-08:00)"
# 일 cap
today_count = DB.execute(
"SELECT count(*) FROM calls WHERE ts >= ?",
(now.replace(hour=0, minute=0, second=0).isoformat(),),
).fetchone()[0]
if today_count >= 50:
return "일 발신 cap (50회) 초과"
return None
@server.tool()
def make_call(to: str, message: str, from_number: str = "") -> dict:
err = policy_check(to)
if err: return {"error": err}
# ... 기존 로직 ...
DB.execute("INSERT INTO calls (ts) VALUES (?)",
(datetime.datetime.now(ZoneInfo("Asia/Seoul")).isoformat(),))
DB.commit()
return {"call_id": call.id}
4. Slack 알림 후크
import httpx
def slack_notify(text: str):
httpx.post("https://slack.com/api/chat.postMessage",
headers={"Authorization": f"Bearer {os.environ['SLACK_BOT_TOKEN']}"},
json={"channel": "ai-clawops", "text": text})
@server.tool()
def make_call_with_prompt(to: str, system_prompt: str, from_number: str = "") -> dict:
# ... 기존 ...
slack_notify(f"[MCP] AI 통화 시작: {to}\n프롬프트: {system_prompt[:200]}")
return {"call_id": call.id}
5. 작동 원리
Claude/Codex/Cursor agent
↓ MCP stdio/HTTP
당신의 mcp_server.py (policy 후크)
↓ ClawOps SDK (Python/Node)
api.claw-ops.com (GCP 서울)
↓ SIP/2.0 + WebSocket bridge to LLM realtime
한국 070 게이트웨이 → 대상 휴대폰
6. 배포 형태
| 환경 | 추천 |
|---|---|
| 개인 macOS dev | Python stdio, ~/.claude/mcp.json 에 등록 |
| 회사 내부 사용 | HTTP + auth_token, Fly.io shared-cpu-1x |
| Public SaaS | HTTP + auth_token per-user + 일 cap + 결제 연동 |
7. 비용
- 자체 호스팅 무료 (Fly/Vercel 무료 plan)
- ClawOps Individual ₩19,000/월
다음 단계
관련 글 더 보기
MCP 전화 tool 설계 — make_call / hang_up / transfer / DTMF 패턴 정리
MCP 서버에서 전화 기능을 노출할 때 어떤 tool 을 어떻게 쪼개야 LLM 이 제대로 쓰는지. ClawOps SDK 기반 실전 patterns.
가이드MCP 음성 서버 — Claude / Claude Code / Cursor agent에 한국 070 전화 붙이기
MCP(Model Context Protocol) 음성 서버 설치로 Claude Desktop, Claude Code, Cursor agent 에 한국 070 번호 전화 tool 을 붙이는 가이드. call-me-mcp 설치 + 실제 통화 데모.
가이드Claude Code 에 한국 070 번호 붙이기 — MCP 한 줄로 'thomi 한테 전화 걸어' 가능하게
Claude Code(터미널 CLI) 안에서 한국 070 번호로 직접 전화 걸고 받게 만드는 가이드. MCP 서버 설정 + ClawOps 070 번호 발급 + Claude Code agent 가 부르는 실제 프롬프트.
가이드Claude Desktop 에 한국 070 번호 붙이기 — MCP 설정 1줄로 전화 거는 Claude 만들기
Claude Desktop(데스크톱 앱)에 한국 070 번호 전화 기능을 붙이는 가이드. claude_desktop_config.json 1줄 설정 + ClawOps 070 발급 + 실제 사용 프롬프트.
가이드OpenClaw 에 한국 070 번호 붙이기 — 오픈소스 컴퓨터 에이전트 + 전화
OpenClaw(오픈소스 computer-use 에이전트)에 한국 070 번호 전화 도구를 붙이는 가이드. MCP 서버 통합 + ClawOps 070 발급 + 실전 시나리오.