2026년 8월 17일에 A2A가 에이전틱 AI 재단(AAIF, Agentic AI Foundation)의 호스티드 프로젝트가 되었습니다. 구글이 2025년 4월에 공개한 프로토콜이 특정 회사의 것에서 중립 재단이 관리하는 것으로 바뀐 것인데, 이 소식이 이번 글을 쓰게 된 계기입니다.
이 재단에는 이미 MCP(Model Context Protocol), AGENTS.md, agentgateway 그리고 Goose가 있었습니다. 이 프로젝트들은 에이전트가 사내 인프라에서 도구를 어떻게 호출하는지, 프로젝트 규칙을 어떻게 읽는지 그리고 그 요청을 어디에서 중계하고 기록하는지까지 이미 담당하고 있었습니다. 여기에 A2A는 에이전트가 다른 조직의 에이전트에게 일을 맡기고 그 진행을 확인하는 역할로 더해져서, 그동안 상대마다 직접 만들어야 했던 에이전트 사이의 연결까지 같은 재단의 규격으로 다룰 수 있게 됩니다. 에이전틱 AI 재단의 앰버서더인 아나 히메네스 산타마리아(Ana Jimenez Santamaria)가 지난 8월 MCP Dev Summit Seoul 키노트에서 이 구성을 한 장으로 정리했는데, 옮겨 보면 다음과 같습니다.

[그림 1] 에이전트 하네스, 열린 프로토콜 규약 그리고 공유 인프라로 층이 나누어지는 구성. 출처는 아나 히메네스 산타마리아(리눅스 재단, 에이전틱 AI 재단 앰버서더)의 MCP Dev Summit Seoul 키노트 발표 자료이고 본인에게 받은 자료입니다.
그림은 세 층으로 나누어져 있습니다. 맨 위가 사람이 직접 쓰는 에이전트 하네스(agent harness)이고 가운데가 그 에이전트가 바깥과 주고받는 규약이며 맨 아래가 그 요청이 지나는 공유 인프라입니다. 가운데 층의 왼쪽이 도구와 데이터로 이어지는 MCP이고 오른쪽이 다른 에이전트로 이어지는 A2A인데, 이번에 에이전틱 AI 재단의 프로젝트가 된 것이 그 오른쪽입니다.
이렇게 나누어진 요소들을 하나로 묶어서 보려고 합니다. 그런데 요즘IT는 A2A를 이미 세 번 다뤘습니다. 2025년 5월에 A2A는 AI 에이전트들의 ’언어’가 될 수 있을까?로 프로토콜을 소개했고 한 달 뒤에는 구글클라우드가 A2A를 리눅스 재단에 이관한 소식을 전했으며 올해 6월에는 AI 에이전트는 왜 서로 통신해야 할까? A2A의 모든 것으로 개념과 산업 동향을 다시 정리했습니다. 그래서 이 글에서는 개념 설명을 다시 하는 대신 스펙에 정해 둔 것이 실제 서버에서 그대로 되는지 확인해 보겠습니다. 공식 SDK로 서버를 직접 실행해 놓고 항목 12개로 확인해 봤고 같은 작업을 MCP와 순수 HTTP로도 만들어서 클라이언트에 드는 비용까지 비교해 봤습니다. 결과부터 말씀드리면 A2A로 얻는 것은 진행 확인(task status)과 취소(cancellation)에 대한 규약이고 그 대신 응답 바이트와 지연 시간이 늘어납니다. 하지만 에이전트 카드가 선언한 인증은 SDK 기본 구성에서 검사되지 않았기 때문에 선언과 강제를 나누어 보셔야 합니다.
A2A가 통신하는 흐름을 그대로 따라가면서 가능한 한 쉽게 이해하실 수 있도록 구성했습니다.
에이전틱 AI 재단의 프로젝트가 되었다는 것이 무엇을 뜻하는지, 그리고 이 통신과 이어지는 다른 프로젝트들이 어떤 역할을 맡는지는 하편에서 이어서 다룹니다.
/.well-known/agent-card.json인데 예전 자료에 나오는 /.well-known/agent.json은 지금 스펙에 없는 경로라 불러도 404가 돌아옵니다.a2a-python입니다. 스펙이 정해 둔 것과 SDK가 실제로 하는 것이 다를 수 있다는 점이 뒤에서 중요해집니다.
이 밖에 A2A를 이루는 개념은 앞서 말씀드린 2025년 5월 글과 올해 6월 글에 자세히 설명되어 있으니 참고하시기 바랍니다.
그러면 A2A는 무엇을 정하는 프로토콜일까요? 개념과 산업 동향은 앞서 말씀드린 6월 글이 자세히 다뤘으니 여기서는 뒤의 측정을 읽는 데 필요한 만큼만 확인하고 넘어가겠습니다. 우선 밖에서 보이는 것은 에이전트 카드, 메시지, 태스크 그리고 아티팩트 4가지뿐입니다. 상대 에이전트가 어떤 도구와 모델을 쓰는지 그리고 어떤 계획으로 일하는지는 보이지 않는다고 가정하기 때문에 안에서 무엇을 어떻게 하는지는 A2A가 정하지 않습니다.

