MCP Apps
MCP Apps (상호작용 UI 리소스)
MCP Apps는 Model Context Protocol (MCP) 툴을 상호작용형 UI 리소스로 확장합니다. 모델은 여전히 일반 MCP 툴을 호출하지만, 툴은 애플리케이션이 샌드박스된 iframe에서 렌더링하는 HTML을 담은 ui:// 리소스를 가리킬 수 있습니다.
AI SDK는 MCP Apps 호스트를 구축하기 위한 두 가지 구성 요소를 제공합니다: MCP Apps 지원 광고, 모델 노출·앱 노출 툴 분리, ui:// 리소스 읽기를 돕는 @ai-sdk/mcp 헬퍼와, 앱 iframe 렌더링과 MCP Apps JSON-RPC 메시지 브리징을 담당하는 @ai-sdk/react 컴포넌트입니다.
출처: 공식문서
본문
호스트 흐름
MCP Apps 호스트는 보통 다음을 수행합니다:
- MCP Apps 클라이언트 기능으로 MCP 서버에 연결합니다.
- 툴을 나열하고 MCP Apps 가시성별로 나눕니다.
- 모델에 보이는 툴만
streamText나generateText에 전달합니다. - 툴 파트가 MCP App 메타데이터를 포함하면 앱의
ui://리소스를 읽습니다. - HTML 리소스를 샌드박스된 iframe에서 렌더링합니다.
- 앱에서 보이는
tools/call같은 허용된 iframe 요청을 MCP 서버로 프록시합니다.
MCP Apps 지원으로 연결
MCP 클라이언트를 만들 때 mcpAppClientCapabilities를 사용하세요. 이는 호스트가 text/html;profile=mcp-app 리소스를 렌더링할 수 있음을 광고합니다.
import { createMCPClient, mcpAppClientCapabilities } from '@ai-sdk/mcp';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
export function createMCPAppsClient(origin: string) {
return createMCPClient({
transport: new StreamableHTTPClientTransport(new URL('/mcp', origin)),
clientName: 'my-mcp-apps-host',
capabilities: mcpAppClientCapabilities,
});
}
호스트가 MCP App 리소스를 안전하게 가져오고 렌더링할 수 있을 때만 이 기능을 광고하세요.
모델에 보이는 툴만 노출
MCP Apps 툴은 _meta.ui.visibility를 선언할 수 있습니다. "model" 가시성의 툴은 모델에 전달할 수 있습니다. "app" 가시성만 있는 툴은 iframe 요청용으로 유지하고 모델에는 노출하지 않아야 합니다.
app/api/chat/route.ts
import { splitMCPAppTools } from '@ai-sdk/mcp';
import {
convertToModelMessages,
createUIMessageStreamResponse,
streamText,
toUIMessageStream,
} from 'ai';
import { createMCPAppsClient } from './mcp-client';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const requestUrl = new URL(req.url);
const client = await createMCPAppsClient(requestUrl.origin);
const { messages } = await req.json();
try {
const definitions = await client.listTools();
const { modelVisible } = splitMCPAppTools(definitions);
const tools = client.toolsFromDefinitions(modelVisible);
const result = streamText({
model: openai('gpt-4o-mini'),
tools,
messages: await convertToModelMessages(messages),
onEnd: async () => {
await client.close();
},
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
} catch (error) {
await client.close();
throw error;
}
}
모델이 앱 기반 툴을 호출하면 MCP 클라이언트는 툴 UI 파트에 앱 메타데이터를 보존합니다. React 렌더러는 그 메타데이터로 툴 파트에 MCP App이 있는지 판단합니다.
앱 리소스 읽기
readMCPAppResource로 앱 리소스를 브라우저 호스트로 보내기 전에 읽고 정규화합니다.
app/api/mcp-app-host/route.ts
import { readMCPAppResource } from '@ai-sdk/mcp';
import { createMCPAppsClient } from '../chat/mcp-client';
export async function POST(req: Request) {
const requestUrl = new URL(req.url);
const { uri } = await req.json();
const client = await createMCPAppsClient(requestUrl.origin);
try {
return Response.json(await readMCPAppResource({ client, uri }));
} finally {
await client.close();
}
}
readMCPAppResource는 리소스가 ui:// URI를 사용하는지 확인하고, MCP Apps MIME 타입을 요구하며, 텍스트 또는 base64 리소스 콘텐츠를 디코딩하고, HTML과 함께 CSP·권한 같은 렌더링 메타데이터를 반환합니다.
앱에서 보이는 툴 호출 프록시
iframe의 앱에서 보이는 툴 호출은 서버에서 검증한 뒤 클라이언트의 callTool로 프록시합니다. iframe 창마다 툴 콜백을 등록하고, 허용된 요청만 MCP 서버로 전달하며, 결과를 iframe에 다시 알립니다.
React로 렌더링
@ai-sdk/react의 experimental_MCPAppRenderer 컴포넌트로 툴 UI 파트를 렌더링합니다. 일반 툴에는 아무것도 렌더링하지 않고, 앱 기반 툴에는 리소스를 로드하고 샌드박스 브리지를 만들며 툴 입력·결과 알림을 iframe에 보내고 지원되는 앱 요청을 핸들러를 통해 전달합니다.
모범 사례
- MCP App HTML을 신뢰할 수 없는 콘텐츠로 취급하세요. 샌드박스된 iframe에서 렌더링하고, 이상적으로는 별도 오리진의 샌드박스 프록시 라우트를 경유하세요.
- 앱 전용 툴을 모델에 절대 전달하지 마세요.
splitMCPAppTools를 쓰고modelVisible툴만 노출하세요. client.callTool을 호출하기 전에 모든 iframe 요청을 서버에서 검증하세요.resourceUri로 앱 리소스를 캐시해서 반복 툴 호출이 동일한 HTML을 다시 가져오지 않게 하세요.- UI 없이도 툴
content와structuredContent가 유용하도록 유지해 텍스트 전용 호스트도 동작하게 하세요. - 응답이나 호스트 요청이 끝나면 수명이 짧은 MCP 클라이언트를 닫으세요.