MCP 서버를 늘려 가다 보면 클라이언트 설정이 서버 수만큼 늘어나고 누가 어떤 도구를 호출할 수 있는지도 서버마다 따로 관리해야 합니다. 그래서 앞에 게이트웨이를 1개 두는 구성을 검토하게 되는데, 에이전틱 AI 재단(AAIF, Agentic AI Foundation) 아래에는 그런 게이트웨이가 2개 있습니다. agentgateway와 2026년 9월에 Agent Router라는 이름을 쓰기 시작한 프로젝트입니다.

뒤의 것은 원래 이름이 Envoy AI Gateway였고 저장소도 envoyproxy/ai-gateway에 있었습니다. Envoy 프로젝트 아래에 있었으니 그때는 클라우드 네이티브 컴퓨팅 재단(CNCF, Cloud Native Computing Foundation)의 거버넌스를 따르고 있었는데, 실제로 2026년 8월에 낸 v1.1.0의 README까지도 CNCF 행동 강령을 따른다고 적혀 있습니다. 그러다 AAIF 프로젝트로 받아들여지는 절차를 밟았고 이사회 승인이 8월 18일에 났습니다. 이름을 바꾼다고 발표한 것은 9월 9일이고 10일부터 새 이름을 쓰고 있으니, 이름만 바뀐 것이 아니라 소속 재단이 옮겨 가면서 이름이 따라온 순서입니다. 저장소도 theagentrouter/agent-router로 옮겼습니다.

agentgateway는 이미 측정해 봤는데 그때 한 가지를 확인했습니다. 도구를 호출할 때 어떤 값을 넘겼는지를 조건으로 삼는 규칙을 MCP 인가 정책에 적으면 호출을 차단하는 단계에서 평가되지 않았고 그래서 조건에 맞는 호출까지 모두 차단되었습니다. 이 동작은 업스트림에 이슈로 제출했고 지금은 라우트 단위 정책에 적는 경로가 따로 열려 있습니다. 그런데 Agent Router는 문서에 인가 규칙의 CEL 식에서 그 값을 쓸 수 있다고 적어 두고 예시까지 함께 안내하고 있었습니다. 그래서 이번에는 그 문장이 실제로 성립하는지를 확인해 보기로 했습니다.

결과부터 말씀드리면 도구 이름만이 아니라 호출할 때 넘긴 값까지 보고 허용 여부를 정하는 것이 실제로 됩니다. 같은 도구를 같은 이름으로 호출하는데 넘긴 값에 따라 a=1은 통과하고 a=2는 차단됩니다. 대신 응답이 20ms 가까이 늦어지고 기본 배포 구성에서는 처리량이 초당 100건 근처에서 더 늘지 않았습니다. 그리고 문서 예시 그대로 적으면 tools/list가 빈 목록을 돌려줘서 에이전트가 도구를 전혀 찾지 못하게 되는데, 이건 CEL 식을 조금 바꾸면 피할 수 있습니다.

측정은 쿠버네티스 1.37.0 클러스터에 Agent Router v1.1.0과 Envoy Gateway v1.8.1을 올려서 진행했습니다. 백엔드로는 도구 8개짜리 MCP 서버와 받은 것을 그대로 돌려주는 서버 1개를 두었습니다. 정책을 바꿔 가며 같은 호출을 보내는 방식으로 2026년 9월 14일부터 16일까지 조건마다 75회씩 반복했습니다. 조건 21종이 75회 모두 같은 결과를 냈고 측정 오류와 파드 재시작은 없었습니다.

[노트] 이 글에서 쓰는 용어
  • MCP(Model Context Protocol): 모델이 외부 도구와 데이터를 쓰기 위한 프로토콜입니다.
  • MCP 게이트웨이: 에이전트와 MCP 서버 사이에 위치해서 여러 서버를 하나로 합쳐 보여 주고 호출을 통제하는 구성 요소입니다.
  • CEL(Common Expression Language, 공통 표현 언어): 쿠버네티스 여러 곳에서 조건을 적을 때 쓰는 식 언어입니다. 인가 규칙의 조건도 이 식으로 적습니다.
  • MCPRoute: Agent Router가 MCP 설정을 적기 위해 제공하는 사용자 정의 리소스입니다. 어느 백엔드를 묶을지와 누가 무엇을 호출할 수 있는지를 여기에 적습니다.

