QLITRE DIALY

飯ログにMCP登録機能を追加した

2026年05月03日

以前このブログでも紹介をしたが、自分は「飯ログ」というサービスをやっている。

新サービス"meshi-log"をはじめた

その名の通り飯を食ったらログに残すというサービスだ。個人的に行った飯屋を記録できるツールが欲しかったので、昨年の10月に自作して立ち上げた。技術スタックはこの日記と同じくmicroCMSとHonoX。

ちなみに一応はサービスの形をとっているが検索流入はほとんどない。

以下の写真はGoogle Search Consoleにおける3ヶ月間の検索流入結果だ。

クリック回数は合計で19回となっている。

これはどんな数字かというと、一言でいえば「誰からも見向きされていない」というような状態である。しかし、行ったお店の記録をする行為は楽しい。だから地道に続けている。

一部例外を除いて基本的に行ったお店は登録し訪問ログを残すようにしている。2026年5月3日現在で訪問記録は172件、店舗登録は125件となっている。

MCP登録機能の追加

ここからが本題。この飯ログは新しいお店に行ったらまずは店舗登録をして、それに紐づく日記を入稿する、というようなデータ構造にしている。下の図でいうshopが店舗データ、visitsが訪問日記だ。

ある程度構造的に検索をするために、外部参照として地区コードであるareaやカテゴリを示すgenreデータを持たせている。そしてこれらの外部参照データのメンテナンスが面倒くさくなってきた。

フィールドは少ないので登録自体はそこまで手間がかからない。しかし、データが増えてくると、登録してあるかどうかをまず調べる、という確認が必要だ。例えば町田市のお店にいったとして「町田市」という地区データが登録されているか調べたりする。これがなかなか面倒だ。しかも地区コードはインターネットで調べる必要があるし、また緯度経度もデータとして持たせていて、こちらも外部サイトに住所を入力して調べたり…などなど意外と店舗登録するにあたりやることは多い。

このサービスの幹の部分は「訪問日記を書く」という点にある。一方で店舗情報は決まり切った情報のため、本質的な部分ではなく、そこに時間と手間を取られていたのが課題だった。

AIで解決しようということで改修を行うことにした。

具体的にはClaudeなどのアプリからmeshi-logのMCPサーバーに接続をする。そして店名を入れて登録を依頼するとクライアントが自律的に働き店舗の登録ができるようにした。mcpサーバー自体はもともと実装していたので、これに登録系の機能を持たせた形になる。

具体的にはデモで動かしてみた動画がわかりやすい。

町田市にある「龍聖軒」というラーメン屋を登録してみた例である。

iOSでも出来る。

店名から住所をweb検索して、登録されているエリアを確認して、町田市が未登録であることを確認。

そして町田市の地区コードを調べて、エリアAPIに登録し、そのエリアを参照する形で新店舗を登録…という一連の流れを自動で行ってくれた。

店舗の説明文、おすすめフラグなどは自分で修正する必要があるが、かなりの手間を省いてくれていると感じる。

工夫した点

基本的にはClaude Codeで実装。設計部分で意識した話など。

クライアントに任せる

クライアントアプリ側で出来ることはクライアント側に任せてサーバー側の実装は最小限にした。

具体的にはiOSのClaudeアプリの利用を前提で考えた。WEB検索、緯度経度検索などは標準機能で実現可能だ。そのため、サーバー側は難しいことはせずに値を受け取って、そのままmicroCMSにWrite APIを投げる、という構成にした。

例えばWEB検索などはTavilyを使ったり緯度経度取得に関してはGoogleのgeocoding APIを使う方法も検討した。しかしこの機能を使うのは自分だけであるし、クライアント側で標準で出来るものをわざわざサーバー側で実装する意味がないよなと思い、実装しないことにした。

例えば「鶴川の龍聖軒を登録して」というメッセージをクライアントアプリが受け取ったら以下のような棲み分けで動きを行うことを期待している。

  1. WEBで住所、ジャンル、緯度経度などを調べる(クライアント機能)
  2. mcpでエリアAPI、ジャンルAPIを調べる(サーバーツール)
  3. 存在してなかったらmcpを使って登録をする(サーバーツール)
  4. 店舗登録に必要なデータが揃ったらmicroCMSに登録する。(サーバーツール)

ネイティブ機能が豊富にあるため、microCMS以外の外部APIを使わずに実装ができた点がよかった。

このあたりはmcpの店舗登録ツールの説明文に記載することで制御した。

「microCMSで新しい店舗情報を作成します。areaはエリアのコンテンツID、genreはジャンルのコンテンツIDの配列です。area_codeはJIS市区町村コードを指定してください。任意でidを指定することでカスタムコンテンツID(URLフレンドリーなスラッグなど)を設定できます。省略した場合はmicroCMSによって自動生成されます。

重要: このツールを呼び出す前に、必ずWeb検索または「Searching for place」ツールを使用して、正確な「住所(address)」および「緯度(latitude)/経度(longitude)」を調査してください。実際の店舗名を検索し、その結果から住所と座標を確認する必要があります。推測や類推、おおよその値を使用しないでください。」

server.registerTool(
      'create_shop',
      {
        title: 'Create Shop',
        description:
          'Create a new shop in microCMS. `area` is the area content ID, `genre` is an array of genre content IDs. `area_code` is the JIS municipality code. Optionally specify `id` for a custom contentId (e.g. a URL-friendly slug); if omitted, microCMS auto-generates one. IMPORTANT: Before calling this tool, you MUST look up both the precise `address` and `latitude`/`longitude` via web search or the "Searching for place" tool — search for the actual shop name and verify the address and coordinates from the results. Do NOT guess, infer, or use approximate values.',
        inputSchema: {
          id: z.string().optional(),
          name: z.string().min(1),
          address: z.string().min(1),
          latitude: z.number(),
          longitude: z.number(),
          area: z.string().min(1),
          area_code: z.string().min(1),
          genre: z.array(z.string().min(1)).min(1),
          memo: z.string(),
          is_recommended: z.boolean().default(false),
          rating: z.number().min(0).max(5).optional().default(4),
          nearest_station: z.string().optional(),
        },
      },
      async (params: {
        id?: string
        name: string
        address: string
        latitude: number
        longitude: number
        area: string
        area_code: string
        genre: string[]
        memo: string
        is_recommended: boolean
        rating?: number
        nearest_station?: string
      }) => {
        const { id, ...body } = params
        const result = await createShop({ client, contentId: id, body })
        return {
          content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
        }
      }
    )

Oauth認証

MCPサーバー自体は一般にも公開しているため、登録系は自分だけ使えるように制御する必要がある。

最初はURLにクエリ文字を含めて認証しようかと思ったが、どうもアクセスログが残るためセキュリティ的にあまりよろしくないらしい。調べたらClaudeアプリからの接続でOauth認証が使えること、またCloudflare Workersにヘルパーがあったのでそちらを使ってみるとよいらしいので実装をした。というかClaude Codeにしてもらった。正直に申し上げて認証認可回りはあまり理解していないがこれはこれから役に立ちそう。

ということで最小の構成で再現してみた。

https://github.com/qlitre/hono-mcp-oauth-sample

飯ログ自体のソースコードはこちら。

https://github.com/qlitre/meshi-log

おわりに

長い間ずっとやりたいと思っていたことが実現できてよかった。これで訪問ログを残すという本質的な作業により集中できる。ではでは。