MCPサーバーの最小構成を作ってみる(Node.js版)

# はじめに
[Model Context Protocol(MCP)](https://docs.anthropic.com/ja/docs/agents-and-tools/mcp)はAnthropic社が提唱した、生成大規模言語モデル(LLM)が外部ツールを利用するためのインターフェースです。

今年(2025年)に入って、OpenAIやGoogle Geminiなど大手AIプロバイダーも次々とMCP規格に対応していました。


# シリーズ記事

| №  | 記事                                          | 内容                                  |
| --- | --------------------------------------------- | ----------------------------------------- |
| 1   | [MCPサーバーの最小構成を作ってみる(Node.js版)](#) | 本編 |
| 2   | [複数MCPツールを同時に利用可能なChatbotを最小構成で作ってみる(Google Gemini版)](https://qiita.com/jerrywdlee/items/ceca3b3a942d61e5b387) | Node.js × GeminiでAI Agentを製作 |
| 3   | MCPサーバーの最小構成を作ってみる(Python版) | Coming Soon |
| 4   | 複数MCPツールを同時に利用可能なChatbotを最小構成で作ってみる(Azure OpenAI版) | Coming Soon |
| 5   | RAGをMCPサーバーとして利用し、簡単な映画推薦Chatbotを作ってみる(Azure OpenAI版) | Coming Soon |

# 実装
## 実行環境
- Node.js (v20以上)

## STDIOを使ったMCPサーバー
最も簡単なMCPサーバーはstandard input/output（標準入出力/stdio）を使った実装です。

### パッケージのインストール

```sh
npm i @modelcontextprotocol/sdk zod
```

### STDIO MCPサーバーの実装

`add.mjs`を作成し、下記のコードを入力します。

```add.mjs
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: "Demo Stdio MCP",
  version: "1.0.0",
});

server.tool(
  "add_test",
  "与えられた数値の足し算をする（さらに10を足す）",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b + 10) }],
  })
);

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

上記のコードだけで一つのMCPサーバーが作られました。

### 検証

Anthropic社本家はご丁寧にLLMなしで、手動でMCPサーバーの検証ができる **[inspector](https://github.com/modelcontextprotocol/inspector)** というツールを提供しています。

https://github.com/modelcontextprotocol/inspector

下記のコマンドで実行すれば、ブラウザ画面でMCPサーバーの操作ができます。

```sh
npx -y @modelcontextprotocol/inspector
```

すると、以下の画面が http://localhost:6274 に立ち上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/b580fe07-2424-48dd-833a-1511de388917.png)

下記のように設定をします。

- **「Transport Type」:** `STDIO`
- **「Command」:** `node`
- **「Arguments」:** `./add.mjs` (inspectorが起動した時のフォルダに対する相対Path)

そして **「Connect」** ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/a73f9020-e4c2-4c27-937d-380c36adb6cc.png)

接続成功したら、次のように右の画面に操作パネルが現れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/fe23ba5e-0743-4141-a3ad-bd72fc3b98db.png)

一番上の **「Tool」** 画面を開き、 **「List Tools」** を押すと、先ほど作った `add_test`というMCPツールが表示されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/c242bc46-27c1-4e03-a9a1-b07b156e2a54.png)

`add_test`をクリックすると、隣のパネルに入力欄が現れ、そこに数値を入力して **「Run Tool」** を押すと、実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/6ad44407-fd64-4aec-9b7a-8c4e7b2c1c9f.png)

### 通信内容の確認

MCPの通信規格は[JSON-RPC](https://ja.wikipedia.org/wiki/JSON-RPC)というプロトコルを用いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/39e8f0d8-a9a3-4d94-be04-7c086d7ce638.png)

上記inspectorツールの  **「History」** パネルは一応一部の通信内容を表示していますが、RAWデータを見たい場合、[megmogmog1965/mcp-server-logger](https://github.com/megmogmog1965/mcp-server-logger)のようなMiddleWareを挟んで、MCPサーバーを実行すれば観察できます。

下記のように設定をします。

- **「Transport Type」:** `STDIO`
- **「Command」:** `npx`
- **「Arguments」:** `mcp-server-logger ./mcp.log node ./add.mjs`

Connectすると、`mcp.log`が作成され、中にアクセスログが記録されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/3aaefb05-f7d8-42d1-b582-4ac70d0e8155.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/3238690d-ca9e-4dae-ae1c-449d1125d4c6.png)


<details><summary>initializeの通信</summary>

Client:

```json
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "sampling": {},
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "mcp-inspector",
      "version": "0.14.0"
    }
  }
}
```

Server:

```json
{
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "Demo Stdio MCP",
      "version": "1.0.0"
    }
  },
  "jsonrpc": "2.0",
  "id": 0
}
```

</details>


<details><summary>tools/listの通信</summary>

Client:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "_meta": {
      "progressToken": 1
    }
  }
}
```

