前回はBlenderの操作自動化でしたが、今回はClaude+MCPで、公式サイトで公開されているQuickstartを試しました。
指定したURLにアクセスして、最新の情報を取得して回答する仕組みです。
- 1. Model Context Protocol (MCP) の公式サイトにはQuickstartが公開されている
- 2. Quickstartを実行し、アメリカの天気を確認する仕組みを追加する
- 3. Quickstartで公開している仕組みでは、どのような処理が行われているか
- 4. おわりに
使用した環境
・Mac OS 14.7
・Node.js 22.14
・Claude Desktop 0.92
1. Model Context Protocol (MCP) の公式サイトにはQuickstartが公開されている
Quickstartでは、MCPの"Server" (自分のPC環境で動作し、ClaudeなどのLLMに情報を伝える仲介役) を自分で作ることで、Claude単体ではできないことを実現する例が書かれています。
たとえば、Claude単体ではインターネットにアクセスできず、あらかじめ学習したモデルの範囲でしか回答できません。そのため、たとえば「2025/4/12の天気を教えて」という質問には答えられません。
しかし、天気を公開しているサイトにアクセスし、天気情報を取得する処理を書いておくことで、先ほどの質問に回答できるようになります。
Quickstartでは、この手順が紹介されています。
2. Quickstartを実行し、アメリカの天気を確認する仕組みを追加する
この手順に沿って進めることになります。
For Server Developers - Model Context Protocol
Python, TypeScript(Node), Java, Kotlin, C# に対応しています。今回はTypeScriptを試してみます。
Node.jsをインストールしていない場合、こちらからLTS版をダウンロードして、インストールします。
MacOSを使っていて、zsh command not found のようなエラーが出る場合、例えば下記の記事を参考にしてパスを通します。
あとはチュートリアル通りに進めます。チュートリアルに記載がない細かい操作などを追記しました。
"▶︎"がついている箇所は、クリックすると詳細を確認できます。
▶︎ファイルの作成、環境構築
# 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", "bin": { "weather": "./build/index.js" }, "scripts": { "build": "tsc && chmod 755 build/index.js" }, "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"] }
次はindex.tsです。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",
capabilities: {
resources: {},
tools: {},
},
});
// 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.tool(
"get-alerts",
"Get weather alerts for a state",
{
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.tool(
"get-forecast",
"Get weather forecast for a location",
{
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,
},
],
};
},
);
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);
});
ここまでできたら、src/index.tsをコンパイルして、build/index.jsファイルを生成します。トップディレクトリで以下のコマンドを実行します。
$ tsc --outDir build/
これで、buildフォルダが新規生成され、その中にindex.jsが生成されます。
次に、MCP Serverとなるindex.jsを実行してサーバを開始します。トップディレクトリで以下のコマンドを実行します。
$ node ./src/index.js Weather MCP Server running on stdio
これでサーバが応答待ちになりました。
次はClaude Desktopにこのサーバを登録します。前回と同様にClaude Desktopの設定 > 開発者 > 構成を編集を選択して、claude_desktop_config.jsonのパスを確認、その上でclaude_desktop_config.jsonを直接編集します。
私はこのように書きました。Blender MCPの内容が残っているので2つ書いています。
{ "mcpServers": { "blender": { "command": "uvx", "args": [ "blender-mcp" ] }, "weather": { "command": "node", "args": [ "/Users/limes/Documents/MCP/WebSearch/build/index.js" ] } } }
これを書いてからClaude Desktopを再起動して、チャット画面のハンマーマークを選択するとindex.tsで書いた"get-alerts", "get-forecast"の2つが増えていることが確認できます。
get-forecastでは、"https://api.weather.gov"というサイトの情報を取得しています。これはアメリカの天気情報を渡すAPIです。そのため、このように問い合わせると、回答してくれます。サンタクララはアメリカの都市です。
一方、"https://api.weather.gov"はアメリカの天気情報のみ提供するため、このようにアメリカ以外の天気を聞いても回答できません。
より理解を深めるため、claude_desktop_config.jsonを再度書き直して、このように"weather"の記述を消しました。
{ "mcpServers": { "blender": { "command": "uvx", "args": [ "blender-mcp" ] } } }
Claude Desktopを再起動して、再度「今日のサンタクララの天気を教えて」と、同じ質問をしてみます。すると、回答が変わることが確認できます。
これにより、Claude単体は2024年10月以降の情報を持っておらず、ローカル環境に立てたMCPサーバなしでは外部のWebサイトにアクセスできないことがわかります。
3. Quickstartで公開している仕組みでは、どのような処理が行われているか
これも公式サイトに書いてあります。
意訳です。
あなたがClaudeに質問すると、以下の処理が行われます。
1. クライアントがあなたの質問をClaudeに送信します
2. Claudeは利用可能なツールを分析し、どのツールを使用するか決定します
3. クライアントはMCPサーバーを通じて選択されたツールを実行します
4. 結果がClaudeに送り返されます
5. Claudeは自然言語の応答を作成します
6. 応答があなたに表示されます!
Quickstartを実施してからこれを読むと、理解が深まります。Blender、weatherなど複数のツールを登録していても、「2. Claudeは利用可能なツールを分析し、どのツールを使用するか決定します」とあるように、何が使えるかはClaudeが判断しているようです。
また、MCPサーバを通じて情報を取得すると、それを元にして「5. Claudeは自然言語の応答を作成します」ということですね。
4. おわりに
MCPについては、チュートリアルを実施すると、何ができるかとか、どうすればやりたいことができるのか、などのイメージが付くように思いました。
今回は処理を自分でプログラミングする例でしたが、外部から情報を取得できる"MCP Server"についても公式サイトで公開されています。
Example Servers - Model Context Protocol
ここをみると、指定したコンテンツの内容を分析できるfetch、任意のWebコンテンツの情報を取得するBrave Search APIなどがあります。
次回はこの辺りを調べてみたいと思います。





