Nodejs开发人员MCP快速入门

开始构建您自己的服务器,供 Claude 桌面应用和其他客户端使用。 在本教程中,我们将构建一个简单的 MCP 天气服务器,并将其连接到主机 Claude 桌面应用。我们将从基本设置开始,然后逐渐过渡到更复杂的用例。

Nodejs开发人员MCP快速入门


开始构建您自己的服务器,供 Claude 桌面应用和其他客户端使用。

在本教程中,我们将构建一个简单的 MCP 天气服务器,并将其连接到主机 Claude 桌面应用。我们将从基本设置开始,然后逐渐过渡到更复杂的用例。

我们将要构建的内容

许多LLMs(包括 Claude)目前无法获取天气预报和严重天气警报。让我们使用 MCP 来解决这个问题!

我们将构建一个服务器,公开两个工具: get-alertsget-forecast 。然后我们将把服务器连接到一个 MCP 主机(在这种情况下是 Claude 桌面应用):

服务器可以连接到任何客户端。我们在这里选择了 Claude 桌面应用,但我们也有关于构建自己客户端的指南,以及其他客户端的列表。

为什么选择桌面版的 Claude 而不是 Claude.ai?

因为服务器是在本地运行的,MCP 目前仅支持桌面主机。远程主机正在积极开发中。

Core MCP Concepts  核心 MCP 概念

MCP 服务器可以提供三种主要类型的功能:

  1. 资源:客户端可以读取的类文件数据(如 API 响应或文件内容)
  2. 工具:可以由LLM(经用户批准)调用的功能
  3. 提示:预先编写的模板,可帮助用户完成特定任务

本教程将主要关注工具。

让我们开始构建我们的天气服务器!您可以在这里找到我们将要构建的完整代码。

预备知识(Nodejs方向)

这个快速入门假设您熟悉:

  • TypeScript
  • LLMs 像 Claude

系统要求

对于 TypeScript,请确保已安装最新版本的 Node。

node --version
npm --version

对于本教程,您需要 Node.js 版本 16 或更高版本。

现在,让我们创建并设置我们的项目:

# Create a new directory for our project
mkdir weather
cd weather

# Initialize a new npm project
npm init -y

# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript

# Create our files
mkdir src
touch src/index.ts

更新您的 package.json 文件,添加 type: "module" 和一个构建脚本:

{
  "type": "module",
  "bin": {
    "weather": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
  },
  "files": [
    "build"
  ],
}

在您的项目根目录中创建一个 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

现在让我们开始构建您的服务器。

构建您的服务器

导入包

将这些内容添加到您的 src/index.ts 的顶部:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

设置实例

然后初始化 NWS API 基本 URL、验证模式和服务器实例:

const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";

// Define Zod schemas for validation
const AlertsArgumentsSchema = z.object({
  state: z.string().length(2),
});

const ForecastArgumentsSchema = z.object({
  latitude: z.number().min(-90).max(90),
  longitude: z.number().min(-180).max(180),
});

// Create server instance
const server = new Server(
  {
    name: "weather",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

实现工具列表

我们需要告诉客户有哪些工具可用。这个 server.setRequestHandler 调用将为我们注册这个列表:

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get-alerts",
        description: "Get weather alerts for a state",
        inputSchema: {
          type: "object",
          properties: {
            state: {
              type: "string",
              description: "Two-letter state code (e.g. CA, NY)",
            },
          },
          required: ["state"],
        },
      },
      {
        name: "get-forecast",
        description: "Get weather forecast for a location",
        inputSchema: {
          type: "object",
          properties: {
            latitude: {
              type: "number",
              description: "Latitude of the location",
            },
            longitude: {
              type: "number",
              description: "Longitude of the location",
            },
          },
          required: ["latitude", "longitude"],
        },
      },
    ],
  };
});

这定义了我们的两个工具: get-alertsget-forecast

辅助函数

接下来,让我们添加用于从国家气象局 API 查询和格式化数据的辅助函数:

