-
통합에서 발견으로 — AI 시대 '진입 장벽 낮추기'의 진짜 정체IT 2026. 8. 3. 23:00
"AI가 앱 개발 진입 장벽을 낮춘다"는 말은 이제 슬로건이 됐다. 그런데 정작 무엇이 낮아진다는 건지 물으면 답이 흐릿하다. 코드가 줄어드는 걸까, 아니면 프레임워크가 똑똑해지는 걸까. 최근 몇 년 사이 서로 다른 진영 — 구글, 애플, 그리고 개방형 표준 — 이 내놓은 설계를 나란히 놓고 보면, 진입 장벽이 낮아지는 지점은 놀랍도록 한 곳으로 수렴한다. 바로 기능을 어떻게 노출하느냐다.
핵심 전환은 이것이다. 예전에는 두 시스템을 잇는 일이 곧 명령형 통합이었다. A가 B의 API를 알아야 하고, 엔드포인트·파라미터·순서를 코드에 박아 넣어야 했다. 상대가 바뀌면 통합 코드도 다시 짰다. AI 시대의 설계는 이 방향을 뒤집는다. 개발자는 앱 기능을 타입 있는 계약으로 '선언'만 하고, 시스템이나 에이전트가 그 선언을 '발견'해 조합한다. 이 선언적 기능 노출과 능력 발견(capability discovery)의 짝이야말로 "진입 장벽 낮추기"의 실체다.
구글이 제안한 A2A(Agent2Agent) 프로토콜은 이 발상을 가장 노골적으로 보여준다. 각 에이전트는 자신이 무엇을 할 수 있는지를 JSON "Agent Card"로 광고하고, 클라이언트 에이전트는 이 카드들을 읽어 작업에 가장 적합한 에이전트를 골라낸다. 공식 문서는 이 방식이 하드코딩된 정적 통합을 대체한다고 명시한다. 상대의 내부를 미리 알 필요 없이, 광고된 능력만 보고 런타임에 연결한다.
통합의 무게 중심이 옮겨간다
▲ 명령형 통합 — 연결하는 쪽이 상대의 세부를 전부 떠안는 방식
다이어그램 설명. 과거의 연결 방식이다. 연결하려는 앱이 상대의 API 엔드포인트·호출 순서·파라미터를 코드에 직접 박아 넣고, 상대가 바뀌면 그 통합 코드를 전부 다시 짠다. 통합의 비용이 소비하는 쪽에 몰려 있고, 상대가 N개면 통합 코드도 N벌이 필요하다.
▲ 선언형 발견 — 제공하는 쪽이 계약을 내걸고 소비하는 쪽이 읽어 가는 방식
다이어그램 설명. 지금의 연결 방식이다. 기능을 제공하는 쪽이 자기 능력을 계약으로 내걸고, 소비하는 쪽은 그 계약을 읽어 필요한 것을 골라 쓴다. 개발자가 짜야 할 코드는 "연결 절차"가 아니라 "무엇을 할 수 있는가의 선언"으로 바뀐다. 진입 장벽이 낮아진다는 말의 물리적 실체가 여기 있다 — 연결 비용이 통합 코드 N벌에서 선언 1벌로 줄어든다.
같은 선언, 다른 카탈로그
애플의 App Intents는 같은 원리를 OS 안에서 구현한다. 개발자는 앱 기능을 typed schema로 선언한다 —
AppIntent로 액션을,AppEntity/AppEnum으로 다루는 대상을,@Parameter로 입력을,perform()에 실제 동작을 담는다. 이 스키마는 컴파일 타임에 생성되어 앱 번들에 함께 들어가고, 앱이 설치되는 순간부터 음성 비서와 시스템 검색에 자동으로 인덱싱된다. 개발자가 별도로 "내 기능을 여기 등록해줘"라고 요청하지 않아도 된다. 나아가 messaging·media·finance 같은 "App Intent Domains"로 액션을 카테고리화해, 서로 다른 앱의 기능을 같은 범주 안에서 조합할 수 있게 한다.네 조각이 실제로 어떻게 맞물리는지는 코드로 보는 편이 빠르다. 독서 기록 앱이 "책에 별점 매기기"라는 기능을 시스템에 내거는 상황을 가정해 보자.
// ① AppEntity — 앱이 다루는 '대상'을 시스템이 이해할 수 있는 타입으로 선언 extension Book: AppEntity { static var typeDisplayRepresentation: TypeDisplayRepresentation = "책" // 비서·검색이 이 대상을 사용자에게 어떤 이름으로 보여줄지 var displayRepresentation: DisplayRepresentation { .init(title: LocalizedStringResource(stringLiteral: title)) } // 시스템이 "어떤 책?"을 되물을 때 후보를 길어올 통로 static var defaultQuery = BookQuery() } struct BookQuery: EntityQuery { // ID로 특정 대상을 복원 (시스템은 값이 아니라 ID를 들고 다닌다) func entities(for identifiers: [UUID]) async throws -> [Book] { BookStore.shared.books.filter { identifiers.contains($0.id) } } // 사용자에게 먼저 제시할 기본 후보 — 여기선 '읽는 중'인 책 func suggestedEntities() async throws -> [Book] { BookStore.shared.books.filter { $0.isReading } } } // ② AppEnum — 값이 닫힌 집합이면 열거형으로 선언한다 enum Rating: String, AppEnum { case one, two, three, four, five static var typeDisplayRepresentation: TypeDisplayRepresentation = "별점" static var caseDisplayRepresentations: [Rating: DisplayRepresentation] = [ .one: "별 하나", .two: "별 둘", .three: "별 셋", .four: "별 넷", .five: "별 다섯", ] } // ③ AppIntent — 하나의 '액션'을 선언 struct RateBookIntent: AppIntent { static var title: LocalizedStringResource = "책에 별점 매기기" // ④ @Parameter — 이 액션이 받는 입력. 타입이 곧 계약이다 @Parameter(title: "책") var book: Book @Parameter(title: "별점") var rating: Rating // ⑤ perform() — 실제 동작. 입력이 다 채워진 뒤에만 호출된다 func perform() async throws -> some IntentResult & ProvidesDialog { BookStore.shared.rate(book, rating) return .result(dialog: "\(book.title)에 \(rating.rawValue)점을 매겼어요.") } }코드 설명.
AppEntity는 앱이 다루는 명사다. 여기서는 "책"이 그것이고, 시스템은 이 선언 덕분에 책이라는 개념을 알게 된다. 중요한 건defaultQuery다 — 시스템이 "어떤 책 말이야?"라고 되물어야 할 때 후보 목록을 어디서 가져올지를 앱이 미리 일러두는 통로다. 시스템은 책 객체를 통째로 들고 다니지 않고 ID만 쥔 채, 필요할 때entities(for:)로 복원한다.AppEnum은 같은 역할을 값이 닫힌 집합에 대해 한다. 별점은 1~5 다섯 가지뿐이므로 열거형으로 선언하고, 각 케이스가 사용자에게 어떤 말로 보일지(caseDisplayRepresentations)까지 함께 내건다.AppIntent는 동사다. "책에 별점 매기기"라는 액션 하나가 곧 타입 하나이고,title은 그 액션이 단축어 앱이나 비서에게 어떤 이름으로 노출될지를 정한다.@Parameter는 그 액션이 받는 입력인데, 여기서 타입이 계약 그 자체로 작동한다.var book: Book이라고 적는 순간 "이 액션은 책 하나를 받는다"가 시스템에 전달되고, 사용자가 책을 지목하지 않은 채 명령하면 시스템이 알아서BookQuery의 후보를 띄워 되묻는다. 개발자가 "입력이 비었으면 선택 화면을 띄워라" 같은 흐름을 짜지 않아도 되는 이유가 여기 있다 — 그 절차는 타입 선언에서 파생된다.perform()은 남은 유일한 명령형 코드다. 모든 입력이 채워진 것이 보장된 상태에서 호출되므로 안에는 실제 도메인 동작만 남는다. 반환 타입조차 선언의 일부다 —ProvidesDialog를 덧붙이면 "이 액션은 사용자에게 말로 응답할 수 있다"는 사실이 함께 광고된다. 정리하면 앞의 세 조각이 무엇을 할 수 있는가를 타입으로 선언하고,perform()하나가 어떻게 하는가를 맡는다. 통합 코드는 어디에도 없다 — 이 파일 어디에도 "비서에게 내 기능을 등록해줘"라고 요청하는 줄이 없다는 점이 핵심이다.MCP(Model Context Protocol, AI 모델과 외부 도구를 잇는 표준)도 뼈대는 같다. 도구는
name·description·inputSchema로 선언되고, 클라이언트는tools/list로 사용 가능한 도구 목록을 받아tools/call로 호출한다. 실행 시점의 그림도 App Intents와 겹친다 — 선언이 목록으로 쌓이고, 사용자의 말이 들어오면 그 목록에서 맞는 것을 골라 호출한다. 애플이라고 해서 사용자의 의도를 신비롭게 알아채는 게 아니라, 인덱싱해둔 인텐트 중에서 고를 뿐이다. 고르는 단계만 놓고 보면 둘은 사실상 같은 일을 한다.차이는 그 앞에 있다. 목록에 무엇이 오르는지를 누가, 언제 정하느냐다. App Intents에서는 설치가 곧 등재다. 앱을 깔면 그 앱의 인텐트가 기기 전체의 목록에 들어가고, 사용자가 무엇을 넣을지 고르는 단계는 없다. 스키마 자체도 컴파일 타임에 굳어 앱과 함께 배포된다. 반면 MCP에서는 붙이기로 설정한 서버의 도구만 목록에 오른다. 범위는 기기가 아니라 세션이고, 누군가 한 번은 "이 서버를 붙이겠다"고 정해줘야 한다. 목록을 런타임에 물어보는 구조라, 서버가 도중에 도구를 바꾸는 것도 가능하다.
그래서 갈리는 것은 발견의 신비로움이 아니라 노출의 기본값이다. 한쪽은 기본이 켜짐이다 — 설치했으면 보인다. 다른 쪽은 기본이 꺼짐이다 — 붙이지 않았으면 없는 것과 같다. 그리고 이 기본값은 그대로 대가가 된다. 켜져 있으면 마찰이 없는 대신 "이 기능은 비서에게 보이고 저 기능은 숨긴다"를 세밀하게 통제하기 어렵고, 꺼져 있으면 통제가 되는 대신 매번 붙이는 수고가 든다. 발견을 쉽게 만드는 설계와 통제를 지키는 설계는 같은 다이얼의 양 끝이다.
왜 하필 '타입 있는' 계약이어야 했나
여기까지 오면 질문이 하나 남는다. 발견이 목적이라면 자연어 설명 한 줄이면 충분하지 않은가. "이 앱은 책에 별점을 매길 수 있습니다"라고 적어두고 LLM더러 알아서 읽으라고 하면 될 일을, 왜 굳이
AppEntity니@Parameter니 하는 타입으로 못 박는가.답은 발견 표면이 발견에서 끝나지 않기 때문이다. 그 표면은 그대로 LLM 하네스로 작동한다.
앞의 별점 코드를 다시 보자. 사용자가 "그 책에 별 다섯"이라고 말하면, 그 말을 어떤 액션으로 옮길지 그리고
book자리에 무엇을 채울지 판단하는 것은 통계적 추론이다. 여기엔 확률이 있고 오답이 있다. 그런데 그 추론이 고를 수 있는 후보는 앱이 선언한 유한한 집합뿐이다. 선언하지 않은 액션은 호출될 수 없고, 타입이 맞지 않는 값은 넘어갈 수 없다. 그리고 일단 고르고 난 뒤perform()안에서 도는 코드는 100% 앱이 작성한 결정적 코드다. 확률이 개입할 틈이 없다.▲ 발견 표면이 곧 하네스 — 통계는 위쪽에만 있고 아래로 내려갈수록 확정된다
다이어그램 설명. 자연어가 실제 동작에 이르기까지의 경로다. 위에서 두 번째 칸, 즉 자연어를 액션과 입력으로 옮기는 판단만이 통계적이다. 그 아래는 전부 가드다. 선언된 액션 집합이 호출 가능한 범위를 닫고, 타입이 값의 형태를 검증하고, 대상이 모호하면 후보를 띄워 사람에게 되묻는다. 마지막 칸에 이르면 남은 것은 앱이 직접 쓴 코드뿐이다. 통계적 모델이 메인 actor로 앞에 서 있고 그 주위를 확정적인 가드가 둘러싸는 구조 — 이것이 하네스의 전형적인 모양이다. 스스로 판단하고 실행하는 에이전트가 아니다.
다만 이 구조가 무엇을 보장하고 무엇을 보장하지 않는지는 정확히 갈라야 한다. 보장되는 것은 행동 공간의 폐쇄성이다. 앱이 내걸지 않은 일은 일어나지 않는다. 보장되지 않는 것은 선택의 정확성이다. 타입이 맞아도 의미는 틀릴 수 있다. 사용자가 말한 책이 《데미안》인데 《수레바퀴 아래서》가 채워지는 사고를 App Intents는 막아주지 않는다. 둘 다 똑같이 유효한
Book이기 때문이다. 그러니 이 설계를 "통계적 동작을 결정적으로 만든다"고 읽으면 과하다. 정확히는 통계적 선택이 일어나는 공간을 앱이 확정한 유한하고 검증 가능한 집합으로 가둔다. 모델은 여전히 확률적으로 고르되, 고를 수 있는 것과 고른 뒤 벌어지는 일이 앱의 손 안에 있다.그리고 이것은 애플만의 이야기가 아니다. MCP의
inputSchema, 모델 API의 function calling, structured output이 전부 같은 모양이다. 통계적 생성기를 타입 있는 계약으로 둘러싸 출력 공간을 닫는다. 앞서 서로 다른 진영이 선언형 발견으로 수렴한다고 했는데, 그 수렴의 다른 얼굴이 바로 이것이다 — 발견 표면으로 수렴하는 일과 하네스로 수렴하는 일이 같은 사건이었다. 기능을 발견 가능하게 만드는 계약과, 확률적 모델을 안전하게 붙들어 두는 계약이 결국 같은 물건이기 때문이다.정리 — 발견되지 않는 기능은 없는 기능이다
"AI가 진입 장벽을 낮춘다"를 코드량이 준다는 이야기로만 읽으면 요점을 놓친다. 실제로 낮아지는 것은 연결의 비용이다. 개발자가 할 일이 "상대와 어떻게 통합할지"에서 "내 기능을 어떻게 선언할지"로 옮겨가고, 조합의 책임은 발견하는 쪽이 진다. 그리고 그 선언은 발견되기 위한 광고인 동시에, 발견한 쪽이 확률적 기계일 때 그것을 유한한 행동 공간 안에 붙들어 두는 가드이기도 하다 — 잘 벼려진 계약 하나가 두 일을 함께 해낸다. 새 기능을 만들 때 물어야 할 질문도 바뀐다 — "이걸 누구와 연결하지?"가 아니라 "이걸 어떤 계약으로 선언하면 발견될까?"다. 발견되지 않는 기능은, 이 세계에서는 없는 기능이다.
참고한 공개 자료:
- A survey of agent interoperability protocols — 프로토콜별 비교의 배경, arXiv (https://arxiv.org/abs/2505.02279)
- A2A: A new era of agent interoperability — Google Developers Blog (https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/)
- App Intents — Apple Developer Documentation (https://developer.apple.com/documentation/appintents)
- Dive into App Intents — WWDC22, 프레임워크 개요와 코드 패턴 (https://developer.apple.com/videos/play/wwdc2022/10032/)
- App Intents vs MCP tools: the frontier — Blake Crosley (https://blakecrosley.com/blog/app-intents-vs-mcp-tools-frontier)
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
스펙이 새로운 소스코드다? — 가장 뜨거운 주장의 팽팽한 뒷면 (0) 2026.08.05 AI는 팀을 고치지 않는다 — DORA 2025가 말하는 '증폭기' 테제 (1) 2026.08.05 AI가 나를 19% 느리게 만들었는데, 나는 20% 빨라졌다고 믿었다 — METR RCT가 흔든 생산성 통념 (0) 2026.08.04 MCP 샌드박싱의 사정거리 — 무엇이 걸리고 무엇이 정문으로 나가는가 (1) 2026.08.04 The S in MCP stands for Security — 로컬 MCP 서버에는 인증이 없다 (0) 2026.08.04 AI가 빨리 짜준 코드의 청구서는 6개월 뒤에 온다 — 30만 커밋이 말하는 기술부채 (0) 2026.08.03 AI는 왜 있는 함수를 두고 새로 짜는가 — 재사용의 병목은 설계가 아니라 '모델의 기억'이다 (1) 2026.08.03 표준이 둘로 갈렸다가 하나로 — ACP가 A2A에 합류한 이야기 (0) 2026.08.02 에이전트 호출의 생명주기 — 왜 그냥 API 호출로는 안 되나 (0) 2026.08.02 에이전트는 서로를 어떻게 찾는가 — 발견, 그리고 에이전트의 '명함' (0) 2026.08.02