본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 05. 20:59

Vite Single File로 만드는 경량 MCP Apps UI: 단일 HTML 제한의 기술적 배경과 양방향 통신의 미니멀 구현

요약

MCP Apps UI를 구현할 때 보안과 호환성을 위해 단일 HTML 파일로 번들링하는 기술적 배경을 설명합니다. Vite와 전용 플러그인을 사용하여 React/Vue 에셋을 인라인화하는 방법을 다룹니다.

핵심 포인트

  • MCP Apps는 보안을 위해 샌드박스화된 iframe 내에서 단일 HTML로 실행됨
  • 단일 HTML 구성은 외부 에셋 로드 문제와 CSP 제한을 해결하는 데 유리함
  • Vite와 vite-plugin-singlefile을 사용하여 모든 에셋을 인라인화 가능
  • UI와 서버 간 통신은 호스트(AI 클라이언트)를 경유하여 보안성 확보

「MCP Apps (SEP-1865)」는 기존의 텍스트나 구조화된 데이터에 의한 응답을 넘어, AI 채팅 화면 내에 HTML 기반의 인터랙티브한 UI를 직접 임베딩하기 위한 확장 사양으로서 2026년 1월 26일에 정식 출시되었습니다. 이 사양에서 UI (View)를 제공하는 리소스는 최종적으로 「단일 HTML」로서 배포되는 구성이 매우 유효합니다.

LLM (대규모 언어 모델)은 최신 정보나 사내 기밀 데이터에 액세스할 수 없는 「데이터의 단절」이라는 과제를 안고 있지만, MCP (Model Context Protocol)는 이를 연결하는 오픈 표준 규격입니다. 비즈니스 관점에서는 연결 대상마다 전용 API를 개별 개발하는 비용을 절감하고, 인증 및 액세스 권한 관리를 일원화할 수 있는 큰 장점이 있습니다. 그 진화형인 MCP Apps의 UI 개발에서 왜 멀티 파일 구성이 아니라 에셋을 하나로 묶은 「단일 HTML」이 요구되는지, 그 기술적 배경과 설계 판단의 트레이드오프 (Trade-off)를 해설합니다.

호스트 (AI 클라이언트)는 서버의 툴 호출 응답에 포함된 _meta.ui.resourceUri 필드에서 ui:// 스킴을 감지하여 대상 UI 리소스를 취득합니다. 이때 취득되는 데이터의 MIME 타입은 text/html;profile=mcp-app으로 제한됩니다.

호스트는 취득한 HTML을 샌드박스화된 iframe 내에 렌더링합니다. 이때 보안 확보를 위해 호스트의 DOM, Cookie, 로컬 스토리지에 대한 액세스는 완전히 차단됩니다. 만약 UI가 여러 개의 CSS나 JS, 이미지 등의 외부 파일을 상대 경로로 불러오는 설계라면, 이 엄격하게 보호된 샌드박스 환경 하에서 의존 에셋을 올바르게 해결(Resolve) 및 로드하는 것이 매우 어려워집니다.

MCP Apps의 큰 특징은 「UI (View)와 서버는 직접 통신하지 않는다」는 설계 방침에 있습니다. iframe 내의 UI는 서버와 직접 통신하지 않고, 모든 통신을 중개역인 호스트 (AI 클라이언트)를 경유하여 수행합니다. 이를 통해 악의적인 조작이나 외부로의 의도치 않은 데이터 유출 (Exfiltration)을 호스트 측에서 검열 및 차단할 수 있습니다.

에셋이 모두 인라인화(Inlined)된 단일 HTML이라면, UI 표시 시 외부로의 추가 에셋 요청이 발생하지 않습니다. 서버 측이 미리 선언한 CSP (Content Security Policy) 메타데이터에 기반하여 호스트가 통신을 엄격하게 제한하는 측면에서도, 단일 HTML 구성은 매우 친화성이 높습니다.

React나 Vue와 같은 컴포넌트 지향 프레임워크를 사용하는 경우, 통상적으로 빌드 프로세스에서 JS나 CSS가 분할 (Code Splitting)됩니다. 이를 「단일 HTML」로 강제적으로 번들링하기 위해, Vite와 vite-plugin-singlefile을 조합한 개발 환경이 강력한 솔루션이 됩니다.

다음은 이 빌드 구성을 실현하기 위한 최소한의 설정 절차입니다.

Vite 프로젝트에 vite-plugin-singlefile과 공식 프런트엔드용 SDK인 @modelcontextprotocol/ext-apps를 도입합니다.

npm install -D vite-plugin-singlefile
npm install @modelcontextprotocol/ext-apps

Vite 설정 파일에서 빌드 시 모든 JS, CSS, 에셋을 HTML 내에 인라인화하도록 플러그인을 배치합니다.

import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
...

이 설정에 의해 npm run build를 실행했을 때, dist/index.html 안에 모든 스타일시트와 실행 스크립트가 내장된 하나의 경량 HTML 파일이 출력됩니다.

단일 HTML로 빌드된 UI는 호스트 (Agent)를 중개하여 서버와 통신을 수행합니다. UI와 호스트 간에는 postMessage를 통한 JSON-RPC 2.0으로 양방향 통신을 실행합니다.

공식 SDK인 @modelcontextprotocol/ext-apps를 사용하면, 저수준 (Low-level) 통신 처리를 직접 작성할 필요 없이 직관적인 코드로 서버의 도구(callServerTool)를 호출하거나, AI 측의 컨텍스트를 동적으로 업데이트(app.updateModelContext())할 수 있습니다.

다음은 UI 측의 버튼을 클릭했을 때 서버 측의 도구를 호출하는 미니멀한 프론트엔드 측 구현 예시입니다.

import { createMcpClient } from "@modelcontextprotocol/ext-apps";
// MCP 클라이언트 초기화
const mcp = createMcpClient();
...

사용자가 UI 상에서 수행한 조작(예: 지도의 확대/축소 또는 스크롤, 폼 선택 등)을 AI 측에 동기화하고 싶을 때는 app.updateModelContext()를 사용합니다. 이를 통해 AI는 "현재 사용자가 어떤 화면을 보고 있는가"라는 최신 컨텍스트를 파악하여, 정확한 다음 대화를 제공할 수 있습니다.

async function onUserInteract(stateData: any) {
// AI 측의 컨텍스트를 동적으로 업데이트
await mcp.updateModelContext({
...

MCP Apps를 도입할 때 고려해야 할 중요한 개념은 "단계적 향상 (Progressive Enhancement)"입니다.

모든 AI 클라이언트가 최신 MCP Apps를 통한 UI 표시를 지원하는 것은 아닙니다. UI를 지원하지 않는 비호환 클라이언트에서 실행되더라도, 시스템이 충돌하지 않고 폴백 (Fallback)으로서 기존의 "텍스트 응답"을 반환하는 하위 호환성이 유지됩니다. 따라서 UI 구축을 위한 단일 HTML을 설계할 때는, 텍스트로 된 설명 정보도 함께 응답에 포함하도록 백엔드 측을 구현하는 것이 권장됩니다.

또한, 중요한 조작(예를 들어, Slack으로 메시지 전송 또는 외부 도구로의 게시 등)을 UI에서 트리거하는 경우에는 보안과 거버넌스 관점에서 실행 전에 미리보기를 표시하고, 사용자의 명시적인 동의 및 확인을 필수적으로 요구하는 프로세스를 설계에 포함하는 것이 필수적입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0