// Helper function for making NWS API requests
async function makeNWSRequest<T>(url: string): Promise<T | null> {
  const headers = {
    "User-Agent": USER_AGENT,
    Accept: "application/geo+json",
  };

  try {
    const response = await fetch(url, { headers });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return (await response.json()) as T;
  } catch (error) {
    console.error("Error making NWS request:", error);
    return null;
  }
}

interface AlertFeature {
  properties: {
    event?: string;
    areaDesc?: string;
    severity?: string;
    status?: string;
    headline?: string;
  };
}

// Format alert data
function formatAlert(feature: AlertFeature): string {
  const props = feature.properties;
  return [
    `Event: ${props.event || "Unknown"}`,
    `Area: ${props.areaDesc || "Unknown"}`,
    `Severity: ${props.severity || "Unknown"}`,
    `Status: ${props.status || "Unknown"}`,
    `Headline: ${props.headline || "No headline"}`,
    "---",
  ].join("\n");
}

interface ForecastPeriod {
  name?: string;
  temperature?: number;
  temperatureUnit?: string;
  windSpeed?: string;
  windDirection?: string;
  shortForecast?: string;
}

interface AlertsResponse {
  features: AlertFeature[];
}

interface PointsResponse {
  properties: {
    forecast?: string;
  };
}

interface ForecastResponse {
  properties: {
    periods: ForecastPeriod[];
  };
}

实现工具执行

