byteforce

CPN 한국어 자습서 · 외부 문서 한국어 미러

MCP 문서 · Extensions

MCP 앱 만들기

Build an MCP App · 원문: modelcontextprotocol.io/extensions/apps/build

아래는 원문을 한국어로 옮긴 미러입니다. 코드·명령은 원문 그대로이며, 가장 최신 정보는 하단 원문 링크에서 확인하세요.

MCP 앱으로 인터랙티브 UI 애플리케이션을 구축하기 위한 시작 가이드

사전 요구 사항

Node.js 18 이상이 필요합니다. MCP 도구리소스에 대한 친숙함이 권장되며, MCP TypeScript SDK에 대한 경험이 있으면 도움이 됩니다.

시작하기

MCP 앱을 만드는 가장 빠른 방법은 MCP 앱 스킬이 있는 AI 코딩 에이전트를 사용하는 것입니다. 프로젝트를 수동으로 설정하려면 수동 설정으로 건너뛰세요.

AI 코딩 에이전트 사용

Claude Code를 사용 중이라면 스킬을 직접 설치할 수 있습니다:

코드 · 명령
/plugin marketplace add modelcontextprotocol/ext-apps
/plugin install mcp-apps@modelcontextprotocol-ext-apps

Vercel Skills CLI를 사용하면 여러 AI 코딩 에이전트에 스킬을 설치할 수 있습니다:

코드 · 명령
npx skills add modelcontextprotocol/ext-apps

또는 ext-apps 저장소를 클론하여 스킬을 수동으로 설치할 수 있습니다:

코드 · 명령
git clone https://github.com/modelcontextprotocol/ext-apps.git

스킬 디렉터리 위치:

에이전트 스킬 디렉터리 (macOS/Linux) 스킬 디렉터리 (Windows)
Claude Code ~/.claude/skills/ %USERPROFILE%\.claude\skills\
VS Code ~/.copilot/skills/ %USERPROFILE%\.copilot\skills\
Gemini CLI ~/.gemini/skills/ %USERPROFILE%\.gemini\skills\
Cline ~/.cline/skills/ %USERPROFILE%\.cline\skills\
Goose ~/.config/goose/skills/ %USERPROFILE%\.config\goose\skills\

참고: 이 목록은 포괄적이지 않습니다. 다른 에이전트는 다른 위치에서 스킬을 지원할 수 있으므로, 에이전트의 문서를 확인하세요.

Claude Code에 전역 설치하는 방법:

코드 · 명령
# macOS/Linux
cp -r ext-apps/plugins/mcp-apps/skills/create-mcp-app ~/.claude/skills/create-mcp-app

# Windows
Copy-Item -Recurse ext-apps\plugins\mcp-apps\skills\create-mcp-app $env:USERPROFILE\.claude\skills\create-mcp-app

그런 다음 AI 코딩 에이전트에 요청합니다:

코드 · 명령
Create an MCP App that displays a color picker

수동 설정

1단계: 프로젝트 구조 만들기

코드 · 명령
my-mcp-app/
  package.json
  tsconfig.json
  vite.config.ts
  server.ts          # 도구 + 리소스가 있는 MCP 서버
  mcp-app.html       # UI 진입점
  src/
    mcp-app.ts       # UI 로직

2단계: 의존성 설치

코드 · 명령
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx

ext-apps 패키지는 서버 측(도구와 리소스 등록)과 클라이언트 측(App 클래스) 모두에 헬퍼를 제공합니다. vite-plugin-singlefile을 사용하면 UI와 에셋을 단일 HTML 파일로 번들링하지만, 이는 선택 사항입니다.

3단계: 프로젝트 설정

package.json:

코드 · 명령
{
  "type": "module",
  "scripts": {
    "build": "INPUT=mcp-app.html vite build",
    "serve": "npx tsx server.ts"
  }
}

tsconfig.json:

코드 · 명령
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["*.ts", "src/**/*.ts"]
}

vite.config.ts:

코드 · 명령
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";

export default defineConfig({
  plugins: [viteSingleFile()],
  build: {
    outDir: "dist",
    rollupOptions: {
      input: process.env.INPUT,
    },
  },
});

MCP 앱 구축

서버 시간을 표시하는 간단한 앱 예제입니다.

서버 구현

코드 · 명령
// 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 앱 리소스임을 알립니다.
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 () => {
    const time = new Date().toISOString();
    return {
      content: [{ type: "text", text: time }],
    };
  },
);

// 번들된 HTML을 제공하는 리소스 등록
registerAppResource(
  server,
  resourceUri,
  resourceUri,
  { mimeType: RESOURCE_MIME_TYPE },
  async () => {
    const html = await fs.readFile(
      path.join(import.meta.dirname, "dist", "mcp-app.html"),
      "utf-8",
    );
    return {
      contents: [
        { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
      ],
    };
  },
);

// HTTP를 통해 MCP 서버 노출
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");
});

핵심 구성 요소:

UI 구현

코드 · 명령
<!-- mcp-app.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Get Time App</title>
  </head>
  <body>
    <p>
      <strong>Server Time:</strong>
      <code id="server-time">Loading...</code>
    </p>
    <button id="get-time-btn">Get Server Time</button>
    <script type="module" src="/src/mcp-app.ts"></script>
  </body>
</html>
코드 · 명령
// 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]";
};

// 사용자가 UI와 상호작용할 때 도구를 직접 호출
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]";
});

핵심 구성 요소:

앱 테스트

UI를 빌드하고 로컬 서버를 시작합니다:

코드 · 명령
# macOS/Linux
npm run build && npm run serve

# Windows
npm run build; npm run serve

기본 설정에서 서버는 http://localhost:3001/mcp에서 사용 가능합니다.

Claude로 테스트

별도의 터미널에서 터널을 실행합니다:

코드 · 명령
npx cloudflared tunnel --url http://localhost:3001

생성된 URL을 복사하여 Claude에 커스텀 커넥터로 추가합니다.

참고: 커스텀 커넥터는 유료 Claude 플랜(Pro, Max, Team)에서 사용할 수 있습니다.

basic-host로 테스트

코드 · 명령
# macOS/Linux
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm start

# Windows
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps\examples\basic-host
npm install
$env:SERVERS='["http://localhost:3001/mcp"]'; npm start

http://localhost:8080으로 이동합니다. 도구를 선택하고 호출하면 UI 리소스를 가져와 샌드박스 iframe에서 렌더링합니다.

더 알아보기

원문(영어): https://modelcontextprotocol.io/extensions/apps/build · 본 문서는 학습용 한국어 번역이며 원본의 권리는 원저작자(Model Context Protocol)에게 있습니다.

원문(영어): https://modelcontextprotocol.io/extensions/apps/build