複数MCPツールを同時に利用可能なChatbotを最小構成で作ってみる(Google Gemini版)

# はじめに
前編[「MCPサーバーの最小構成を作ってみる(Node.js版) 」](https://qiita.com/jerrywdlee/items/d20de494f47091babf7e)では、[Model Context Protocol(MCP)](https://docs.anthropic.com/ja/docs/agents-and-tools/mcp)プロトコルの仕様を軽く解説し、OpenAIやClaudeなどのAPIを持たなくてもMCPツールの開発及び検証の方法を紹介しました。

本編では、実際にAIサービス(Google Gemini)のAPIを使って、本物のAI Agentを開発してみます。

# シリーズ記事
| №  | 記事                                          | 内容                                  |
| --- | --------------------------------------------- | ----------------------------------------- |
| 1   | [MCPサーバーの最小構成を作ってみる(Node.js版)](https://qiita.com/jerrywdlee/items/d20de494f47091babf7e) | AIサービスのアカウントを持たなくてもMCPツールの開発ができます |
| 2   | [簡単なMCP利用可能なChatbotを作ってみる(Google Gemini版)](#) | 本編 |
| 3   | MCPサーバーの最小構成を作ってみる(Python版) | Coming Soon |
| 4   | 簡単なMCP利用可能なChatbotを作ってみる(Azure OpenAI版) | Coming Soon |
| 5   | RAGをMCPサーバーとして利用し、簡単な映画推薦Chatbotを作ってみる(Azure OpenAI版) | Coming Soon |


# 実装
## 実行環境

- [Node.js](https://nodejs.org/ja) (v20以上)

## Google Gemini APIキーの申請
下記のページにアクセスし、 **「Google AI Studio で無料で取得」** をクリックして、案内に従えば無料に取得できます。

https://ai.google.dev/gemini-api/docs/quickstart?hl=ja

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/9d73cf17-f073-41e4-8b18-da610a5ca789.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/994653d1-9e4f-48dc-a510-93c673f801e9.png)

Google GeminiのAPIキーを取得したら、 環境変数 `GEMINI_API_KEY` をセットアップします。


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

```sh
npm i @google/genai @modelcontextprotocol/sdk dotenv
```

また、試したいMCPツールがあれば、事前にインストールすると、後ほどスムーズに実行できるので、 事前インストールをおすすめします。

### Geminiチャットボットの実装

まずはMCPツールを持たない単純なAIチャットボットを作ります。使ったのはGemini SDKの[マルチターンの会話（チャット）](https://ai.google.dev/gemini-api/docs/text-generation?hl=ja#multi-turn-conversations)APIです。

```gemini_demo.js
import { GoogleGenAI } from '@google/genai';
import { createInterface } from "readline/promises";
import dotenv from "dotenv";
dotenv.config(); // .envファイルを読み込む

// クライアントを設定
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const chat = ai.chats.create({
  model: "gemini-2.0-flash",
  config: {
    systemInstruction: "あなたはMCPを使って、いろんな操作を行うことができます。",
  },
});

// ユーザー入力用のreadlineインターフェースを作成
const rl = createInterface({
  input: process.stdin,
  output: process.stdout,
});

// readlineインターフェースのプロンプトを設定
rl.setPrompt(`Waiting for chat input... \n> `);
rl.prompt();

rl.on("line", async (line) => {
  const trimmedLine = line.trim();

  // 入力が空の場合は再度プロンプトを表示
  if (!trimmedLine) {
    rl.prompt();
    return;
  }

  try {
    const response = await chat.sendMessage({
      role: "user",
      message: { type: "text", text: trimmedLine },
    });

    console.log("Gemini: ", response.text);
  } catch (error) {
    console.error("Error: ", error);
  } finally {
    rl.prompt();
  }
});
```

Node.jsの[Readline](https://nodejs.org/api/readline.html)を使って、非同期的にコマンドラインの入力を取得し、GeminiのチャットAPIで送信しています。


**実行結果:**

```sh
$ node gemini_demo.mjs 
Waiting for chat input...
> こんにちは
Gemini:  こんにちは！何かお手伝いできることはありますか？

Waiting for chat input...
>
```

## MCPツールの活用

今回の記事は前回で作られた[「Streamable HTTPを使ったMCPサーバー」](https://qiita.com/jerrywdlee/items/d20de494f47091babf7e#streamable-http%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Fmcp%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC) `add_http.mjs`、及び公式の[Filesystem MCP Server](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem)を使って `Streamable HTTP` と `STDIO` 二種類のClientをDemoします。

**[注意]:**　Google Geminiは現在(2025-06-18)MCPサポートが試験運用であるため、ツールのみがサポートされ、リソースやプロンプトはサポートされないとのことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/2e8ba92a-df47-4d46-9848-ff3409a0dc91.png)


### MCP Clientの初期化
尺を考慮して、ここは追加分のコードのみ展示します。フルバージョンのソースコードをみたい方は文末の **[Full Souce Code](#full-souce-code)** を参照してください。

```js
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

// MCPサーバーの設定
const stdioTransport = new StdioClientTransport({
  command: "npx",
  args: ['-y', '@modelcontextprotocol/server-filesystem', './'],
});

const client1 = new Client(
  {
    name: "server-filesystem",
    version: "1.0.0"
  }
);

await client1.connect(stdioTransport, { timeout: 30000 });

// HTTP Transportの設定
const httpTransport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp"),
  {
    sessionId: undefined,
    requestInit: { headers: { } },
  },
);

const client2 = new Client(
  {
    name: "demo-http-client",
    version: "1.0.0"
  }
);

await client2.connect(httpTransport);
```

### Geminiにツールを提供する

ここも変更点だけ表示します。。フルバージョンのソースコードをみたい方は文末の **[Full Souce Code](#full-souce-code)** を参照してください。

```diff
- import { GoogleGenAI } from '@google/genai';
+ import { GoogleGenAI, mcpToTool } from '@google/genai';

...

// クライアントを設定
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const chat = ai.chats.create({
  model: "gemini-2.0-flash",
  config: {
    systemInstruction: "あなたはMCPを使って、いろんな操作を行うことができます。",
+    tools: [ mcpToTool(client1), mcpToTool(client2) ],
  },
});

...

```

見た通り、Gemini SDKは `mcpToTool()`という便利メソッドを提供していて、標準のMCP SDKのClientをGeminiが利用できるツール形式に変換してくれます。変換したツールは、Geminiのサービスを初期化する際に、`tools`フィールドに提供すれば利用できます。

**実行結果:**

まずは別のターミナルで `node add_http.mjs` で自前のMCPサーバーを立ち上げます。

```sh
$ node gemini_demo.mjs 
Secure MCP Filesystem Server running on stdio
Allowed directories: [
  'PATH/TO/YOUR/DIR'
]
Waiting for chat input...
> こんにちは、現在使えるMCPツールの一覧を出してください。
Gemini:  現在使えるMCPツールは以下の通りです。

- read_file
- read_multiple_files
- write_file
- edit_file
- create_directory
- list_directory
- directory_tree
- move_file
- search_files
- get_file_info
- list_allowed_directories
- add_test

また、アクセス可能なディレクトリは `PATH/TO/YOUR/DIR` です。
Waiting for chat input...
> MCPツールadd_testは何ができます？
Gemini:  MCPツール`add_test`は、与えられた2つの数値（aとb）を足し算し、さらにその結果に10を足した値を返します。

Waiting for chat input...
> このツールで12+20を計算して
Gemini:  12 + 20 の計算結果は 42 です。
Waiting for chat input...
> 上記結果を./result.csvに保存して
Gemini:  結果を ./result.csv に保存しました。
Waiting for chat input...
> ./result.csvの内容を読み取って
Gemini:  ./result.csv の内容は "42" です。
Waiting for chat input...
>
```

## Full Souce Code

<details><summary>gemini_demo.mjs</summary>

```gemini_demo.mjs
import { GoogleGenAI, mcpToTool } from '@google/genai';
import { createInterface } from "readline/promises";
import dotenv from "dotenv";
dotenv.config(); // .envファイルを読み込む

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

// MCPサーバーの設定
const stdioTransport = new StdioClientTransport({
  command: "npx",
  args: ['-y', '@modelcontextprotocol/server-filesystem', './'],
});

const client1 = new Client(
  {
    name: "server-filesystem",
    version: "1.0.0"
  }
);

await client1.connect(stdioTransport, { timeout: 30000 });

// HTTP Transportの設定
const httpTransport = new StreamableHTTPClientTransport(
  new URL("http://localhost:3000/mcp"),
  {
    sessionId: undefined,
    requestInit: { headers: {} },
  },
);

const client2 = new Client(
  {
    name: "demo-http-client",
    version: "1.0.0"
  }
);

await client2.connect(httpTransport);


// クライアントを設定
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const chat = ai.chats.create({
  model: "gemini-2.0-flash",
  config: {
    systemInstruction: "あなたはMCPを使って、いろんな操作を行うことができます。",
    tools: [ mcpToTool(client1), mcpToTool(client2) ],
  },
});

// ユーザー入力用のreadlineインターフェースを作成
const rl = createInterface({
  input: process.stdin,
  output: process.stdout,
});
// readlineインターフェースのプロンプトを設定
rl.setPrompt(`Waiting for chat input... \n> `);
rl.prompt();

rl.on("line", async (line) => {
  const trimmedLine = line.trim();

  // 入力が空の場合は再度プロンプトを表示
  if (!trimmedLine) {
    rl.prompt();
    return;
  }

  // "exit"と入力された場合は終了
  if (trimmedLine.toLowerCase() === 'exit') {
    console.log("Exiting...");
    // readlineインターフェースを閉じる
    rl.close();
    // クライアントを閉じる
    console.log("Closing clients...");
    await Promise.all([
      client1.close(),
      client2.close(),
    ]).catch(console.error);
    process.exit(0);
  }

  try {
    const response = await chat.sendMessage({
      role: "user",
      message: { type: "text", text: trimmedLine },
    });

    console.log("Gemini: ", response.text);
  } catch (error) {
    console.error("Error: ", error);
  } finally {
    rl.prompt();
  }
});

```

</details>



# 余談： Geminiの読み方
天文学や西洋占星術に詳しい方だと、 Gemini は「ふたご座」を意味すると気付くでしょう。星座を表す時、Geminiの発音は「ジェミナイ」（英語発音: [ˈdʒɛmɨnaɪ]）ですが、 Google Gemini語源は星座ではなく、アメリカが1960年代実行した有人宇宙計画[「ジェミニ計画」](https://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%A7%E3%83%9F%E3%83%8B%E8%A8%88%E7%94%BB)ですので、故に読み方は「ジェミニ」と確定です。

まぁ、本家のビデオや宣伝資料もご丁寧に「Gemini（ジェミニ）」と書いていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87438/34363c78-531a-4427-b09e-b6280a0c1909.png)

https://youtu.be/itU3bVd6HRw?si=Iyc1dGcJFtLmiwG7


# 参考
- [知らないとやばいreadlineモジュール #Node.js - Qiita](https://qiita.com/Naoki_ganbarimasu/items/f98ab28a86720f671a01)
- [Gemini API を使用した関数呼び出し - Model Context Protocol(MCP)](https://ai.google.dev/gemini-api/docs/function-calling?hl=ja&example=chart#model_context_protocol_mcp)