들어가며
agentgateway 에 MCP backend 를 여러 개 붙여서, 하나의 listener 포트 아래에서 path 로만 갈라서 노출하고 싶었다.
예를 들면:
- /agentgateway/mcp1 → 팀 A 의 LangFlow MCP (도구 21개)
- /agentgateway/mcp2 → 외부 mcpbin (테스트용 도구)
- /agentgateway/mcp3 → 내부 검색 도구 MCP
각 path 가 사실상 테넌트 역할이고, 테넌트별로 인증·도구셋·정책이 다르다. agentgateway 가 이걸 지원하는지 확인하고, UI 를 약간 손봐서 운영자 친화적으로 만든 회고. 그리고 미등록 path 의 default 동작이 함정이라 그것도 정리.
핵심 키워드: agentgateway, multi-MCP routing, pathPrefix multiplexing, MCP tenant, AI Gateway, fallback route.
시스템 컨텍스트
요구사항:
- 한 포트(예: :3000) 아래 path 로 여러 MCP 서버 노출.
- 각 path 가 독립적 MCP 백엔드 + 독립적 Authorization 정책.
- 운영자가 UI 에서 route 를 동적으로 추가/삭제·MCP 셋 바꿀 수 있어야 함.
- route 별로 외부에 안내할 full URL 을 한 번에 복사할 수 있어야 함 (테넌트 온보딩 편의성).
- 미등록 path 호출은 명시적 404 (디폴트 응답 노출 금지).
LLM Client
│
│ /agentgateway/mcp1 ─────────────┐
│ /agentgateway/mcp2 ──────┐ │
│ /agentgateway/mcp3 ─┐ │ │
▼ │ │ │
agentgateway listener :3000│ │ │
├──────────────────────────┐ │ │
│ route mcp1 ─────────────────────> LangFlow MCP A
│ route mcp2 ──────────────────> mcpbin (external)
│ route mcp3 ─────────> 사내 검색 MCP
└─ fallback ──> 404
문제 상황
처음 잡혀 있던 상태:
- agentgateway 자체는 떠 있고, 하나의 MCP backend (LangFlow) 만 붙어 있음.
- 다른 팀이 자기 MCP 도 같이 노출하고 싶다고 요청.
- 각 팀이 정책을 따로 가지고 있어서, "모두 한 path 에 합쳐서" 는 안 됨.
또한 UI 사용성 이슈:
- 자체 UI 에서 "MCP Backend" 와 "MCP Authorization" 카드가 평면 구조로 분리 노출 → "어떤 route 에 어떤 backend·정책이 묶였는지 한 눈에 안 보임".
- route 의 path prefix 만 표시되고 외부 full URL 은 별도 조합 필요 → 운영자가 매번 손으로 합쳐서 안내.
시도한 것들
시도 1 — 지원 가능성 확인
agentgateway 의 라우팅 구조를 뜯어봤다.
bind:
port: 3000
listeners:
- name: mcp-main
routes:
- name: route-a
pathPrefix: /agentgateway/mcp1
backend: backend-a
- name: route-b
pathPrefix: /agentgateway/mcp2
backend: backend-b
핵심 메커니즘:
- bind → 포트 listener.
- listener → 그 포트 아래에서 protocol·hostname 매칭.
- route → path prefix 단위로 backend 를 매칭.
- backend → 실제 MCP server (streamable HTTP) 또는 다른 protocol.
route.pathPrefix 가 곧 multiplexing 키. 같은 bind 포트 아래에서 prefix 가 다르면 서로 다른 backend 로 라우팅. 완전히 가능.
시도 2 — 자체 UI 의 한계 파악
자체 UI 로 route 추가는 되지만 운영자 관점에서 불편한 점들.
- MCP Backend 카드와 MCP Authorization 카드가 route 와 분리된 평면 구조 → 어떤 route 에 어떤 backend·정책이 묶여 있는지 한 눈에 안 들어옴.
- route 의 path prefix 만 표시되고, 외부 full URL 은 별도 조합 필요. 운영자가 매번 https://gateway.example.com + path 를 합쳐서 안내해야 함.
- Route name / Path prefix 수정 UX 가 모달 깊숙이.
시도 3 — 운영자용 thin UI 한 겹 얹기
agentgateway 위에 운영자용 작은 관리 UI 를 한 겹 얹는 방향으로 갔다. 표시 모델만 바꾸면 되므로 코드량이 크지 않다. 핵심 변경:
- route 카드 안에 MCP Backend / MCP Authorization 를 종속 표시 (sub-card 로). 이렇게만 해도 인지 부하가 크게 줄어든다.
- route 헤더에 Route Name (Tenant) 형태로 라벨링. 한 줄로 어떤 테넌트의 route 인지 식별.
- 각 route 의 path prefix 옆에 full URL 과 copy 버튼 노출. https://gateway.example.com/agentgateway/mcp1 한 번 클릭으로 복사.
- Route Name / Path Prefix 인라인 편집 (별도 모달 진입 없이).
시도 4 — Route 별 Authorization 분리
MCP Authorization 도 route 단위로 묶이도록 매핑. Cedar 정책은 route id 를 attribute 로 받아서 평가:
// tenant-a.cedar
forbid (
principal,
action,
resource
) unless {
context.route.id == "tenant-a" &&
mcp.tool.name in [
"search",
"rag-query"
]
};
테넌트 A 의 화이트리스트와 테넌트 B 의 화이트리스트가 다른 정책 파일로 갈라진다. CI 에서 각 정책을 개별 검증 가능 (전날 다룬 Cedar 검증 절차 그대로 적용).
이 구조의 장점:
- 격리성. 한 테넌트의 정책 변경이 다른 테넌트에 영향 안 줌.
- 검증성. 정책 파일 단위로 syntax + 시뮬레이션 검증.
- 추적성. 정책 변경 PR 에 어떤 테넌트인지가 명확.
시도 5 — 미등록 path 의 default 동작 (함정)
agentgateway 의 미등록 path (예: http://agentgateway.<ns>.svc.cluster.local:3000/agentgateway/mcp-unknown) 로 요청이 들어오면, 기본적으로 게이트웨이 root 또는 정의되지 않은 응답으로 매칭된다. 그래서:
- 운영 관점에서 어떤 path 가 valid 한지 모호함.
- 디버깅 시 "이 path 는 안 잡혔다" 라는 게 명확하지 않음.
해결: listener level 에서 fallback route 를 명시적으로 추가. 우선순위가 가장 낮은 / prefix route 가 404 응답을 반환하도록.
- name: fallback-404
pathPrefix: /
response:
status: 404
body: "route not found"
이렇게 두면 미등록 path 호출은 404 로 명확히 응답. 디버깅 / 보안 모두 개선.
시도 6 — Path Prefix 표시 개선
자체 UI 가 path prefix 만 보여주는 게 불편하다고 사용자가 명시적으로 요청했다.
"Path Prefix 가 /agentgateway/mcp1 이게 아니라 http://agentgateway.<ns>.svc.cluster.local:3000/agentgateway/mcp1 이거로 뜨도록 해줘. 그리고 옆에 copy 버튼 하나 눌러서, 이 주소를 카피하기 쉽도록 만들어줘."
내부 통신용 full URL 과 외부 노출용 full URL 을 둘 다 표시하는 게 베스트.
Route: tenant-a-langflow (Tenant)
Path Prefix: /agentgateway/mcp1
Internal URL: http://agentgateway..svc.cluster.local:3000/agentgateway/mcp1 [copy]
External URL: https://gateway.example.com/agentgateway/mcp1 [copy]
운영자가 어떤 환경에 어떤 URL 을 안내할지 한 화면에서 결정.
해결 — 최종 구성
# agentgateway 라우팅 의사 코드
bind:
port: 3000
listeners:
- name: mcp-main
routes:
- name: tenant-a-langflow # Route Name (Tenant)
pathPrefix: /agentgateway/mcp1
backend:
type: mcp
url: http://langflow-a:7860/api/v1/mcp/project/<UUID_A>/streamable
authorization: tenant-a.cedar
- name: tenant-b-mcpbin
pathPrefix: /agentgateway/mcp2
backend:
type: mcp
url: https://mcpbin.example.com/remote/mcp
authorization: tenant-b.cedar
- name: tenant-c-search
pathPrefix: /agentgateway/mcp3
backend:
type: mcp
url: http://search-mcp:8080/mcp
authorization: tenant-c.cedar
- name: fallback-404
pathPrefix: /
response:
status: 404
body: "route not found"
운영자 UI 의 표시 모델:
┌─ Route: tenant-a-langflow (Tenant) ─────────────────┐
│ External: https://gateway.example.com/.../mcp1 [c] │
│ │
│ ├─ MCP Backend │
│ │ type: mcp │
│ │ upstream: http://langflow-a:7860/... │
│ │ │
│ └─ MCP Authorization (tenant-a.cedar) │
│ allow tools: search, rag-query, ... │
│ │
│ [edit name] [edit prefix] [delete route] │
└─────────────────────────────────────────────────────┘
테넌트 추가는 카드 복제 → 이름·path·backend URL·정책 파일만 갈아끼우면 끝.
검증 절차
# 1. 각 route 에서 tools/list 가 정상 응답하는지
for prefix in mcp1 mcp2 mcp3; do
echo "=== $prefix ==="
curl -sS -X POST https://gateway.example.com/agentgateway/$prefix \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| jq '.result.tools | length'
done
# 2. 미등록 path 는 404
curl -sS -o /dev/null -w "%{http_code}\n" \
https://gateway.example.com/agentgateway/mcp-unknown
# 404 가 나와야 정상
# 3. 테넌트 A 의 정책이 테넌트 B 에 영향 없는지 (정책 분리 검증)
# A 에서 deny 된 도구가 B 에서는 영향 없이 호출되어야 함
트레이드오프 — 멀티 listener 와의 비교
여러 MCP backend 를 노출하는 방법은 path-prefix multiplexing 외에도 두 가지가 더 있다.
방법장점단점
| path prefix multiplexing (이번 선택) | 1개 포트로 통합. URL 만 다르면 됨. 외부 ingress 단순. | path naming 규약 필요. wildcard tool name 충돌 가능. |
| host-based virtual host | hostname 분리 → 인지 명확. ingress / certificate 분리 가능. | DNS / cert 관리 부담. dev 환경에서 hosts 조작 필요. |
| 포트별 listener 분리 | 완전 격리. 정책·재시작 영향 분리. | 외부 포트 multiple 노출. 방화벽 정책 늘어남. |
PoC ~ 초기 운영 단계는 path prefix 가 가장 가성비. tenant 가 늘어나면서 격리 요구가 강해지면 host-based 로 점진 전환 가능.
배운 점
- pathPrefix multiplexing 은 MCP 게이트웨이의 핵심 패턴. tenant 격리·정책 분리·URL 안내 모두 path 한 줄로 표현된다.
- 자체 UI 의 평면 구조는 운영 친화성이 떨어진다. route 를 1차 객체로, backend / authz 를 종속 객체로 시각화하면 인지 부하가 크게 줄어든다. 1차 객체 / 2차 객체 라는 객체 모델 자체가 UI 결정의 출발점.
- 미등록 path 에 대한 fallback 을 명시적으로 정의해두자. 디폴트로 root 응답이 노출되면 디버깅·보안 양쪽 모두 골치.
- Cedar 정책에 route id 를 attribute 로 흘려 받게 설계. 테넌트 추가 시 정책 파일만 한 개 더 늘리면 된다. 정책 격리·검증·추적성 모두 깔끔.
- 운영자 UI 에 internal / external URL 둘 다 표시 + copy 버튼. 작은 UX 개선이지만 안내 메시지 작성 시간을 크게 줄여준다.
다음 단계
- tenant onboarding 자동화. 새 tenant 가 들어오면 route + 정책 파일 + (필요 시) DNS / cert 까지 한 번에 provision 하는 IaC.
- traffic 통계. route 별 호출량 / latency / error rate 대시보드.
- 점진적 host-based 전환 PoC. 격리 요구가 강해지는 시점 대비.
참고
- agentgateway docs - routing 구조: https://agentgateway.dev/docs/concepts/routing/
- MCP spec: https://spec.modelcontextprotocol.io/
- Cedar policy attributes: https://docs.cedarpolicy.com/policies/syntax-policy.html
- Mark Zhu, "agentgateway pathPrefix multiplexing" 블로그 (path 멀티플렉싱 설명).