工具执行处理程序负责实际执行每个工具的逻辑。让我们添加它:

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === "get-alerts") {
      const { state } = AlertsArgumentsSchema.parse(args);
      const stateCode = state.toUpperCase();

      const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
      const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);

      if (!alertsData) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to retrieve alerts data",
            },
          ],
        };
      }

      const features = alertsData.features || [];
      if (features.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: `No active alerts for ${stateCode}`,
            },
          ],
        };
      }

      const formattedAlerts = features.map(formatAlert).slice(0, 20) // only take the first 20 alerts;
      const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join(
        "\n"
      )}`;

      return {
        content: [
          {
            type: "text",
            text: alertsText,
          },
        ],
      };
    } else if (name === "get-forecast") {
      const { latitude, longitude } = ForecastArgumentsSchema.parse(args);

      // Get grid point data
      const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(
        4
      )},${longitude.toFixed(4)}`;
      const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);

      if (!pointsData) {
        return {
          content: [
            {
              type: "text",
              text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
            },
          ],
        };
      }

      const forecastUrl = pointsData.properties?.forecast;
      if (!forecastUrl) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to get forecast URL from grid point data",
            },
          ],
        };
      }

      // Get forecast data
      const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
      if (!forecastData) {
        return {
          content: [
            {
              type: "text",
              text: "Failed to retrieve forecast data",
            },
          ],
        };
      }

      const periods = forecastData.properties?.periods || [];
      if (periods.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: "No forecast periods available",
            },
          ],
        };
      }

      // Format forecast periods
      const formattedForecast = periods.map((period: ForecastPeriod) =>
        [
          `${period.name || "Unknown"}:`,
          `Temperature: ${period.temperature || "Unknown"}°${
            period.temperatureUnit || "F"
          }`,
          `Wind: ${period.windSpeed || "Unknown"} ${
            period.windDirection || ""
          }`,
          `${period.shortForecast || "No forecast available"}`,
          "---",
        ].join("\n")
      );

      const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join(
        "\n"
      )}`;

      return {
        content: [
          {
            type: "text",
            text: forecastText,
          },
        ],
      };
    } else {
      throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      throw new Error(
        `Invalid arguments: ${error.errors
          .map((e) => `${e.path.join(".")}: ${e.message}`)
          .join(", ")}`
      );
    }
    throw error;
  }
});

运行服务器

最后,实现主函数来运行服务器:

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Weather MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

确保运行 npm run build 来构建您的服务器!这是让您的服务器连接的非常重要的步骤。

让我们现在从现有的 MCP 主机 Claude 桌面应用测试您的服务器。

使用 Claude 桌面应用测试您的服务器

Claude 桌面应用尚未在 Linux 上提供。Linux 用户可以继续查看“构建客户端教程”,构建一个连接到我们刚刚构建的服务器的 MCP 客户端。

首先,请确保您已安装了桌面版的 Claude。您可以在这里安装最新版本。如果您已经安装了桌面版的 Claude,请确保它已更新到最新版本。

我们需要为您想要使用的任何 MCP 服务器配置 Claude 桌面应用。要做到这一点,请在文本编辑器中打开您的 Claude 桌面应用程序配置 ~/Library/Application Support/Claude/claude_desktop_config.json 。确保在文件不存在时创建该文件。

例如,如果您已安装了 VS Code:

code ~/Library/Application\ Support/Claude/claude_desktop_config.json

然后在 mcpServers 键中添加您的服务器。只有在至少一个服务器正确配置时,MCP UI 元素才会显示在 Claude 桌面应用中。

在这种情况下,我们将像这样添加我们的单个天气服务器:

{
    "mcpServers": {
        "weather": {
            "command": "node",
            "args": [
                "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"
            ]
        }
    }
}

这告诉 Claude 桌面应用

  1. 有一个名为“weather”的 MCP 服务器
  2. 通过运行 node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js 来启动它

保存文件,并重新启动 Claude 桌面应用。

Test with commands  测试命令

让我们确保 Claude 桌面应用正在获取我们在 weather 服务器中暴露的两个工具。您可以通过查找锤子

图标来完成此操作。

点击锤子图标后,您应该看到列出了两个工具:

如果您的服务器未被 Claude 桌面应用检测到,请继续前往故障排除部分获取调试提示。

如果锤子图标已显示出来,您现在可以通过在 Claude 桌面应用中运行以下命令来测试您的服务器:

  • 萨克拉门托的天气如何?
  • 德克萨斯州有哪些活跃的天气警报?
由于这是美国国家气象局,查询仅适用于美国地点。

内部到底发生了什么

当你提出一个问题:

  1. 客户将您的问题发送给 Claude
  2. Claude 分析可用工具并决定使用哪一个
  3. 客户端通过 MCP 服务器执行所选的工具
  4. 结果将发送回 Claude
  5. Claude 制定了一个自然语言回复
  6. 显示响应!

故障排除

Claude 桌面集成问题:

从 Claude 获取桌面日志

Claude.app 与 MCP 相关的日志记录写入到日志文件中 ~/Library/Logs/Claude

  • mcp.log 将包含有关 MCP 连接和连接失败的一般日志记录。
  • 文件名为 mcp-server-SERVERNAME.log 的文件将包含来自命名服务器的错误(stderr)日志记录。

您可以运行以下命令来列出最近的日志并跟踪任何新日志:

# Check Claude's logs for errors
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log

服务器未显示在 Claude 中

  1. 检查您的 claude_desktop_config.json 文件语法
  2. 确保项目路径是绝对路径,而不是相对路径
  3. 完全重新启动 Claude 桌面

工具调用失败时静默处理

如果 Claude 尝试使用工具但失败:

  1. 检查 Claude 的日志以查找错误
  2. 验证您的服务器构建并运行无错误
  3. 尝试重新启动 Claude 桌面应用

这一切都不起作用。我该怎么办?

请参考我们的调试指南,获取更好的调试工具和更详细的指导。

天气 API 问题

错误:无法检索网格点数据

这通常意味着:

  1. 定位位于美国之外
  2. NWS API 存在问题
  3. 您正在受到速率限制

修复:

  • 验证您是否使用的是美国坐标
  • 在请求之间添加一个小延迟
  • 检查 NWS API 状态页面

错误:STATE 没有活动警报

这不是错误 - 这只是表示该州目前没有天气警报。尝试选择其他州,或在恶劣天气期间检查。

对于更高级的故障排除,请查看我们关于调试 MCP 的指南