MCP Apps 만들기
MCP Apps 만들기 (Build an MCP App)
MCP Apps는 MCP 서버가 대화 안에 차트·폼·비디오 플레이어 같은 상호작용 UI 요소를 인라인으로 표시할 수 있게 해주는 확장이에요. 이 가이드는 MCP Apps를 시작부터 끝까지 만드는 절차를 다뤄요. 가장 빠른 방법은 Skills를 지원하는 AI 코딩 에이전트로 스캐폴딩하는 거고, 프로젝트를 직접 구성하고 싶다면 수동 설정 섹션을 따라가면 돼요.
사전 준비(Prerequisites)
Node.js 18 이상이 필요해요. MCP Apps가 두 프리미티브를 결합하므로 MCP 도구와 리소스 개념에 익숙한 게 좋고, MCP TypeScript SDK 경험은 서버 쪽 패턴 이해에 도움이 돼요.
시작하기(Getting started)
가장 빠른 방법은 create-mcp-app 스킬을 가진 AI 코딩 에이전트를 쓰는 거예요. Claude Code라면 플러그인으로 설치하거나, Vercel Skills CLI(npx skills add modelcontextprotocol/ext-apps)로 설치할 수 있어요. 에이전트에 "Create an MCP App that displays a color picker"처럼 빌드를 요청하면 스킬이 관련 있는지 인식하고 전체 프로젝트(서버·UI·설정 파일)를 스캐폴딩해요.
프로젝트 구조
전형적인 MCP App 프로젝트는 서버 코드와 UI 코드를 분리해요. 서버는 도구와 UI 리소스를 등록하고, UI 리소스는 최종적으로 deny-by-default CSP 설정으로 보안 iframe 안에서 렌더링돼요. CSS·JS 에셋이 있다면 CSP를 구성하거나 vite-plugin-singlefile 같은 도구로 에셋을 HTML에 번들하면 돼요.
서버 구현
서버는 두 가지를 해야 해요: _meta.ui.resourceUri 필드를 포함한 도구를 등록하고, 번들된 HTML을 서빙하는 리소스 핸들러를 등록하는 거죠.
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import cors from "cors";
import express from "express";
import fs from "node:fs/promises";
import path from "node:path";
const server = new McpServer({ name: "My MCP App Server", version: "1.0.0" });
// ui:// 스킴은 호스트가 이것이 MCP App 리소스임을 알게 한다. 경로 구조는 자유롭다.
const resourceUri = "ui://get-time/mcp-app.html";
registerAppTool(
server,
"get-time",
{ title: "Get Time", description: "Returns the current server time.", inputSchema: {}, _meta: { ui: { resourceUri } } },
async () => ({ content: [{ type: "text", text: new Date().toISOString() }] }),
);
registerAppResource(
server, resourceUri, resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => ({ contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: await fs.readFile(path.join(import.meta.dirname, "dist", "mcp-app.html"), "utf-8") }] }),
);
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
expressApp.listen(3001, (err) => {
if (err) { console.error("Error starting server:", err); process.exit(1); }
console.log("Server listening on http://localhost:3001/mcp");
});
핵심 부분을 보면 resourceUri의 ui:// 스킴이 MCP App 리소스임을 알리고, registerAppTool이 _meta.ui.resourceUri 필드로 도구를 등록해 호스트가 도구를 호출할 때 UI를 가져와 렌더링하고 결과를 전달해요. registerAppResource는 호스트가 UI 리소스를 요청할 때 번들 HTML을 서빙해요.
UI 구현
UI는 HTML 페이지와 App 클래스로 호스트와 통신하는 TypeScript 모듈로 구성돼요.
// src/mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
const app = new App({ name: "Get Time App", version: "1.0.0" });
app.connect(); // 호스트와 통신 확립
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
getTimeBtn.addEventListener("click", async () => {
const result = await app.callServerTool({ name: "get-time", arguments: {} });
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
app.connect()는 초기화 시 한 번 호출해 호스트와 통신을 확립하고, app.ontoolresult는 호스트가 도구 결과를 앱으로 밀어 넣을 때(예: 첫 호출 후 UI 렌더링) 발생하는 콜백이에요. app.callServerTool()은 앱이 서버의 도구를 능동적으로 호출하게 해주며, 매 호출이 왕복을 수반하므로 UI는 지연을 우아하게 처리하도록 설계해야 해요.
테스트
기본 구성에서 서버는 http://localhost:3001/mcp에서 제공돼요. MCP Apps를 지원하는 호스트(예: Claude)에서 테스트하려면 로컬 서버를 인터넷에 노출해야 하는데, npx cloudflared tunnel --url http://localhost:3001로 터널을 만들고 생성된 URL을 Claude의 커스텀 커넥터로 추가하면 돼요. ext-apps 저장소의 basic-host는 SERVERS='["http://localhost:3001/mcp"]' npm start처럼 실행해 http://localhost:8080에서 개발용 테스트 인터페이스를 제공해요.
더 알아보기
- 잘 이해하기: MCP Apps 개요 · 월확장 개요
- API 문서: https://apps.extensions.modelcontextprotocol.io/api/
- 소스 코드 & 이슈: https://github.com/modelcontextprotocol/ext-apps