튜토리얼
Agent Plugins가 AI 코딩 에이전트에 새 능력을 붙이는 방법: Plugin Manifest, Skills, MCP, 설치 후의 JSON
플러그인을 켠다고 모델이 똑똑해지지 않는다. 클라이언트가 고정 경로에서 JSON 몇 개를 더 읽을 뿐이다.
앞의 두 글은 상자가 어떻게 생겼는지, 그리고 언제 상자에 넣지 말아야 하는지였다. 오늘은 이미 Plugin을 내기로 한 전제다. 독자가 진짜 묻는 말은 이것이다. Cursor, Claude Code, Antigravity가 그 디렉터리를 깐 뒤, 모델이 갑자기 청구서를 조회하고 주간 요약을 쓰는 이유는 뭔가? 가중치 갱신도 아니고 시스템 프롬프트를 고친 것도 아니다. Agent Plugins 1.0.0은 단호하다. 루트의 plugin.json을 먼저 읽고, 고정 위치의 skills/와 mcp.json을 찾는다. Google Cloud Developer Plugin도 같은 길을 걷는다. 설치 후 JSON hop을 끝까지 보고 MCP와 JSON Schema 검증으로 넘긴다.
다섯 홉이다. “학습했다”가 아니다
“에이전트가 자동으로 능력을 얻었다”는 말은 모델이 기술을 익힌 것처럼 들린다. 구현은 다섯 홉이고, 하나도 건너뛸 수 없다. 첫 홉: 클라이언트가 디렉터리를 플러그인 루트에 둔다. 해석된 경로가 루트 밖으로 나가면 거부한다. 루트 밖을 가리키는 심볼릭 링크도 같다. 둘째 홉: plugin.json을 읽고 닫힌 Schema를 통과시킨 뒤 name과 명세 버전을 가져온다. 여기서 실패하면 패키지 전체를 거절하고 나머지 홉은 돌지 않는다. 셋째 홉: skills/가 있으면 직속 자식만 보고, 이름이 정확히 SKILL.md인 일반 파일이 있는 항목에서 name과 description을 꺼내 컨텍스트에 넣는다. 넷째 홉: mcp.json이 있으면 각 항목의 type으로 붙고, 핸드셰이크 뒤 tools/list. 다섯째 홉: 모델이 설명이나 도구 이름에 맞은 뒤에야 Skill 본문을 읽거나 tools/call을 보낸다.
명세는 설치 버튼이 어떻게 생겼는지를 일부러 정의하지 않는다. Google Developers Blog도 설치, 권한, 샌드박스, 확인 UX는 각 클라이언트의 의무라고 적었다. Agents CLI, Cursor, Claude Code의 대화상자가 달라도 된다. 이식되는 것은 디렉터리와 닫힌 JSON 두 장이다. 리뷰에서 물을 말은 “사용자가 어디를 클릭하나”가 아니라 “깐 뒤 메모리에 무엇이 늘었나”다. Manifest가 없으면 뒤 홉은 없다. Manifest는 통과했는데 mcp.json의 $schema가 plugin.json과 어긋나면 MCP만 멈추고 스킬은 남긴다. SKILL.md 하나가 Agent Skills에 맞지 않으면 그 항목만 건너뛴다.
가로 위임은 이 파이프라인 밖이다. 다른 팀의 청구 에이전트를 어떻게 찾느냐는 Agent Card이고, A2A 글에 있다. 오늘은 이 코딩 에이전트가 스킬과 도구 묶음을 어떻게 키우는지다. “새 능력”을 발견의 결과로 부른 다음, 각 홉의 JSON을 본다.
| 이 홉 | 클라이언트가 지금 가진 JSON | 모델이 지금 할 수 있는 일 |
|---|---|---|
plugin.json 읽기 | 신원 객체: name / version / $schema | 아직 할 일 없음. 상자가 합법일 뿐 |
skills/ 걷기 | 스킬 메타데이터 배열 (본문은 아직 없음) | 설명서는 고를 수 있다. 본문은 필요할 때 |
MCP 연결 후 tools/list | 도구 이름과 inputSchema | Schema에 맞춰 인자는 채울 수 있다. 실행은 아직 |
먼저 Manifest: plugin.json은 신원 계약
클라이언트는 컴포넌트를 찾기 전에 루트 plugin.json을 읽어야 한다. 파일 이름을 바꿀 수 없고, 스킬이나 MCP를 Manifest에 인라인할 수도 없다. Schema는 닫혀 있다. 허용 최상위 키는 $schema, name, version, description, author, homepage, repository, license, keywords, extensions뿐이다. 나머지 최상위 키는 보고하고 무시한다. 그 이유로 거절하면 안 된다. 치명적인 것은 필수 누락, 타입 오류, name 위반이다. 그때는 패키지 전체를 거절하고 컴포넌트를 하나도 찾지 않는다. 1.0.0의 $schema는 https://agent-plugins.org/schemas/1.0.0/plugin.schema.json이어야 한다. 클라이언트는 그 값으로 로컬 규칙을 고르고, 로드 중에 Schema를 받아오면 안 된다.
name은 식별자다. 스토어 표시 이름이 아니다. 길이 1–64. 소문자, 숫자, 하이픈, 점만. 처음과 끝은 영숫자. --와 ..는 금지. My-Plugin, -start는 패키지 전체가 거절된다. version은 SemVer를 권하지만 “SemVer처럼 안 보인다”는 거절 사유가 아니다. author 객체에 넣을 수 있는 것은 name / email / url뿐이다. 클라이언트 전용 값은 extensions.com.example.client나 루트의 역도메인 디렉터리에 둔다. hooks를 위한 다섯 번째 최상위 키를 만들지 마라. plugin.json 최상위에 hooks를 쓰면 명세는 무시하라고 한다. 지금 IDE가 우연히 읽어도 다음 클라이언트에서는 사라진다.
아래는 저장소에 넣을 수 있는 완전한 Manifest다. 최소 두 필드보다 사람용 메타데이터가 많다. 설치 버튼을 논하기 전에 parse되고 공식 Schema를 통과하게 하라. keywords는 카탈로그 검색용이다. description은 사람이 설치할지 정하는 용이고, 모델이 도구를 고르는 데는 도움이 되지 않는다. 스킬은 SKILL.md의 description으로 고르고, 도구는 tools/list로 고른다. Manifest 설명을 도구 설명서로 써도 발견면은 비어 있다.
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "invoice-ops",
"version": "1.2.0",
"description": "Invoice query MCP plus the weekly-summary skill, shipped as one directory",
"author": {
"name": "Finance Platform",
"url": "https://docs.example.com/invoice-ops"
},
"homepage": "https://docs.example.com/invoice-ops",
"repository": "https://github.com/example/invoice-ops",
"license": "MIT",
"keywords": ["invoices", "weekly-summary", "mcp"]
}
설치 후: 클라이언트가 쥐고 있는 능력 스냅샷
명세는 “설치된 능력” 파일 내보내기를 요구하지 않는다. 리뷰는 그래도 깐 뒤 메모리에 무엇이 있는지 봐야 한다. 다섯 홉의 결과를 스냅샷 JSON 하나로 접는다. plugin 신원, 발견된 스킬(메타데이터만), MCP 연결 상태, tools/list가 돌려준 도구 계약. 이 스냅샷은 plugin.schema.json의 인스턴스가 아니다. 포장 Schema로 검증하지 마라. CI 픽스처다. 스킬 수, 도구 이름, inputSchema 필수를 단언한다. 설치는 됐는데 스냅샷이 안 맞으면 발견이나 핸드셰이크가 깨진 것이다. “모델이 아직 못 배웠다”가 아니다.
“자동으로 얻었다”는 이 객체에서 보인다. skills[].loaded는 metadata이지 body가 아니다. 시작 때 대략 백 토큰, 본문은 필요할 때. tools[]는 핸드셰이크 뒤 tools/list에서 온다. plugin.json에 손으로 적은 목록이 아니다. mcpServers[].status는 런타임이지 포장 계약이 아니다. 스냅샷의 tools가 비었는데 MCP가 connected면 악수는 끝났고 서버가 도구를 안 낸 것이다. Manifest를 고치지 말고 서버를 보라. 반대로 Manifest는 합법인데 스킬이 0이면 SKILL.md를 한 단 더 깊게 숨긴 건 아닌지 보라.
Google Cloud Developer Plugin을 깐 뒤에도 클라이언트 쪽 모양은 같다. 상자 신원, gcloud 가드레일 스킬 메타데이터, Developer Knowledge MCP 도구 목록. 사용자는 “에이전트가 Cloud 문서를 찾게 됐다”고 말한다. 데이터로는 tools가 몇 줄 늘었을 뿐이다. 스냅샷과 저장소의 plugin.json / mcp.json을 나란히 diff하면 런타임 상태를 포장 파일에 다시 쓴 사람이 바로 보인다. 그건 drift이지 명세 필드가 아니다.
{
"plugin": {
"name": "invoice-ops",
"version": "1.2.0",
"spec": "1.0.0"
},
"skills": [
{
"name": "write-weekly-summary",
"description": "Turn invoice query results into the weekly summary finance reads. Use when the user asks for a week-end report.",
"path": "skills/write-weekly-summary/SKILL.md",
"loaded": "metadata"
}
],
"mcpServers": [
{
"id": "invoice-tools",
"type": "streamable-http",
"status": "connected"
}
],
"tools": [
{
"name": "query_invoices",
"server": "invoice-tools",
"inputSchema": {
"type": "object",
"required": ["week"],
"properties": {
"week": { "type": "string", "pattern": "^[0-9]{4}-W[0-9]{2}$" },
"status": { "type": "string", "enum": ["open", "paid", "overdue"] }
}
}
}
]
}
스킬 홉: frontmatter가 발견면이 된다
Agent Plugins는 SKILL.md를 다시 쓰지 않는다. 발견 규칙은 하나다. skills/의 직속 자식 디렉터리에 이름이 정확히 SKILL.md인 일반 파일이 있을 것. skills/deploy/extra/SKILL.md에 숨기면 안 보인다. Agent Skills에 안 맞는 항목은 건너뛰고, 다른 스킬과 MCP는 계속 읽는다. 컨텍스트에 들어가는 것은 frontmatter의 name과 description, 그리고 본문·scripts/·references/를 나중에 읽기 위한 경로다.
description에는 무엇을 하는지와 언제 쓰는지가 같이 있어야 한다. invoice-ops-skill-v2나 1인칭 슬로건이면 발견면은 0이다. 상자는 깔렸는데 모델이 고르지 않고, 사용자는 플러그인이 고장 났다고 본다. 고장난 것은 설명서다. scripts/는 여전히 “이미 가진 셸로 이것을 실행하라”이고, 인자는 argv다. tools/list의 일등 도구가 아니다. 스냅샷 tools[]에 스크립트 이름을 넣지 마라. 들어가 있다면 Skill 첨부물을 MCP로 착각한 것이다.
본문을 필요할 때 읽는 것은 창을 아끼기 위해서다. 스냅샷을 loaded: metadata로 두는 것은 런북 전체를 시스템 프롬프트에 넣는 회귀를 잡기 위해서다. 설명서 때문에 창이 터지는 것은 Plugin 형식의 잘못이 아니라 클라이언트 로딩 정책의 잘못이다. 명세가 보장하는 것은 스킬을 찾을 수 있다는 것뿐이다. 모델과 사용자에게 어떻게 보여줄지는 클라이언트가 정한다.
MCP 홉: 붙인 다음 tools/list
mcp.json은 루트에 있어야 한다. plugin.json에 인라인하면 안 되고, 다른 코어 경로로 옮겨도 안 된다. 최상위는 $schema와 mcpServers뿐이다. $schema는 https://agent-plugins.org/schemas/1.0.0/mcp.schema.json에 고정하고, Manifest가 선언한 명세 버전과 같아야 한다. 어긋나면 그 플러그인의 MCP만 멈춘다. 각 서버는 type을 명시해야 한다. stdio, streamable-http, 선택적 레거시 sse. 객체 모양으로 전송을 추측하면 안 된다. streamable-http의 url은 절대 http/https여야 하고, 루프백이 아니면 https여야 한다. headers는 보이는 포장 데이터이지 비밀 칸이 아니다.
붙인 뒤의 발견면이 tools/list다. 포장 파일은 “어디에 붙을지”에 답한다. 도구 계약은 “이번 홉 arguments가 합법인지”에 답한다. inputSchema를 plugin.json에 복사하지 말고, name / version을 inputSchema에 복사하지 마라. 인증 실패는 그 서버의 연결 실패이지 플러그인 설정 불법이 아니다. 명세는 이식 가능한 OAuth 필드를 정의하지 않는다. 키는 클라이언트 런타임에 남긴다. 선 프로토콜은 MCP란 무엇인가에 있다.
아래는 원격 MCP의 이식 조각이다. 저장소 픽스처에 API Key를 쓰지 마라. 붙인 뒤 tools/list 결과를 스냅샷의 tools[]에 넣는다. 핸드셰이크 실패는 그 서버만 건너뛰고, 다른 서버와 스킬은 계속한다. 이 실패 경계는 명세에 있다. 제품 구호가 아니다.
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"invoice-tools": {
"type": "streamable-http",
"url": "https://billing.example.com/mcp"
}
}
}
| 실패 | 죽는 것 | 남는 것 |
|---|---|---|
plugin.json name에 대문자 | 패키지 전체. 컴포넌트를 찾지 않음 | 없음 |
mcp.json $schema가 Manifest와 불일치 | 그 플러그인의 MCP 전부 | 스킬은 계속 로드 |
SKILL.md 하나의 frontmatter가 깨짐 | 그 스킬 하나 | 다른 스킬 + MCP |
독립 실패, JSONVue에 남기는 픽스처
리뷰 픽스처는 적어도 네 장. 위의 합법 plugin.json, 능력 스냅샷, 이식 가능한 mcp.json, query_invoices arguments 한 번. 첫 장은 공식 plugin.schema.json을 통과해야 한다. 둘째는 자체 스냅샷 Schema이거나 구조 단언이다. 포장 Schema를 씌우지 마라. 셋째는 mcp.schema.json. 넷째는 도구 inputSchema. 네 장 모두 JSON이지만 일은 다르다. 신원, 재고, 연결, 호출.
음수 예를 두 장 더한다. name을 Invoice-Ops로 쓴다. mcp.json 한 줄에서 type을 뺀다. 전자는 패키지 전체를 거절해야 한다. 후자는 그 서버만 건너뛴다. CI가 알 수 없는 최상위 필드를 치명으로 보면 클라이언트보다 엄하다. 명세는 보고하고 무시하고 계속 읽으라고 한다. 저장소에 들어가는 픽스처에 비밀을 넣지 마라.
브라우저에서 끝난다.JSON 포맷으로 Manifest, 스냅샷, mcp.json이 parse되는지 보고;JSON Schema 검증으로 $schema, name, mcpServers, inputSchema를 보고;JSON Diff으로 스냅샷과 포장 파일을 비교해 런타임을 되돌린 drift를 잡는다. 데이터는 기기를 떠나지 않는다. 이어서 MCP와 JSON Schema, Plugins 개요, 상자에 넣을지 판단.
관련: Google Agent Plugins 2026, Skills vs MCP vs Plugins, MCP와 JSON Schema, MCP란 무엇인가.
자주 묻는 질문
Plugin을 깔면 모델이 미세조정되나?
되지 않는다. 가중치는 그대로다. 클라이언트가 늘리는 것은 신원 객체, 스킬 메타데이터, tools/list가 돌려준 도구 계약이다. “할 수 있게” 보이는 것은 발견면이 바뀌어서이지, 모델이 청구 업무를 배워서가 아니다.
도구 목록을 plugin.json에 넣고 mcp.json을 생략할 수 있나?
없다. Manifest는 컴포넌트를 인라인할 수 없고 발견 경로도 바꿀 수 없다. 도구 목록은 핸드셰이크 뒤 tools/list에서 온다. plugin.json 최상위에 쓰면 무시된다. extensions에 쓰면 그 클라이언트에만 의미가 있고 다음 클라이언트에서는 사라진다.
능력 스냅샷을 공식 plugin.schema.json으로 검증할 수 있나?
없다. 공식 Schema는 상자 신원만 그린다. 스냅샷은 클라이언트가 조립한 재고이고, 런타임 status와 도구 inputSchema가 들어 있다. 스냅샷 Schema를 따로 쓰거나 CI에서 필요한 필드만 단언하라.
설치 UX가 클라이언트마다 다른데 Plugin은 이식 가능한가?
포장은 이식된다. 설치는 이식되지 않아도 된다. 명세는 설치, 권한, 샌드박스를 일부러 빼 두었다. 클라이언트를 바꿔도 디렉터리와 닫힌 JSON 두 장은 갈라지지 않는다. 확인 대화상자와 기업 정책은 달라도 된다.
정리와 다음 단계
2026년에 “Plugin이 코딩 에이전트에 능력을 자동으로 붙인다”는 한 줄로 줄어든다. 새 능력은 발견의 결과이지 가중치가 아니다. 다섯 홉이 끝나면 메모리에 있는 것은 신원, 스킬 메타데이터, 도구 계약이다. 한 홉이 빠지면 사용자는 “깔았는데 못 한다”고 보고한다.
내는 순서는 이렇다. 먼저 plugin.json을 공식 Schema에 통과시킨다. skills/와 mcp.json을 걷는다. 결과를 스냅샷 픽스처로 굳힌다. 첫 tools/call은 inputSchema로 arguments를 본다. 네 계약은 JSONVue에 남긴다. 상자 개요는 Plugins 글, 넣을지는 결정 글, 선 프로토콜은 MCP 글.