측정해서 알게 된 것을 먼저 한 장으로 정리하면 이렇습니다.

Agent Router를 앞에 두면 무엇이 좋아지고 어떤 비용이 드는가

아래에서 이 그림의 항목을 하나씩 근거와 함께 풀어 봅니다. 그 전에 동작 원리를 1가지만 알아 두면 결과를 읽기 수월합니다.

MCP 프록시는 Envoy 프록시 파드 안의 사이드카로 실행되는 Go 서버라서 파드가 따로 만들어지지는 않습니다. 설계 제안에 그 이유가 적혀 있는데, Envoy의 확장 메커니즘이 스트리밍 응답을 필터에서 직접 만들지 못하고 임의의 업스트림으로 스트리밍 호출도 하지 못해서 클라이언트 SSE를 끊는 것과 여러 서버의 알림을 합치는 것을 필터로는 할 수 없었다고 합니다. 그래서 Go 서버로 빼되 주고받는 트래픽은 모두 Envoy가 전달하도록 하였습니다.

그래서 MCP 요청은 Envoy를 2번 지납니다. 수신 리스너로 들어와 로컬 포트 9856의 프록시로 갔다가, 프록시가 로컬 포트 10088로 되돌려 보내 MCP 리스너로 다시 들어옵니다. 두 구간 모두 같은 파드 안의 로컬 TCP 포트입니다. 이 모양을 알아 두시면 아래의 지연 시간과 처리량 결과를 읽기 수월합니다.

MCP 요청이 지나는 경로

호출할 때 넘긴 값에 따라 통과와 차단이 달라집니다

우선 인가 규칙을 1개 걸어 보겠습니다. get-sum이라는 도구를 허용하되 그 도구를 호출할 때 넘긴 값 가운데 a가 1일 때만 허용하는 조건을 CEL로 적었습니다. 이렇게 도구에 넘기는 값을 인자라고 부르고 아래에서도 그 뜻으로 씁니다.

securityPolicy:
  authorization:
    rules:
      - action: Allow
        target:
          tools:
            - backend: mcpb
              tool: get-sum
        cel: 'request.mcp.params.arguments.a == 1'

이 상태에서 같은 도구를 인자만 바꿔 2번 호출해 보면 결과가 달라집니다.

호출결과
tools/call mcpb__get-sum a=1200, 결과 3.0
tools/call mcpb__get-sum a=2403

반복해도 결과가 달라지지 않았고 문서에 적힌 대로 동작합니다. 도구 이름 단위로만 차단할 수 있는 게이트웨이와 비교하면 도구 이름에 더해 호출 인자까지 조건으로 삼을 수 있다는 뜻입니다. 같은 재단 아래의 agentgateway는 MCP 인가 정책에서는 이것을 하지 못하고 라우트 단위 정책에 따로 적어야 하는데, 그 경로는 이 글이 측정한 v1.5.0 다음에 열렸습니다.

다만 여기서 주의하실 부분이 있습니다. 규칙에는 get-sum이라고 적었는데 호출은 mcpb__get-sum으로 했습니다. Agent Router는 클라이언트에 도구 이름을 보여 줄 때 백엔드이름__도구이름 형태로 바꾸어 내보내는데, 정책이 기준으로 삼는 것은 이름을 바꾸기 전의 원래 이름입니다. 클라이언트가 보는 이름을 정책에 적으면 아무 규칙도 맞지 않아 모든 호출이 차단됩니다.

그런데 도구 목록에 아무것도 남지 않습니다

같은 정책을 걸어 둔 채로 tools/list를 호출해 보면 돌아오는 도구가 전혀 없습니다. 규칙이 대상으로 지목한 get-sum 1개만 사라지는 데서 끝나지 않고 그 백엔드가 가진 도구 8개가 모두 없어집니다.

인자 값을 조건으로 걸면 목록이 비는 이유

프록시 로그를 보면 이유가 그대로 적혀 있습니다.

level=ERROR msg="failed to evaluate authorization CEL" component=mcp-proxy
  error="no such key: arguments" expression="request.mcp.params.arguments.a == 1"

소스를 따라가 보면 목록을 거를 때 프록시가 하는 일이 조금 특이합니다. 도구를 차례로 확인하면서 “이 도구를 호출하면 허용되는가"를 인가 로직으로 평가하는데, 이때 request.mcp.method에는 tools/call을 넣고 params에는 방금 들어온 tools/list 요청의 params를 그대로 넣습니다. 목록 요청의 params에는 arguments 키가 없기 때문에 인자를 참조하는 CEL 식은 평가 단계에서 반드시 오류가 납니다.

