카테고리 없음

agentgateway 에서 여러 MCP 백엔드를 path prefix 로 멀티플렉싱하기 (테넌트별 라우팅 + 운영자 UI 설계)

임마누엘한 2026. 5. 14. 15:44

들어가며

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.

시스템 컨텍스트

요구사항:

  1. 한 포트(예: :3000) 아래 path 로 여러 MCP 서버 노출.
  2. 각 path 가 독립적 MCP 백엔드 + 독립적 Authorization 정책.
  3. 운영자가 UI 에서 route 를 동적으로 추가/삭제·MCP 셋 바꿀 수 있어야 함.
  4. route 별로 외부에 안내할 full URL 을 한 번에 복사할 수 있어야 함 (테넌트 온보딩 편의성).
  5. 미등록 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. 격리 요구가 강해지는 시점 대비.

참고