記事内に広告が含まれています

HonoとCloudflare Workersで自作MCPサーバーを作って、ChatGPTから地震情報を取得できるようにする!

テクノロジー
この記事は約16分で読めます。

こんにちは!なりかくんです。
みなさん、生成AIは使っていますか?私は最近よく生成AIを使っています。ちなみに、この記事は人間が書いています。

生成AIって便利なんですけど、ハルシネーション(嘘をつくこと)を起こしたり情報ソースが無い事を言い出すことが多々あります。

そんな時に便利なのが、MCPと呼ばれる共通規格です。今回は、このMCPをHonoとCloudflare Workersで一から作ってみようと思います。この記事では最終的に、地震情報のAPIから最新の地震情報を取得するところまで行きたいと思います。

MCPとは?

最近は多くのサービスが「MCPに対応しました!」と言っていますが正直「MCPってなんやねん」ってなっている方も多いかと思います。私も一時期はその人間でした。

ただし、使い方や便利さに気づくとやめられなくなる代物なんです。MCPの正式名称はModel Context Protocolで、生成AIと外部のデータや業務ツールを繋げるための共通規格のことです。

有名どころのSaaSで言うと、freeeやマネーフォワードなどの会計サービスでは、売上確認や経費分析、請求系に関して、MCPを使ってデータ取得を行わせることができたり、SlackやNotion、Googleドライブと連携することも可能で、Googleドライブと連携すれば、許可した範囲のファイルをAIから参照できるようになるため、社内資料や個人ドキュメントの検索にも活用できます。

裏話なんですけど、ChatGPT Enterpriseを契約することで社内知識というモードが増えて、すべてのデータを読めるようになる機能があるので便利だったりします。また、エンタープライズ向けということでモデル学習に使われないという安全面もあるので、ご家庭に複数アカウントいかがでしょうか。

余談はさておき、今回この記事では、MCPと呼んでいますが実際にはMCPサーバーを作ります。

どのような共通規格なの?

MCPサーバーは、よく使われる代表的な機能として以下の3つがあります。

  • Tools AIが実行できる機能
    • DBを検索する、地震情報を取得する など
  • Resources AIが読めるデータ
    • ファイル、ログ、ドキュメント、APIの結果 など
  • Prompts よく使う指示テンプレート
    • 「このログを解析して」「このコードをレビューして」

通信は基本的に、JSON-RPCという形式で行われます。この形式は、JSONと入っている通りJSON形式で通信が行われるのですが、JSON形式でやりとりをするためルールが定められている規格をJSON-RPCといいます。

JSON-RPCの詳細な仕様書 : https://www.jsonrpc.org/specification

Honoでプロジェクトを作ろう!

まず、Honoを使ってHello worldが返ってくるプログラムを作ってみます。今回私の環境は、Node.js v24.14.0のmacOSの環境で作成しています。

まず最初に、npx createでhonoのプロジェクトを作りましょう!

npm create hono@latest .

実行すると、どのテンプレートを使うかと質問があります。今回は、Cloudflare Workersにデプロイをしようと思うので、Cloudflare workersを選びます。

その後は、以下の出力の通りに進めました。「🎉 Copied project files」と出てきたらHonoのプロジェクト作成は成功です!

Need to install the following packages:
create-hono@0.19.4
Ok to proceed? (y) y

> npx
> "create-hono" .

create-hono version 0.19.4
✔ Using target directory … .
✔ Which template do you want to use? cloudflare-workers
✔ Do you want to install project dependencies? Yes
✔ Which package manager do you want to use? npm
✔ Cloning the template
✔ Installing project dependencies
🎉 Copied project files

さっそくHonoを起動してみようと思います。npm run devでローカル環境で動作させることができます。

npm run dev

起動できたら、http://localhostから始まるURLが表示されるので、そのリンクを開いてみましょう!

開いてみると、Honoの初期テンプレートである「Hello Hono!」と表示されます。もしこれが表示されていなければ、どこかで問題が発生しているので、エラー文を一度生成AIに投げてみましょう。おそらく解決できるでしょう。

現在の「Hello Hono!」と表示しているコードがこちらです。「/」のURLにGETリクエストが飛んできたら、「Hello Hono!」と返すだけの単純なプログラムです。

// src/index.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

export default app

実際にMCPサーバーを作る

MCPには、先ほども説明した通りToolsなどの機能がありますが、今回はToolsだけで一旦作ってみようと思います。

今回はMCPの規格として2025-06-18というプロトコルバージョンを使います。このプロトコルバージョンの仕様はここに書かれています。
https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.ts

