
MCP 서버 만들기 FastMCP 병렬 검색 Grok API Gemini Search Brave Search
🤔 왜 검색 엔진 하나로는 부족할까요?
AI 에이전트를 만들다 보면 웹 검색이 필수인 순간이 와요. 최신 뉴스를 찾거나, 트렌드를 조사하거나, 기술 문서를 검토할 때 — 검색 없이는 환각(hallucination)만 늘어나죠. 그런데 검색 엔진 하나만 쓰면 편향이 생겨요. Google은 SEO에 최적화된 결과가 많고, Brave는 독립 인덱스라 색다른 소스를 보여주고, Grok은 X(Twitter) 실시간 데이터까지 가져오거든요.
그래서 3개의 검색 소스를 동시에 호출하고, 결과를 교차 검증해서 종합 리포트를 만드는 MCP 서버를 직접 만들었어요. 이름은 terry-research. MCP 서버 만들기에 관심 있다면, 오늘 설계부터 asyncio 버그 해결, 실전 테스트까지 솔직하게 공유할게요.
🏗️ 전체 아키텍처 — 오케스트레이터 패턴
terry-research의 핵심은 오케스트레이터(Orchestrator) 패턴이에요. 하나의 지휘자가 여러 검색 에이전트에게 동시에 일을 시키고, 결과를 모아서 합치는 구조예요.
AI 모델이 외부 도구를 호출하는 표준 프로토콜이에요. Claude, Cursor 같은 AI 클라이언트가 MCP 서버의 도구를 자동으로 발견하고 실행할 수 있어요. 공식 사이트에서 스펙을 확인할 수 있어요.

| 계층 | 모듈 | 역할 | 코드량 |
|---|---|---|---|
| 진입점 | server.py | FastMCP 서버 + 4개 도구 정의 | 226줄 |
| 오케스트레이터 | orchestrator.py | 병렬 디스패치 + 결과 수집 | 176줄 |
| 에이전트 | grok / gemini / brave | 개별 검색 API 호출 | 345줄 |
| 코어 | models + merger + config | 데이터 구조 + 병합 + 설정 | 238줄 |
전체 1,066줄. 작은 코드베이스지만, 에이전트 분리와 병렬 처리 덕에 확장하기 쉬운 구조예요.
🔧 4개의 MCP 도구
terry-research는 4개의 도구를 제공해요. research()가 메인이고, 나머지 3개는 개별 소스를 직접 호출할 때 써요.
@mcp.tool()
def research(
query: str,
topic_type: str = "general",
depth: str = "standard",
sources: list[str] | None = None,
include_images: bool = False,
language: str = "ko",
max_results_per_source: int = 10,
) -> str:
"""Multi-source research
with parallel dispatch."""
| 도구 | 소스 | 특징 |
|---|---|---|
research() | 자동 선택 | 토픽 라우팅 + 병렬 + 교차 검증 |
grok_search() | xAI | 웹 + X(Twitter) 실시간 검색 |
gemini_search() | Google Search 그라운딩 + 씽킹 | |
brave_search() | Brave | 독립 인덱스, 국가/언어 필터 |
🎯 토픽 라우팅 — 주제에 맞는 소스 자동 선택
research()를 호출할 때 topic_type을 지정하면, config.yaml의 라우팅 테이블에 따라 최적의 소스 조합이 자동으로 선택돼요.
# config.yaml
topic_routing:
general:
- grok
- gemini
fashion_trend:
- grok
- gemini
sns_trend:
- grok # X(Twitter) 데이터
tech:
- gemini
- brave # 독립 인덱스
news:
- grok
- gemini
예를 들어 SNS 트렌드는 Grok만 쓰는데, X의 실시간 포스트를 직접 검색할 수 있는 건 Grok뿐이거든요. 기술 주제는 Gemini + Brave 조합이 좋아요 — Google의 방대한 인덱스와 Brave의 독립적인 시각을 함께 볼 수 있어요.
depth 파라미터로 검색 깊이도 조절할 수 있어요.
| Depth | 소스 수 | 모델 | 용도 |
|---|---|---|---|
quick | 1개 | 기본 | 빠른 팩트체크 |
standard | 2-3개 | 기본 | 일반 조사 |
deep | 전부 | 추론 모델 | 심층 리서치 |
deep 모드에서는 Grok이 grok-4-1-fast-reasoning으로, Gemini가 gemini-3.1-pro-preview로 업그레이드되고, 검색 턴 수도 3에서 10으로 늘어나요.
⚡ 병렬 디스패치 — ThreadPoolExecutor
가장 고민했던 부분이에요. 3개의 API를 동시에 호출해야 하니까 비동기 처리가 필수였거든요. 처음에는 당연히 asyncio를 썼어요.
# 처음 시도 (실패!)
async def _dispatch(self, sources):
tasks = [
self._search(s) for s in sources
]
return await asyncio.gather(*tasks)
FastMCP 자체가 이미 async 이벤트 루프 안에서 돌아가요. 그 안에서 asyncio.run()이나 asyncio.gather()를 호출하면 “이벤트 루프가 이미 실행 중” 에러가 나요. nested event loop 문제예요.
해결책은 ThreadPoolExecutor였어요. 스레드 기반 병렬 처리는 이벤트 루프와 충돌하지 않거든요.
def _dispatch_parallel(
self, query, sources, deep, lang
) -> list[SourceResponse]:
with ThreadPoolExecutor(
max_workers=len(sources)
) as pool:
futures = {
pool.submit(
self._search_single,
src, query, deep, lang
): src
for src in sources
}
results = []
for fut in as_completed(
futures, timeout=self.timeout
):
try:
results.append(fut.result())
except Exception as e:
src = futures[fut]
results.append(
SourceResponse(
source=src,
error=str(e)
)
)
return results
이렇게 하면 각 검색 에이전트가 별도 스레드에서 독립적으로 실행돼요. 한 소스가 느려도 다른 소스는 영향을 안 받고, 타임아웃(기본 30초)이 지나면 아직 안 끝난 소스는 건너뛰어요.
한 소스에서 에러가 나도 전체 파이프라인이 죽지 않아요. 에러는 SourceResponse.error에 기록되고, 성공한 소스의 결과만으로 리포트를 만들어요. 이게 실전에서 정말 중요해요 — API 키가 만료되거나 서비스가 잠깐 다운되는 건 일상이니까요.

