mcpことはじめ（最小構成）

## はじめに
ぐわーっと成果物を作って、Qiitaに乗せるための説明文は生成AIをうまくつかっていく。だからまぁ文章がやや硬いというかなんか妙に感じるかもしれないけど便利よ生成AI

## ファイル構成
```
docker_world/
├─ mcp_client/
│  ├─ .env.example
│  ├─ .gitignore
│  ├─ eslint.config.mjs
│  ├─ package-lock.json
│  ├─ package.json
│  ├─ README.md
│  ├─ tsconfig.json
│  │
│  ├─ .devcontainer/
│  │   ├─ devcontainer.json
│  │   ├─ Dockerfile
│  │   └─ setup.sh
│  │
│  ├─ .vscode/
│  │   └─ launch.json
│  │
│  └─ src/
│      ├─ main.ts
│      └─ types/
│          └─ main.ts
│
└─ mcp_server/
    ├─ .flake8
    ├─ .gitignore
    ├─ README.md
    ├─ requirements.txt
    │
    ├─ .devcontainer/
    │   ├─ devcontainer.json
    │   ├─ Dockerfile
    │   └─ setup.sh
    │
    ├─ .vscode/
    │   └─ launch.json
    │
    └─ src/
        ├─ main.py
        │
        ├─ domain/
        │   ├─ __init__.py
        │   └─ valueobject/
        │       ├─ reverse_text.py
        │       └─ __init__.py
        │
        └─ tests/
            └─ test_main.py
```

## mcp-client
### .devcontainer フォルダを作成する
1. プロジェクトフォルダ `mcp_client` を VS Code で開きます
1. プロジェクトルート（mcp_client 直下）に `.devcontainer` という名前のフォルダを作成します

### .devcontainer/devcontainer.json
`.devcontainer` フォルダ内に、`devcontainer.json` ファイルを新規作成します。`devcontainer.json` は、VS Codeの `Dev Containers` で開発環境を簡単に構築・再現するための設定ファイルです。以下は、MCPクライアント開発用に作成した `devcontainer.json` の例です。この設定では、以下のポイントを押さえています。

- dockerfile と context の指定で、ビルドに使用するDockerfileとコンテキストの場所を明示
- コンテナ起動時に特定のネットワーク（ここでは dev_network）に接続
- コンテナ内でBashをデフォルトシェルとして使用
- コンテナ作成後に自動で setup.sh を実行し、依存関係などのセットアップを実施
- VS Codeの拡張機能やフォーマッターの設定もプリセット
- remoteUser は vscode に固定しておくことで、権限やファイルパーミッションのトラブルを防止

```json:mcp_client/.devcontainer/devcontainer.json
{
	"name": "MCP Client Example Dev Container",
	"build": {
		"dockerfile": "./Dockerfile",
		"context": "."  // 必要: buildの基準位置を示す
	},
	"runArgs": ["--network=dev_network"],
	"containerEnv": {
		"SHELL": "/bin/bash"
	},
	"postCreateCommand": ".devcontainer/setup.sh",
	"customizations": {
		"vscode": {
			"settings": {
				"editor.formatOnSave": true,
				"editor.formatOnPaste": true,
				"[typescript]": {
					"editor.defaultFormatter": "esbenp.prettier-vscode"
				}
			},
			"extensions": [
				"esbenp.prettier-vscode",
				"dbaeumer.vscode-eslint",
				"humao.rest-client"
			]
		}
	},
	"remoteUser": "vscode"  // リテラルで縛ることが重要
}

```

:::note info
補足：VS Codeの「Dev Container: 構成ファイルの追加」から新規作成する場合
もし 新規作成をウィザードで進めたい場合 は、以下の手順でも作成可能です。
（.devcontainer フォルダも作ってくれる）

- `Ctrl + Shift + P` を押して、コマンドパレットを開きます
- コマンドを検索する
`Dev Containers: Add Dev Container Configuration Files...` を選択します

🔍 インクリメンタルサーチで打つなら
`dev cont add` くらいまで入力すれば候補が絞れます。

