创建您的第一个 MCP 应用
Source: Dev.to
TL;DR
MCP 应用为对话代理和其他 MCP 客户端带来交互式 UI。本教程演示如何创建一个既简单又强大的应用(源代码 此处),它可以作为更大项目的模板。该应用将展示最近抵达某机场的航班。
我们将分三步构建该应用:
- 创建 MCP 服务器
- 注册工具
- 注册资源并将其关联到工具
步骤 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
步骤 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 文件夹:
{
"compilerOptions": {
"outDir": "./dist"
}
}
运行服务器:
npm run dev
使用 MCP Jam 测试服务器
npx @mcpjam/inspector@latest
添加一个 URL 为 http://localhost:3000/mcp 的服务器。连接应当成功。
步骤 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(见步骤 2)中注册。重新启动服务器后,任何兼容 MCP 的聊天 Playground 都可以调用此工具。
步骤 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
},
},
},
});
更新 nodemon.json,在启动服务器前先构建 UI:
{
"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(见步骤 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。 -
使用机场代码(例如
KJFK)调用get‑flights工具。 -
工具返回模拟航班数据,flight‑card 资源中定义的 UI 会将其渲染为交互式卡片。
恭喜!你已经构建了一个具备后端工具和前端 UI 的完整 MCP 应用。可以以此项目为基础,进行更复杂的集成开发。