5.1さらうどん

@giginetの技術ブログ。ゲーム開発、iOS開発、その他いろいろ

AI Agentからxcodeprojを操作するxcodeproj-mcp-serverを作った - SwiftSDKでのMCPサーバー開発入門

最近AIの話ばかりだけど・・・・・・。

5月末ぐらいから、Claude Codeを使ったiOSアプリ開発を試してる。Claude Maxも加入し、あまりの完成度の高さに日々めっちゃ驚いている。

ほとんどのユースケースはClaude CodeをXcodeBuildMCPに接続することで問題なく開発できているけど、xcodeprojの操作が発生した場合、Claude Codeが*.xcodeprojファイルを強引に読み書きする操作が発生することがある。

*.xcodeprojはAI readableではない*1ので、Xcodeプロジェクトの操作がよく失敗する。例えばTargetの追加、変更。また、実装は正しくできていても、新規作成したファイルがプロジェクトに追加されていなくてビルドが転けることもある。*2

xcodeproj-mcp-server

ということで、*.xcodeprojを効率よく参照、操作するためのMCPサーバー、xcodeproj-mcp-serverを作った。

公式のMCP SwiftSDKを使って実装している。Swift製のMCPサーバー実装例としてもどうぞ。

ユースケース

とりあえずiOS開発をする際にMCPとして追加しておくと、適切な場面で賢く使ってくれる。例えば前述のファイル追加時のプロジェクト操作とか。

ターゲットの追加などの複雑な操作も成功率が上がると思う。

READMEのスクリーンショットではPost Build Scriptの追加をしている。「ビルド後にswift-format実行するようにして」などと言ったファジーな指示で、自動的にlinterが設定された。とても便利。

Post Build Scriptの自動追加

実装はほとんどClaude Code

前回、ClineでGitHub Actionを作った時代*3の記事では、AIの実装を見つめていて、適宜人間が指示を出していたが、今回は最初の設計段階から、動くようになるまでほとんどオートメーション化できた。

MCPは実装単位がToolごとで分けやすいので、1 Toolごとに実装して、コミットを分けるように指示したところ、自動的にツール単位でコミットまでやってくれた。

これによって実装はClaudeに丸投げして、人間は『ELDEN RING NIGHTREIGN』をしていた。

最終成果物のコードに細かな不満はあって一部直してもらったが、全体の設計や実装精度は非の打ち所がない。

GitHub MCPで依存ライブラリの使い方を把握してもらう

開発時にはGitHubの公式MCPサーバーを有効にすることで、依存ライブラリのREADMEを見に行ってもらい、そのAPIを使ったコードを生成してもらうことができた。

今回はMCP SwiftSDKと、*.xcodeprojの操作にtuist/Xcodeprojを使っているため、これらを調べてもらうことで、特に指示なくライブラリを使ってくれた。賢すぎる。デフォで装備しておいてよいMCP。

MCPサーバーの開発とデバッグ

MCPサーバーのデバッグは若干面倒だった。動作確認には公式のinspectorを使う。

$ npx @modelcontextprotocol/inspector swift run -c release

これで、以下のようなWebアプリが立ち上がり、引数毎に正しい出力ができているかを確認できる。

modelcontextprotocol/inspectorでMCPの挙動をテスト

これだけではあくまで、引数を組み立てるLLM部分のテストをすることができないので、E2Eテストのためには、最終的に開発中のMCPを実際に各種ツールから呼び出す必要がある。

LLMからtoolを呼び出すための引数は、自然言語で書かれたinstructionを元に組み立てられるが、この辺りがstrictではないため不安が残る。 Claude Codeでは動かせたが、モデルによっては正しく引数を組み立てられないといったケースはありそう。

distributionがとにかく面倒問題

MCPサーバーを使ってもらうに当たって、いちいちバイナリをダウンロードして設置してもらったり、ましてやビルドしてもらう必要があるのは、導入がとても面倒。設定ファイルにワンライナーを記述するだけで動いて欲しい。

その障壁として、Swift製のプロダクトあるあるのバイナリのdistribution手法が確立していないという問題がある。

MCPの開発環境として主流なNodeやPythonでは、npxuvxのような、リモートパッケージの取得、ビルド、キャッシュ、実行を一気に行ってくれる仕組みがメジャーだ。一方で、Swiftのエコシステムにはそんなものはない。

最終的に、バイナリをDockerコンテナに包み、Docker経由で実行することで使い勝手を実現している。

$ claude mcp add xcodeproj -- docker run --rm -i -v $PWD:/workspace ghcr.io/giginet/xcodeproj-mcp-server /workspace

macOSでしか動かさないバイナリを、高級バイナリ配布手法としてコンテナに隠蔽してしまうのは、とても無駄な設計だが、ほかのMCPサーバーと作法を併せつつ、依存も減らせるのでこのような仕組みにした。また、原理的に指定ディレクトリ以外を操作できなくなるので、安全性の面でも優れている。

Dockerでビルド済みのMCPサーバーを配布するのは、ほかの主要な実装を見ているとわりとメジャーな手法に見えた。代表例として、先述のGitHub MCPはGoのバイナリを同様の手法で配布している。

ローカルファイルシステムの参照が大変問題

Dockerを使った配布はコンテナからホスト側のファイルシステムを参照しづらいという問題がある。xcodeproj-mcp-serverはその性質上、ローカルファイルの取得、編集が不可欠だ。

この辺の解決には、Anthropic公式のfilesystem MCPを参考にした。このMCPサーバーも、ローカルファイルシステムの参照が必須にもかかわらず、Docker配布を採用している。

仕組みとしては、Docker起動時に現在のワークツリーをコンテナ上にマウントして、MCPサーバー側で相対パスをマウント先のパスに読み替えるだけという仕組みだ。

例えばxcodeproj-mcp-serverの場合、プロジェクトパスが/workspaceにマウントされる。MCPサーバーにはMyApp.xcodeprojのようなプロジェクトルートからの相対パスが渡されるので、MCPサーバー側で/workspace/MyApp.xcodeprojのように読み替えをしている。

今後のSwiftバイナリ配布手法

Swiftのバイナリ配布の問題は悩ましい。これまでにSwiftバイナリ配布のためのツール、nestを推進したり、Artifact Bundleの配布を簡単にするための仕組み作りを進めてきた。 ただ、現状nestでは、設定不要でバイナリだけをアドホックに実行できる仕組みがない。

今回は試さなかったが、miseで配布するという方法もあるなと考えていた。実際にmise x経由でのインストールを推奨しているMCP実装も散見される。

miseにはexperimentalとしてSwift Packageバックエンドもサポートされているが、現状バイナリを使う手法ではなく、インストール時にビルドするだけのようだ。

結局、デファクトスタンダードなバイナリレジストレーションの仕組みがないことが大きな問題なので、今後もしばらくこの問題と向き合っていく必要がありそう。

MCPサーバーを雑に立てよう!

Claude CodeによるMCP自体の実装は、細かなコード品質を妥協すれば驚くほど上手くワークした。AI自身をAIを使ってアップグレードしていってる感覚がありおもしろい。ほぼ丸投げで作れるので、プロジェクト固有のユースケースに対しても機動力高く作っていきやすい気がする。

xcodeproj-mcp-serverはiOSアプリ開発の際に併せて有効にしておいて損はないので、どうぞご利用ください。

*1:もちろん人間も読めない

*2:この問題はあらかじめディレクトリ構造をプロジェクトGroupに一致させる設定にしておくと解決する

*3:とはいえ4ヶ月前!