그런데 그다음에 일어나는 동작 때문에 도구가 전부 빠집니다. internal/mcpproxy/authorization.go의 규칙 평가 반복문은 CEL 평가가 오류로 끝나면 그 규칙을 건너뛰고 다음 규칙으로 넘어갑니다. 규칙이 1개뿐이었으니 맞는 규칙이 없는 상태로 반복문이 끝나고 남는 것은 defaultAction입니다. 그 기본값이 Deny라서 도구가 전부 목록에서 빠집니다.

이게 실제로 그런지는 진단용 규칙 3개로 확인했습니다. 조건을 request.mcp.method == "tools/list"로 적으면 목록이 0개가 되고 request.mcp.method == "tools/call"로 적으면 1개가 남습니다. 목록을 거르는 시점에 프록시가 호출로 가정하고 평가한다는 뜻입니다.

운영하는 입장에서 이 동작이 까다로운 것은 호출 자체가 정상으로 처리되기 때문입니다. 에이전트는 tools/list를 보고 무엇을 호출할지 정하기 때문에 목록에 없는 도구는 통과할 호출이어도 시도하지 않습니다. 인자 값을 조건으로 삼는 허용 규칙을 문서 예시대로 켜는 순간 그 엔드포인트는 에이전트에게 도구가 없는 서버로 보입니다.

우회할 방법을 4가지 만들어 전부 측정해 봤는데 온전히 쓸 수 있었던 것은 1개였습니다.

순번정책목록a=1a=2다른 도구
기준없음8개통과통과통과
기준문서 예시 그대로0개통과차단차단
1defaultAction을 Allow로 바꾸고 Allow 규칙 유지8개통과통과통과
2defaultAction을 Allow로 두고 조건을 뒤집어 Deny 규칙8개통과차단통과
3request.mcp.method != "tools/call" || ...0개통과차단차단
4!has(request.mcp.params.arguments) || ...1개통과차단차단

그런데 1번은 기본 동작이 Allow가 되어 버려서 인자 조건이 아무것도 차단하지 못하게 되고 2번은 인자 조건이 그대로 강제되지만 규칙에 없는 나머지 도구가 전부 허용됩니다. 3번이 언뜻 맞아 보이는데 실제로는 동작하지 않습니다. 목록을 거를 때 프록시가 tools/call로 평가하기 때문에 왼쪽 조건이 거짓이 되고 오른쪽이 평가되면서 똑같이 오류가 나기 때문입니다.

4번이 제대로 동작했습니다. !has()는 인자가 없을 때 참입니다. 목록을 거르는 시점에는 인자가 없으니 왼쪽이 참이 되고 식이 거기서 끝납니다. 실제 호출에는 인자가 있으니 왼쪽이 거짓이 되고 오른쪽 조건이 평가됩니다. 목록에는 허용한 도구가 남고 인자 조건도 그대로 강제됩니다.

cel: '!has(request.mcp.params.arguments) || request.mcp.params.arguments.a == 1'

거부는 HTTP 403 평문으로 돌아옵니다

그러면 인가에 걸린 호출은 어떤 형태로 돌아올까요? HTTP 403에 본문은 access denied라는 13바이트짜리 평문이었습니다.

거부의 형태

같은 상황에서 agentgateway는 JSON-RPC 응답 안에 Unknown tool을 넣어 도구 자체가 없는 것처럼 보이게 했습니다. 두 게이트웨이가 이 자리에서 서로 다른 형태로 응답하는 셈인데, 이 응답을 읽는 쪽이 에이전트 루프라서 차이가 생깁니다. JSON-RPC 오류는 정상 응답 안에 들어 있으니 루프가 읽고 다음 수를 정할 수 있지만 HTTP 403은 전송 계층 오류라서 SDK에 따라 예외로 처리됩니다. 어느 쪽이 낫다기보다 도입 전에 쓰고 계신 에이전트 SDK가 이 둘을 어떻게 다루는지 확인해 두시는 편이 좋겠습니다.