[그림 2] A2A에서 밖으로 보이는 것은 총 4가지
이 4가지를 JSON-RPC, HTTP+JSON(SDK에서는 REST라고 부릅니다) 그리고 gRPC 중 하나로 실어 보낸다는 것까지가 A2A의 범위입니다. MCP와의 관계는 여러 글이 이미 설명했으니 한 문단으로만 적겠습니다. 둘은 상대편에 무엇이 있느냐가 다른데, 스펙 부록의 표현을 그대로 옮기면 MCP는 에이전트가 도구와 자원을 사용하는 방식을 표준화하고 A2A는 에이전트가 다른 에이전트에 일을 맡기는 방식을 표준화합니다. 도구는 호출하면 결과가 돌아오는 것이고 에이전트는 일을 받아서 시간을 들여 처리하다가 중간에 되물을 수도 있고 거절할 수도 있는 상대라, MCP에는 도구 목록과 도구 호출이 있고 A2A에는 에이전트 카드, 태스크 그리고 상태 변화가 있습니다. 여기서 한 가지 오해하기 쉬운 것이 둘의 관계인데 순서대로 거쳐야 하는 것이 아니라 상황에 따라 고르는 것입니다. 도구를 직접 사용하려면 MCP만 있으면 되고 다른 에이전트에게 일을 맡길 때는 A2A를 쓰면 됩니다.

[그림 3] 요청을 보내는 대상이 다른 MCP와 A2A
측정에 쓴 버전도 함께 밝혀 두는 것이 좋겠습니다. 스펙은 2026년 3월 12일에 고정된 v1.0.0의 후속인 v1.0.1(5월 28일)입니다. v1.0 앞에는 v0.3이 있었고 지금도 두 버전이 함께 쓰이는데 그 이야기는 뒤에서 다루겠습니다. 공식 파이썬 SDK는 a2a-python 1.1.2입니다. 비교 대상인 MCP 서버는 mcp 2.0.0으로, HTTP 서버는 표준 라이브러리로 만든 최소 JSON-RPC 서버로 두었습니다. 환경은 버추얼박스(VirtualBox) 3노드 쿠버네티스 1.37.0이라 절대 수치보다는 구현 사이의 상대 비교로 읽으시면 좋겠습니다.
같은 작업 함수를 구현 3개로 감싸서 15초짜리 일을 맡겨 보면 결과를 받기까지 걸린 시간은 셋 다 15.1~15.2초로 같았습니다. 차이가 생기는 것은 그 15초 동안 클라이언트가 무엇을 할 수 있느냐입니다. 이때 맡긴 일이 어디까지 됐는지 아는 방법은 3가지이고 A2A는 이 방법을 모두 스펙으로 정해 두고 있습니다.
순수 HTTP와 MCP에도 같은 것을 만들 수는 있습니다. 그렇지만 규약으로 주어지는 것이 없어서 전부 직접 구현해야 하고 호출 이름도 응답 형식도 서버마다 따로 정하기 때문에 연결할 상대가 바뀌면 클라이언트도 다시 손보게 됩니다. 이번 측정에서는 양쪽 다 상태를 조회하는 호출만 만들어 1초 간격으로 확인했고 스트리밍과 푸시에 해당하는 것은 만들지 않았습니다. MCP에는 선택지가 하나 더 있는데 15초를 그대로 기다리는 블로킹(blocking) 호출입니다. 상태 확인 왕복이 0회라는 점은 좋지만 그동안 어디까지 됐는지 확인할 방법이 없고 타임아웃을 넘기면 결과를 잃게 됩니다.