🔀 결과 병합 — 교차 검증의 힘
3개 소스에서 결과가 돌아오면, Merger가 중복을 제거하고 교차 검증해요.
def merge(self, responses):
# 1. URL 정규화 (www, 슬래시 제거)
# 2. 중복 인용 제거
# 3. multi_source_urls 추적
# — 2개 이상 소스에서 등장한 URL
# 4. per_source 개별 답변 보존
핵심은 multi_source_urls예요. 같은 URL이 Grok과 Gemini 모두에서 나타나면, 그 정보는 신뢰도가 높다는 뜻이에요. 리포트에 “교차 검증됨” 표시가 붙어서, AI 클라이언트가 더 신뢰할 수 있어요.
🧩 에이전트별 특징
각 에이전트는 해당 API의 장점을 최대한 활용하도록 만들었어요.
| 에이전트 | SDK/API | 고유 기능 |
|---|---|---|
| GrokAgent | xAI Responses API | web_search + x_search 동시 사용, 날짜 필터 |
| GeminiAgent | google-genai SDK | Google Search 그라운딩, 씽킹 레벨 (0-8192 토큰) |
| BraveAgent | REST API (httpx) | 독립 인덱스, 국가/언어/최신순 필터 |
Grok은 X 포스트까지 검색해주는 게 최대 장점이에요. SNS 트렌드 조사에서 실시간 반응을 볼 수 있거든요. Gemini는 grounding_metadata에서 검색 근거를 URI/제목 쌍으로 돌려줘서 인용이 정확해요. Brave는 Google과 겹치지 않는 독립 인덱스라 다양성 확보에 좋아요.
📊 실전 테스트 결과
“2026 한국 패션 트렌드”를 topic_type: fashion_trend로 검색한 결과예요.
| 소스 | 응답 크기 | 인용 수 | 특이사항 |
|---|---|---|---|
| Grok | 5,300자 | 12개 | X 포스트 3건 포함 |
| Gemini | 4,281자 | 8개 | Google 그라운딩 메타데이터 |
| Brave | 10건 | 10개 | Google에 없는 독립 소스 |
| 종합 리포트 | 10,724자 | 24개 | 33초, 교차 URL 5건 |
33초 만에 24개 인용이 포함된 10,724자 리포트가 나와요. 개별 소스를 순차적으로 호출했다면 60초 이상 걸렸을 텐데, 병렬 디스패치 덕에 거의 절반으로 줄었어요.
1) 패션 트렌드 (Grok+Gemini) · 2) 기술 리서치 (Gemini+Brave) · 3) SNS 트렌드 (Grok only) · 4) 뉴스 팩트체크 (전체 소스 deep). 토픽 라우팅에 따라 소스 조합이 자동으로 바뀌는 걸 확인할 수 있어요.
🛠️ 직접 써보기 — 설치와 설정
직접 MCP 서버 만들기에 도전하고 싶다면, terry-research 구조가 좋은 출발점이에요. GitHub에 공개 예정이고, Claude Code나 Cursor에 등록하면 바로 쓸 수 있어요.
# 1. 클론 (공개 후)
git clone https://github.com/goandon/terry-research.git
cd terry-research
# 2. 의존성 설치
pip install -r requirements.txt
# 3. API 키 설정 (.env)
XAI_API_KEY=your-xai-key
GEMINI_API_KEY=your-gemini-key
BRAVE_API_KEY=your-brave-key
# 4. MCP 서버 실행
python server.py
API 키가 없는 소스는 자동으로 건너뛰어요. Brave 키 하나만 있어도 brave_search() 도구는 정상 동작해요.
.mcp.json에 아래처럼 추가하면 돼요.
{
"terry-research": {
"command": "python",
"args": ["server.py"],
"cwd": "/path/to/terry-research"
}
}
💡 만들면서 배운 것들
- FastMCP + asyncio 조합은 함정이에요 — FastMCP 내부에서 이벤트 루프가 이미 돌고 있으니, 병렬 처리에는 ThreadPoolExecutor가 안전해요
- 에러 격리는 선택이 아니라 필수 — 외부 API를 3개나 호출하면 하나는 반드시 문제가 생겨요. per-source 에러 핸들링이 없으면 전체가 무너져요
- 교차 검증이 진짜 가치 — 여러 소스에서 같은 URL이 나타나면 신뢰도가 올라가요. 단순히 결과를 합치는 것과는 차원이 달라요
- 토픽 라우팅으로 비용 절약 — 모든 소스를 매번 호출하면 비용과 시간이 낭비돼요. 주제에 맞는 소스만 골라 쓰는 게 실용적이에요
- 1,066줄이면 충분해요 — 과하게 추상화하지 않고, 에이전트 하나당 파일 하나로 유지하면 코드 읽기도 쉽고 유지보수도 편해요