もし興味がある人はぜひ読んでみてください。
ちなみに、通信フローについては、以下の記事が分かりやすかったので、あわせて読むと理解しやすいと思います。

手作りして学ぶMCPの仕組み

MCPの仕組みを理解して、まず何を入力しても「hello world」というテキストを返すMCPを作りました。それが以下のコードです。

import { Hono } from 'hono'

const app = new Hono()

type JsonRpcRequest = {
  jsonrpc: '2.0'
  id?: string | number | null
  method: string
  params?: any
}

const serverInfo = {
  name: 'earthquake-mcp',
  version: '1.0.0',
}

app.get('/', (c) => {
  return c.text('Hello world')
})

app.post('/mcp', async (c) => {
  const req = await c.req.json<JsonRpcRequest>()

  if (req.method === 'initialize') {
    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        protocolVersion: '2025-06-18',
        capabilities: {
          tools: {},
        },
        serverInfo,
      },
    })
  }

  if (req.method === 'tools/list') {
    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        tools: [
          {
            name: 'hello_world',
            description: '何を入力しても Hello world を返します',
            inputSchema: {
              type: 'object',
              properties: {},
            },
          },
        ],
      },
    })
  }

  if (req.method === 'tools/call') {
    const toolName = req.params?.name

    if (toolName !== 'hello_world') {
      return c.json({
        jsonrpc: '2.0',
        id: req.id,
        error: {
          code: -32602,
          message: `Unknown tool: ${toolName}`,
        },
      })
    }

    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        content: [
          {
            type: 'text',
            text: 'Hello world',
          },
        ],
      },
    })
  }

  return c.json({
    jsonrpc: '2.0',
    id: req.id,
    error: {
      code: -32601,
      message: `Method not found: ${req.method}`,
    },
  })
})

export default app

実際にこのコードを実装して、リクエストを送ってみようと思います。

MCPクライアントがMCPサーバーに接続する時、最初に初期化を行うフェーズのリクエストを模擬的に送ってみましょう。

curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

結果として、以下のようなJSONが返ってきます。中身としてはプロトコルバージョンやMCPの名前、バージョンが返ってきますね。

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"serverInfo":{"name":"earthquake-mcp","version":"1.0.0"}}}

次に、tools/callでhello_worldという関数を呼び出してみようと思います。

curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"hello_world","arguments":{}}}'

送ってみると、しっかりとコード通りにHello worldと返ってきました。MCPサーバーとして動いていそうですね。

