AI Agent 정의부터 구성까지 — MCP, A2A, 그리고 실제로 붙일 때 걸리는 것들
AI Agent를 실제 제품에 붙이는 설계를 검토하면서 정리한 내용이다. 정의부터 시작해서, MCP와 A2A가 각각 뭘 해결하는지, 그리고 막상 붙이려고 하면 어디서 걸리는지까지 순서대로 적었다.
AI Agent란 뭔가
한 줄로 쓰면 사용자의 목표를 LLM이 해석하고, 목표 달성에 필요한 도구를 스스로 골라 호출하며 작업을 수행하는 시스템이다. 답변 텍스트만 만들어내는 챗봇과의 차이는 실제로 동작을 실행한다는 점이다.
재밌는 건 업계에 합의된 정의가 없다는 것이다. 벤더마다 범위가 다르고, 실무적으로는 “도구를 가지고 행동을 취하는 AI 시스템” 정도로 넓게 쓴다.
Workflow와 Agent는 다르다 — 이게 제일 중요하다
Anthropic의 Building Effective Agents에 나오는 구분인데, 이거 하나만 알고 가도 설계가 달라진다.
| Workflow | Agent | |
|---|---|---|
| 제어 흐름 | 사전에 정의된 코드 경로로 LLM과 도구를 오케스트레이션 | LLM이 자기 처리 과정과 도구 사용을 동적으로 결정 |
| 개발자가 소유하는 것 | 모든 분기(branch) | 목표와 가드레일 |
| 적합한 상황 | 경로를 미리 확정할 수 있는 작업 | 경로는 확정 못 하지만 진행 상태는 검증 가능한 작업 |
Anthropic의 권고는 “평가를 통과하는 가장 단순한 패턴을 쓰라”는 것이다. 실제 프로덕션 LLM 시스템 대부분은 Agent가 아니라 Workflow이고, 잘 설계된 단일 LLM 호출로 끝나는 경우도 많다.
Agent는 경로를 하드코딩할 수 없을 때만 선택하는 것이다. “요즘 다들 Agent 만드니까”가 이유가 되면 안 된다. 정해진 순서대로 도는 발행 프로세스 같은 건 Agent로 만들면 검증만 어려워진다.
구성 요소
| 요소 | 역할 |
|---|---|
| LLM | 요청 해석, 다음 행동 결정 |
| Tool | 호출할 수 있는 외부 기능 (API, DB, 검색). 요즘은 MCP로 표준화해서 노출 |
| Memory / Context | 대화 이력, 작업 상태, 참조 문서 |
| Feedback Loop | 도구 실행 결과를 다시 입력으로 받아 다음 행동을 재결정하는 순환 |
MCP와 A2A — 축이 다르다
둘을 경쟁 관계로 보는 글이 종종 보이는데, 축이 다른 보완 관계다.
| MCP (Model Context Protocol) | A2A (Agent2Agent Protocol) | |
|---|---|---|
| 방향 | 수직 — 모델 ↔ 도구·데이터 | 수평 — Agent ↔ Agent |
| 해결하는 문제 | 하나의 Agent가 외부 기능을 어떻게 호출할 것인가 | 여러 Agent가 어떻게 협업해 하나의 목표를 달성할 것인가 |
| 대상 | DB, API, 파일 등 도구 | 벤더·프레임워크가 다른 Agent |
그림으로 보면 이렇다.
Agent A ←—— A2A (수평, 작업 위임) ——→ Agent B
| |
| MCP (수직) | MCP (수직)
↓ ↓
MCP Server A MCP Server B
↓ ↓
System A Data B
프로덕션 멀티 Agent 시스템은 결국 양쪽을 다 쓴다. 각 Agent는 MCP로 자기 도구에 접근하고, Agent 사이의 작업 위임은 A2A로 처리한다.
A2A 핵심 개념
| 개념 | 설명 |
|---|---|
| Agent Card | Agent의 능력·엔드포인트·스킬·인증 방식을 담은 JSON 메타데이터. 다른 Agent가 이걸 읽고 발견(discovery)과 연동 설정을 한다 |
| A2A Client / Server | Client는 사용자를 대신해 요청을 시작하고, Server(remote agent)는 작업을 처리해 결과를 반환. Server 내부 구현은 Client에게 불투명(opaque)하게 유지된다 |
| Task | 고유 ID와 생명주기를 가진 상태 단위. 장시간 작업과 멀티턴 상호작용을 추적 |
| Message | 하나의 대화 턴. user / agent role |
| Part | Message와 Artifact를 구성하는 최소 콘텐츠 단위. text, raw, url, data 중 정확히 하나 |
| Artifact | 작업 결과로 나오는 산출물. 하나 이상의 Part로 구성 |
| 전송 | HTTP(S) 위에서 JSON-RPC 2.0 |
Server가 opaque하다는 게 핵심이다. 상대 Agent가 내부적으로 뭘 쓰는지 몰라도 협업이 된다. 그래서 벤더가 달라도 붙는다.
A2A 흘러온 과정
| 시점 | 내용 |
|---|---|
| 2025-04 | Google Cloud가 A2A 발표, 50개 이상 파트너 참여 |
| 2025년 중 | Google Cloud가 A2A를 Linux Foundation에 기부, 독립 프로젝트로 전환 |
| 2026-03 | v1.0 정식 스펙. 멀티 프로토콜 바인딩과 버전 협상, 엔터프라이즈 멀티테넌시, 서명된 Agent Card(암호학적 신원 검증) 추가 |
| 2026-04 | 1년 시점 150개 이상 조직 참여. Azure AI Foundry / Copilot Studio, AWS Bedrock AgentCore Runtime, Google Cloud에 통합. SDK도 Python 하나에서 JS·Java·Go·.NET까지 5개로 확대 |
| 2026-08 | A2A가 Agentic AI Foundation(AAIF)에 합류. MCP와 같은 재단 산하로 묶였다 |
MCP와 A2A가 같은 재단으로 들어간 게 개인적으로 제일 의미 있어 보인다. 두 축의 표준이 따로 놀 걱정이 줄었다.
다만 참여 조직 수와 플랫폼 통합 발표에 비해 실제 프로덕션 배포 규모 지표는 공개가 제한적이라는 지적도 있다. 표준 채택 발표와 실사용 규모는 구분해서 보는 게 맞다.
실제로 붙이려고 하면 걸리는 것들
여기부터가 실제로 시간을 쓴 부분이다. 개념은 깔끔한데 붙이려고 하면 네 군데서 걸린다.
1. 인증은 사라지지 않는다, 위치가 바뀐다
MCP 서버를 로컬에 설치해서 쓰는 방식(설치형)과, 이미 로그인한 웹 화면에 챗봇을 넣는 방식은 인증 구조가 완전히 다르다.
| 설치형 (로컬) | 웹 내장 | |
|---|---|---|
| 사용자 행동 | 직접 로그인해 토큰 발급받고 로컬 설정에 등록 | 없음. 이미 로그인한 상태로 사용 |
| 신원 전달 | 로컬에 저장된 토큰을 MCP 서버가 사용 | 브라우저 세션의 신원을 Agent → MCP → API로 전달 |
| 설계 부담 | 사용자마다 설치·토큰 관리 | 요청마다 “누구의 요청인지”를 실어 나르는 구조 |
처음엔 “웹에 넣으면 인증 과정이 생략된다”고 생각했는데 틀렸다. 사용자 입장에서 절차가 사라질 뿐, 그 역할을 서버가 대신 맡는 것이다. 로컬에 있던 토큰 관리 책임이 서버의 세션 신원 전달 책임으로 옮겨간다. 오히려 서버 쪽 설계는 늘어난다.
멀티테넌시가 여기서 붙는다. 요청마다 신원이 다르니, MCP 세션도 그에 맞게 격리돼야 한다.
2. 스트리밍이 없으면 동작해도 고장 난 것처럼 보인다
Agent가 도구를 여러 개 순차 호출하는 동안 화면이 멈춰 있으면, 기능이 멀쩡해도 사용자는 안 되는 줄 안다.
그래서 툴 실행 단위로 결과를 내려보내고 화면이 즉시 반영하는 구조가 사실상 필수다. 완성본을 한 번에 주는 게 아니라, 항목이 하나씩 추가되는 과정이 그대로 보여야 한다.
프론트엔드에서 필요한 것:
- 스트리밍 응답 수신과 점진적 렌더링 (SSE / WebSocket)
- 툴 실행 진행 상태 표시 — 실행 중 / 완료 / 실패
- 새로고침 없는 화면 갱신
- 대화를 이어가며 수정 요청하는 흐름
서버와 이벤트 타입 규격을 먼저 합의해야 병렬로 작업할 수 있다. 이거 안 정하고 시작하면 양쪽이 서로 기다린다.
3. 대화 이력은 Redis가 맞다
대화를 이어가려면 이력을 어딘가 저장해야 한다. 결론부터 쓰면 Redis다.
- TTL이 네이티브다. 만료를
EXPIRE로 끝내고 정리 배치를 안 만들어도 된다 - 수명이 짧고 매 턴 읽고 쓰는 데이터다. RDB에 넣을 이유가 없다
- 세션 종속이라 유실돼도 치명적이지 않다
RDB도 되긴 하는데 TTL이 없어서 삭제 배치를 직접 만들어야 하고, 짧게 살다 죽는 데이터로 write 부하를 만든다. 얻는 게 내구성뿐인데 그게 필요 없는 데이터다.
in-memory는 PoC 한정이다. 컨테이너가 2개 이상 뜨는 순간 깨진다. 다음 요청이 다른 인스턴스로 가면 대화가 없다.
Spring AI를 쓴다면 직접 만들 필요도 없다. ChatMemory + ChatMemoryRepository 추상화가 있고 in-memory / JDBC / Cassandra / Neo4j / MongoDB / Redis 구현이 제공된다. 다만 RedisChatMemoryRepository는 Redis Stack(Query Engine + RedisJSON)을 요구하니, 순정 Redis만 있다면 ChatMemoryRepository를 직접 구현하는 게 낫다. 인터페이스가 작아서 직렬화한 JSON을 키에 넣고 EXPIRE 거는 정도로 끝난다.
4. 세션 만료는 시간이 아니라 로그인에 묶어라
여기서 좀 헤맸다. “챗봇 세션을 몇 분 뒤에 만료시킬까”를 고민했는데, 시간은 잘못된 기준이었다.
웹 내장 구조에서는 인증을 브라우저 로그인 세션에서 승계한다. 그러면 로그인 세션이 끝날 때 대화도 끝나는 게 맞다. 시간만으로 잡으면 로그아웃 후에도 이전 사용자의 대화 이력이 남고, 같은 브라우저로 다른 사람이 로그인했을 때 권한 경계가 흐려진다.
정리하면 세 축이다.
- 유휴 sliding TTL (30분 정도로 시작) — 요청마다 갱신
- 로그아웃 시 즉시 삭제
- “새 대화” 버튼 — 만료 정책을 정교하게 만드는 것보다 이 버튼 하나가 대부분을 해결한다
그리고 컨텍스트 윈도우와 세션 만료는 다른 문제다. “모델에 무엇을 넣을지”는 슬라이딩 윈도우가 담당하고, “대화를 언제 버릴지”가 만료다. Spring AI의 MessageWindowChatMemory는 기본 20개 메시지를 유지한다(턴이 아니라 메시지 수 기준이다 — 한 턴이 여러 메시지가 될 수 있으니 체감 대화 길이는 더 짧다). 대신 잘라낼 때 턴 단위로 제거해서 tool call이 그 응답과 분리되는 일은 없다. 윈도우는 이미 해결된 문제라 신경 쓸 건 만료뿐이다.
처음부터 최적값을 맞추려 하지 않는 게 좋다. 짧게 잡고 띄운 뒤 실제 대화 길이 분포를 보고 조정하면 된다. 짧아서 생기는 불만은 사용자가 바로 말해주지만, 길어서 생기는 문제(비용, 데이터 잔존)는 조용히 쌓인다.
개인화는 생각보다 싸다
“사용자별 개인화”라고 하면 보통 학습형을 떠올리는데, 실제로 체감되는 개인화의 대부분은 이미 시스템에 있는 그 사람의 문맥을 조회해서 프롬프트에 넣는 것으로 나온다.
1단계 — 저장소 없이 되는 것. 로그인 세션에서 신원을 이미 받으니, 그 사용자의 권한·최근 작업 대상·기본 설정을 시스템 프롬프트에 넣으면 된다. 되묻지 않는 것만으로 개인화처럼 느껴진다.
2단계 — 설정 테이블 하나. 선호 언어, 기본 작업 대상, 응답 톤. 명시적이라 사용자가 이해하고 바꿀 수 있다.
3단계 — 학습형. 과거 대화에서 선호를 추출하거나 벡터 검색으로 유사 이력을 끌어오는 방식. 별개 프로젝트 규모다. 1·2단계 없이 여기부터 시작하면 대부분 실패한다.
한 가지 주의할 건, 개인화가 권한을 넘지 않게 하는 것이다. 컨텍스트에 담는 정보는 그 사용자가 원래 볼 수 있는 것만이어야 한다. “최근 작업 목록”을 권한 체크 없이 조회하면, 권한이 회수된 대상의 정보가 프롬프트를 통해 새어 나간다. 격리는 저장소가 아니라 조회 시점의 권한 검사가 결정한다.
정리
정리하면서 제일 크게 바뀐 생각은 두 가지다.
하나. Agent를 만들기 전에 Workflow로 되는지 먼저 봐야 한다. 대부분은 Workflow로 된다.
둘. 새로 만드는 것보다 이미 있는 데이터를 어디로 어떻게 흘려보낼지가 대부분의 작업이다. 개인화도, 실시간 렌더링도, 인증도 그랬다. 그래서 생각보다 싸게 된다. 대신 비싼 건 눈에 안 보이는 쪽이다 — 세션 신원 전달, 권한 경계, 스트리밍 이벤트 규격. 화면에는 안 나오지만 여기가 틀어지면 전부 다시 해야 한다.