📚 참고 자료
- terry-research GitHub — 전체 소스코드와 설치 가이드 (공개 예정)
- Model Context Protocol 공식 사이트 — MCP 스펙과 SDK 문서
- FastMCP GitHub — Python MCP 서버 프레임워크
- xAI API 문서 — Grok Responses API + 검색 도구
- Gemini API 문서 — Google Search 그라운딩
- Brave Search API — 독립 인덱스 검색 API
- BookStack 위키 셀프호스팅 — 같은 인프라 시리즈 (관련 포스트)
- Synology NAS SSL 설정 가이드 — MCP 서버 호스팅에 활용 가능 (관련 포스트)
✅ 마무리
terry-research MCP의 핵심은 세 가지예요 — 토픽 라우팅으로 최적 소스 선택, ThreadPoolExecutor로 병렬 디스패치, URL 교차 검증으로 신뢰도 확보. 이 패턴은 검색뿐 아니라, 여러 API를 동시에 호출해야 하는 모든 MCP 서버에 응용할 수 있어요.
직접 MCP 서버 만들기에 도전해보면 AI 에이전트의 가능성이 확 넓어지는 걸 느낄 수 있어요. 검색 하나만 추가해도 에이전트가 훨씬 똑똑해지거든요. 여러분도 나만의 MCP 서버, 한번 도전해보세요!