Server:

```json
{
  "result": {
    "tools": [
      {
        "name": "add_test",
        "description": "与えられた数値の足し算をする（さらに10を足す）",
        "inputSchema": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ],
          "additionalProperties": false,
          "$schema": "http://json-schema.org/draft-07/schema#"
        }
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 1
}
```

</details>


<details><summary>tools/callの通信</summary>

Client:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "_meta": {
      "progressToken": 2
    },
    "name": "add_test",
    "arguments": {
      "a": 12,
      "b": 20
    }
  }
}
```

Server:

```json
{
  "result": {
    "content": [
      {
        "type": "text",
        "text": "42"
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 2
}
```

</details>


## Streamable HTTPを使ったMCPサーバー
インタネット越しでMCPサーバーを利用したい場合、現在提供された方法は `SSE(Server-Sent Events)` と `Streamable HTTP`2種類があります。2025年4月以降、`SSE` が非推奨となったため、本文は`Streamable HTTP`の実装のみ説明します。

<small>_`Streamable HTTP`実はMCPのために独自実装した`SSE`であるため、同じ通信規格を使った別のサービスはまだほとんどありません。_</small>

### パッケージのインストール

```sh
npm i @modelcontextprotocol/sdk zod express
```

### Streamable HTTP MCPサーバーの実装

`add_http.mjs`を作成し、下記のコードを入力します。

```add_http.mjs
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import express from "express";

const app = express();
app.use(express.json());

app.all("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  // POSTメソッド以外は受付ない
  if (req.method !== "POST") {
    return res.status(405).json({
      jsonrpc: "2.0",
      error: {
        code: -32000,
        message: `Method not allowed: ${req.method}`,
      },
      id: null
    });
  }

  const server = new McpServer({
    name: "Demo HTTP MCP Server",
    version: "1.0.0",
  });

  try {
    server.tool(
      "add_test",
      "与えられた数値の足し算をする（さらに10を足す）",
      { a: z.number(), b: z.number() },
      async ({ a, b }) => ({
        content: [{ type: "text", text: String(a + b + 10) }],
      })
    );

    res.on('close', () => {
      console.log('Request closed');
      transport.close();
      server.close();
    });

    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error('Error handling MCP request:', error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: '2.0',
        error: {
          code: -32603,
          message: 'Internal server error',
        },
        id: null,
      });
    }
  }
});

app.listen(3000, () => {
  console.log('MCP HTTP server is running on http://localhost:3000/mcp');
});

// graceful shutdown
process.on("SIGINT", async () => {
  console.log("Shutting down server...");

  process.exit(0);
});
```

コアのロジックはSTDIO版MCPサーバーとほとんど一緒で、STDIOと変わってアクセスを`express`を使って、`/mcp`に出しています。（Python版は暗黙の了解で入口を`/mcp`にしているので、js版もそれに合わせて`/mcp`にしています）

### 検証

まずはMCPサーバーを起動します。

```sh
node add_http.mjs 
```


そしてinspectorツールを下記のように設定をし、 **「Connect」** ボタンを押して、前述STDIO版と同じようにアクセスできます。

- **「Transport Type」:** `Streamable HTTP`
- **「Url」:** `http://localhost:3000/mcp`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/0d7390fd-a5d9-43c8-b02d-2ddfb398cc9b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/7bbab42c-0e56-4744-99b0-542b67184d05.png)


