첫 번째 MCP 앱 만들기
Source: Dev.to
TL;DR
MCP 앱은 대화형 에이전트와 기타 MCP 클라이언트에 인터랙티브 UI를 제공합니다. 이 튜토리얼에서는 간단하지만 강력한 앱(소스 코드 here)을 만드는 방법을 보여주며, 이는 더 큰 프로젝트의 템플릿으로 사용할 수 있습니다. 이 앱은 공항에 도착한 최신 항공편을 표시합니다.
앱은 다음 세 단계로 구축합니다:
- MCP 서버 만들기
- 도구 등록하기
- 리소스를 등록하고 도구와 연결하기
Step 1 – 프로젝트 초기화
npm init --yes && npm pkg set type="module"
tsc --init
.gitignore 파일을 생성합니다:
echo -e "node_modules\ndist" > .gitignore
필요한 의존성을 설치합니다:
npm i @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps express zod \
&& npm i -D @types/express nodemon
Step 2 – MCP 서버 만들기
server.ts 파일을 생성합니다:
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { registerGetFlightsTool } from "./tools/get-flights.js";
import { registerFlightCardResource } from "./resources/flight-card/flight-card.js";
const server = new McpServer({
name: "My First MCP App",
version: "0.0.1",
});
// Register tools and resources.
registerGetFlightsTool(server);
registerFlightCardResource(server);
// Set up Express server to handle MCP requests.
const app = express();
app.use(express.json());
app.use("/mcp", async (req, res, next) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body).catch(next);
});
// Start the server.
app.listen(3000, () => {
console.log("MCP server listening on http://localhost:3000/mcp");
});
nodemon을 이용한 개발 워크플로우
nodemon.json 파일을 생성합니다:
{
"watch": ["."],
"ext": "ts,html,css",
"ignore": ["dist", "node_modules"],
"exec": "tsc && node dist/server.js"
}
package.json에 dev 스크립트를 추가합니다:
{
"scripts": {
"dev": "nodemon"
}
}
컴파일된 파일이 dist 폴더에 들어가도록 tsconfig.json에 다음 라인을 주석 해제(또는 추가)합니다:
{
"compilerOptions": {
"outDir": "./dist"
}
}
서버를 실행합니다:
npm run dev
MCP Jam으로 서버 테스트하기
npx @mcpjam/inspector@latest
URL http://localhost:3000/mcp 로 서버를 추가합니다. 연결이 성공해야 합니다.
Step 3 – Get Flights 도구 만들기
tools/get-flights.ts 파일을 생성합니다:
// tools/get-flights.ts
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import z from "zod";
import { flightCardResourceUri } from "../resources/flight-card/flight-card.js";
export function registerGetFlightsTool(server: McpServer) {
server.registerTool(
"get-flights",
{
description: "Retrieves flight arrivals for a given airport code",
inputSchema: {
code: z.string().describe("The ICAO airport code, e.g. 'KJFK'"),
},
},
async (input: { code: string }) => {
// Mock flight data – replace with a real API call in production.
const mockFlights = [
{ flightNumber: "AA100", airline: "American Airlines" },
{ flightNumber: "DL200", airline: "Delta Airlines" },
{ flightNumber: "UA300", airline: "United Airlines" },
];
return {
content: [{ type: "text", text: JSON.stringify(mockFlights, null, 2) }],
structuredContent: { flights: mockFlights },
};
}
);
}
도구는 server.ts(Step 2)에서 이미 등록되었습니다. 서버를 재시작하면 MCP‑호환 채팅 플레이그라운드 어디서든 이 도구를 호출할 수 있습니다.
Step 4 – Vite와 Flight‑Card 리소스로 UI 추가하기
추가 의존성 설치
npm i vite vite-plugin-singlefile glob
Vite 설정
vite.config.js 파일을 생성합니다:
// vite.config.js
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
outDir: "dist",
emptyOutDir: true,
rollupOptions: {
input: {
"flight-card": "resources/flight-card/mcp-app/flight-card-mcp-app.html",
// Add more HTML resources here as needed
},
},
},
});
UI를 빌드한 뒤 서버를 시작하도록 nodemon.json을 업데이트합니다:
{
"watch": ["."],
"ext": "ts,html,css",
"ignore": ["dist", "node_modules"],
"exec": "vite build && tsc && node dist/server.js"
}
Flight‑Card 리소스 구현
resources/flight-card/flight-card.ts 파일을 생성합니다:
// resources/flight-card/flight-card.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import fs from "node:fs/promises";
import path from "node:path";
export const flightCardResourceUri = "ui://flight-card.html";
export function registerFlightCardResource(server: McpServer) {
server.registerResource(
flightCardResourceUri,
flightCardResourceUri,
{},
async () => {
const html = await fs.readFile(
path.join(import.meta.dirname, "mcp-app/flight-card-mcp-app.html"),
"utf-8"
);
return {
contents: [
{
uri: flightCardResourceUri,
mimeType: "text/html;profile=mcp-app",
text: html,
},
],
};
}
);
}
리소스는 server.ts(Step 2)에서 이미 등록되었습니다.
Flight‑Card HTML 템플릿
resources/flight-card/mcp-app/flight-card-mcp-app.html 파일을 생성합니다:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flight Cards</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f0f2f5;
padding: 40px;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.card {
background: #fff;
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.flight-number {
font-weight: bold;
font-size: 1.2rem;
}
.airline {
color: #555;
}
</style>
</head>
<body>
<h1>Flight Cards</h1>
<div id="cards" class="container"></div>
<script>
// Placeholder script – replace with real rendering logic.
const mockFlights = [
{ flightNumber: "AA100", airline: "American Airlines" },
{ flightNumber: "DL200", airline: "Delta Airlines" },
{ flightNumber: "UA300", airline: "United Airlines" },
];
const container = document.getElementById("cards");
mockFlights.forEach((f) => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = `
<div class="flight-number">${f.flightNumber}</div>
<div class="airline">${f.airline}</div>
`;
container?.appendChild(card);
});
</script>
</body>
</html>
(원한다면 인라인 스크립트를 별도의 flight-card-mcp-app.ts 모듈로 교체할 수 있습니다.)
최종 점검
-
개발 서버를 실행합니다:
npm run dev -
MCP Jam(
npx @mcpjam/inspector@latest)을 열고http://localhost:3000/mcp에 연결합니다. -
get‑flights도구를 공항 코드(예:KJFK)와 함께 호출합니다. -
도구가 모의 항공편 데이터를 반환하고, flight‑card 리소스에 정의된 UI가 이를 인터랙티브 카드 형태로 렌더링합니다.
축하합니다! 백엔드 도구와 프론트엔드 UI를 갖춘 기능적인 MCP 앱을 만들었습니다. 이 프로젝트를 기반으로 더 복잡한 통합을 진행해 보세요.