[그림 4] 15초 동안 구현 3개가 주고받는 왕복(확인 횟수는 조회 주기에 따라 달라짐)
취소도 마찬가지입니다. 사용자가 취소를 누르거나 상위 작업이 실패해서 맡겨 둔 일을 중간에 멈춰야 하는 경우인데, 오래 걸리는 일일수록 이런 상황이 생깁니다. A2A는 취소가 스펙에 있고 SDK가 그것을 AgentExecutor(앱이 작업 로직을 넣는 클래스)의 취소 훅(hook)에 연결해 두었기 때문에, 진행 중인 태스크와 되물음을 기다리는 태스크가 실제로 취소되었습니다. 순수 HTTP와 MCP에는 취소 규약이 아예 없어서 이번에는 표시만 바꾸는 최소 구현으로 두었고 그 구현에서는 작업 스레드가 그대로 계속 실행되었습니다. 진짜 취소를 붙이는 것은 물론 가능하고 그 작업량이 규약이 없을 때 드는 비용입니다. 덧붙여 상대 에이전트가 처리 도중에 되묻는 경우도 있는데, A2A에는 그 상태(input-required)가 있어서 같은 태스크로 답을 이어 보내면 하던 일이 재개됩니다. 그에 반해 mcp 2.0.0 코어에는 그 상태가 없어서 앱이 따로 만들지 않으면 호출을 처음부터 다시 하게 됩니다.
얻는 것을 숫자로 보면 이렇습니다. 진행 확인과 취소를 직접 만들려면 상대 에이전트에 대해 5가지를 알아야 합니다. 어떤 호출로 일을 시작하는지(message/send), 그 일을 가리키는 식별자 필드 이름이 무엇인지(taskId), 어떤 호출로 상태를 조회하는지(tasks/get), 돌아오는 상태 값에는 어떤 것이 있는지(8종) 그리고 어떻게 취소하는지(tasks/cancel)입니다. 괄호 안은 v0.3 표기이고 v1.0에서는 SendMessage, GetTask 그리고 CancelTask로 바뀌었는데, 이름이 바뀌어도 스펙이 정해 준다는 것은 같습니다. 순수 HTTP와 MCP에서는 이 5가지를 에이전트마다 각자 정하기 때문에 연결할 상대가 하나 늘 때마다 그쪽 규칙을 확인하고 클라이언트 코드를 새로 만들게 됩니다. A2A에서는 전부 스펙에 있으니 상대가 바뀌어도 쓰던 코드를 그대로 쓸 수 있습니다. 물론 사내 에이전트 1개만 호출한다면 이 숫자는 의미가 없고 호출할 곳이 5군데쯤 되거나 외부 업체가 운영하는 에이전트라면 그때부터는 규약이 정해져 있는 쪽이 실제로 도움이 됩니다.
얻는 것이 있으면 잃는 것도 있습니다. 짧은 작업에서는 세 구현이 돌려준 결과 문자열이 글자까지 같았고 다른 것은 왕복 수, 지연 그리고 응답 바이트뿐이라, 이를 표로 정리하면 다음과 같습니다.

