[개발일기] MCP 제작기 : Claude와 daglo(다글로)를 연결하기
TL;DR
- MCP(Model Context Protocol)로 Claude에 daglo STT에 연결
- GPT가 모르는 개념을 In-context learning으로 학습시킴
- async → sync 방식 전환, 로컬 파일 시스템 접근 등 시행착오 겪음
- MCP 도구를 직접 구현해 로컬 음성 파일 → 텍스트 변환 흐름 완성
- LLM이 외부 도구를 다루는 인터페이스 실험의 첫 사례 기록
1. MCP 때문에 긁힌 썰.txt
은 내가 나를 긁었던 것..
요즘 AI 커뮤니티는 첫 글부터 마지막 글까지 MCP로 도배가 되었다고 해도 과언이 아니다.
“아니, 아무리 그래도 3년차 AI 연구원인 내가 이걸 모른다고?”
어디 가서 아는 척이라도 해야 면이 서겠다 싶어 슬쩍 찾아봤다.
Model Context Protocol….?
Protocol…. 개발자들 용어 같기도 하고?
AI 연구원이라고 소개하면, 사람들은 내가 AI인 줄 알고 질문을 쏟아낸다.
Protocol에 대해서도 대충 얼버무리고 말았는데, 이 기회에 ‘마스터 해보겠다’는 호기로운 다짐을 했다.
더군다나 MCP는 LLM이 외부 도구를 직접 호출할 수 있도록 해주는 프로토콜이란다.
그렇다면 우리 액션파워의 서비스 daglo를 Claude에 붙여서,
LLM이 음성까지 직접 인식하게 만들어야겠다는 아이디어가 번뜩였다.
‘도전하지 않을 이유가 없다’는 생각과 함께 ‘해볼 만하겠는데?’ 싶어 그 길로 MCP 개발 도전을 시작했다.
(해볼만 ←이라는 생각부터가 대형 사고의 시작이었음을…)
2. 믿는 LLM에 발등 찍힌다?
MCP를 사용하려면 구현 방법과 개념적인 구조를 더 깊이 이해야 한다.
그래서 나의 소울 메이트 GPT와 Claude에게 물었다.
“MCP 알아?”
알긴 뭘 알아.
돌아오는 답변은 부족했다.
그럴듯하게 알려주는데, 도움이 전혀 안된다.
그 길로 소울 메이트와의 연을 끊었다.
그냥 메이트 인걸로…
가장 큰 한계는 역시나 ‘최신 정보를 모른다’는 것이다.
Cut-off date 이후에 나온 기술에 대해서는 아예 개념이 없거나, 틀린 정보를 주기도 한다.
GPT에게 하고 싶은 걸 설명만 하면, 코드를 뚝딱 만들어서 바로 테스트해 볼 수 있는 이른바 vibe coding 시대라는데..
그래서 MCP 서버 정도는 그렇게 쉽게 끝낼 줄 알았는데..
해내겠다고 주변에 호언장담 해두었는데…
나 어떡하지..?
아 그냥 때려 치울까? !%$#%ㅃ#ㅃ$#ㅆ%%ㅎㅍㅊ ^^..
3. In-context learning과 NEW 소울 메이트 관계 체결
문제 해결의 실마리는 욕심을 내려놓을 때 발견된다고 하지 않는가.
몸과 마음을 비우기 위해 화장실에서 시간을 보내던 중,
In-context learning이라는 단어가 머릿속을 스쳤다.
“GPT가 MCP를 모르면, 내가 알려주면 되잖아?”
MCP를 공부하면서 모아둔 자료를 선별해서 GPT에게 던졌다.
그리고 다시 한번 물었다.
“너, 이제 MCP 알아?” (알지? 알아야 해, 반드시)
오… 이제 된다. 드디어 가능성이 보인다.
4. Vibe coding의 시작 (사실은 삽질의 시작)
그럼 이제 남은 건 하나.
Claude가 daglo API를 사용할 수 있게 만들어 주는 것.
daglo 공식 API 가이드 문서를 참고해서,
GPT에게 API 사용법과 함께 MCP 구축 방법을 물어보며
본격적인 vibe coding을 시작했다. 그렇게 몇 번의 시도 끝에…
Claude 입력창에 망치 하나가 생겼다.
드디어 내가 만든 도구가 Claude에 등록된 것이다.
부푼 마음을 안고 테스트를 시작했다.
그런데 돌아온 결과는 …
이상한 문장이 튀어나온다.
제대로 음성인식을 한 게 아니라, 그냥 URL을 보고 음성 내용을 지어낸 것 같은 결과였다.
내가 널 어떻게 키웠는데…….
5. 디버깅은 결국 사람 몫이다
딱 이 짤이 떠올랐다.
그래도 명색이 AI 연구원인데, vibe coding을 한다고 해도 문제의 원인은 파악하면서 물어봐하지 않겠나.
의심 가는 부분을 디테일하게 하나씩 짚어가며 GPT와 대화를 이어갔다.
그렇게 채팅창은 점점 길어지고,
LLM은 같은 말만 반복하고,
본체를 때려보기도 하고,
마우스는 이미 저 멀리 날아가있고,
나의 멘탈도 산산조각 나려고 하는 찰나..
“잠깐, 혹시… API 요청은 제대로 들어가고 있는 거 맞나?”
문득 궁금해져서 daglo 콘솔 창을 열어봤다.
요청은 잘 들어오고 있다.
내가 요청을 많이도 보냈네 ㅋㅋㅋㅋ 근데 몇 가지 요청은 아직 완료가 안 돼 있다.
“MCP가 async를 제대로 처리 못 해서,
응답이 오기 전에 Claude가 그냥 자기 맘대로 말하는 건 아닐까?”
“그렇다면, 응답을 빠르게 받을 수 있는 sync 방식으로 바꾸면 되지 않을까?”
근데 역시 세상은 호락호락하지 않다.
Sync 방식은 URL이 아니라 음성 파일을 직접 보내야 한다.
그 와중에 Claude는 음성 파일 업로드가 안 된다.
1nnn차 위기..^^
그래도 연구 인생 헛살진 않았나 보다.
이내 방법이 떠올랐다.
“아 맞다. MCP는 로컬 파일 시스템에 접근할 수 있지!”
그렇다면,
로컬에 저장된 음성 파일을 Claude가 직접 읽도록 MCP에 파일 시스템 기능을 붙이면 된다.
바로 GPT에게 그 구조를 다시 짜달라고 시켰다.
그리고 시도.
결과는…
오… 된다!!! 🎉
6. 이번 실험의 핵심은 ‘이것’이었다
이제 Claude는,
내가 만든 MCP 도구를 통해 로컬 음성 파일을 읽고,
daglo STT API를 호출해서 텍스트로 변환한 뒤,
그 결과를 Claude 창에 직접 출력해 준다.
말 그대로,
Claude가 내 컴퓨터에 있는 음성 파일을 인식하고 대답하는 환경을 구현했다.
🔧 전체 구조는 이렇게 되어 있다:
- Claude는 내가 만든 도구를 호출하고,
- MCP 서버는 로컬 파일을 읽어서 daglo에 전송하고,
- 인식 결과를 다시 Claude에게 전달한다.
이걸 만들기 위해 내가 한 일은 다음과 같다:
- MCP 구조 이해
- daglo API 활용
- In-context learning으로 LLM 학습 유도
- vibe coding으로 서버 구축
- async/sync 요청 방식 차이 해결
- 로컬 파일 시스템 연동
기능적으로는 단순해 보일 수 있지만,
이 모든 연결을 Claude 안에서 작동시키는 것,그게 바로 이번 실험의 핵심이었다.
7. MCP를 통해 가능해진 것들, 그리고 다음 이야기
처음엔 단순한 호기심이었다.
“LLM이 음성 파일을 직접 처리할 수 있다면 어떨까?”
하지만 실제로 만들어보니, 단순한 음성인식 연동이 아니었다.
Claude라는 인터페이스를 통해,
우리가 만든 기술(daglo STT)을 LLM이라는 두뇌에 연결
MCP는 단순한 확장 API가 아니라,
LLM이 외부 세상과 소통할 수 있도록 설계된 진짜 인터페이스다.
이번 작업은 그 가능성을 보여주는 아주 작은 예시일 분이다.
회사에서 만든 기술이
LLM이라는 강력한 두뇌에 연결되었을 때,
어떤 새로운 인터페이스가 만들어질 수 있을까?
앞으로는…
- 카메라 연결?
- 엑셀 분석?
- 브라우저 컨트롤?
LLM이 단순히 대답하는 도구가 아니라
진짜 에이전트가 되는 시대.
그 가능성은, 생각보다 멀지 않다.
Claude는 MCP를 몰라도,
우리는 Claude에게 능력을 가르칠 수 있다.
그리고 이 글은, 그 첫 번째 실험에 대한 기록이다.
실험에 사용한 주요 명령어 & 코드 예시
“아래는 시행착오 끝에 만들어진 MCP 서버 코드입니다. 비슷한 작업을 한다면 도움이 될 거예요.”
- MCP 서버 템플릿 생성
uvx create-mcp-server --path stt_service
2. 서버 구현 (server.py)
import asyncio
import os
import httpx
from dotenv import load_dotenv
from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server
import mcp.server.stdio
import mcp.types as types
from mcp.types import TextContent, Tool
from typing import Any
# 🌱 환경 변수 로드
load_dotenv()
DAGLO_API_TOKEN = os.getenv("DAGLO_API_TOKEN")
# 🛠️ MCP 서버 인스턴스
server = Server("stt-server")
# 🔧 MCP 도구 목록
@server.list_tools()
async def handle_list_tools() -> list[Tool]:
return [
Tool(
name="transcribe-local-audio",
description="로컬의 짧은 음성 파일을 Daglo 동기 STT로 전송합니다.",
inputSchema={
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "로컬 머신의 음성 파일 절대 경로 (예: /Users/jc/Desktop/audio.wav)"
}
},
"required": ["file_path"],
},
)
]
# 🧠 Claude/Zed가 도구 호출했을 때 실행되는 함수
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict[str, Any] | None) -> list[TextContent]:
if name != "transcribe-local-audio":
raise ValueError(f"Unknown tool: {name}")
if not arguments or "file_path" not in arguments:
raise ValueError("Missing 'file_path' in arguments")
file_path = arguments["file_path"]
print(f"🎯 [MCP 도구 호출됨] file_path = {file_path}")
if not os.path.exists(file_path):
return [TextContent(type="text",
text=f"❌ 파일을 찾을 수 없습니다: `{file_path}`"
)
]
try:
async with httpx.AsyncClient(timeout=60.0) as client:
with open(file_path, "rb") as f:
print("📤 Daglo STT 요청 전송 중...")
response = await client.post(
"https://apis.daglo.ai/stt/v1/sync/transcripts",
headers={"Authorization": f"Bearer {DAGLO_API_TOKEN}"},
files={"file": (os.path.basename(file_path), f, "audio/wav")},
)
print(f"📥 Daglo 응답 수신: {response.status_code}")
response.raise_for_status()
result = response.json()
transcript = result["sttResult"]["transcript"]
print(f"✅ 인식 결과:\n{transcript}")
return [
TextContent(
type="text",
text=f"📝 인식 결과:\n\n```\n{transcript}\n```"
)
]
except Exception as e:
print(f"❗️예외 발생: {str(e)}")
return [
TextContent(
type="text",
text=f"❌ STT 요청 중 오류 발생: {str(e)}"
)
]
# 🚀 MCP 서버 실행
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="stt-server",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
if __name__ == "__main__":
asyncio.run(main())
3. Claude 연동 설정 예시 (claude_desktop_config.json)
"mcpServers": {
"stt-server": {
"command": "uv",
"args": [
"--directory",
"/Users/YOUR_NAME/Desktop/stt_service",
"run",
"stt-server"
]
}
}
💡 참고
- daglo 공식 API 문서: https://developers.daglo.ai
- MCP 위키 문서: https://wikidocs.net/268827
“이 글이 여러분의 MCP 도전에도 작은 도움이 되길 바랍니다.”