2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Claude] MCP server 構築のチュートリアルをやってみた

Posted at

はじめに

MCPの利用はしていますが、MCP Serverはまだ自作できていません。
今回は勉強のため、公式のMCPサーバー構築チュートリアルを実際にやってみたいと思います。

基本写経しているだけですが、同じ環境で躓いた、という人は参考にしてみてください。

前提条件

  • OS: macOS
  • 言語:TypeScript

何を作るか?

  • get_alerts: MCPサーバーに保存されているアラートを取得するエンドポイント
  • get_forecast: MCPサーバーに保存されている予測を取得するエンドポイント

前提知識

MCPサーバーのロギング

  • STDIO-based servers
    • stdoutは使ってはいけない
      • print(), console.log(), fmt.Println()など
  • HTTP-based servers
    • stdoutが使える

ベストプラクティス

  • stderrやログファイルへの出力はLoggingライブラリを使う
  • JSではconsole.log()がstdoutに出力されるため注意

環境準備

nodejsがインストールされていることを確認

% node --version
npm --version
v25.2.1
11.6.2
# 執筆時点の最新版を入れていますが、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@3
npm install -D @types/node typescript

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

package.jsonの編集

typeとscriptsを追加します。
(dependencies, devDependenciesは消さないように注意)

{
  "type": "module",
  "bin": {
    "weather": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js"
  },
  "files": ["build"]
}

tsconfig.jsonの作成

プロジェクトルートに 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"]
}

MCPサーバーの実装

./src/index.ts の編集

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

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

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

Helper関数の追加

以下のコードを ./src/index.ts に追加します。

// 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[];
  };
}

ツール実行ハンドラの追加

続いて、以下のコードも追加します。

// Register weather tools

server.registerTool(
  "get_alerts",
  {
    description: "Get weather alerts for a state",
    inputSchema: {
      state: z
        .string()
        .length(2)
        .describe("Two-letter state code (e.g. CA, NY)"),
    },
  },
  async ({ state }) => {
    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);
    const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;

    return {
      content: [
        {
          type: "text",
          text: alertsText,
        },
      ],
    };
  },
);

server.registerTool(
  "get_forecast",
  {
    description: "Get weather forecast for a location",
    inputSchema: {
      latitude: z
        .number()
        .min(-90)
        .max(90)
        .describe("Latitude of the location"),
      longitude: z
        .number()
        .min(-180)
        .max(180)
        .describe("Longitude of the location"),
    },
  },
  async ({ latitude, longitude }) => {
    // 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,
        },
      ],
    };
  },
);

main関数の追加

最後にサーバーを起動する main関数を追加します。

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);
});

起動してみる

ここまでで、MCPサーバーの実装自体は完了です。
ビルド&起動を試してみましょう。

npm run build
node build/index.js
# 以下が表示されればOK
# Weather MCP Server running on stdio

セットアップ

Claude for Desktop で実際に利用してみます。
本当はClaude Codeで利用したかったのですが、まだProを契約していないため利用できませんでした...

MCP Serverの設定追加

~/Library/Application\ Support/Claude/claude_desktop_config.json を作成します

{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["{Absolute path to parent folder}/weather/build/index.js"]
    }
  }
}

Claudeの再起動

Claude for Desktop を再起動すると「weather」の項目が追加されています。

image.png

image.png

Get forecast のお試し

シカゴの天気を聞いてみると、MCP Serverを使った結果が返ってきました。

APIが米国の天気しか対応していないため、日本の天気を聞くとWeb検索になります
https://api.weather.gov

image.png

Get alerts のお試し

同じく警報情報を聞くと、MCP Serverを使って結果が返ってきます。

image.png

終わりに

チュートリアルをなぞっただけではありますが、MCP Serverを実際に作って動かしてみました。
Tool の description をきちんと書いておけば、上手いこと使ってくれますね。
次はオリジナルのMCP Serverを作ってみたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?