덧붙여 설정을 잘못 적어 엔드포인트 전체가 차단되는 경우도 있었습니다. 백엔드를 고르는 backendSelector에 규칙을 비워 두면 고를 수 있는 백엔드가 없어져서 initialize부터 403이 되고 이후 호출은 세션이 없다는 400을 받게 됩니다.

통과 비용은 응답당 20ms 가까이 됩니다

게이트웨이를 앞에 두면 비용이 얼마나 드는지도 측정했습니다. 백엔드를 직접 호출한 경로와 게이트웨이를 거친 경로를 같은 방식으로 측정했고 여기에 경로를 1개 더 두었습니다. 같은 게이트웨이의 같은 Envoy를 지나되 MCP 프록시만 거치지 않는 HTTPRoute를 만들어서 비용이 어느 구간에서 생기는지 나누어 보았습니다.

통과 비용이 생기는 구간

경로동시 1동시 16
백엔드 직접806rps / 0.91ms675rps / 10.8ms
같은 Envoy, MCP 프록시 없음755rps / 1.06ms683rps / 10.8ms
MCP 프록시 경유50rps / 19.5ms98rps / 159.6ms

표의 값은 75회를 반복한 캠페인 회차에서 나온 것입니다. 아래에서 다시 나오는 47rps와 21.0ms는 반복 횟수를 확인하려고 따로 돌린 1회차의 같은 조건 값입니다.

게이트웨이를 거치면서 늘어난 18.6ms 가운데 Envoy를 지나느라 늘어난 것은 0.15ms입니다. 나머지 18.4ms가 MCP 프록시 구간에서 생깁니다. 동시 16에서는 Envoy만 지나는 경로와 백엔드를 직접 부르는 경로가 둘 다 10.8ms로 차이가 잡히지 않습니다.

사실 이 비용은 정책의 복잡도와 관계가 없었습니다. 정책을 걸지 않았을 때와 인자 조건 CEL을 걸었을 때, 규칙을 20개까지 늘렸을 때가 전부 같았습니다. 규칙을 검사하는 비용은 아니라는 뜻입니다.

Envoy 접근 로그를 보면 구간이 더 정확하게 나누어집니다. 한 요청이 로그에 두 줄을 남기는데, MCP 프록시가 백엔드를 호출하는 구간은 p50이 1ms이고 클라이언트를 상대하는 구간은 22ms입니다.

그 22ms는 세션 ID를 푸는 데 쓰입니다. MCP 프록시는 요청마다 따라오는 세션 ID를 풀어야 어느 백엔드로 보낼지 알 수 있습니다. 그런데 그 복호화 키를 PBKDF2(Password-Based Key Derivation Function 2)로 매번 새로 만들고 캐시하지 않습니다. 반복 횟수는 helm 값 controller.mcp.sessionEncryption.iterations로 정하고 기본값이 10만입니다.

그래서 그 값만 1,000으로 낮추고 다른 조건은 그대로 둔 채 같은 부하를 다시 걸어 봤습니다.

측정반복 10만 (기본값)반복 1,000
저부하 응답 p5021.0ms1.55ms
저부하 처리량초당 47건초당 596건
포화 처리량 (동시 16)초당 96건초당 805건
목표 초당 100건97.2 달성, 168건 유실100.0 달성, 유실 0
부하 중 프록시 CPU 사용량1,874m329m

같은 동시 1에서 대조군이 1.06ms였으니 프록시가 더하는 몫이 20ms에서 0.5ms로 줄어듭니다. 0이 되지는 않습니다.

CPU 사용량을 읽는 방법에도 확인할 것이 있습니다. 부하를 걸자마자 kubectl top을 읽으면 한 자릿수 밀리코어가 나옵니다. metrics-server가 15초에서 60초 간격으로 집계하기 때문에 그 시점에는 부하 이전 구간을 보게 됩니다. 부하를 180초 이어서 걸고 15초마다 읽으면 1m, 237m, 913m, 1,848m으로 오르다가 1,860m 근처에서 멈춥니다. 2코어 노드의 1.87코어를 쓰고 있었습니다. 같은 파드의 Envoy 컨테이너는 같은 부하에서 30~44m입니다.

산술도 맞습니다. 포화 부하에서 초당 96건을 처리하며 1.87코어를 쓰니 요청당 CPU가 약 19.5ms이고 동시 1에서 측정한 응답 시간 21.0ms에 가깝습니다. 이 지연은 계산에 쓴 CPU 시간이었습니다.

