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_ping、chimr_get_today_events、chimr_get_events、chimr_get_events_range、chimr_join_video_meeting、chimr_show_notification といった tool を公開しています。Claude などの MCP クライアントから「今日の予定を見せて」「次の会議に参加して」「ToDo が終わったら通知して」のように頼めば、対応する tool が呼び出される設計です。
「ユーザーに JSON を書かせない」を CLI に倒すと収まりがいい
Chimr の場合、command に渡したい絶対パスが Chimr 自身しか正しく答えられない情報でした。手順書を整備する選択肢もありましたが、CommandLine.arguments[0] という Swift の標準機能で完結する以上、Chimr 自身に答えさせるほうが手数が少なくて済みます。--mcp-install と --mcp-config を足してからは、MCP セットアップに関する問い合わせが目に見えて減りました。
MCP サーバーを配布するアプリ側で「クライアント向けに setup を吐く CLI フラグを用意しておく」アプローチは、手順書を維持し続ける運用よりも安定します。今後ほかの自前 MCP サーバーを作るときも、同じ設計から始めるつもりです。
