Skip to main content
Glama

mcp-router

수백 개 MCP tool을 다루는 AX 에이전트 백엔드에서 tool routing·context budgeting·서빙 신뢰성을 end-to-end로 다룬 프로젝트. 오프라인 벤치(M3)로 라우팅 전략을 계측하고, 같은 라우팅 코어를 실제 게이트웨이(M1)로 서빙한다. 순수 stdlib로 결정적으로 돌아가며(오프라인 기본), 실제 임베딩·Claude· MCP SDK는 opt-in 어댑터다.

한 줄 요약. 강한 주장을 세우고 실측으로 스스로 반증했다. ① 약한 렉시컬 유사도(mock)에선 semantic top-k가 tool을 놓치는 recall cliff가 가팔랐다(recall@1 0.70→0.13). ② 실제 임베딩 (bge-small)에선 훨씬 완만했다(0.89→0.70), hybrid 이점도 marginal. ③ 실제 MCP 서버 22곳의 tool 247개를 재보니 근접 중복은 실재하나(70%가 코사인 0.80+ 이웃) 합성이 실제보다 과하게 붐볐다 (중앙값 0.94 vs 0.84). 결론: 문제는 진짜지만 초기 헤드라인은 과장 — 어느 상황에서 라우팅이 실제로 중요한지를 재는 도구다.


실무에서 마주친 깊은 문제와 해결

문제 (production)

핵심 고민

해결

Tool sprawl / context budgeting — tool이 수백 개면 스키마가 컨텍스트를 잠식하고 top-k가 정답 tool을 누락

토큰을 아끼면서 recall을 지키는 지점은?

4개 라우팅 전략을 recall@k로 계측, hybrid(벡터+렉시컬)로 recall 회복. "전량 노출 대비 3-of-N 토큰"의 트레이드오프를 수치화

벤치 외적 타당성 — 합성 데이터가 문제를 과장하지 않았나

mock 결론이 실제로도 성립하나

실제 임베딩(bge-small)·실제 tool 코퍼스(22서버/247)로 자기 검증 → 초기 주장 하향 보정

가짜 엄밀성 — 약한 신호 위 통계 남발

지표가 라우팅 품질을 실제로 구분하나

fractional recall@k, gold-cluster bootstrap(클러스터 의존성), McNemar+Benjamini-Hochberg, phi로 task_success↔recall 탈-폐회로 입증

서빙 신뢰성 — 업스트림 장애 전파·권한·중복

한 서버 장애가 전체를 멈추지 않게

per-upstream circuit breaker(closed/open/half-open, 스레드 안전·단일 프로브), RBAC(테넌트 allow/deny·deny 우선·fnmatchcase로 결정적), federation(server.tool 네임스페이싱)

async↔sync 통합 — 비동기 MCP SDK를 동기 게이트웨이에

세션을 안전하게 오래 유지

업스트림별 전용 event-loop 스레드 브리지 + send_ping 헬스체크·지수 백오프 자동 재연결

미인증 캐시 증식(DoS)X-Tenant 원문 키잉

공격자 제어 입력이 메모리를 무한 점유

캐시 키를 유효 정책 아이덴티티로 축약 + 상한

결정성/재현성

같은 seed면 같은 결과인가

순수 stdlib·주입 clock·seed 봉투 → 벤치 수치 바이트 단위 재현

과설계 회피

안 쓸 인프라를 넣지 않기

pgvector/OTel/latency 파이프라인 절단, 실제 어댑터는 .[extra] opt-in

각 항목은 적대적 리뷰(총 7회)로 검증했다 — 라우팅 폐회로, breaker 썬더링 허드, readonly RBAC 우회, 캐시 DoS, MCP 세션 누수 등 실제 결함을 매번 잡아 고쳤다.


시스템 아키텍처

%%{init: {"theme":"base","themeVariables":{"fontFamily":"Pretendard, Segoe UI, sans-serif","primaryColor":"#F0EEE6","primaryTextColor":"#29261B","primaryBorderColor":"#C6714F","lineColor":"#B5654A","clusterBkg":"#FAF9F5","clusterBorder":"#DED7C9"}}}%%
flowchart TB
    CLIENT["MCP 클라이언트 / 에이전트"]

    subgraph M1["M1 · 서빙 게이트웨이 (gateway/)"]
        TR["Transport<br/>MCP stdio(SDK) · HTTP JSON-RPC"]
        RB["RBAC<br/>테넌트 allow/deny · deny 우선"]
        CB["Circuit Breaker<br/>closed · open · half-open"]
        FD["Federation<br/>server.tool 네임스페이싱"]
        UP["MCP 업스트림<br/>stdio/HTTP · 헬스체크 · 자동 재연결"]
    end

    subgraph CORE["공유 라우팅 코어 (routing/ · providers/)"]
        EM["Embedder → Vector Index<br/>bge-small · mock · exact cosine"]
        RT["Router<br/>passthrough · semantic_topk<br/>hierarchical · hybrid"]
    end

    subgraph M3["M3 · 오프라인 평가 (bench/)"]
        CAT["카탈로그<br/>합성 staircase + 실제 tool 247"]
        MET["Metrics<br/>recall@k · cluster bootstrap<br/>McNemar+BH · phi"]
        RPT["Report<br/>md · csv · svg · traces"]
    end

    CLIENT --> TR --> RB --> RT
    RT --> CB --> FD --> UP
    EM --> RT
    CAT --> RT --> MET --> RPT

    classDef m1 fill:#C6714F,stroke:#A8543A,color:#FCF9F3;
    classDef core fill:#D9A883,stroke:#BE7C57,color:#2B2013;
    classDef m3 fill:#EADFCB,stroke:#CBB897,color:#29261B;
    class TR,RB,CB,FD,UP m1;
    class EM,RT core;
    class CAT,MET,RPT m3;

