こんにちは。GMOインターネット ネットワークソリューション事業本部 ソリューションパートナー事業部の横手です。2025年05月20日、Microsoft Build 2025 にて今後のWindowsでMCP(Model Context Protocol)がサポートされるというMicrosoftの発表がありました。すでにAnthropic、Figma、Perplexityらと協力して、MCPの機能を統合したWindows対応のAIアプリケーションを開発する動きも出ています。今後、顧客への直接的なサービス提供や、顧客向けサービスを作成するために、MCPと連携した開発の機会は増えていくと考えられます。そこで今回は、業務でそういった機会があった場合に迅速に対応できるよう、MCPとの連携機能の開発入門をまとめました。
はじめに
2025年以降、AIエージェントやAIアプリケーションの開発現場でMCP(Model Context Protocol)との連携が重要になってきます。
本記事では、MCPの概要と、TypeScriptでMCPサーバーを構築し、AIクライアント(今回はClaude Desktop)と連携するまでの流れを解説します。
MCPとは
MCPとはModel Context Protocolの略で、AIエージェントやアプリケーションと外部ツール・データ・プロンプトを連携するための通信プロトコルです。
MCPのアーキテクチャモデルはクライアント・サーバモデルを採用しており、MCPクライアント(例: VSCode, Claude Desktop)とMCPサーバー(各種AIツールやデータ連携サーバー)が連携します。
MCPサーバーは主に以下の3つの機能を提供します。
Prompts: AIに問い合わせるプロンプトの生成(テンプレート化可能)
Resources: AIが参照するファイルやデータの提供
Tools: AIに実行させるアクション・関数の定義
有名なMCPサーバーとしては Playwright MCP server (Microsoft公式) や GitHub's official MCP Server (GitHub公式) などがあり、CloudflareやAzureなど一部クラウドではMCPサーバーのホスティングにも対応し始めています。
今回作成するMCPアプリケーション
MCPサーバーをTypeScriptで作成し、Claude DesktopをMCPクライアントとして連携・検証します。
MCPクライアント
今回はClaude Desktopを利用します。
MCPサーバー
fastmcp (TypeScript製のMCPサーバー) を使い、以下2つのサンプルアプリケーションを構築します。
1. UUIDを返すだけの簡素なもの
2. 用意したCSVファイルに対して分析処理を行うもの
1. UUIDを返すだけの簡素なもの
fastmcpのREADME.mdの内容に従い、最小構成のMCPサーバーを作成します。
import { FastMCP } from "fastmcp";
import { randomUUID } from "node:crypto";
import { z } from "zod"; // Or any validation library that supports Standard Schema
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
});
server.addTool({
name: "generate-uuid",
description: "Generate a UUID",
parameters: z.object({}),
execute: async () => {
return randomUUID();
},
});
server.start({
transportType: "stdio",
});
Claude Desktopでの連携設定
公式手順: For Claude Desktop Users - Model Context Protocolを参考に、Claude Desktopの設定ファイルにMCPサーバーを登録します。
{
"mcpServers": {
"prac-mcp-typescript": {
"command": "npx",
"args": [
"tsx",
"/PATH/TO/YOUR_PROJECT/src/index.ts"
// "/PATH/TO/YOUR_PROJECT/src/index.mts" // ESMの場合
],
"env": {
}
}
}
}
Claude Desktopを使った確認
ClaudeクライアントでMCP利用スイッチをONにし、「UUIDを生成して」とプロンプトを入力すると、uuidが生成されることを確認できます。
2. CSVファイル分析AIアプリ
サンプルCSVとしてオープンデータ取組済自治体資料|デジタル庁の「市区町村」「都道府県」データを利用します。
ファイル名と名称の対応は以下list.csvの通りです。
"name","file"
"都道府県一覧","20250318_resources_opendata_lg_pref_list_02.csv"
"市区町村一覧","20250318_resources_opendata_lg_mani_list_03.csv"
CSVのデータ処理
CSVファイルは DuckDB (OLAP向けのデータベースシステム) を使って読み込み、SQLでデータ操作を行います。
ライブラリは duckdb-async (Node.js製ライブラリ) を利用します。
MCPサーバー
Prompts, Resources, Tools を連携させて分析を行うMCPサーバーを構築します。
import { FastMCP } from "fastmcp";
import { Database } from "duckdb-async";
import { z } from "zod"; // Or any validation library that supports Standard Schema
import * as fs from 'node:fs';
const PWD = "/path/to/repo"
// DuckDB接続の初期化
const db = await Database.create(
':memory:', // メモリ内DBまたはファイルパスを指定
);
// FastMCPサーバーの作成
const server = new FastMCP({
name: "CSV Data Analysis Server",
version: "1.0.0",
});
// 特定のCSVファイルをリソースとして追加
server.addResource({
uri: `file://${PWD}/data/list.csv`,
name: "利用ファイル一覧",
mimeType: "text/csv",
async load() {
// CSVファイルの内容を返す
return {
text: await fs.promises.readFile(`${PWD}/data/list.csv`, "utf-8"),
};
},
});
server.addTool({
name: "get-enable-file-list",
description: "利用ファイル一覧",
parameters: z.object({}),
execute: async (args, { log }) => {
// CSVファイルの内容を返す
return {
text: await fs.promises.readFile(`${PWD}/data/list.csv`, "utf-8"),
type: "text"
};
}
})
server.addTool({
name: "load-csv",
description: "CSVファイルをDuckDBに読み込む",
parameters: z.object({
filename: z.string().describe("CSVファイル名"),
tablename: z.string().describe("作成するテーブル名"),
}),
execute: async (args, { log }) => {
try {
log.info(`${args.filename}を読み込んでいます...`);
// CSVファイルをDuckDBテーブルに読み込む
await db.exec(`
CREATE TABLE IF NOT EXISTS ${args.tablename} AS
SELECT * FROM read_csv_auto('${PWD}/data/${args.filename}')
`);
// テーブル情報を取得
const result = await db.all(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = '${args.tablename}'
`);
log.info(`テーブル ${args.tablename} が作成されました`);
return `${args.tablename} テーブルが作成されました。\n列情報: ${JSON.stringify(result, null, 2)}`;
} catch (error) {
log.error(`CSVファイルの読み込みに失敗しました: ${error.message}`);
throw new Error(`CSVファイルの読み込みに失敗しました: ${error.message}`);
}
},
});
server.addTool({
name: "run-query",
description: "DuckDBでSQLクエリを実行する",
parameters: z.object({
query: z.string().describe("実行するSQLクエリ"),
}),
execute: async (args, { log, reportProgress }) => {
try {
log.info(`クエリを実行しています: ${args.query}`);
reportProgress({ progress: 0, total: 100 });
// クエリ実行
const result = await db.all(args.query);
reportProgress({ progress: 100, total: 100 });
log.info('クエリが完了しました');
// 結果を整形して返す
if (Array.isArray(result) && result.length > 0) {
// 結果がある場合はテーブル形式で返す
return `クエリ結果 (${result.length}行):\n${JSON.stringify(result, null, 2)}`;
}
// 結果がない場合
return "クエリは正常に実行されましたが、結果はありません。";
} catch (error) {
log.error(`クエリ実行エラー: ${error.message}`);
throw new Error(`クエリ実行エラー: ${error.message}`);
}
},
});
server.addTool({
name: "aggregate-data",
description: "テーブルのデータを集計する",
parameters: z.object({
tableName: z.string().describe("集計するテーブル名"),
groupByColumn: z.string().describe("グループ化するカラム名"),
aggregateColumn: z.string().describe("集計するカラム名"),
aggregateFunction: z.enum(["SUM", "AVG", "MIN", "MAX", "COUNT"]).describe("集計関数"),
}),
execute: async (args, { log }) => {
try {
log.info(`${args.tableName}のデータを集計しています...`);
const query = `
SELECT
${args.groupByColumn},
${args.aggregateFunction}(${args.aggregateColumn}) AS result
FROM ${args.tableName}
GROUP BY ${args.groupByColumn}
ORDER BY result DESC
`;
log.debug(`実行クエリ: ${query}`);
// クエリ実行
const result = await db.all(query);
return `${args.tableName}の${args.groupByColumn}ごとの${args.aggregateColumn}の${args.aggregateFunction}:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
log.error(`データ集計エラー: ${error.message}`);
throw new Error(`データ集計エラー: ${error.message}`);
}
},
});
server.addTool({
name: "get-table-info",
description: "テーブルの構造情報を取得する",
parameters: z.object({
tablename: z.string().describe("情報を取得するテーブル名"),
}),
execute: async (args, { log }) => {
try {
// テーブル構造の取得
const columns = await db.all(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = '${args.tablename}'
`);
// サンプルデータの取得
const sampleData = await db.all(`
SELECT * FROM ${args.tablename} LIMIT 5
`);
return {
content: [
{
type: "text",
text: `テーブル: ${args.tablename}\n\n列情報:\n${JSON.stringify(columns, null, 2)}\n\nサンプルデータ:\n${JSON.stringify(sampleData, null, 2)}`
}
]
};
} catch (error) {
log.error(`テーブル情報取得エラー: ${error.message}`);
throw new Error(`テーブル情報取得エラー: ${error.message}`);
}
},
});
server.addPrompt({
name: "analyze-data",
description: "データ分析のためのプロンプト",
arguments: [
{
name: "tableName",
description: "分析するテーブル名",
required: true,
},
{
name: "analysisGoal",
description: "分析の目的",
required: true,
},
],
load: async (args) => {
// テーブル構造の取得
const columns = await db.all(`
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = '${args.tableName}'
`);
return `
テーブル「${args.tableName}」のデータを分析し、${args.analysisGoal}を達成するためのSQLクエリを作成してください。
テーブル構造:
${JSON.stringify(columns, null, 2)}
分析目標: ${args.analysisGoal}
適切なSQLクエリを提案し、その結果の解釈方法を説明してください。
`;
},
});
// サーバー起動
await server.start({
transportType: "stdio", // CLIツールとして使用する場合
});
検証
例として「都道府県一覧から更新日の新しい順にtop5を作成し都道府県名と更新日の対応表を作る」流れを指定し、Resources/Prompts/Toolsの連携を確認します。
Resources
Prompts
Tools
get-enable-file-list以外のload-csv,run-query等に対しても許可します
最終的な結果
無事Resourcesを参照し、Promptsを構築、Toolsに定義された処理を駆使し目的の検索操作を行うことができました。
結論
比較的少ないコード量で汎用的な分析が可能な環境を構築できました。
プロダクション環境に組み込む場合、下記課題は最低限解決が必要です。
SQLがチャットに漏れ出ているためセキュリティ設計が必要
データソースの拡充
クラウドへのホスティングや認証機能の追加
MCPを含めたAIエージェントは始まったばかりなので、今後も定期的にキャッチアップしていきたいと思います。