- Where would you like to create your container configuration?
    - `Add configuration to workspace`（こっち。当プロジェクト専用につくる）
    - `Add configuration to user data folder`
- Select a container configuration template or enter a custom template id
    -  `Node.js & TypeScript`
- Node.js version (use -bookworm, -bullseye variants on local arm64/apple silicon): 
    - `22-bookworm (default)`
- Select additional features to install
    - `OK`
- Include the following optional files/directories
    - `OK`
:::

### .devcontainer/Dockerfile
この Dockerfile は、VS Code の Dev Container 用にカスタムイメージを作成するための設定です。ベースイメージに `node:20.17-slim`（軽量な Node.js 20 の公式イメージ）を使用し、以下の内容を含みます。

- `vscode` というユーザーを作成（UID と GID は 1001 に設定し、デフォルトの node ユーザー（UID 1000）と衝突しないように調整）
- タイムゾーンを `Asia/Tokyo` に設定
- 開発でよく使うツールとして、`curl`、`git`、`openssh-client` をインストール
- 最後にユーザーを `vscode` に切り替え、コンテナ内での操作をこのユーザー権限で行うように設定

この構成により、Node.js の開発に必要な環境を軽量かつ安全に整えています。

```Dockerfile:mcp_client/.devcontainer/Dockerfile
FROM node:20.17-slim

ARG USER_NAME=vscode
# node ユーザは 1000 番なので被らないようにする
ARG USER_UID=1001
ARG USER_GID=$USER_UID

ENV TZ=Asia/Tokyo

# Create vscode user
RUN groupadd --gid $USER_GID $USER_NAME \
    && useradd --uid $USER_UID --gid $USER_GID -m $USER_NAME

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    curl git openssh-client

# Switch to vscode user
USER $USER_NAME

```

### .devcontainer/setup.sh
この `setup.sh` は、Dev Container の `postCreateCommand` で実行されるセットアップスクリプトです。コンテナが初回起動したタイミングで、自動的に以下の処理を行います。

- `npm install`  
  必要なパッケージをインストールし、開発環境をすぐに使える状態にします
- `.env.example` を `.env` にコピー  
  環境変数ファイルの雛形を実際の `.env` ファイルとして作成し、ローカル開発環境がすぐに動作するように準備します

これにより、開発者がコンテナに入った直後からすぐに開発を開始できる状態が整います。

```sh:mcp_client/.devcontainer/setup.sh
#!/bin/bash

npm install
cp .env.example .env

```

### .env.example
この `.env.example` は、Dev Container のセットアップスクリプト（`setup.sh`）が実行時にコピーする「環境変数ファイルの雛形」です。

```env:mcp_client/.env.example
MCP_SERVER_URL=http://host.docker.internal:8000/mcp

```

setup.sh 内で以下のコマンドが実行されます。
```
cp .env.example .env
```

これにより、.env.example を .env として自動生成し、開発環境がすぐに動くようになります。

:::note info
host.docker.internal とは？
host.docker.internal は、コンテナからホストマシン（開発PC）のネットワークにアクセスするための特別な名前です。

これを使うことで、コンテナ内からホスト側で動かしているサーバー（例：localhost:8000）にアクセスできます。