그러면 이 값을 낮추면 되는 것일까요? 보안과 맞바꾸는 값이라 그렇게 단순하지는 않습니다. 다만 같은 Helm 차트의 주석에는 시드 기본값이 default-insecure-seed로 되어 있으니 운영에서는 안전한 난수 문자열로 바꾸라고 적혀 있습니다. 반복 횟수를 올리는 것은 추측하기 쉬운 시드를 대입해 보는 공격에 시간이 더 걸리게 만드는 장치이므로, 시드를 제대로 넣었는지가 반복 횟수보다 먼저 볼 일입니다.

복제본을 늘릴 때는 트래픽 정책을 같이 봐야 합니다

처리량이 부족하면 복제본을 늘리는 방법을 먼저 떠올리게 되는데, 기본 설정에서는 효과가 없었습니다.

복제본과 트래픽 정책

게이트웨이가 만드는 서비스의 externalTrafficPolicy 기본값이 Local이고 MetalLB가 L2로 주소를 알리는 환경에서는 그 주소를 알리는 노드에 들어온 요청이 그 노드에 있는 파드로만 전달됩니다. 그래서 복제본을 2개로 늘려도 한쪽 파드가 요청을 모두 받게 되는데, 실제로 파드별 요청 수를 세어 보니 한쪽이 0건이었습니다.

네 조합을 한 회차에서 연달아 측정했습니다. 연결을 요청마다 새로 여는 방식입니다.

조건 (목표 초당 100건)달성p50보내지 못한 요청
복제본 1개, Local (기본값)100.099.0ms0
복제본 1개, Cluster99.2132.6ms24
복제본 2개, Local99.3118.2ms21
복제본 2개, Cluster100.025.7ms0

복제본만 늘리면 지연이 99.0ms에서 118.2ms로 오히려 오르고 트래픽 정책만 바꿔도 132.6ms로 오릅니다. 둘을 같이 바꿔야 25.7ms로 부하가 없을 때 수준에 돌아옵니다. 설계 제안에는 세션 정보를 세션 식별자에 암호화해서 넣기 때문에 어느 인스턴스든 세션을 처리할 수 있다고 적혀 있는데, 두 파드에 요청이 실제로 나뉘면서 오류가 한 건도 나지 않았으니 그 서술은 성립하는 것으로 보입니다.

트레이스 헤더가 백엔드에 도달하지 않습니다

관측 쪽은 조금 다른 방법으로 살펴봤습니다. 받은 헤더와 JSON-RPC _meta 필드를 그대로 돌려주는 서버를 따로 만들어서 백엔드에 무엇이 도착하는지 확인했습니다.

클라이언트가 보낸 것직접 호출게이트웨이 경유
HTTP 헤더 traceparent받습니다받지 못합니다
params._meta.traceparent받습니다받습니다
아무것도 보내지 않음없음없음

_meta가 백엔드에 남아 있는 것은 JSON 본문이 그대로 전달되었기 때문이지 게이트웨이가 넣어 준 값은 아닙니다. 아무것도 보내지 않았을 때는 백엔드에도 아무것도 없었으니 게이트웨이가 넣어 준 값은 아니라고 볼 수 있습니다. agentgateway는 같은 자리에서 같은 trace-id에 자기 span-id를 붙여서 연결하고 _meta에도 넣어 주었기 때문에 이 부분은 두 게이트웨이가 다릅니다.

다만 이건 트레이싱을 켜지 않은 상태에서 측정한 것입니다. Agent Router에는 OTel(OpenTelemetry) 환경 변수를 넣어 트레이싱을 켜는 방법이 따로 있고 이번 측정에는 넣지 않았습니다. 그래도 트레이싱을 켜지 않았다고 해서 클라이언트가 보낸 헤더를 지울 이유는 없어 보이니, 종단 추적이 필요하시면 켠 상태에서 한 번 더 확인해 보시는 게 좋겠습니다.

덧붙여 확인해 두실 것이 더 있습니다. initialize 응답의 protocolVersion이 클라이언트가 무엇을 요청하든 2025-06-18로 고정되어 돌아옵니다. 존재하지 않는 날짜를 보내도 같은 값이 옵니다. 옛 스펙만 지원하는 클라이언트를 붙이실 계획이라면 미리 확인해 보시기 바랍니다.

그래서 언제 쓰면 좋을까요?

