MCP サーバー で外部 API を安全に呼ぶ:Tool 定義 → スキーマ検証 → エラー処理まで実装手順
MCP(Model Context Protocol)サーバーを活用すれば、AI エージェントから外部 API を安全に呼び出すことが可能です。しかし、実際に運用レベルで動かすには Tool 定義、スキーマ検証、エラーハンドリングなど、考慮すべきポイントがいくつもあります。
本記事では、MCP サーバーにおいて外部 API を呼び出す際の設計・実装の流れを、初心者の方でも理解できるように段階的に解説していきます。TypeScript と Node.js を使った具体例を交え、実践的な知識を身につけられるでしょう。
背景
MCP サーバーとは
MCP(Model Context Protocol)は、AI モデルと外部ツールを連携させるためのプロトコル仕様です。Claude などの LLM(大規模言語モデル)が、外部のデータソースや API にアクセスする際、標準化されたインターフェースを介してやり取りを行います。
MCP サーバーは、このプロトコルを実装したサーバーアプリケーションであり、以下の役割を担います。
- AI エージェントからのリクエストを受け取る
- 外部 API やデータベースへアクセスする
- 取得したデータを AI エージェントへ返す
なぜ外部 API を MCP サーバー経由で呼ぶのか
AI エージェントが直接外部 API を呼ぶのではなく、MCP サーバーを経由することで、以下のメリットが得られます。
| # | メリット | 説明 |
|---|---|---|
| 1 | セキュリティ強化 | API キーやトークンを AI エージェント側に渡さず、サーバー側で管理できる |
| 2 | 入力検証の集約 | スキーマ検証やバリデーションをサーバー側で一元化できる |
| 3 | エラーハンドリングの統一 | 外部 API のエラーを適切に変換し、AI エージェントへ分かりやすく返せる |
| 4 | レート制限・リトライ制御 | API 呼び出しの制御ロジックをサーバー側で実装できる |
このように、MCP サーバーを中継層として配置することで、安全性・保守性・拡張性が向上します。
課題
MCP サーバーで外部 API を呼び出す際には、以下のような課題が発生します。
1. Tool 定義の複雑さ
MCP サーバーでは、AI エージェントが利用できる「ツール(Tool)」を定義する必要があります。この Tool 定義には以下の情報が必要です。
- ツール名
- 説明文(AI エージェントが理解できる形式)
- 入力パラメータのスキーマ(JSON Schema 形式)
- 出力形式
Tool 定義が不適切だと、AI エージェントが正しくツールを呼び出せません。
2. スキーマ検証の不足
AI エージェントから送られてくるパラメータが、期待する形式と異なる場合があります。たとえば以下のようなケースです。
- 必須パラメータが欠けている
- 数値型のはずが文字列で送られてくる
- 許可されていない追加プロパティが含まれている
これらを実行前に検証しないと、外部 API 呼び出し時にエラーが発生し、原因の特定が難しくなります。
3. エラー処理の不統一
外部 API は様々なエラーを返します。
- HTTP ステータスコード:
400 Bad Request、401 Unauthorized、500 Internal Server Errorなど - API 固有エラー: レート制限、タイムアウト、データ不正など
これらを適切にハンドリングし、AI エージェントへ分かりやすいエラーメッセージを返す仕組みが必要です。
以下の図は、これらの課題がどのフェーズで発生するかを示しています。
mermaidflowchart TD
agent["AI エージェント"] -->|"Tool 呼び出し"| mcp["MCP サーバー"]
mcp -->|"1. Tool 定義の複雑さ"| tool_def["Tool 定義<br/>不適切な定義"]
mcp -->|"2. スキーマ検証の不足"| validation["入力検証<br/>不正パラメータ"]
mcp -->|"3. エラー処理の不統一"| api_call["外部 API 呼び出し<br/>多様なエラー"]
api_call -->|"エラー"| error_handling["エラーハンドリング<br/>不統一な返却"]
error_handling -->|"分かりにくいエラー"| agent
図で理解できる要点
- MCP サーバーは AI エージェントと外部 API の中継役を担う
- Tool 定義、入力検証、エラー処理の各段階で課題が発生しやすい
- 各課題を解決することで、安全で信頼性の高い API 呼び出しを実現できる
解決策
上記の課題を解決するため、以下の3 つのステップで実装を進めます。
ステップ 1: Tool 定義の設計
ステップ 2: スキーマ検証の実装
ステップ 3: エラー処理の統一
それぞれを詳しく見ていきましょう。
ステップ 1: Tool 定義の設計
Tool 定義とは
MCP プロトコルでは、サーバーが提供する機能を「Tool」として定義します。Tool は以下の要素で構成されます。
| # | 要素 | 説明 |
|---|---|---|
| 1 | name | ツールの一意な識別子(例: fetch_weather) |
| 2 | description | AI エージェントが理解できる説明文 |
| 3 | inputSchema | 入力パラメータの JSON Schema |
Tool 定義の例
以下は、天気情報を取得する Tool の定義例です。
typescript// Tool 定義の型
interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, unknown>;
required?: string[];
};
}
Tool 定義の型を定義します。inputSchema は JSON Schema 形式でパラメータを記述します。
typescript// 天気情報取得 Tool の定義
const weatherTool: ToolDefinition = {
name: 'fetch_weather',
description:
'指定された都市の現在の天気情報を取得します。都市名は英語で指定してください。',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: '都市名(例: Tokyo, London)',
},
units: {
type: 'string',
enum: ['metric', 'imperial'],
description:
'温度の単位(metric: 摂氏、imperial: 華氏)',
},
},
required: ['city'],
},
};
実際の Tool 定義では、city を必須パラメータとし、units はオプションで指定できるようにしています。description は AI エージェントがツールの使い方を理解するための重要な情報です。
Tool 定義のポイント
Tool 定義で押さえておくべきポイントは以下の通りです。
- 明確な description: AI エージェントがツールの目的と使い方を正しく理解できるように記述する
- JSON Schema による型定義: パラメータの型、必須/任意、制約(enum、pattern など)を明示する
- required フィールド: 必須パラメータを明示し、AI エージェントが適切な引数を渡せるようにする
ステップ 2: スキーマ検証の実装
なぜスキーマ検証が必要か
AI エージェントから送られてくるパラメータは、必ずしも Tool 定義に従っているとは限りません。以下のようなケースが考えられます。
- 必須パラメータが欠けている
- 型が一致しない(文字列のはずが数値など)
- 許可されていない値が渡される(enum で定義した値以外など)
これらを実行前に検証することで、外部 API 呼び出し前にエラーを検出し、適切なエラーメッセージを返せます。
スキーマ検証ライブラリの選定
TypeScript では、JSON Schema によるバリデーションを行うライブラリがいくつかあります。
| # | ライブラリ | 特徴 |
|---|---|---|
| 1 | Ajv | 高速で JSON Schema 標準に完全準拠 |
| 2 | Zod | TypeScript との親和性が高く、型推論が強力 |
| 3 | Yup | React フォームバリデーションで人気 |
本記事では、Ajv を使用します。Ajv は JSON Schema に完全対応しており、MCP の Tool 定義との相性が良いためです。
Ajv のインストール
Yarn を使って Ajv をインストールします。
bashyarn add ajv ajv-formats
ajv-formats は、日付やメールアドレスなどの形式バリデーションを追加するパッケージです。
スキーマ検証の実装
以下のコードで、Tool 定義に基づいた入力パラメータの検証を行います。
typescriptimport Ajv from 'ajv';
import addFormats from 'ajv-formats';
// Ajv インスタンスの作成
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
allErrors: true を設定することで、すべてのバリデーションエラーを取得できます。これにより、AI エージェントへ詳細なエラー情報を返せます。
typescript// スキーマ検証関数
function validateInput(
schema: ToolDefinition['inputSchema'],
input: unknown
): { valid: boolean; errors?: string[] } {
const validate = ajv.compile(schema);
const valid = validate(input);
if (!valid && validate.errors) {
const errors = validate.errors.map(
(err) => `${err.instancePath} ${err.message}`
);
return { valid: false, errors };
}
return { valid: true };
}
ajv.compile でスキーマをコンパイルし、validate(input) で検証を実行します。エラーがある場合は、人間が読みやすい形式に変換して返します。
スキーマ検証の使用例
実際に Tool 呼び出し時にスキーマ検証を行う例です。
typescript// Tool 呼び出しハンドラ
function handleToolCall(toolName: string, input: unknown) {
// Tool 定義の取得(ここでは weatherTool を使用)
const tool = weatherTool;
// スキーマ検証
const validation = validateInput(tool.inputSchema, input);
if (!validation.valid) {
return {
success: false,
error: `入力パラメータが不正です: ${validation.errors?.join(
', '
)}`,
};
}
// 検証が通った場合、外部 API を呼び出す
return callExternalAPI(input);
}
スキーマ検証を通過しない場合は、外部 API を呼び出さずにエラーを返します。これにより、不正なリクエストによる API コールを防げます。
スキーマ検証のフロー図
以下の図は、スキーマ検証のフローを示しています。
mermaidflowchart TD
start["AI エージェントからのリクエスト"] --> validate["スキーマ検証"]
validate -->|"検証成功"| api["外部 API 呼び出し"]
validate -->|"検証失敗"| error_response["エラーレスポンス返却"]
api --> response["成功レスポンス返却"]
error_response --> done["完了"]
response --> done
図で理解できる要点
- スキーマ検証は外部 API 呼び出し前に行う
- 検証失敗時は即座にエラーを返し、API コストを削減できる
- 検証成功時のみ外部 API を呼び出す
ステップ 3: エラー処理の統一
外部 API のエラーパターン
外部 API を呼び出す際、様々なエラーが発生する可能性があります。
| # | エラータイプ | 例 |
|---|---|---|
| 1 | ネットワークエラー | タイムアウト、接続失敗 |
| 2 | HTTP エラー | 400 Bad Request、401 Unauthorized、500 Internal Server Error |
| 3 | API 固有エラー | レート制限超過、データ不正 |
| 4 | パースエラー | レスポンスが JSON でない、形式が不正 |
これらを適切にハンドリングし、AI エージェントへ分かりやすいエラーメッセージを返す必要があります。
エラーハンドリングの基本方針
以下の方針でエラー処理を実装します。
- エラー種別の分類: ネットワーク、HTTP、API、パースの 4 種類に分類
- 統一エラー形式: AI エージェントへ返すエラー形式を統一
- ログ記録: デバッグ用に詳細なエラー情報をログに記録
- リトライ制御: 一時的なエラーの場合はリトライを試みる
統一エラー型の定義
まず、エラー情報を表す型を定義します。
typescript// エラーレスポンスの型定義
interface ErrorResponse {
success: false;
errorType:
| 'network'
| 'http'
| 'api'
| 'parse'
| 'validation';
errorCode?: string;
message: string;
details?: unknown;
}
errorType でエラーの種類を分類し、errorCode で具体的なエラーコードを記録します。details には元のエラー情報を含めることができます。
外部 API 呼び出し関数の実装
以下は、天気 API を呼び出す関数の例です。
typescriptimport axios, { AxiosError } from 'axios';
// axios のインストール(事前に yarn add axios を実行)
API 呼び出しには axios ライブラリを使用します。エラーハンドリングが容易で、TypeScript との相性も良好です。
typescript// 天気 API の呼び出し
async function fetchWeather(
city: string,
units: string = 'metric'
) {
try {
const response = await axios.get(
'https://api.openweathermap.org/data/2.5/weather',
{
params: {
q: city,
units: units,
appid: process.env.OPENWEATHER_API_KEY,
},
timeout: 5000, // 5秒でタイムアウト
}
);
return {
success: true,
data: response.data,
};
} catch (error) {
return handleAPIError(error);
}
}
axios.get で API を呼び出し、エラーが発生した場合は専用のエラーハンドラで処理します。timeout を設定することで、長時間の待機を防ぎます。
エラーハンドラの実装
次に、エラーを分類して適切なエラーレスポンスを返す関数を実装します。
typescript// エラーハンドリング関数
function handleAPIError(error: unknown): ErrorResponse {
// Axios エラーの場合
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
// ネットワークエラー(タイムアウト、接続失敗など)
if (!axiosError.response) {
return {
success: false,
errorType: 'network',
errorCode: axiosError.code,
message: `ネットワークエラーが発生しました: ${axiosError.message}`,
};
}
// HTTP エラー
const status = axiosError.response.status;
return {
success: false,
errorType: 'http',
errorCode: `HTTP_${status}`,
message: `HTTP エラー ${status}: ${getHTTPErrorMessage(
status
)}`,
details: axiosError.response.data,
};
}
// その他のエラー
return {
success: false,
errorType: 'api',
message: '予期しないエラーが発生しました',
details: error,
};
}
axios.isAxiosError で Axios 特有のエラーかを判定します。response が存在しない場合はネットワークエラー、存在する場合は HTTP エラーとして処理します。
typescript// HTTP ステータスコードに応じたメッセージ
function getHTTPErrorMessage(status: number): string {
switch (status) {
case 400:
return 'リクエストが不正です';
case 401:
return '認証が必要です';
case 403:
return 'アクセスが拒否されました';
case 404:
return 'リソースが見つかりません';
case 429:
return 'API リクエスト制限を超過しました';
case 500:
return 'サーバー内部エラーが発生しました';
case 503:
return 'サービスが一時的に利用できません';
default:
return 'エラーが発生しました';
}
}
HTTP ステータスコードごとに、AI エージェントが理解しやすいメッセージを返します。
リトライ処理の追加
一時的なエラー(タイムアウト、503 など)の場合、リトライを試みることで成功率を向上させられます。
typescript// リトライ処理を含む API 呼び出し
async function fetchWeatherWithRetry(
city: string,
units: string = 'metric',
maxRetries: number = 3
) {
let lastError: ErrorResponse | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await fetchWeather(city, units);
if (result.success) {
return result;
}
lastError = result as ErrorResponse;
// リトライ対象のエラーかチェック
if (shouldRetry(lastError)) {
console.log(
`リトライ ${attempt}/${maxRetries}: ${lastError.message}`
);
await sleep(1000 * attempt); // 指数バックオフ
continue;
}
// リトライ不要なエラーの場合は即座に返す
return result;
}
return lastError!;
}
最大 3 回までリトライを試み、指数バックオフ(待機時間を徐々に増やす)でサーバー負荷を軽減します。
typescript// リトライすべきエラーか判定
function shouldRetry(error: ErrorResponse): boolean {
// ネットワークエラーはリトライ
if (error.errorType === 'network') {
return true;
}
// 503(Service Unavailable)はリトライ
if (error.errorCode === 'HTTP_503') {
return true;
}
// 429(Rate Limit)は少し待ってリトライ
if (error.errorCode === 'HTTP_429') {
return true;
}
return false;
}
// スリープ関数
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
ネットワークエラーや一時的なサーバーエラーの場合のみリトライを行い、認証エラーなど恒久的なエラーは即座に返します。
エラー処理フロー図
以下の図は、エラーハンドリングとリトライのフローを示しています。
mermaidflowchart TD
startNode["API 呼び出し"];
callNode["HTTP リクエスト送信"];
successQ{"成功?"};
successDone["成功レスポンス"];
classifyNode["エラー分類"];
networkQ{"ネットワークエラー?"};
retryCheck["リトライ可能か判定"];
httpErrQ{"HTTPエラー?"};
errorReturn["エラーレスポンス返却"];
retryQ{"リトライ回数内?"};
waitNode["待機(指数バックオフ)"];
doneNode["完了"];
startNode --> callNode;
callNode --> successQ;
successQ --|はい|--> successDone;
successQ --|いいえ|--> classifyNode;
classifyNode --> networkQ;
networkQ --|はい|--> retryCheck;
networkQ --|いいえ|--> httpErrQ;
httpErrQ --|503/429|--> retryCheck;
httpErrQ --|その他|--> errorReturn;
retryCheck --> retryQ;
retryQ --|はい|--> waitNode;
retryQ --|いいえ|--> errorReturn;
waitNode --> callNode;
successDone --> doneNode;
errorReturn --> doneNode;
図で理解できる要点
- エラーを種類ごとに分類し、適切な処理を行う
- リトライ可能なエラーは自動的に再試行する
- 恒久的なエラーは即座に AI エージェントへ返す
具体例
ここまでの内容を統合し、実際に動作する MCP サーバーの実装例を示します。
プロジェクト構成
以下のようなディレクトリ構成でプロジェクトを作成します。
bashmcp-weather-server/
├── package.json
├── tsconfig.json
├── .env
└── src/
├── index.ts # エントリーポイント
├── tools.ts # Tool 定義
├── validator.ts # スキーマ検証
├── weatherAPI.ts # 外部 API 呼び出し
└── errorHandler.ts # エラーハンドリング
package.json
プロジェクトの依存関係を定義します。
json{
"name": "mcp-weather-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc"
},
"dependencies": {
"axios": "^1.6.2",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"dotenv": "^16.3.1"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"tsx": "^4.7.0"
}
}
tsx は TypeScript を直接実行できる開発用ツールです。
tsconfig.json
TypeScript のコンパイル設定を行います。
json{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}
.env
環境変数として API キーを設定します(実際の値は各自取得してください)。
iniOPENWEATHER_API_KEY=your_api_key_here
OpenWeatherMap の API キーは公式サイトで無料取得できます。
src/tools.ts
Tool 定義を管理するファイルです。
typescript// Tool 定義の型
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, unknown>;
required?: string[];
};
}
typescript// 天気情報取得 Tool
export const weatherTool: ToolDefinition = {
name: 'fetch_weather',
description:
'指定された都市の現在の天気情報を取得します。都市名は英語で指定してください。',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description:
'都市名(例: Tokyo, London, New York)',
},
units: {
type: 'string',
enum: ['metric', 'imperial'],
description:
'温度の単位(metric: 摂氏、imperial: 華氏)',
default: 'metric',
},
},
required: ['city'],
},
};
typescript// すべての Tool を配列で管理
export const tools: ToolDefinition[] = [weatherTool];
src/validator.ts
スキーマ検証を行うモジュールです。
typescriptimport Ajv from 'ajv';
import addFormats from 'ajv-formats';
import type { ToolDefinition } from './tools.js';
// Ajv インスタンスの作成
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);
typescript// スキーマ検証結果の型
export interface ValidationResult {
valid: boolean;
errors?: string[];
}
typescript// スキーマ検証関数
export function validateInput(
schema: ToolDefinition['inputSchema'],
input: unknown
): ValidationResult {
const validate = ajv.compile(schema);
const valid = validate(input);
if (!valid && validate.errors) {
const errors = validate.errors.map(
(err) =>
`${err.instancePath || 'root'} ${err.message}`
);
return { valid: false, errors };
}
return { valid: true };
}
src/errorHandler.ts
エラーハンドリングを行うモジュールです。
typescriptimport axios, { AxiosError } from 'axios';
// エラーレスポンスの型
export interface ErrorResponse {
success: false;
errorType:
| 'network'
| 'http'
| 'api'
| 'parse'
| 'validation';
errorCode?: string;
message: string;
details?: unknown;
}
typescript// エラーハンドラ
export function handleAPIError(
error: unknown
): ErrorResponse {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
// ネットワークエラー
if (!axiosError.response) {
return {
success: false,
errorType: 'network',
errorCode: axiosError.code,
message: `ネットワークエラー: ${axiosError.message}`,
};
}
// HTTP エラー
const status = axiosError.response.status;
return {
success: false,
errorType: 'http',
errorCode: `HTTP_${status}`,
message: `HTTP ${status}: ${getHTTPErrorMessage(
status
)}`,
details: axiosError.response.data,
};
}
return {
success: false,
errorType: 'api',
message: '予期しないエラーが発生しました',
details: error,
};
}
typescript// HTTP ステータスメッセージ
function getHTTPErrorMessage(status: number): string {
const messages: Record<number, string> = {
400: 'リクエストが不正です',
401: '認証が必要です(API キーを確認してください)',
403: 'アクセスが拒否されました',
404: '指定された都市が見つかりません',
429: 'API リクエスト制限を超過しました',
500: 'サーバー内部エラー',
503: 'サービスが一時的に利用できません',
};
return messages[status] || 'エラーが発生しました';
}
typescript// リトライ判定
export function shouldRetry(error: ErrorResponse): boolean {
return (
error.errorType === 'network' ||
error.errorCode === 'HTTP_503' ||
error.errorCode === 'HTTP_429'
);
}
typescript// スリープ関数
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
src/weatherAPI.ts
外部 API 呼び出しを行うモジュールです。
typescriptimport axios from 'axios';
import {
handleAPIError,
shouldRetry,
sleep,
} from './errorHandler.js';
import type { ErrorResponse } from './errorHandler.js';
// 成功レスポンスの型
interface SuccessResponse {
success: true;
data: unknown;
}
type APIResponse = SuccessResponse | ErrorResponse;
typescript// 天気 API 呼び出し(単発)
async function fetchWeatherOnce(
city: string,
units: string
): Promise<APIResponse> {
try {
const response = await axios.get(
'https://api.openweathermap.org/data/2.5/weather',
{
params: {
q: city,
units: units,
appid: process.env.OPENWEATHER_API_KEY,
},
timeout: 5000,
}
);
return {
success: true,
data: response.data,
};
} catch (error) {
return handleAPIError(error);
}
}
typescript// 天気 API 呼び出し(リトライ付き)
export async function fetchWeather(
city: string,
units: string = 'metric',
maxRetries: number = 3
): Promise<APIResponse> {
let lastError: ErrorResponse | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await fetchWeatherOnce(city, units);
if (result.success) {
return result;
}
lastError = result;
if (shouldRetry(lastError) && attempt < maxRetries) {
console.log(
`リトライ ${attempt}/${maxRetries}: ${lastError.message}`
);
await sleep(1000 * attempt);
continue;
}
return result;
}
return lastError!;
}
src/index.ts
MCP サーバーのエントリーポイントです。
typescriptimport 'dotenv/config';
import { tools, weatherTool } from './tools.js';
import { validateInput } from './validator.js';
import { fetchWeather } from './weatherAPI.js';
// Tool 呼び出しハンドラ
async function handleToolCall(
toolName: string,
args: unknown
) {
// Tool 定義の取得
if (toolName !== 'fetch_weather') {
return {
success: false,
errorType: 'validation',
message: `未知の Tool: ${toolName}`,
};
}
// スキーマ検証
const validation = validateInput(
weatherTool.inputSchema,
args
);
if (!validation.valid) {
return {
success: false,
errorType: 'validation',
message: `入力パラメータエラー: ${validation.errors?.join(
', '
)}`,
};
}
// 型安全な引数の取得
const { city, units = 'metric' } = args as {
city: string;
units?: string;
};
// 外部 API 呼び出し
return await fetchWeather(city, units);
}
typescript// メイン処理(テスト用)
async function main() {
console.log('MCP Weather Server - Tool 一覧:');
tools.forEach((tool) => {
console.log(`- ${tool.name}: ${tool.description}`);
});
console.log('\n--- テスト 1: 正常なリクエスト ---');
const result1 = await handleToolCall('fetch_weather', {
city: 'Tokyo',
});
console.log(JSON.stringify(result1, null, 2));
console.log('\n--- テスト 2: パラメータエラー ---');
const result2 = await handleToolCall('fetch_weather', {});
console.log(JSON.stringify(result2, null, 2));
console.log('\n--- テスト 3: 存在しない都市 ---');
const result3 = await handleToolCall('fetch_weather', {
city: 'InvalidCity12345',
});
console.log(JSON.stringify(result3, null, 2));
}
main();
実行方法
以下のコマンドでプロジェクトをセットアップし、実行します。
bash# 依存パッケージのインストール
yarn install
bash# 開発サーバーの起動
yarn dev
実行すると、以下のような出力が得られます。
luaMCP Weather Server - Tool 一覧:
- fetch_weather: 指定された都市の現在の天気情報を取得します。都市名は英語で指定してください。
--- テスト 1: 正常なリクエスト ---
{
"success": true,
"data": {
"weather": [...],
"main": { "temp": 15.2, ... }
}
}
--- テスト 2: パラメータエラー ---
{
"success": false,
"errorType": "validation",
"message": "入力パラメータエラー: root must have required property 'city'"
}
--- テスト 3: 存在しない都市 ---
{
"success": false,
"errorType": "http",
"errorCode": "HTTP_404",
"message": "HTTP 404: 指定された都市が見つかりません"
}
実装のポイント
この実装例では、以下のポイントを押さえています。
- Tool 定義の分離:
tools.tsで Tool を一元管理 - スキーマ検証の徹底: Ajv による事前検証で不正なリクエストを防ぐ
- エラー処理の統一: すべてのエラーを
ErrorResponse型で返す - リトライ機能: 一時的なエラーに対する自動リトライ
- 型安全性: TypeScript の型システムを活用し、実行時エラーを削減
拡張案
この実装をベースに、以下のような拡張が可能です。
| # | 拡張内容 | 説明 |
|---|---|---|
| 1 | キャッシュ機能 | 同じリクエストの結果をキャッシュして API コールを削減 |
| 2 | レート制限 | クライアントごとの呼び出し回数を制限 |
| 3 | 複数 Tool 対応 | 天気以外の API にも対応できるよう Tool を追加 |
| 4 | ロギング強化 | 構造化ロギングでデバッグやモニタリングを容易に |
| 5 | テスト追加 | Jest などでユニットテスト・統合テストを実装 |
まとめ
本記事では、MCP サーバーで外部 API を安全に呼び出すための実装手順を解説しました。
重要ポイントの再確認
- Tool 定義: AI エージェントが理解できる形で、入力スキーマと説明を明確に定義する
- スキーマ検証: Ajv などのライブラリで事前検証を行い、不正なリクエストを防ぐ
- エラー処理: ネットワーク、HTTP、API エラーを分類し、統一形式で返す
- リトライ制御: 一時的なエラーには自動リトライを実装し、成功率を向上させる
セキュリティ上の注意点
MCP サーバーを運用する際は、以下のセキュリティ対策も忘れずに実施してください。
| # | 対策 | 説明 |
|---|---|---|
| 1 | API キーの保護 | 環境変数で管理し、コードにハードコードしない |
| 2 | レート制限 | 悪意あるリクエストによる過負荷を防ぐ |
| 3 | 入力サニタイゼーション | SQL インジェクションなどの攻撃を防ぐ |
| 4 | HTTPS 通信 | 外部 API との通信は必ず暗号化する |
次のステップ
MCP サーバーの基本が理解できたら、以下のステップへ進むことをおすすめします。
- 実際のプロダクション環境での運用設計
- モニタリング・アラート機能の追加
- 複数の Tool を管理するアーキテクチャの構築
- AI エージェントとの統合テスト
MCP サーバーを活用することで、AI エージェントの能力を大きく拡張できます。本記事の内容が、皆さんの開発の一助となれば幸いです。
関連リンク
articleMCP サーバー で外部 API を安全に呼ぶ:Tool 定義 → スキーマ検証 → エラー処理まで実装手順
articleMCP サーバー 設計ベストプラクティス:ツール定義、権限分離、スキーマ設計の要点まとめ
articleMCP サーバー セットアップ完全ガイド:インストール・環境変数・ポート/証明書設定の最短手順
articleMCP サーバー とは?Model Context Protocol の基礎・仕組み・活用メリットを徹底解説
articlePlaywright MCP で複数プロジェクトのテストを一元管理
articlePlaywright MCP 活用事例集:現場で効く業務効率化テクニック
articleShell Script とは?初心者が最短で理解する基本構文・実行モデル・活用領域
articleNode.js 本番メモリ運用:ヒープ/外部メモリ/リーク検知の継続監視
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleNext.js でインフィニットスクロールを実装:Route Handlers +`use` で滑らかデータ読込
articleDocker を用いた統一ローカル環境:新人オンボーディングを 1 日 → 1 時間へ
articleMermaid でデータ基盤のラインジを図解:ETL/DAG/品質チェックの全体像
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来