A2A의 첫 왕복은 에이전트 카드를 받아 오는 호출이고 858바이트가 듭니다. 응답이 큰 이유는 태스크 객체를 매번 함께 보내기 때문인데, 그 안에 식별자, 상태 그리고 이력이 들어 있고 나중에 진행을 확인하거나 취소할 때 쓰라고 미리 실어 주는 것입니다. 그런데 1초 안에 끝나는 호출에서는 나중에 확인하거나 취소할 일 자체가 생기지 않으므로 늘어난 바이트를 쓰지 않고 버리게 됩니다.
진행 확인을 폴링으로 하면 이 부담이 반복됩니다. 상태를 한 번 확인할 때마다 태스크 객체 전체가 돌아오기 때문에 진행 중에 확인하면 423바이트가 돌아왔고 완료를 확인하는 마지막 응답은 571바이트였습니다. 순수 HTTP 쪽에 직접 만든 상태 조회가 92바이트였으니 완료 응답은 6배가 넘고 진행 중 응답도 4배가 넘습니다. 15초짜리 작업을 1초 간격으로 조회하면 완료까지 16번 확인하게 되고 그동안 6,916바이트를 받았습니다. 다만 이것은 어디까지나 폴링을 골랐을 때의 값입니다. 같은 일을 스트리밍으로 확인하면 이벤트 4개를 합쳐 1,287바이트였고 푸시로는 서버가 보낸 요청 2건을 합쳐 465바이트였습니다. 응답마다 크기가 큰 것은 맞지만 오래 걸리는 일에서는 폴링을 쓰지 않는 쪽이 훨씬 적게 주고받고 끝납니다.
지연도 늘어납니다. 세 구현에 같은 부하를 걸어 확인했는데 초당 요청 수(rps, requests per second) 2가지와 연결 방식 2가지를 조합해 조건 4개를 만들고 회차를 10번 돌렸습니다. 그 중앙값을 표로 정리하면 다음과 같습니다.

표의 값은 호출당 지연의 p50(요청 절반이 그 안에 끝나는 시간)이고 단위는 ms입니다. 초당 50건과 100건은 부하 생성기의 동시성 설정이 달라서 행끼리 비교하지 않고 한 행 안에서 구현 사이의 차이만 읽으시면 됩니다. 여기서 A2A와 순수 HTTP의 차이(1.2~2.1ms)를 A2A와 MCP의 차이(0~0.4ms)와 비교해 보면 비용의 큰 부분이 A2A라서 생기는 것이 아니라 구조화된 프로토콜이라면 공통으로 생긴다는 것을 알 수 있습니다. 따라서 이미 MCP를 쓰고 계셔서 그 정도 지연에 익숙하시다면 A2A를 더해도 지연이 크게 늘지는 않고 순수 HTTP만 쓰고 계셨다면 1.2~2.1ms가 그대로 더 들게 됩니다.

[그림 5] 프로토콜에 따라 얻는 것과 잃는 것이 다름
지금까지는 얻는 것과 잃는 것을 설명드렸습니다. 이제부터는 스펙이 얼마나 그대로 동작하는지 직접 확인해 본 결과를 말씀드리겠습니다. 스펙이 반드시 지키라고 하거나 지키는 편이 좋다고 적어 둔 항목 가운데 12개를 골라 공식 SDK의 기본 구성으로 만든 서버에 하나씩 호출해 봤습니다. 그 결과를 판정별로 나누어 표로 정리하면 다음과 같습니다.

