Engineering

ChimrのMCPセットアップを手で書かせないために --mcp-install と --mcp-config を足した

  • Chimr
  • MCP
  • Claude Code
  • Swift
  • macOS

Chimr は macOS 用のメニューバーアプリで、カレンダー予定のリマインダー、Web 会議リンクの自動オープン、そして MCP (Model Context Protocol) サーバー機能を提供しています。クライアント側の設定 JSON を「ユーザーに手で書いてもらう」前提から、「Chimr 自身が吐き出す」前提に切り替えました。--mcp-install / --mcp-config / --mcp の3つの CLI フラグで、それぞれ Claude Code 用のコマンド出力 / 設定 JSON 出力 / MCP サーバー本体起動を担当しています。

# Claude Code に登録
$(/Applications/Chimr.app/Contents/MacOS/Chimr --mcp-install)
# => claude mcp add chimr --scope user -- '/Applications/Chimr.app/Contents/MacOS/Chimr' --mcp

# Claude Desktop / Cursor / Cline などの mcpServers JSON が要るクライアント向け
/Applications/Chimr.app/Contents/MacOS/Chimr --mcp-config
# => 設定 JSON を標準出力に吐く
ChimrChimr - Never Miss Another MeetingMenu bar meeting reminder with MCP server for Mac. Floating notifications with one-click join for Zoom, Meet, Teams. Let Claude access your calendar.

この記事では、MCP セットアップの最初の壁になっていた bundle path 入力、--mcp-install / --mcp-config の中で CommandLine.arguments[0] を呼ぶ理由、そして --mcp 本体が StdioTransport で動く構成を扱います。

MCP セットアップで最初に詰まるのは bundle path

Chimr の MCP サーバーは、Claude Code や Claude Desktop、Cursor、Cline といった MCP クライアントから呼び出して使います。クライアント側はおおむね「コマンドのパスと引数」を設定 JSON で持ちます。Chimr の場合は次の形です。

{
  "mcpServers": {
    "chimr": {
      "command": "/Applications/Chimr.app/Contents/MacOS/Chimr",
      "args": ["--mcp"]
    }
  }
}

command に bundle path をそのまま書きます。問題は、この絶対パスをユーザーが手で書く前提が崩れやすいことです。

  • Mac App Store からのインストールなら /Applications/Chimr.app/Contents/MacOS/Chimr
  • DMG を別の場所へ展開した人もいる
  • 開発ビルドなら Xcode の DerivedData 配下
  • 自分のホーム配下の ~/Applications/ に置いている人もいる

「アプリ名を mdfind してください、その結果のパスをコピペしてください」とドキュメントに書くのは、ドキュメントとしては成立しても、ユーザーから見れば余計な手順です。FAQ で「コピペする command の値が分からない」という問い合わせが続いたあと、Chimr 側で command を出してもらう方針に切り替えました。

CommandLine.arguments[0] で自分の bundle path を取る

実装は Swift の main.swift 冒頭で完結しています。CLI 引数を解釈する前段で、フラグを見て対応する分岐に振り分けます。

let arguments = CommandLine.arguments

if arguments.contains("--mcp-install") {
  let execPath = CommandLine.arguments[0]
  let scope = arguments.contains("--local") ? "local" : "user"
  print("claude mcp add chimr --scope \(scope) -- '\(execPath)' --mcp")
  exit(0)
}

CommandLine.arguments[0] には、プロセスを起動したコマンドが渡したパス(C/POSIX の argv[0] 相当)が入ります。MCP クライアントは設定 JSON の command フィールドに絶対パスを書いて Chimr を起動するため、この経路では argv[0] から実行ファイルのフルパスが取れます。Mac App Store からインストールされた Chimr なら /Applications/Chimr.app/Contents/MacOS/Chimr、DMG を別の場所に展開していればそのパスが返ります。ユーザーがどこに置いていても、Chimr 自身が「自分の path」を答えます。

--mcp-install は標準出力に claude mcp add chimr ... を吐きます。シェルでそのまま実行してもらえる形なので、ユーザー側の作業は次の1行になります。

$(/Applications/Chimr.app/Contents/MacOS/Chimr --mcp-install)

--local を付ければ --scope local、付けなければ --scope user に切り替わります。Claude Code をユーザー全体で使うか、現在のプロジェクトでだけ使うかをここで選びます。

JSON が要るクライアント向けには --mcp-config

Claude Code には claude mcp add という CLI コマンドが用意されていますが、Claude Desktop や Cursor、Cline などの MCP クライアントは設定 JSON を直接編集する形を取ります。こちら向けには --mcp-config を用意しました。

if arguments.contains("--mcp-config") {
  let execPath = CommandLine.arguments[0]
  let escapedPath = execPath
    .replacingOccurrences(of: "\\", with: "\\\\")
    .replacingOccurrences(of: "\"", with: "\\\"")
  let config = """
    {
      "mcpServers": {
        "chimr": {
          "command": "\(escapedPath)",
          "args": ["--mcp"]
        }
      }
    }
    """
  print(config)
  exit(0)
}

CommandLine.arguments[0] のパス文字列をそのまま JSON に埋め込むと、パスにバックスラッシュやダブルクォートが含まれる場合に壊れます。JSON String のエスケープを最低限通してから出力しています。クライアント側の mcpServers セクションへコピペしてもらえれば、そのまま動きます。

--mcp が呼ばれたら StdioTransport で MCP サーバーを起動する

3つ目のフラグ --mcp が、実際の MCP サーバー本体を起動します。

if arguments.contains("--mcp") {
  let semaphore = DispatchSemaphore(value: 0)
  Task {
    do {
      try await MCPServer.run()
    } catch {
      fputs("MCP Server error: \(error)\n", stderr)
      exit(1)
    }
    semaphore.signal()
  }
  semaphore.wait()
  exit(0)
}

MCPServer.run() の中身は MCP の Swift SDK を使って Server を組み立て、StdioTransport を渡して起動します。クライアントは Chimr のプロセスを command + args で立ち上げ、その標準入出力越しに MCP プロトコルを交わします。stdio 経由なので、Chimr の GUI を起動する代わりに「サブプロセスとして CLI 実行」する経路です。

サーバーは chimr_pingchimr_get_today_eventschimr_get_eventschimr_get_events_rangechimr_join_video_meetingchimr_show_notification といった tool を公開しています。Claude などの MCP クライアントから「今日の予定を見せて」「次の会議に参加して」「ToDo が終わったら通知して」のように頼めば、対応する tool が呼び出される設計です。

App StoreChimr - Meeting Reminder App - App StoreDownload Chimr - Meeting Reminder by Labee LLC on the App Store. See screenshots, ratings and reviews, user tips and more games like Chimr - Meeting Reminder.

「ユーザーに JSON を書かせない」を CLI に倒すと収まりがいい

Chimr の場合、command に渡したい絶対パスが Chimr 自身しか正しく答えられない情報でした。手順書を整備する選択肢もありましたが、CommandLine.arguments[0] という Swift の標準機能で完結する以上、Chimr 自身に答えさせるほうが手数が少なくて済みます。--mcp-install--mcp-config を足してからは、MCP セットアップに関する問い合わせが目に見えて減りました

MCP サーバーを配布するアプリ側で「クライアント向けに setup を吐く CLI フラグを用意しておく」アプローチは、手順書を維持し続ける運用よりも安定します。今後ほかの自前 MCP サーバーを作るときも、同じ設計から始めるつもりです。