튜토리얼
A2A 1.0 Agent Card JSON 실전: 능력 주장, Skills 형태, 에이전트 간 통신 데이터를 어떻게 검증하는가
검증이 실패하면 먼저 어느 층이 초록이었는지 묻는다. parse됐는가, 생성 Schema를 통과했는가, 명세 필수표에 맞았는가.
지난글 발견 점검은 well-known, 카탈로그, 그다음 Card까지 걸었다. 필드 쓰는 법은 더 앞의 Agent Card 필드 튜토리얼이다. 오늘은 필드를 다시 채우지 않고 홉도 다시 걷지 않는다. 이미 카드를 쥐고 있거나 방금 GET했다. 질문은 이것이다. 적법하다고 어떻게 단언할 것이며, 보낼 Task가 쓰레기로 취급되지 않을 것인가. 원문은 A2A 1.0 명세 §4.4와 §3.3.4. 사이트의 a2a.json은 스스로 적는다. proto에서 뽑은 비규범 JSON Schema 묶음이다. 0.3에서 1.0으로의 이동은 v1.0 변경에 있다.
초록은 통과가 아니다
생성 묶음을 Ajv에 넣고 루트를 가리킨 뒤 초록을 보는 것——리뷰에서 가장 흔한 가짜 통과다. 루트는 Card가 아니다. AgentCard, AgentSkill, Task, Message는 $defs 아래에 있다. 묶음 루트를 검증하면 아무것도 검증하지 않은 것이다. 올바른 포인터는 #/$defs/AgentCard다. 통신은 #/$defs/Task 또는 #/$defs/Message. Task를 Card Schema로 감싸지 마라.
포인터가 맞아도 생성 묶음은 자주 required를 빠뜨린다. 2026년 9월 사이트의 AgentCard와 AgentSkill은 additionalProperties: false를 두지만 명세표 필수를 required에 나열하지 않는다. 빈 name, tags 없는 skill도 초록일 수 있다. 필수의 원천은 명세표다. name, description, version, supportedInterfaces, capabilities, defaultInputModes, defaultOutputModes, skills. 각 skill에는 여전히 id / name / description / tags가 필요하다.
그래서 적어도 두 번 본다. 한 번째: parse되고 생성 AgentCard가 extra key로 터지지 않는다. 두 번째: 명세표가 비어 있지 않음을 단언한다. 첫 번째만 하면 0.3 잔재 최상위 url은 additionalProperties: false로 잡힌다. tags 없는 skill은 빠져나간다. 두 번째만, 생성 묶음 없이는 끼어든 inputSchema를 놓친다. 둘 다 필요하다.
| 이 층 | 잡는 것 | 놓치는 것 |
|---|---|---|
| JSON.parse | 깨진 문법, 잘림, 끝 쉼표 | 모양이 맞는지 |
생성 #/$defs/AgentCard | 0.3 최상위 url, skill의 inputSchema | 명세 필수가 쓰였는지 |
| 명세 §4.4 필수표 | 빈 name, 빠진 tags, 빈 skills | 플래그가 곧 보낼 동작과 맞는지 |
네 층, 한 층은 한 종류의 오류만 잡는다
세 번째 층은 능력 플래그다. 명세 §3.3.4는 단호하다. streaming이 거짓인데 구독하면 에이전트는 UnsupportedOperationError를 돌려줘야 한다. pushNotifications가 거짓인데 webhook을 구성하면 PushNotificationNotSupportedError. extendedAgentCard가 거짓인데 확장 카드를 가져오면 같은 미지원이다. Schema는 초록, 필수 키도 있는데 다음 홉은 여전히 튕길 수 있다. 점검 데이터는 「Card는 적법」과 「이 동작은 플래그가 허용한다」를 두 단계로 써야 한다.
네 번째 층은 통신이다. 보내는 것은 Task 또는 Message다. Card를 다시 POST하지 않는다. InvalidAgentResponseError는 응답 모양이지 명함이 아니다. MIME은 defaultInputModes 또는 skill의 inputModes와 맞아야 한다. 어긋나면 ContentTypeNotSupportedError. A2A skill을 MCP inputSchema로 보지 마라. 1.0의 힌트는 examples와 MIME이다.
생성 묶음에는 snake_case patternProperties도 있다(supported_interfaces, default_input_modes). 명세 JSON은 camelCase다. 새 카드는 명세표를 따른다. CI가 두 키 집합을 모두 받으면 Diff가 먼저 더러워진다. 호환 층을 명시해 키우는 것이 아니면 proto 필드 이름을 공개 명함에 복사하지 마라. 점검 데이터에 그렇게 적어라.
Card: 번들 루트가 아니라 AgentCard를 가리켜라
저장소에 a2a.json을 고정하고 공개 묶음 버전과 맞춘다. CI는 떠다니는 latest URL을 치지 마라. Ajv(또는 어떤 2020-12 구현)의 schema 포인터는 #/$defs/AgentCard. instance는 방금 GET한 카드이거나 저장소의 agent-card.json. path와 keyword를 내보낸다. path가 점검 데이터와 안 맞으면 카드보다 포인터를 먼저 의심하라.
첫 번째에서 가장 값진 거절은 extra key다. 1.0의 엔드포인트는 supportedInterfaces에 있다. 남은 최상위 url, protocolVersion, supportsAuthenticatedExtendedCard는 additionalProperties: false에서 빨개진다. 0.3 잔재이지 「필드가 많을수록 안전」이 아니다. 필드 튜토리얼이 이동을 이미 썼다. 오늘 요구하는 것은 하나다. 검증기는 그 키를 오류로 표시해야 한다. 무시하면 안 된다.
인터페이스 항목은 셋을 본다. 운영 url은 절대 HTTPS(gRPC는 host:port), protocolBinding은 클라이언트가 말하는 바인딩, protocolVersion은 1.0 같은 프로토콜 버전이지 에이전트 자신의 version이 아니다. 둘 다 version이라고 부른다. 점검 데이터는 나눠 단언해야 한다. 첫 항목이 우선이다. 공통 바인딩이 없으면 통화가 없다.
skills: 명세는 필수, Schema는 자주 묻지 않는다
생성 AgentSkill도 자주 required가 없다. tags 없는 skill은 Ajv에서 초록이어도 검색면이 비어 있다. 발견 글에서 이미 썼다. 오늘의 단언: 모든 skill은 비어 있지 않은 id, name, description과 태그 하나 이상을 가진다. 빈 skills 배열은 명세표를 통과하지 못한다. 호스트만 있는 NO_SPEC 항목은 등록 전에 빨개져야 한다. 키워드 홉이 허공을 친 뒤가 아니다.
skill에 inputSchema를 달지 마라. additionalProperties: false는 extra key로 본다. 그것은 MCP 도구 이야기다. 사이트 안 Schema 글을 보라. 1.0 skill이 보여주는 것은 examples와 MIME이다. 함수 인자표를 명함에 부으면 첫 번째에서 실패해야 한다. 검증에서의 실패가 Task를 보낸 뒤 확인보다 싸다.
아래 카드는 첫 번째나 두 번째에서 빨개지라고 만든 것이다. 「거의 쓸 수 있는」 초안이 아니다. 0.3 최상위 url, tags 없는 skill, MCP 식 inputSchema가 섞여 있다. 저장소 Returns Specialist와 나란히 Diff하라. 세 빨강은 세 path에 떨어져야 한다.
{
"name": "Returns Specialist",
"description": "Classifies return requests.",
"version": "1.0.3",
"url": "https://agents.example.com/returns/a2a",
"protocolVersion": "1.0",
"supportedInterfaces": [
{
"url": "https://agents.example.com/returns/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"capabilities": {
"streaming": true
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [
{
"id": "classify-return",
"name": "Classify a return",
"description": "Decide the request type.",
"inputSchema": {
"type": "object",
"required": ["orderId"],
"properties": {
"orderId": { "type": "string" }
}
}
}
]
}
| 검사 | 실패의 모습 | 다음 홉 |
|---|---|---|
| parse + AgentCard 포인터 | 끝 쉼표; 최상위 url; skill inputSchema | JSON을 고치거나 0.3 키를 버린다 |
| 명세 필수 / 비어 있지 않음 | 빈 name; tags 없는 skill; 빈 skills | §4.4에서 필드를 채운다 |
| 플래그 vs 동작 | streaming이 거짓인데 구독; 선언하지 않은 확장 카드를 가져옴 | 클라이언트를 바꾸거나 Card 플래그를 바꾼다 |
통신 데이터: Task는 명함이 아니다
Card가 통과한 뒤에야 message/send다. 본문은 바인딩을 따른다. JSON-RPC, gRPC, HTTP+JSON. 안의 업무 객체는 Message 또는 Task이지 AgentCard가 아니다. 요청을 Card 정의로 보면 반드시 빨개지고 배울 것이 없다. 생성 묶음에는 별도의 Task, Message, Part, Artifact가 있다. 통신 점검 데이터는 포인터를 바꿔라. Card 한 줄을 재사용하지 마라.
응답도 보라. 명세는 약정에 맞지 않는 에이전트 회신을 InvalidAgentResponseError로 접는다. 상태 기계는 submitted / working / completed / failed / canceled / rejected, 그리고 중단 상태 input-required, auth-required. completed만 성공으로 보면 「한 문장 더」가 장애 표가 된다. 그 키는 명함에 없다. 발견은 성공하고 통화는 실패했다——표는 층을 적어야 한다.
아래 JSON은 검증 보고용 데이터이지 어느 카탈로그나 A2A의 공식 RPC가 아니다. 네 층을 한 객체로 접어 저장소 Card와 한 번의 message/send와 나란히 diff하기 쉽게 했다. 서명 검사는 모양만 단언한다. signatures[] 각 항목에 protected와 signature(RFC 7515 JWS). 진짜 키와 진짜 검증은 보안 리뷰에 남긴다. 개인 키를 점검 데이터에 넣지 마라.
{
"kind": "card-validation-report",
"note": "CI/review fixture — not an official A2A RPC",
"target": "agent-card.json",
"schema": {
"bundle": "https://a2a-protocol.org/latest/spec/a2a.json",
"pointer": "#/$defs/AgentCard",
"normative": false
},
"parse": true,
"schemaPass": false,
"schemaErrors": [
{ "path": "/url", "keyword": "additionalProperties" },
{ "path": "/skills/0/inputSchema", "keyword": "additionalProperties" }
],
"specChecks": [
{ "id": "required-name", "pass": true },
{ "id": "skills-tags-nonempty", "pass": false },
{ "id": "no-top-level-url", "pass": false }
],
"capability": {
"streaming": true,
"clientWillStream": true,
"ok": true
},
"next": "fix-card"
}
서명, 점검 데이터, 로컬 대조
명세는 signatures를 RFC 7515 모양으로 허용한다. 배열이 있으면 먼저 두 필수 문자열이 비어 있지 않은지 단언한 뒤 검증 여부를 정한다. 배열이 없다고 카드가 불법이 되지는 않는다. 필드는 선택이다. 공개 카드는 가져갈 전제로 쓴다. 정적 비밀이나 사내 암호를 Card에 쓰지 마라. 확장 카드는 세션을 따른다. 공개 카드와 「이미 검증됨」 캐시를 공유하지 마라.
Plugin SKILL.md나 MCP tools/list를 같은 Card 검사에 붓지 마라. 상자 안 스킬은 이 저장소 Coding Agent용이다. 다른 팀의 자기 서술은 Card다. 하나의 capability.json에 집어넣으면 실패한 세 층이 같은 표 한 줄에 떨어진다.
브라우저에서 끝낸다. JSON 포맷으로 카드와 보고가 parse되는지 보고, JSON Schema 검증으로 공식 묶음을 AgentCard에 맞춘 뒤 포인터를 바꿔 Task를 보며, JSON Diff로 적법 카드와 빨간 카드를 비교해 extra key와 빠진 tags를 잡는다. 데이터는 이 기기를 떠나지 않는다. 이어서 Agent Card 필드 튜토리얼, 발견 점검. 카탈로그가 무엇인지는 Registry 개요.
관련: A2A Agent Card JSON Schema, Agent가 다른 Agent를 찾는 법, Google Agent Registry.
자주 묻는 질문
Ajv가 a2a.json에서 초록이면 두 번째는 없어도 되나?
있어야 한다. 사이트 묶음은 스스로 비규범이라 쓰고, 생성 정의는 자주 required를 빠뜨린다. 필수의 원천은 명세표다. 초록은 extra key와 유형을 밟지 않았다는 뜻일 뿐이다.
한 Schema로 Card와 Task를 모두 볼 수 있나?
없다. 포인터를 바꿔라. AgentCard와 Task는 두 개의 $defs다. 요청을 Card로 감싸면 실패 메시지가 다음 홉을 잘못 이끈다.
skill에 JSON Schema를 입력으로 걸어도 되나?
1.0 skill은 inputSchema를 받지 않는다. 그것은 MCP 도구다. 생성 묶음은 extra key로 본다. 확정 인자는 도구 홉에 남긴다. 명함에 쓰지 마라.
signatures가 없으면 불법인가?
아니다. 명세에서 signatures는 선택이다. 배열이 있으면 JWS 두 필수 문자열을 단언한다. 없으면 그래도 §4.4 필수와 플래그를 본다.
정리와 다음 단계
2026년에 「Agent Card를 검증한다」는 네 층으로 접힌다. parse, 생성 묶음 포인터, 명세 필수표, 그다음 플래그와 통신. 카탈로그와 발견은 입구다. 검증은 위임해도 되는지의 문지기다.
출하 순서. a2a.json을 고정하고 포인터는 AgentCard. 명세표 비어 있지 않음 단언을 돌리고, Task를 보내기 전 플래그와 MIME을 본다. 통신 데이터는 Task / Message로 바꾼다. 필드 쓰는 법은 필드 튜토리얼, 카드를 찾은 법은 발견 글. 적법 카드, 빨간 카드, 보고는 JSONVue에서 대조한다.