🧩

MCPの仕組みと軽く自作してみる

に公開

はじめに

Claude Code、Cursor、GitHub Copilotなどを触ってると、最近やたら出てくるのが MCP

MCPは Model Context Protocol の略で、LLMアプリと外部データソース・ツールをつなぐためのオープンプロトコル。公式仕様でも「LLMアプリケーションと外部データソース・ツールをシームレスに統合するためのオープンプロトコル」と書かれてる。(Model Context Protocol)

一言でいうと、MCPは AIエージェント用のUSB-C みたいなもの。

ただ、MCPは「普通のWeb APIの置き換え」じゃない。

MCPは、AIクライアントに対して、

このツールを使えます
この引数を渡せます
このデータを読めます
このプロンプトテンプレートを使えます

と標準的な形で伝えるための仕組み。

ということで、MCPの基本構造をざっと整理して、TypeScriptで最小のMCP Serverを軽く自作してみる。


この記事でわかること

この記事で扱うのはこのあたり。

項目 内容
MCPとは何か AIエージェントと外部ツールをつなぐ標準プロトコル
Host / Client / Server MCPの登場人物
Tools / Resources / Prompts MCP Serverが公開できる3つの要素
stdio / Streamable HTTP MCPの代表的な通信方式
TypeScript実装 add Toolを持つ最小MCP Server
実務設計 安全なTool設計、Read-only開始、認可、エラー設計
.NET実装 C# SDKでの最小構成

対象読者はこんな感じ。

  • Claude CodeやCursorでMCPという言葉を見たけど、まだ仕組みが腹落ちしてない人
  • 自社SaaSや社内ツールをAIエージェントから操作できるようにしたい人
  • TypeScriptかC#でMCP Serverを軽く自作してみたい人

MCPが解決する課題

AIエージェントに外部操作をさせたいとき、よくある要望はこんな感じ。

やりたいこと MCPなし MCPあり
GitHub Issueを読む AIツールごとに個別連携を作る GitHub用MCP Serverをつなぐ
DBを検索する 独自APIを作り、AI側にも専用実装を書く DB検索用MCP Toolを作る
Figmaの情報を読む Figma APIをAIツールごとに実装する Figma用MCP Serverをつなぐ
社内ドキュメントを検索する 独自RAG連携を作る Document Search用MCP Serverを作る
ローカルファイルを操作する クライアント依存の実装になる Filesystem系MCP Serverをつなぐ

MCPの嬉しさは、AIクライアントとツールの間に共通の接続規格を作れること

従来は、Claude用、Cursor用、社内AI用にそれぞれ個別の連携を作る必要があった。

MCPを使うと、ツール側を MCP Server として実装し、AIアプリ側は MCP Client として接続する。


MCPの基本構造

MCPには、主に3つの登場人物がいる。

公式仕様でも、MCPは Host / Client / Server の構成で説明されてる。HostはLLMアプリ、ClientはHost内の接続役、Serverは外部データや機能を提供する側。通信にはJSON-RPC 2.0が使われる。(Model Context Protocol)

Host

Hostは、Claude Code、Claude Desktop、Cursorのような AIアプリ本体

ユーザーと会話し、LLMを動かし、必要に応じてMCP Client経由で外部ツールを呼び出す。

Client

Clientは、Hostの中にある MCP Serverとの接続役

ClientはMCP Serverに対して、

どんなToolがありますか?
どんなResourceがありますか?
このToolを実行してください

といったリクエストを送る。

Server

Serverは、ツールやデータを提供する側。

自作する場合、多くの開発者が実装するのはこの MCP Server

たとえばこんなものをMCP Serverとして作れる。

MCP Serverの例 できること
GitHub Server Issue作成、PR取得、コード検索
DB Server SQL実行、顧客情報検索
Figma Server デザイン情報取得、レイヤー解析
社内ツール Server 勤怠、見積、案件情報の取得
自作SaaS Server 予約確認、ユーザー検索、売上集計

MCP Serverが公開できる3つの要素

MCP Serverは、主に以下の3つをClientに公開できる。

公式仕様では、Serverが提供できる機能として Resources / Prompts / Tools が定義されてる。Resourcesはコンテキストやデータ、Promptsはテンプレート化されたメッセージやワークフロー、ToolsはAIモデルが実行できる関数。(Model Context Protocol)

種類 役割
Tools AIが実行できる関数 create_issue, search_customer, run_sql
Resources AIやユーザーが参照できるデータ file://README.md, db://customers/123
Prompts 再利用できるプロンプトテンプレート コードレビュー用、議事録要約用、PR作成用

Tools