公式ドキュメント: [Docker Desktop Networking](https://docs.docker.com/desktop/networking/#use-cases-and-workarounds)
:::

### .gitignore
```mcp_client/.gitignore
/node_modules
.env

```

### .vscode/launch.json
この `launch.json` は、VS Code でデバッグ実行（F5）を行うための設定ファイルです。

- VS Code で `F5` を押すと、`npm run start` が自動で実行され、Web サーバーが起動します
- 手動でターミナルを開いて `npm run start` を打つ手間を省くことができます

| 項目        | 説明                                                         |
|-------------|--------------------------------------------------------------|
| `name`      | 実行構成名。VS Code のデバッグ実行リストに表示される名前です。   |
| `type`      | 実行タイプ。`node-terminal` は Node.js をターミナル上で起動する指定です。 |
| `request`   | 起動方法。`launch` は新しくプロセスを立ち上げる指定です。            |
| `command`   | 実行コマンド。ここでは `npm run start` が実行されます。          |


```json:mcp_client/.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Run Server",
        "type": "node-terminal",
        "request": "launch",
        "command": "npm run start"
      }
    ]
  }
  
```

:::note info
`npm run start` の内容は、通常 `package.json` の `scripts` に記載されている起動コマンドです。これにより、`F5` 一発で開発用サーバーを立ち上げることができる便利な仕組みを実現できます。
:::

### package.json
この `package.json` は、プロジェクトの依存関係と実行スクリプトを管理するファイルです。

- `npm run start` を実行すると、`ts-node` で `src/main.ts` が実行され、開発用サーバーが起動します
- これは、先ほどの `launch.json` の設定と連携しており、VS Code で `F5` を押すとこのスクリプトが呼び出されます
- `ts-node` は TypeScript ファイルを直接実行するためのツールで、ビルドなしで開発サーバーを動かせます
- `@modelcontextprotocol/sdk` は MCP クライアントに必要なライブラリです
- 依存パッケージは `.devcontainer/setup.sh` で自動的にインストールされる仕組み になっています

```json:mcp_client/package.json
{
    "name": "mcp_client",
    "version": "1.0.0",
    "dependencies": {
        "@modelcontextprotocol/sdk": "^1.12.2",
        "@types/node": "20.17",
        "axios": "1.9.0",
        "dotenv": "16.5.0",
        "express": "5.1.0",
        "ts-node": "10.9.2"
    },
    "scripts": {
        "start": "ts-node src/main.ts"
    },
    "devDependencies": {
        "@eslint/js": "^9.28.0",
        "@types/express": "5.0.3",
        "eslint": "^9.28.0",
        "typescript": "5.8.3",
        "typescript-eslint": "^8.34.0"
    }
}

```

### mcp_client/README.md
```md:README.md
# mcp client

## ネットワークを作成する
`docker network create dev_network`

## mcp-client 設定

- `mcp_client` フォルダを開く
- `devcontainer` で開く
- `F5` で Web サーバを起動
- 処理結果が返ってくることを確認

```

### eslint.config.mjs
このプロジェクトでは ESLint のフラット構成（eslint.config.mjs） を採用しています。従来の .eslintrc ではなく、ESLint v8.21.0 以降で推奨されている新しい書き方です。

```mjs:mcp_client/eslint.config.mjs
import importPlugin from "eslint-plugin-import"
import js from "@eslint/js"

export default [
  js.configs.recommended,
  importPlugin.flatConfigs.recommended,
  {
    files: ["**/*.{js,mjs,cjs}"],
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
    },
    rules: {
      "no-unused-vars": "off",
      "import/no-dynamic-require": "warn",
      "import/no-nodejs-modules": "warn",
      "semi": ["error", "never"]
    }
  }
]
```

:::note info
ESLint の初期化は以下のコマンドで行います。
```
> npx eslint --init
  You can also run this command directly using 'npm init @eslint/config@latest'.
  @eslint/create-config: v1.9.0

  √ What do you want to lint? · javascript, json, jsonc, md
  √ How would you like to use ESLint? · problems
  √ What type of modules does your project use? · esm
  √ Which framework does your project use? · none
  √ Does your project use TypeScript? · no / yes
  √ Where does your code run? · browser
  √ What flavor of Markdown do you want to lint? · commonmark
  The config that you've selected requires the following dependencies:

  eslint, @eslint/js, globals, typescript-eslint, @eslint/json, @eslint/markdown
  √ Would you like to install them now? · No / Yes
  √ Which package manager do you want to use? · npm
```
公式ドキュメント 👉 [ESLint Getting Started](https://eslint.org/docs/latest/use/getting-started)
フラット構成の公式ガイド 👉 [ESLint Flat Config Migration Guide](https://eslint.org/docs/latest/use/configure/migration-guide)
:::

### tsconfig.json
このファイルは TypeScript コンパイラの設定ファイルです。TypeScriptのコンパイル時の挙動を細かく制御できます。

```mcp_client/tsconfig.json
{
    "compilerOptions": {
      "target": "ES2020",               // 出力するJavaScriptのバージョン
      "module": "CommonJS",             // モジュールシステムの指定（Node.js向け）
      "moduleResolution": "node",       // モジュールの解決方法（Node.js方式）
      "esModuleInterop": true,          // CommonJSとES Moduleの互換性を向上
      "skipLibCheck": true,             // 型チェックをライブラリファイルではスキップ（高速化）
      "strict": true                   // 厳密な型チェックを有効化
    }
  }

```
:::note info
TypeScriptの初期化は以下のコマンドで行います。
```
tsc --init
```
このコマンドを実行すると、`tsconfig.json` が生成されます。
必要に応じて上記のように編集してください。

公式ドキュメント（TypeScript公式）：https://www.typescriptlang.org/tsconfig

特に `Node.js` で動くサーバーやCLIツールの開発では、`module` は "CommonJS" が一般的です。
`target` は実行環境に応じて変えてください（ES2020は比較的新しい環境向け）。
`strict` を有効にすると型安全性が高まります。
:::

### src/main.ts
```ts:mcp_client/src/main.ts
/**
 * 環境変数を読み込むための dotenv 設定。
 */
import dotenv from "dotenv";
dotenv.config();

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";

import express from "express";
import { McpToolCallRequest, McpToolCallResponse } from "./types/main";

const app = express();
const port = 3000;

/**
 * サーバーの起動処理。
 * MCP Server に接続し、利用可能なツールの一覧を表示し、
 * サンプルツール 'reverse_text' を実行する。
 *
 * @see {@link https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#writing-mcp-clients Writing MCP Clients}
 */
app.listen(port, async () => {
  const mcpServerUrl = process.env.MCP_SERVER_URL;
  if (!mcpServerUrl) {
    throw new Error("MCP_SERVER_URL environment variable is required");
  }

  console.log(`🚀 MCP Client Test Server running on port: ${port}`);

  const client = await createClient(mcpServerUrl);

  console.info("\n🗒️ 使用可能なツールの一覧: ");
  console.log(await client.listTools());

  const request: McpToolCallRequest = {
    name: "reverse_text",
    arguments: {
      text: "こんにちは",
    },
  };

  const result = (await client.callTool(request)) as McpToolCallResponse;
  console.info("\n✅️ 処理結果:");
  console.log(result);
});

/**
 * MCP クライアントを作成し、適切なトランスポートで接続を行う。
 * まず Streamable HTTP で接続を試み、失敗した場合は SSE にフォールバックする。
 *
 * @param url MCP Server のエンドポイント URL
 * @returns 接続済みの Client インスタンス
 *
 * @see {@link https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#client-side-compatibility MCP TypeScript SDK - Client Side Compatibility}
 */
async function createClient(url: string): Promise<Client> {
  let client: Client | undefined = undefined;
  const baseUrl = new URL(url);
  try {
    client = new Client({
      name: "streamable-http-client",
      version: "1.0.0",
    });
    const transport = new StreamableHTTPClientTransport(new URL(baseUrl));
    await client.connect(transport);
    console.log("Connected using Streamable HTTP transport");
  } catch (error) {
    // If that fails with a 4xx error, try the older SSE transport
    console.log(
      "Streamable HTTP connection failed, falling back to SSE transport"
    );
    client = new Client({
      name: "sse-client",
      version: "1.0.0",
    });
    const sseTransport = new SSEClientTransport(baseUrl);
    await client.connect(sseTransport);
    console.log("Connected using SSE transport");
  }
  return client;
}

```

### src/types/main.ts
```ts:mcp_client/src/types/main.ts
/**
 * MCPツール呼び出しリクエストの型
 * @description reverse_textツールの呼び出しリクエスト
 * @property {string} name - ツール名（固定で "reverse_text"）
 * @property {{ text: string }} arguments - ツールに渡す引数オブジェクト
 * @property {string} arguments.text - 逆順にしたい文字列
 * @note MCPクライアントライブラリが内部的に_metaプロパティやその他の追加プロパティを付与する可能性があるため、index signatureを含めています
 */
export interface McpToolCallRequest {
  name: "reverse_text";
  arguments: {
    text: string;
  };
  [key: string]: any;
}

/**
 * MCPツール呼び出しレスポンスの型
 * @property {{ type: string; text: string; }[]} content - ツールの実行結果を含む配列
 * @property {"text"} content[].type - コンテンツのタイプ（固定で "text"）
 * @property {string} content[].text - 出力される文字列
 */
export interface McpToolCallResponse {
  content: Array<{
    type: "text";
    text: string;
  }>;
}

```

## mcp-server
### .devcontainer フォルダを作成する
1. プロジェクトフォルダ `mcp_server` を VS Code で開きます
1. プロジェクトルート（mcp_server 直下）に `.devcontainer` という名前のフォルダを作成します

### .devcontainer/devcontainer.json
`.devcontainer` フォルダ内に、`devcontainer.json` ファイルを新規作成します。`devcontainer.json` は、VS Codeの `Dev Containers` で開発環境を簡単に構築・再現するための設定ファイルです。以下は、MCPクライアント開発用に作成した `devcontainer.json` の例です。この設定では、以下のポイントを押さえています。

- dockerfile と context の指定で、ビルドに使用するDockerfileとコンテキストの場所を明示
- コンテナ起動時に特定のネットワーク（ここでは dev_network）に接続
- コンテナ内でBashをデフォルトシェルとして使用
- コンテナ作成後に自動で setup.sh を実行し、依存関係などのセットアップを実施
- VS Codeの拡張機能やフォーマッターの設定もプリセット
- remoteUser は vscode に固定しておくことで、権限やファイルパーミッションのトラブルを防止

```json:mcp_server/.devcontainer/devcontainer.json
{
	"name": "MCP Server Example Dev Container",
	"build": {
		"dockerfile": "./Dockerfile",
		"context": "."  // 必要: buildの基準位置を示す
	},
	"runArgs": ["--network=dev_network"],
	"containerEnv": {
		"SHELL": "/bin/bash"
	},
	"postCreateCommand": ".devcontainer/setup.sh",
	"customizations": {
		"vscode": {
			"settings": {
				"python.defaultInterpreterPath": "./.venv/bin/python",  // 必要: セットアップ完了後に activate する
				"editor.defaultFormatter": "ms-python.black-formatter",
				"editor.formatOnSave":true,
				"editor.formatOnPaste": true
			},
			"extensions": [
				"ms-python.black-formatter",
				"ms-python.flake8",
				"humao.rest-client"
			]
		}
	},
	"remoteUser": "vscode"  // リテラルで縛ることが重要
}

```

### .devcontainer/Dockerfile
この Dockerfile は Python 開発用の devcontainer 環境 を構築するための設定ファイルです。
- `Python 3.12.9` がインストールされた軽量な `Debian (bullseye)` イメージを使用しています

- `ARG USER_NAME=vscode`: 作成するユーザー名。`VS Code Remote Container` でよく使われるユーザーです
- `ARG USER_UID=1000`: ユーザーID。Linux でのデフォルトユーザーは通常 1000 番です
- `ARG USER_GID=$USER_UID`: グループIDもユーザーIDと合わせています
- `ENV TZ=Asia/Tokyo`: コンテナ内のタイムゾーンを日本時間に設定します
- `groupadd / useradd`: vscode ユーザーとグループを作成しています
- `apt-get install`: 開発に必要なパッケージをインストールしています
- `USER $USER_NAME`: 以降の作業ユーザーを vscode に切り替えています

```Dockerfile:mcp_server/.devcontainer/Dockerfile
FROM python:3.12.9-slim-bullseye

ARG USER_NAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

ENV TZ=Asia/Tokyo

# Create vscode user
RUN groupadd --gid $USER_GID $USER_NAME \
    && useradd --uid $USER_UID --gid $USER_GID -m $USER_NAME

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    curl git openssh-client python3-venv

# Switch to vscode user
USER $USER_NAME

```

### .devcontainer/setup.sh
この `setup.sh` は Python 仮想環境のセットアップとパッケージインストール を自動化するスクリプトです。

```sh:mcp_server/.devcontainer/setup.sh
#!/bin/bash

# 仮想環境のディレクトリを指定
VENV_DIR="./.venv"

# venvが存在しなければ作成
if [ ! -d "$VENV_DIR" ]; then
    python -m venv $VENV_DIR
    echo "仮想環境を作成しました: $VENV_DIR"
fi

# 仮想環境をアクティブにしてパッケージをインストール
source $VENV_DIR/bin/activate
pip install --upgrade pip
pip install -r ./requirements.txt

echo "仮想環境がセットアップされました。"

```

### .flake8
このファイルは **flake8 の静的解析ルール** を記述する設定ファイルです。

```mcp_server/.flake8
[flake8]
ignore = E501

```
:::note info
E501 は「行の長さが制限（デフォルトは 79 文字）を超えている」という警告です。この設定により、行の長さに関するチェックが無視されます。

初期化のための特別なコマンドはありませんが、設定ファイル .flake8 をルートディレクトリに作成すれば自動的に読み込まれます。

公式ドキュメント: https://flake8.pycqa.org/en/latest/user/configuration.html
:::

### .gitignore
```.gitignore
__pycache__/
.pytest_cache/
*.log
*.swp

.env
.venv/
```

### .vscode/launch.json

```json:mcp_server/.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Run Serve",
        "type": "python",
        "request": "launch",
        "module": "uvicorn",
        "args": [
          "src.main:app",
          "--reload",
          "--port", "8000",
          "--host", "0.0.0.0",
        ],
      },
      {
        "name": "Run Tests",
        "type": "python",
        "request": "launch",
        "module": "pytest",
        "args": ["-vv"]
      }
    ]
  }
  
```

### README.md
```md:mcp_server/README.md
# mcp server

## ネットワークを作成する
`docker network create dev_network`

## mcp-server 設定

- `mcp_server` フォルダを開く
- `devcontainer` で開く
- `F5` で Web サーバを起動
```

### mcp_server/requirements.txt

```requirements.txt
fastapi-mcp==0.3.4
uvicorn==0.34.3
pytest==8.4.0

```

### src/domain/__init__.py
```py:mcp_server/src/domain/__init__.py
```

### src/domain/valueobject/__init__.py
```py:mcp_server/src/domain/valueobject/__init__.py
```

### src/domain/valueobject/reverse_text.py
```py:mcp_server/src/domain/valueobject/reverse_text.py
from pydantic import BaseModel


class ReverseTextRequest(BaseModel):
    """文字列逆順化リクエストのVO"""

    text: str


class ReverseTextResponse(BaseModel):
    """文字列逆順化レスポンスのVO"""

    original_text: str
    reversed_text: str

```

### src/main.py
```py:mcp_server/src/main.py
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
from src.domain.valueobject.reverse_text import ReverseTextRequest, ReverseTextResponse

# Create a FastAPI app
app = FastAPI()


# MCPツールとして登録されるエンドポイント（mcp作成前に定義）
@app.post(
    "/reverse-text/",
    operation_id="reverse_text",
    summary="文字列を逆順にする",
    description="入力された文字列を逆順にして返します",
)
async def reverse_text(request: ReverseTextRequest):
    """文字列を逆順にするMCPツール"""
    reversed_text = f"{request.text[::-1]}🚀"
    return ReverseTextResponse(original_text=request.text, reversed_text=reversed_text)


# Create an MCP server based on this app（エンドポイント定義後）
mcp = FastApiMCP(app)

# Mount the MCP server directry to your app
mcp.mount()

```

### src/tests/test_main.py
```py:mcp_server/src/tests/test_main.py
```