표의 순서대로 하나씩 보겠습니다.
우선 카드 경로는 /.well-known/agent-card.json 하나에서만 응답했고 다른 경로에는 없었습니다. 태스크는 접수, 처리 중 그리고 완료로 옮겨 갔고 처리 도중에 되물으면 같은 태스크로 답을 이어 보내 재개됐습니다. 단건 조회와 목록 조회도 모두 동작했고 스트리밍은 첫 이벤트가 태스크이고 마지막 이벤트가 완료 표시였습니다. 이 5개는 문서에 적힌 대로 쓰시면 됩니다.
TaskNotCancelableError(-32002)가 아니라 내부 오류(-32603)로 옵니다.A2A-Version 헤더 값 하나로 나누어집니다. 스펙 3.6.1절이 클라이언트는 이 헤더를 반드시 보내라고 하면서 같은 문장에서 헤더가 없으면 v0.3으로 본다고 함께 적어 두었기 때문에, v1.0 클라이언트가 헤더를 빠뜨리면 오류가 아니라 v0.3 요청으로 처리되어 버립니다. 저도 처음에는 “JSON-RPC는 v0.3 형식만 받는구나” 하고 잘못 판단했다가 헤더를 붙여서 다시 호출해 보고 나서야 같은 주소가 v1.0 메시지 형식을 그대로 받는다는 것을 알게 되었습니다.
12개 항목 밖이지만 서버를 띄워 보면 바로 마주치는 것도 하나 있습니다. SDK 기본값이 태스크를 파드(Pod) 메모리에 담는 InMemoryTaskStore라서 백엔드를 재시작하면 맡겨 둔 일의 상태가 사라집니다.
셋 다 에이전트 카드와 관련이 있고 그중 2개는 원인이 같습니다. 인증부터 보겠습니다. 스펙 7.4절은 서버가 에이전트 카드에 선언한 요구에 따라 모든 요청을 인증하도록 정해 두었습니다. 그래서 에이전트 카드에 API 키, HTTP Bearer 그리고 OAuth2 3가지 인증 스킴(security scheme, 어떤 방식으로 인증할지를 가리키는 이름)을 한꺼번에 선언해 두고 셋 중 어느 것으로 막히는지 그리고 틀린 키를 넣으면 어떤 오류가 오는지를 보려고 했습니다. 그런데 자격 증명 없이 그냥 호출해 보니 200이 돌아왔고 틀린 키를 실은 요청도 같았습니다. 막히는 방식을 비교하려고 만든 서버였는데 애초에 막히지가 않았습니다.
확장 카드도 마찬가지였습니다. 확장 에이전트 카드(extended agent card, 기본 카드에 더해 자세한 정보를 담아 두는 카드)는 스펙이 인증된 요청에만 돌려주도록 정해 두었지만 인증하지 않은 요청에도 공개 카드에 없는 스킬(skill, 그 에이전트가 할 수 있는 일을 카드에 적어 둔 목록)까지 그대로 돌려주었습니다. 둘의 원인은 같은데, SDK의 기본 핸들러에 호출자가 누구인지 확인하는 부분이 아예 없기 때문입니다. 에이전트 카드의 securitySchemes는 클라이언트에게 이런 방식으로 인증해서 오라고 알려 주는 선언일 뿐이고 서버가 그 선언대로 검사하는 코드는 앱이 직접 만들어야 합니다. 따라서 에이전트 카드를 읽고 인증을 붙였다고 해서 서버가 막아 준다고 보시면 안 되고 인증은 앱 안에서 직접 걸거나 앞단에 인증을 담당하는 계층을 따로 두어야 합니다. 확장 카드 쪽도 같은데 인증을 통과한 상대에게만 보여 주려고 정보를 넣어도 주소를 아는 누구에게나 나가게 됩니다.
마지막은 카드에 적힌 인터페이스가 실제와 다른 것입니다. v0.3 호환을 끈 서버의 에이전트 카드에도 v0.3 인터페이스가 그대로 남아 있는데 그 주소를 실제로 호출하면 거절당합니다. 카드에 적어 두는 목록과 서버가 실제로 받는 주소가 따로 관리되기 때문입니다. 앞의 인증과 함께 놓고 보면 에이전트 카드에 적힌 내용을 그대로 믿으면 안 된다는 점을 이 프로토콜에서 다시 확인하게 됩니다.
이번 글은 여기까지입니다. 다음편에서는 에이전틱 AI 재단의 프로젝트가 되었다는 것이 무엇을 뜻하는지, A2A와 이어지는 MCP, agentgateway, Goose가 각각 어떤 역할을 맡는지, 그리고 지금 도입한다면 확인할 것과 앞으로 지켜볼 것을 정리하겠습니다.
➡️ 다음편 A2A 도입 전에 확인할 것 7가지

ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.