Toolsは、AIが実際に呼び出す関数。

たとえばこんなイメージ。

searchCustomer({ name: "田中" })

外部APIを呼ぶ、DBを検索する、Issueを作る、計算する、みたいな処理をToolとして公開する。

重要なのは、AIが引数を組み立てて呼ぶ という点。

なので、Toolの名前、説明、入力スキーマはかなり重要。

Resources

Resourcesは、AIが読み取れるデータ。

たとえばこんなURIで表現される。

file:///project/spec.md
customer://123

Resourcesは「操作する関数」というより、参照できる文脈やデータ に近い。

Prompts

Promptsは、よく使う指示テンプレート。

たとえばこんなテンプレートをMCP Server側から提供できる。

このPRを、セキュリティ・保守性・テスト観点でレビューしてください

チームで共通のレビュー観点や、定型ワークフローを配るときに便利。


Tools / Resources / Prompts の使い分け

最初はここが少し分かりにくい。

ざっくり分けると、こう考えると理解しやすい。

やりたいこと 使うもの 理由
DBから顧客を検索したい Tool 検索処理を実行するから
READMEをAIに読ませたい Resource データを参照させたいから
PRレビューの定型指示を使いたい Prompt 指示テンプレートを再利用したいから
GitHub Issueを作りたい Tool 外部システムに副作用があるから
設計書一覧をAIに見せたい Resource 文脈として読ませたいから

MCPの通信方式

MCPでは、メッセージ形式としてJSON-RPCを使う。

Transportとしては、標準入出力を使う stdio と、ネットワーク越しに使う Streamable HTTP が標準として定義されてる。公式仕様では、MCPはJSON-RPCメッセージを使い、標準Transportとして stdioStreamable HTTP を定義してる。(Transports - Model Context Protocol)

Transport 用途
stdio ローカル実行 Claude Codeからローカルプロセスとして起動する
Streamable HTTP リモート実行 クラウド上や社内ネットワーク上のMCP Serverに接続する

stdio

stdioは、MCP Serverをローカルプロセスとして起動し、標準入力・標準出力で通信する方式。

ローカル開発では、まずstdioから始めるのが簡単。

Streamable HTTP

Streamable HTTPは、ネットワーク越しにMCP Serverへ接続する方式。

TypeScript SDKのServer Guideでも、リモートサーバーにはStreamable HTTP、ローカルのプロセス起動型連携にはstdioを選ぶ、と説明されてる。(TypeScript SDK Server Guide)

チーム利用、SaaS化、社内基盤化するならStreamable HTTPを検討する。


MCP Serverを軽く自作してみる

ここからは、TypeScriptで最小のMCP Serverを作っていく。

今回作るのはこのTool。

add(a, b) -> a + b を返す

なお、この記事では安定して使いやすい @modelcontextprotocol/sdk の構成で説明する。


1. プロジェクトを作成する

mkdir my-first-mcp-server
cd my-first-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk@^1 zod
npm install -D typescript tsx @types/node

package.json はこんな感じにする。

{
  "type": "module",
  "scripts": {
    "dev": "tsx src/server.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.25.0"
  },
  "devDependencies": {
    "@types/node": "latest",
    "tsx": "latest",
    "typescript": "latest"
  }
}

2. MCP Serverを実装する

src/server.ts を作る。

mkdir src
touch src/server.ts

実装はこんな感じ。

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

const server = new McpServer({
  name: "my-first-mcp-server",
  version: "1.0.0",
});

