【備忘録】今更MCPサーバを勉強した

# 記事の概要
Google Cloud Next 25でGCPがMCP (Model Context Protocol) を取り入れているのを見て、今更ながらMCPを勉強してみました。
本記事は、勉強過程の記録です。


# 勉強過程
## STEP1: MCPサーバの概要を学ぶ
下記の「松尾研究所テックブログ」が分かりやすかったです。

https://zenn.dev/mkj/articles/0ed4d02ef3439c

## STEP2: 公開されているMCP Serverをたててみる
### MCPサーバ
公開されているMCPサーバは主に以下2種類に大別されます。
- TypeScript製
- Python製

私はDockerを用いてサーバをたてており、サーバごとにコンテナを分けています。
TypeScript製・Python製いずれもDockerコンテナで立ちあげてしまえば、サーバごとに環境を分離できて便利です。

下記に実際にたてたMCPサーバを記載します。

:::note warn
公開されているMCPサーバを利用する際は悪意あるコードが潜んでいないか十分注意しましょう。

参考: [最小限のPython MCPサーバを作ってLLMを騙してみる](https://zenn.dev/karaage0703/articles/bc369a11a82263)
:::

### MCPクライアント
MCPクライアントにはClaude Desktopを利用しました。
MCPの設定を記載する`claude_desktop_config.json`のパスはMac OSでは`/Users/<ユーザ名>/Library/Application Support/Claude/claude_desktop_config.json`です。



### 実際にたてたMCPサーバ
#### Anthoropic公式 MCPサーバ (Filesystem MCP Server)
GitHubリポジトリ：

https://github.com/modelcontextprotocol/servers


解説記事：

https://qiita.com/hmkc1220/items/90c5d3901ed82c304cc0


#### GitHub
解説記事：

https://zenn.dev/takna/articles/mcp-server-tutorial-05-github

GitHubリポジトリ：

https://github.com/github/github-mcp-server

#### AWS Documentation MCP Server
解説記事：

https://zenn.dev/fusic/articles/473a9d1a1dfc43

公式ページ：

https://awslabs.github.io/mcp/servers/aws-documentation-mcp-server/

#### Notion
解説記事：

https://dev.classmethod.jp/articles/notion-mcp-server-guide-with-claude-desktop/

GitHubリポジトリ：

https://github.com/makenotion/notion-mcp-server

#### LINE
解説記事：

https://dev.classmethod.jp/articles/line-bot-mcp-server/

GitHubリポジトリ：

https://github.com/line/line-bot-mcp-server

## STEP3: MCPサーバを作って学ぶ
基本、下記Anthoropic公式Quick Startが参考になります。

https://modelcontextprotocol.io/quickstart/server

### Python
`FastMCP`を利用します。

前提：
Claude Desktopアプリは立ち上がっていない状態

手順：
1. 下記の通り各種ファイルを作成
1. プロジェクトのルートディレクトリ (`Dockerfile`と同じ階層) で`docker build -t my-mcp-server-python .` を実行し、Docker image作成
1. Claude Desktopを立ち上げる

```bash:ディレクトリ構造
.
├── Dockerfile
├── app.py
└── pyproject.toml
```

```Dockerfile:Dockerfile
FROM python:3.12-slim-bookworm

WORKDIR /app

COPY pyproject.toml ./
COPY app.py ./

RUN pip install uv
RUN uv venv
RUN . .venv/bin/activate
RUN uv add "mcp[cli]"

CMD ["uv", "run", "app.py"]
```

```toml:pyproject.toml
[project]
name = "my-mcp-server-python"
version = "0.1.0"
dependencies = [
    "mcp[cli]"
]
```

```python:app.py
# server.py
from mcp.server.fastmcp import FastMCP

# Create an MCP server with debug enabled
mcp = FastMCP("my-mcp-server-python", debug=True)


# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

# Add this part to run the server
if __name__ == "__main__":
    # stdioトランスポートを使用
    print("Starting MCP server in stdio mode")
    mcp.run(transport="stdio")
```

```json:claude_desktop_config.json
{
  "mcpServers": {
    "my-mcp-server-python": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "my-mcp-server-python"
      ]
    }
  }
}
```

コード参考；

https://zenn.dev/karaage0703/articles/bc369a11a82263

### TypeScript
`@modelcontextprotocol/sdk`を利用します。

前提：
- Claude Desktopアプリは立ち上がっていない状態
- node, npmのバージョンは下記の通り
    - node: v22.7.0
    - npm: 10.8.2

手順：
1. プロジェクトのルートディレクトリ (`Dockerfile`と同じ階層) で`npm init -y`を実行
1. プロジェクトのルートディレクトリで下記を実行
    ```
    npm install @modelcontextprotocol/sdk zod
    npm install -D @types/node typescript vitest
    ```
1. 下記の通り`Dockerfile`, `package.json`, `tsconfig.json`, `src/index.test.ts`, `src/index.ts`を作成 (`package-lock.json`は自動生成されたものをそのまま使用)
1. プロジェクトのルートディレクトリで`npm run test`を実行し、パスするのを確認
1. プロジェクトのルートディレクトリで`npm run build`を実行し、正常にビルドできることを確認 (`build`ディレクトリ配下に`index.js`と`index.test.js`が作成される) 
1. プロジェクトのルートディレクトリで`docker build -t my-mcp-server-typescript .` を実行し、Docker image作成
1. Claude Desktopを立ち上げる

```bash:ディレクトリ構造
.
├── Dockerfile
├── build
│   ├── index.js
│   └── index.test.js
├── package-lock.json
├── package.json
├── src
│   ├── index.test.ts
│   └── index.ts
└── tsconfig.json
```

```Dockerfile:Dockerfile
FROM node:22-slim

WORKDIR /app

# 全てのファイルを一度にコピー
COPY . .

# 依存関係のインストール
RUN npm install

# TypeScriptのビルドとdistディレクトリの作成を確認
RUN npm run build && \
    if [ -d "build" ]; then \
        chmod +x build/*.js || true; \
    else \
        echo "Error: build directory was not created" && exit 1; \
    fi

# コンテナ起動時のコマンド
CMD ["node", "build/index.js"]
```

```json:package.json
{
  "name": "my-mcp-server-typescript",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "bin": {
    "my-mcp-server": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "test": "vitest",
    "build": "tsc && chmod 755 build/index.js"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.10.2",
    "zod": "^3.24.3"
  },
  "devDependencies": {
    "@types/jest": "^29.5.14",
    "@types/node": "^22.15.3",
    "typescript": "^5.8.3",
    "vitest": "^3.1.2"
  }
}
```

```json: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"],
  "types": ["jest", "node"],
}
```

```ts:src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as z from "zod";

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

server.tool(
  "add",  // 提供するtoolの名前
  "足し算をする",  // toolの説明
  {
    a: z.number().describe("数値1"),
    b: z.number().describe("数値2"),
  },  // toolのパラメータ定義
  ({ a, b }) => ({ content: [{ type: "text", text: (a + b).toString() }] }),  // toolが提供する機能の本体
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Example MCP Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});
```

```ts:src/index.test.ts
import { describe, it, expect } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { server } from "./index.js";
 
describe("my-mcp-serverのテスト", () => {
  it("add: 足し算の結果を返す", async () => {
    // テスト用クライアントの作成
    const client = new Client({
      name: "test client",
      version: "0.1.0",
    });
 
    // インメモリ通信チャネルの作成
    const [clientTransport, serverTransport] =
      InMemoryTransport.createLinkedPair();
      
    // クライアントとサーバーを接続
    await Promise.all([
      client.connect(clientTransport),
      server.connect(serverTransport),
    ]);
 
    // 6面サイコロを振る
    const result = await client.callTool({
      name: "add",
      arguments: {
        a: 3, b: 8
      },
    });
 
    // 結果が1-6の範囲の数字であることを確認
    expect(result).toEqual({
      content: [
        {
          type: "text",
          text: "11",
        },
      ],
    });
  });
});
```

```json:claude-desktop-config.json
{
  "mcpServers": {
    "my-mcp-server-typescript": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "my-mcp-server-typescript"
      ]
    }
  }
}
```

コード参考：

https://note.com/hi_noguchi/n/n02bdeca0a835

https://azukiazusa.dev/blog/typescript-mcp-server/

# 余談：uvについて
気がついたらPythonの`uv`というライブラリが一世を風靡していた。

https://github.com/astral-sh/uv

# 感想
MCPとはなにか？はAnthoropicがMCPを公開した際に簡単に勉強しましたが、ここまで人気になるとは思わず、長らく放置してしまいました。
Node.js（TypeScript）があまり得意ではないというのも放置の原因ではありました。Pythonでも書けるのなら、もっと早く手をつけておけばよかったです（Node.jsも勉強しろ）。
個人的には、もっといろんなサービスが公式MCPを作成してくれると安心して使えるのになぁ...と思いました。