도구를 쓰게 할지 말지를 이름만이 아니라 호출할 때 넘긴 값까지 보고 정해야 하고 부하가 초당 수십 건 규모라면 Agent Router가 맞는 선택입니다. 도구 이름 단위로 차단하는 것으로 충분하거나 지연 시간이 빠듯한 서비스라면 응답당 20ms는 작지 않은 비용입니다. 처리량이 문제라면 복제본을 늘리는 방법이 있지만 트래픽 정책을 같이 바꿔야 한다는 점을 기억해 두시면 좋겠습니다.

도입하신다면 확인할 것들

  • 인자 조건 CEL은 !has(request.mcp.params.arguments) ||로 감싸고 정책을 적용한 뒤 tools/list를 한 번 호출해서 도구가 남아 있는지 확인합니다.
  • 정책의 target.tools[].tool에는 백엔드 쪽 원래 도구 이름을 적습니다. 클라이언트가 보는 바뀐 이름을 적으면 모든 호출이 차단되고 백엔드 이름을 앞에 붙이지 않게 하는 설정도 없습니다.
  • target.tools[].backend*는 쓸 수 없으니 백엔드 이름을 정확히 적습니다.
  • backendSelector를 쓰신다면 규칙을 비워 두지 않습니다. 비워 두면 initialize부터 403이 되어 엔드포인트 전체가 차단됩니다.
  • 거부가 HTTP 403 평문으로 오므로 쓰고 계신 에이전트 SDK의 오류 처리를 미리 맞춰 둡니다.
  • 클라이언트가 보낸 traceparent가 백엔드에 도달하지 않으니 종단 추적이 필요하면 트레이싱을 켠 경로를 따로 확인합니다.
  • protocolVersion이 고정값으로 돌아오니 옛 스펙 클라이언트를 붙이기 전에 확인해 보시는 게 좋습니다.
  • 처리량을 늘리실 때는 복제본보다 externalTrafficPolicy를 먼저 봅니다.
  • Envoy Gateway에는 확장 관리자 설정을 따로 얹어야 합니다. 기본 설치만으로는 Agent Router가 동작하지 않습니다.

한계

트레이싱을 켠 경로는 측정하지 않았습니다. 반복 횟수는 10만과 1,000 두 값만 비교해서 그 사이에서 지연과 처리량이 어떻게 달라지는지는 보지 않았습니다. 백엔드로 쓴 서버 둘 다 도구가 적어서(8개와 2개) 도구가 수백 개인 서버에서 목록을 거르는 데 비용이 얼마나 드는지는 이번 범위에서 보지 않았습니다. 복제본은 2개까지만 측정했고 OAuth를 쓰는 인가는 이번 범위에서 뺐습니다. 클러스터 1개를 호스트 1대에서 돌린 arm64 환경이라 절대 수치는 환경에 따라 달라질 수 있고 이 측정으로 볼 수 있는 것은 경로 사이의 상대 비교까지입니다. 그리고 agentgateway와의 직접 비교는 아닙니다. 두 게이트웨이의 배치 구조가 달라서 조건부터 정해야 하는데 그건 다음 단계로 남겨 두었습니다.

마치며

처음에는 문서에 적혀 있는 대로 인자 값을 조건으로 삼는 허용 규칙이 실제로 동작하는지를 확인하려고 했고 실제로 동작한다는 것을 확인했습니다. 다만 그 기능을 문서 예시대로 켜면 에이전트가 도구를 찾지 못하게 되는 동작도 함께 일어나고 CEL 식을 has()로 감싸는 것으로 피할 수 있었습니다. 통과 비용은 응답 1개당 20ms 정도가 더 들고 기본 배포에서는 처리량이 초당 100건 근처에서 멈췄습니다. 정책이 복잡해서 생기는 비용이 아니라 프록시가 요청마다 세션 ID를 푸는 계산 때문이었고 helm 값 1개를 낮추자 응답이 1.55ms로 처리량이 초당 805건으로 바뀌었습니다.

측정에 쓴 매니페스트와 하네스, 조건 정의와 원자료는 GitHub 저장소에 공개해 두었으니 참고하시기 바랍니다. 같은 재단의 다른 MCP 게이트웨이를 같은 틀로 먼저 측정한 기록은 agentgateway-study에 있습니다. 다른 환경에서 재현해 보신 결과가 있다면 알려 주시면 좋겠습니다.