### 手動送信で通信内容を確認

Postmanなどを使って、下記の内容を手動で送信すると通信内容が確認できます。

<details><summary>initialize</summary>

送信内容:

```
POST http://localhost:3000/mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Connection: keep-alive

{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "sampling": {},
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "mcp-http",
      "version": "0.1.0"
    }
  }
}
```

受信内容:

```
event: message
data: {
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {
        "listChanged": true
      }
    },
    "serverInfo": {
      "name": "Demo HTTP MCP Server",
      "version": "1.0.0"
    }
  },
  "jsonrpc": "2.0",
  "id": 0
}
```

</details>


<details><summary>tools/list</summary>

送信内容:

```
POST http://localhost:3000/mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Connection: keep-alive

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "_meta": {
      "progressToken": 1
    }
  }
}
```

受信内容:

```
event: message
data: {
  "result": {
    "tools": [
      {
        "name": "add_test",
        "description": "与えられた数値の足し算をする（さらに10を足す）",
        "inputSchema": {
          "type": "object",
          "properties": {
            "a": {
              "type": "number"
            },
            "b": {
              "type": "number"
            }
          },
          "required": [
            "a",
            "b"
          ],
          "additionalProperties": false,
          "$schema": "http://json-schema.org/draft-07/schema#"
        }
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 1
}
```

</details>


<details><summary>tools/call</summary>

送信内容:

```
POST http://localhost:3000/mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Connection: keep-alive

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "_meta": {
      "progressToken": 2
    },
    "name": "add_test",
    "arguments": {
      "a": 2,
      "b": 30
    }
  }
}
```

受信内容:

```
event: message
data: {
  "result": {
    "content": [
      {
        "type": "text",
        "text": "42"
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 2
}
```

</details>



### [Optional]本物のAI Agent(VSCode GitHub Copilot Agent)に繋ぐ

__*VSCodeが最新版に更新され、かつGitHub Copilotのアカウントを持つことが前提です。*__

任意のフォルダをVS Codeのプロジェクトとして開き、 下記の内容で`.vscode/settings.json`ファイルを作ります。

```settings.json
{
  "mcp": {
    "inputs": [],
    "servers": {
      "Demo-HTTP-MCP-Server": {
        "type": "http",
        "url": "http://localhost:3000/mcp",
      }
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/eed7660a-72e9-44a1-ba42-9246de103977.png)

前文で作ったStreamable HTTP版のMCPサーバーを起動し、VS CodeのCopilotをAgentモードにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/640b642b-64b1-4078-b09d-453c29df30c2.png)

ツールのマークをクリックすると、いっぱいのMCPツールの中に、先ほど自作したものも出ています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/d26bb9df-0571-4901-b36e-a196436c5285.png)

試しに「MCPツールを使って、12足す20を計算してください。」と聞くと、MCPツールのアクセス申請が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/2ee3945e-91c0-413b-9f5c-458b01f7f0e5.png)

「続行」を押すと、ちゃんとMCPツールで計算されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/418b1fa6-181b-47c3-83c1-1a0e4541db67.png)


# 参考
- [MCP サーバーの Streamable HTTP transport を試してみる](https://azukiazusa.dev/blog/mcp-server-streamable-http-transport/)
- [MCP (Model Context Protocol) の仕組みを知りたい！](https://qiita.com/megmogmog1965/items/79ec6a47d9c223e8cffc#%E5%8B%95%E4%BD%9C%E4%BE%8B-1%EF%B8%8F%E2%83%A3-%E8%87%AA%E4%BD%9C%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88)
- [TypeScript版の公式SDK で自作MCPクライアントを試す（自作MCPサーバーのツール利用） #JavaScript - Qiita](https://qiita.com/youtoy/items/8d6fc082e8c4b23dcd21)
- [MCP Server のデバッグ/ロギングツールを作りました](https://qiita.com/megmogmog1965/items/9d4597b42eba18b5e233)
- [modelcontextprotocol/typescript-sdk: The official Typescript SDK for Model Context Protocol servers and clients](https://github.com/modelcontextprotocol/typescript-sdk)