server.tool(
  "add",
  "Add two numbers and return the result.",
  {
    a: z.number().describe("First number"),
    b: z.number().describe("Second number"),
  },
  async ({ a, b }) => {
    const result = a + b;

    return {
      content: [
        {
          type: "text",
          text: `${a} + ${b} = ${result}`,
        },
      ],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

これで、AIクライアントから呼び出せる add Tool ができた。


3. 動かしてみる

npm run dev

ここで注意。

stdioのMCP Serverは、普通のWebサーバーみたいにブラウザで開くものじゃない。

AIクライアントがプロセスを起動して、標準入力・標準出力でJSON-RPCメッセージをやり取りする。

なので、npm run dev しても画面にWebページが表示されるわけじゃない。


4. MCP Inspectorでテストする

MCP Serverは、MCP Inspectorを使うとテストしやすい。

npx -y @modelcontextprotocol/inspector

Inspectorを起動したら、Transportに stdio を選んで、こんなコマンドで接続する。

npx tsx /absolute/path/to/my-first-mcp-server/src/server.ts

接続後、add Toolを選んで、こんな引数で実行する。

{
  "a": 1,
  "b": 2
}

こんな結果が返れば成功。

1 + 2 = 3

5. Claude Codeに接続する

Claude CodeにstdioのMCP Serverを追加する場合、こう登録できる。

Claude Code公式ドキュメントでも、ローカルstdioサーバーは claude mcp add [options] <name> -- <command> [args...] の形式で追加すると説明されてる。(Claude Code Docs)

claude mcp add --transport stdio my-first-mcp-server -- \
  npx tsx /absolute/path/to/my-first-mcp-server/src/server.ts

登録できたか確認する。

claude mcp list

Claude Code内では、こんなコマンドで接続状態を確認できる。

/mcp

その後、Claude Codeにこう聞いてみる。

add toolを使って、123と456を足してください

Claude CodeがMCP Toolを呼び出して、結果を返してくれれば接続成功。


もう少し実用的なToolを作る

足し算だけだと実務感が薄いので、次は「プロジェクト配下のファイルを読むTool」を作ってみる。

ただ、AIにファイル操作を許す場合は安全設計が重要。

最初にありがちな危険な実装はこんなやつ。

const text = await readFile(path, "utf-8");

これだと、AIが意図せずプロジェクト外のファイルを読んでしまう可能性がある。

なので今回は プロジェクトルート配下だけ読める ようにする。

import path from "node:path";
import { readFile } from "node:fs/promises";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const projectRoot = process.cwd();

const server = new McpServer({
  name: "project-helper",
  version: "1.0.0",
});

server.tool(
  "read_project_file",
  "Read a text file under the current project directory.",
  {
    relativePath: z
      .string()
      .describe("Path relative to the project root. Example: README.md"),
  },
  async ({ relativePath }) => {
    const targetPath = path.resolve(projectRoot, relativePath);

    if (!targetPath.startsWith(projectRoot + path.sep)) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: "Access denied. Only files under the project root can be read.",
          },
        ],
      };
    }

    try {
      const text = await readFile(targetPath, "utf-8");

      return {
        content: [
          {
            type: "text",
            text,
          },
        ],
      };
    } catch (error) {
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Failed to read file: ${relativePath}`,
          },
        ],
      };
    }
  }
);

await server.connect(new StdioServerTransport());

ポイントはこのあたり。

観点 内容
入力 絶対パスではなく、プロジェクトルートからの相対パスにする
パス解決 path.resolve で正規化する
範囲制限 projectRoot 配下だけ許可する
エラー isError: true でAIが理解しやすい形にする

MCPのTools仕様でも、Tool実行エラーは isError: true で返せると説明されてる。Tool Execution Errorは、モデルが自己修正して再試行できるような実行時エラーに使われる。(Tools - Model Context Protocol)


MCP Server設計のベストプラクティス

1. 最初はRead-onlyから始める

MCP Serverは、AIエージェントに外部操作を許す仕組み。

なので、最初から更新・削除・送信みたいな副作用の強いToolを作ると危険。

まずはRead-onlyから始めるのがおすすめ。

特にこのあたりは慎重に扱った方がいい。

対象 注意点
DB 最初はSELECT専用にする
ファイル プロジェクト配下だけに制限する
Git commit / push / force push は承認を挟む
クラウド 課金や削除につながる操作は制限する
顧客情報 認可、監査ログ、出力制限を入れる
メール・Slack 送信前に人間の確認を入れる

公式仕様でも、MCPは任意のデータアクセスやコード実行経路を可能にするため、セキュリティとTrust & Safetyを慎重に扱う必要があるとされてる。(Model Context Protocol)


2. Toolは小さく作る

悪い例。

manage_project()

これだと、何ができるのか曖昧。

良い例。

list_issues()
get_issue_detail()
create_issue()
summarize_issue()

AIに使わせるToolは、人間向けAPIよりも 意図が明確 な方が使いやすい。


3. Tool名はAIが理解しやすくする

良いTool名の例。

良いTool名 理由
search_customer_by_name 何を検索するか明確
create_github_issue 副作用が明確
get_latest_sales_summary 取得対象が明確
validate_sql_readonly 安全目的が明確

悪いTool名の例。

悪いTool名 問題
exec 何でもできて危険
run 何を実行するか不明
handle 抽象的すぎる
process_data 入出力が分からない

MCPのTools仕様でも、Toolは名前、説明、入力スキーマなどのメタデータを持つと説明されてる。Tool名は1〜128文字で、スペースや特殊文字を避けることが推奨されてる。(Tools - Model Context Protocol)


4. 入力スキーマを厳密にする

MCP Toolは、AIが引数を組み立てる。

なので、曖昧な入力は避けた方がいい。

悪い例。

query: z.string()

良い例。

customerId: z.string().describe("Customer ID. Example: CUST-001")
fromDate: z.string().describe("Start date. Format: YYYY-MM-DD")
toDate: z.string().describe("End date. Format: YYYY-MM-DD")

AIが迷わないように、このあたりを明確にする。

観点
string / number / boolean / enum
フォーマット YYYY-MM-DD、ISO 8601など
CUST-0012026-05-01
制約 最大件数、許可値、必須項目

5. エラーはAIが直せる形で返す

悪い例。

Error: invalid input

良い例。

fromDate must be YYYY-MM-DD format. Example: 2026-05-01

AIエージェントは、エラー内容が具体的なら自分で修正して再実行できる。

特にこういう情報を返すと、AIが復旧しやすくなる。

含める情報
何が悪いか fromDate の形式が不正
期待する形式 YYYY-MM-DD
正しい例 2026-05-01
次の行動 日付を修正して再実行してください

C# / .NETで作る場合

.NETエンジニアなら、C# SDKでMCP Serverを作るのも自然な選択。

公式C# SDKのGetting Startedでは、WithStdioServerTransport()WithToolsFromAssembly() を使って、属性付きメソッドをToolとして公開する例が紹介されてる。(MCP C# SDK)

最小のstdio MCP Serverはこう書ける。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddConsole(consoleLogOptions =>
{
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

await builder.Build().RunAsync();

[McpServerToolType]
public static class EchoTool
{
    [McpServerTool, Description("Echoes the message back to the client.")]
    public static string Echo(string message) => $"hello {message}";
}

WPF、ASP.NET Core、社内業務API、金融系ツールなどをMCP化したい場合、C# SDKは相性が良い。

特に既存の.NET資産がある場合は、こんなMCP Serverを作れる。

既存資産 MCP化の例
WPF業務アプリ 操作補助、設定確認、ログ取得
ASP.NET Core API 既存APIをAI向けToolとしてラップ
SQL Server / Oracle Read-only検索Tool
バッチ処理 実行前確認付きのジョブ起動Tool
社内管理画面 申請、確認、レポート取得Tool

実用例:自社SaaSをMCP化するなら

たとえば、予約管理SaaSをMCP化するなら、こんなTool設計が考えられる。

Tool 説明 最初の安全度
list_reservations 指定日の予約一覧を取得
get_customer_profile 顧客情報を取得
summarize_monthly_sales 月次売上を集計
list_unpaid_invoices 未払い請求一覧を取得
create_reservation 予約を作成
cancel_reservation 予約をキャンセル

この構成にすると、AIに対してこう依頼できる。

明日の予約状況を確認して、空き枠を教えて

AIはMCP Server経由で予約情報を取得して、自然言語で回答できる。

ただ、予約作成やキャンセルみたいな副作用のある操作は、最初から完全自動化しない方が安全。


MCPを自作すべきケース・しないケース

MCPは便利だけど、何でもMCPにすればいいわけじゃない。

判断軸 MCPが向いている MCPでなくてよい
AIから使わせたい
人間向けWeb API
複数AIクライアントで使いたい
ローカルツール連携
単純なWebアプリ
社内ツールのAI操作
高リスクな本番操作 慎重に設計 直接自動化は避ける

MCPは「普通のAPIの代替」じゃない。

むしろ、AIエージェントが安全に使えるようにラップしたインターフェース層 と考えると分かりやすい。


まとめ

MCPは、AIエージェントに外部ツールやデータソースを接続するための標準プロトコル。

重要なポイントはこのあたり。

ポイント 内容
MCPの目的 LLMアプリと外部ツール・データを標準接続する
主な構成 Host / Client / Server
通信形式 JSON-RPC 2.0
公開できるもの Tools / Resources / Prompts
Transport stdio / Streamable HTTP
自作の第一歩 小さなstdio Toolから作る
実務の注意 Read-only、入力スキーマ、安全設計、認可、監査ログ

AIエージェント時代の開発では、単にアプリを作るだけじゃなく、AIが安全に操作できるインターフェースを設計する力 が重要になってくる。

最初に作るなら、こういう小さなMCP Serverがおすすめ。

- プロジェクトのREADMEを読む
- package.jsonを解析する
- TODOコメントを検索する
- Git branch状況を要約する
- DBをread-onlyで検索する

小さなToolを積み上げることで、自分専用のAI開発環境を作れる。

MCPは、AIに「知識」だけじゃなく「手」を与えるための仕組み。


参考

GitHubで編集を提案
Accenture Japan (有志)

Discussion