{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Hello world"}]}}

Cloudflareにデプロイする

では、実際にインターネット上にデプロイしてみようと思います。wranglerでまずCloudflareと接続します。

npx wrangler login

コマンドを打つと、ログインURLが表示される or 自動でブラウザが起動して認証画面に移動するので、アカウントを選択して、権限を確認し「Authorize」をクリックして承認します。

承認が完了したら、コンソール画面の方に戻って「Successfully logged in.」と表示されます。これでログインは完了です。

最近は、AIコーディングエージェントを自動でインストールできるオプションが表示されるみたいですね。Cloudflare MCPを使えるよ〜って言ってるんだと思います。

今回は不要なので、noを選択しました。

では、ログインも成功したところでデプロイしてみましょう。npm run deployで簡単にデプロイすることができます。

npm run deploy

デプロイが完了したら、デプロイ先のURLが表示されます。これがMCPサーバーのURLとなります。

> deploy
> wrangler deploy --minify

 ⛅️ wrangler 4.104.0
────────────────────
Total Upload: 20.48 KiB / gzip: 8.46 KiB
Worker Startup Time: 1 ms
Uploaded earthquake-mcp (3.68 sec)

Deployed earthquake-mcp triggers (1.91 sec)
  https://earthquake-mcp.narikakun.workers.dev
Current Version ID: efb9bd8c-db26-4104-b1e4-6ca651cf5865

ChatGPTにMCPをインストールする

では、今回はChatGPTにMCPをインストールしようと思います。

ChatGPTの設定画面を開いて、「アプリ」を開きます。有効化されたアプリの一番下に「高度な設定」があると思うので開きます。

開いたら、まず「開発者モード」を有効化します。開発者モードを有効にしないと自作したMCPを追加できません。

開発者モードを有効化したら、少し上に「アプリを作成する」ボタンが現れますので、押します。

押したら、新しいアプリを作るモーダルウィンドウが開きます。ここに、作ったMCPサーバーの情報を入力します。

名前は、分かりやすいMCPの名前をつけます。今回は最終的に地震情報を取得するMCPにするので、「Earthquake MCP」と名付けました。

接続は、先ほどCloudflare WorkersにデプロイしたURLに「/mcp」を付け足したURLを入力します。認証は、今回実装していないので「認証なし」を選択して、リスクのチェックをつけて、「作成する」をクリックします。

少し待つと、ChatGPTと作ったMCPを接続するモーダルウィンドウが開きます。自分が作ったMCPなので「接続する」をクリックします。

これで、接続は完了です。作ったMCPの情報が正しく表示されているか確認しておきましょう。

アプリ詳細の下の方に行くと、アクションとして hello_world が表示されていれば問題ないです!

実際に試しに作ったMCPを使ってみる

では、実際にMCPを使ってHello worldが返ってくるか確認してみましょう。
「Earthquake MCPを使って」と投げてみます。すると、しっかりと「Hello world」と返ってきました。Cloudflare Workers側でアクセスログを確認すると、しっかりとリクエストが飛んできていたので、MCPサーバーを使われていることがわかります。

地震情報のJSONを返すMCPを作る

では、次は実際に地震情報のJSONを返してくれるMCPサーバーを作ります。地震情報を取得するAPIは、「nakn.jp Earthquake List API」を使おうと思います。

このAPIの仕様書は、以下に公開されてます。
https://ntool.online/apidoc/earthquakeList

シンプルに使うのであれば、https://earthquake-api-v2.nakn.jp/api/v2/list にGETリクエストを投げると最新100件の地震情報を返してくれます。

以下が地震情報のAPIを問い合わせてJSONをそのまま返すMCPサーバーのコードです。

import { Hono } from 'hono'

const app = new Hono()

type JsonRpcRequest = {
  jsonrpc: '2.0'
  id?: string | number | null
  method: string
  params?: any
}

const serverInfo = {
  name: 'earthquake-mcp',
  version: '1.0.0',
}

app.get('/', (c) => {
  return c.text('Hello world')
})

app.post('/mcp', async (c) => {
  const req = await c.req.json<JsonRpcRequest>()

  if (req.method === 'initialize') {
    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        protocolVersion: '2025-06-18',
        capabilities: {
          tools: {},
        },
        serverInfo,
      },
    })
  }

  if (req.method === 'tools/list') {
    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        tools: [
          {
            name: 'earthquake_list',
            description: '最新の地震情報一覧を取得して返します',
            inputSchema: {
              type: 'object',
              properties: {},
            },
          },
        ],
      },
    })
  }

  if (req.method === 'tools/call') {
    const toolName = req.params?.name

    if (toolName !== 'earthquake_list') {
      return c.json({
        jsonrpc: '2.0',
        id: req.id,
        error: {
          code: -32602,
          message: `Unknown tool: ${toolName}`,
        },
      })
    }

    const response = await fetch('https://earthquake-api-v2.nakn.jp/api/v2/list')

    if (!response.ok) {
      return c.json({
        jsonrpc: '2.0',
        id: req.id,
        error: {
          code: -32000,
          message: `Earthquake API error: ${response.status}`,
        },
      })
    }

    const data = await response.json()

    return c.json({
      jsonrpc: '2.0',
      id: req.id,
      result: {
        content: [
          {
            type: 'text',
            text: JSON.stringify(data, null, 2),
          },
        ],
      },
    })
  }

  return c.json({
    jsonrpc: '2.0',
    id: req.id,
    error: {
      code: -32601,
      message: `Method not found: ${req.method}`,
    },
  })
})

export default app

実際に地震情報を取得する関数にリクエストを投げてみるとAPIから取得した情報をそのまま返せていることを確認しました。

curl -X POST http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"earthquake_list","arguments":{}}}'

これを先ほどと同じようにデプロイして、ChatGPT側でMCPを再読み込みします。MCP詳細画面のアクション一覧に表示されていたらOKです!

実際に作ったMCPサーバーを使って地震情報を取得してみます。しっかりと作成したEarthquake MCPを使って地震情報を教えてくれる他、最近の地震のコメントなども追加してくれるので、実用的に使えそうですね。

最後に

今回は、HonoとCloudflare Workersを使って、MCPサーバーを作ってみました。いかがでしたか?私は最近、Discordと連携できるMCPサーバーを作ってChatGPTからDiscordに投稿できるようにしたりとよく遊んだり便利に使えるように試行錯誤しています。

みなさんもぜひMCPをうまく活用して、業務の効率化を試してみてください!

最後までお読みいただきありがとうございました!

コメント

タイトルとURLをコピーしました