게이트웨이 요청 흐름 (tools/call)

%%{init: {"theme":"base","themeVariables":{"fontFamily":"Pretendard, Segoe UI, sans-serif","primaryColor":"#F0EEE6","primaryTextColor":"#29261B","primaryBorderColor":"#C6714F","lineColor":"#B5654A"}}}%%
flowchart LR
    Q(["tools/call<br/>tenant · name · args"]) --> P{"RBAC 허용?"}
    P -->|deny| D["거부<br/>-32001"]
    P -->|allow| B{"breaker 상태"}
    B -->|open| F["fast-fail<br/>-32002"]
    B -->|closed / half-open| C["업스트림 호출"]
    C -->|성공| OK(["결과"])
    C -->|실패| T["실패 집계 → 임계 초과 시 open"]
    H["헬스 프로브<br/>send_ping"] -.->|사망 감지| RC["백오프 재연결"]

    classDef n fill:#F6F3EA,stroke:#DED7C9,color:#29261B;
    classDef hot fill:#C6714F,stroke:#A8543A,color:#FCF9F3;
    class P,B,C,OK,T,H,RC n;
    class D,F hot;

핵심 결과: mock(BoW) vs 실제(bge-small)

카탈로그 300 · semantic_topk의 fractional recall@1 낙폭, 그리고 전략별 recall@k:

mock r@1 (100→300)

bge r@1 (100→300)

300에서 recall@3 (mock / bge)

semantic_topk

0.70 → 0.13

0.89 → 0.70

0.62 / 0.90

hybrid

0.92 / 0.93

passthrough

1.00 (tokens ~31k)

1.00

1.00 / 1.00

  • 방향은 둘 다 성립(카탈로그↑ → recall↓, hybrid는 큰 k에서 도움). 크기는 실제 임베딩에서 훨씬 작다.

  • 전량 노출은 토큰 ~100배(≈31k vs ≈316@k=3). 괜찮은 임베딩 + k≥3이면 그 recall을 거의 따라잡는다.

recall cliff

증거는 docs/에 커밋(mock/bge 차트, 리포트/CSV, 실제 tool 유사도 분석). make bench / make bench-real / make similarity로 재현.


실행

make bench          # 오프라인·순수 stdlib·같은 seed면 바이트 단위 재현
make test           # 41 unittest (cliff·에이전트·게이트웨이·재연결·실제 MCP 왕복)
make gateway-demo   # 게이트웨이 walk-through: federation → RBAC → routing → breaker 트립/복구
make bench-real     # bge-small 실제 임베딩 (pip install .[local])
make similarity     # 실제 MCP tool 코퍼스 pairwise 유사도

# 실제 MCP 연결 (pip install .[mcp])
python -m mcp_router gateway serve --transport mcp-stdio         # 게이트웨이를 MCP 서버로 서빙
python -m mcp_router gateway serve --config deploy/gateway.mcp.config.json   # 실제 stdio/HTTP 업스트림

무엇이 실제고 무엇이 mock인가

  • 실행 검증: bge-small 임베딩, 실제 MCP SDK 양방향 stdio 왕복(업스트림 연결 + 게이트웨이 서빙), 실제 tool 코퍼스 유사도.

  • mock(기본): hashed BoW/char-trigram 임베딩, Jaccard 선택 에이전트, in-process 업스트림.

  • 미실행(로드맵): Claude tool-use 에이전트(cassette), 실제 토크나이저 토큰 계측.

  • task_success는 약한 보조 지표(정답 개수 미고지) — phi≈0.29로 recall과 독립임을 명시. 라벨 리포트는 사람 검증이 아닌 self-consistency(κ≈0.40).

구조

src/mcp_router/
  catalog/     합성 staircase(키워드 충돌 distractor) + 쿼리 생성 + Spec
  routing/     4 전략 + RoutingContext            providers/  임베딩(mock/mock_char/bge) + llm(mock/claude)
  vectorstore/ exact cosine index                labeling/   self-consistency 라벨러 + kappa
  bench/       선택 에이전트 · metrics · runner · report
  gateway/     federation · rbac · breaker · server · transport(HTTP)
               mcp_upstream(SDK client·재연결) · mcp_server(SDK stdio) · factory
tests/         41 unittest (.[mcp] 없으면 실제-MCP만 skip)

로드맵

streamable-HTTP 멀티테넌트 서버, cassette 커밋한 Claude 에이전트 실행, 실제 토크나이저 토큰 계측, 더 넓은 tool 표본.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HongJungWan/mcp-router'

If you have feedback or need assistance with the MCP directory API, please join our Discord server