创建您的第一个 MCP 应用

发布: (2025年12月13日 GMT+8 07:05)
6 min read
原文: Dev.to

Source: Dev.to

TL;DR

MCP 应用为对话代理和其他 MCP 客户端带来交互式 UI。本教程演示如何创建一个既简单又强大的应用(源代码 此处),它可以作为更大项目的模板。该应用将展示最近抵达某机场的航班。

我们将分三步构建该应用:

  1. 创建 MCP 服务器
  2. 注册工具
  3. 注册资源并将其关联到工具

步骤 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 模块。)

最终检查

  1. 运行开发服务器:

    npm run dev
  2. 打开 MCP Jamnpx @mcpjam/inspector@latest)并连接到 http://localhost:3000/mcp

  3. 使用机场代码(例如 KJFK)调用 get‑flights 工具。

  4. 工具返回模拟航班数据,flight‑card 资源中定义的 UI 会将其渲染为交互式卡片。

恭喜!你已经构建了一个具备后端工具和前端 UI 的完整 MCP 应用。可以以此项目为基础,进行更复杂的集成开发。

Back to Blog

相关文章

阅读更多 »

实验性 Hono auth npm 包

我正在构建的东西:我正在创建一个 auth package,开发者可以将其直接放入他们的应用中,而无需编写常规的登录、注册、JWT、电子邮件验证等样板代码。