投稿者: 毎輝高野

  • デジタル庁が政府AI源内をオープンソース公開 — コードの中身と行政DXの現在地

    デジタル庁が政府AI源内をオープンソース公開 — コードの中身と行政DXの現在地

    2026年4月24日、デジタル庁のガバメントAI源内(げんない)のソースコードがGitHubで公開された。リポジトリを開いてまず目に入るのは、CONTRIBUTING に書かれた一文だ。Pull Requestは受け付けていない。Issueも、データ損失やサービス障害、法令違反、重大なアクセシビリティ障壁に限定されている。

    多くのOSSプロジェクトがPR歓迎の文言を掲げるなか、真逆の方針が堂々と書かれている。最初は違和感を覚えるが、読み進めるほどデジタル庁なりの筋が見えてくる。

    そもそも源内とは何か

    源内は、デジタル庁が内製で開発・運用している政府職員向けの生成AI利用環境だ。ガバメントクラウド上に構築されており、2025年5月にデジタル庁内部での提供が始まった。名前の由来は、Generative AI を縮めた GenAI の音読みと、エレキテルを復元した江戸時代の発明家・平賀源内を重ねたもので、役所の命名としてはかなり攻めている。

    アーキテクチャは4層に分かれている。一番下がガバメントクラウドのコンピューティングリソース、その上がAIエンジン層で、ここで複数のLLMを切り替えながら実行できる。さらにAPI層が既存の府省庁システムと繋がり、最上位には行政実務に特化した20種類以上のAIアプリケーションが並ぶ。

    アプリ層の顔ぶれが興味深い。汎用のチャット、要約、校正に加えて、法制度調査を支援する Lawsy、国会答弁検索、公用文チェッカーといった業務特化型が揃う。法律条文を横断検索して回答するようなAIは、民間のチャットボットラッパーでは手が出しにくい領域になる。

    ここで見逃せないのが、源内そのものがさらに上流のOSSを土台にしている事実だ。ベースになっているのは、AWS Japan のボランティアが中心になって開発している Generative AI Use Cases、略して GenU。民間OSSを政府が拡張して庁内で運用し、その拡張分をふたたびOSSとして戻している。往復構造ができあがっているわけだ。

    利用実績レポートから見える現場の姿

    ここからが面白い。デジタル庁は2025年8月、職員による源内の利用実績を公開しており、数字を追うとかなり具体的な状況が見えてくる。

    3ヶ月間の集計で、対象約1,200人のうち950人が利用。利用率にして約80%にのぼる。総利用回数は65,032回、1人あたり平均70回。週ごとの利用回数は3,000〜6,000回のレンジで推移している。業務効率化を実感したかというアンケートでは、110人中79.1%が肯定的に回答した。

    具体的な時短事例も公開されている。Teams会議の議事録作成で10分程度、VBAマクロの自動生成で数時間、マニュアル検索で30分〜1時間の短縮。役所仕事の地層を成しているのが議事録と表計算で、そこが素直に省力化できている事実は、そこそこ効いている。

    一方で、同じレポートのなかで目を引くのは、使われ方の偏りのほうだ。ヘビーユーザー150人以上が3ヶ月で100回以上使っているのに対して、170人は5回未満にとどまる。さらに踏み込むと、係員級の半数以上が50回以上使っている一方で、課長級の半数は利用実績ゼロ、という段差が出ている。民間から出向してきた専門人材は最高利用率を示した。

    これは源内の性能の話ではなく、組織とリテラシーの話だ。末端で使うほどメリットの大きい道具を、権限を握っている層が触っていない。下から有効性を訴えても、上にピンと来ていなければ、申請フローや許容される使い道は広がらない。OSS公開というニュースの裏には、この上の層にどう触らせるかという、技術では解けない宿題が残っている。

    Lawsy開発チームの内幕

    源内を語るうえで、法制度調査AI Lawsy(ロージー)は外せない。もとはデジタル庁主催の法令×デジタルハッカソンから生まれたプロトタイプで、その後、源内の行政実務特化アプリのひとつとして磨き上げられていった。

    開発チームは PolicyGarage の note に詳細な記録を残している。コアメンバーは3名。国家公務員でデータ分析とAIを担当する鈴木宏和氏、DSPy実装を中心に技術面を支えた白川達也氏、フロントエンドの神代洋明氏。この小さなチームが、本番LLMに OpenAI GPT-4o、フレームワークにスタンフォード大学発のリサーチツール STORM が採用する DSPy、フロントエンドに Streamlit を選んで作り上げた。

    興味深いのが、途中でターゲットペルソナを変更した経緯だ。当初は専門家レベルの法令調査を助けるツールを目指していたが、ハッカソン期間内では精度が間に合わず、法令に詳しくない人向けの解説ツールへと舵を切り直している。この手の方向転換は民間のプロダクト開発では日常茶飯事だが、官公庁発のプロジェクトで公然と記録に残っていることのほうが珍しい。

    技術の苦労も具体的だ。e-gov の法令データはXMLで提供されているが、チャンク化しようとすると文脈の維持と処理効率のトレードオフにぶつかる。Web検索APIの文字数上限、具体的には Tavily の400字制限に長いクエリが引っかかり、クエリ自動最適化のワークフローを挟むことになった。STORM の6段階パイプラインを Lawsy 独自の9段階に再編したのも、法令特化ゆえの要請だった。

    こういう手触りの話が一次情報として残っているのは貴重で、源内を巨大な箱ものとして語るイメージを壊してくれる。実際には、顔の見える小さなチームが、民間のハッカソンと変わらない試行錯誤を重ねた成果物に、政府の名前が冠されている。

    公開されたリポジトリの中身

    公開されているリポジトリは大きく2つ。Web側の digital-go-jp/genai-web と、アプリ側の digital-go-jp/genai-ai-api だ。

    Web側 — digital-go-jp/genai-web

    ユーザーインターフェースを担うのがこちら。言語構成は TypeScript 99%、主要ライブラリは React と Tailwind CSS、インフラは AWS CDK。冒頭で触れたとおり、AWS の GenU をベースに、デジタル庁が機能を追加した格好になっている。拡張内容は次のあたりが中心になる。

    • チーム管理機能
    • AIアプリ管理機能。外部マイクロサービスとして生成AIアプリを追加し、実行できる
    • デジタル庁デザインシステムの適用
    • 庁内アクセシビリティチームによる試験の反映
    • 運用監視とモニタリング機能

    GenU がAIアプリの一般的なひな型だとすれば、源内Webは大規模組織での運用に耐える業務基盤まで踏み込んだ拡張になっている。この差分を読むだけでも、組織内AIプラットフォームを構築したい人にとっては教材として値打ちがある。

    ライセンスは MIT が中心だが、一部の Lambda や CDK ファイルは Amazon Software License の対象になる。フォークして再配布するなら、ファイル単位のヘッダ確認が必要だ。

    アプリ側 — digital-go-jp/genai-ai-api

    行政実務向けAIアプリの実装が置かれているリポジトリ。言語比率は Python 44%、Bicep 25%、TypeScript 25%。Web側とはまったく異なるスタックで、ここにマルチクラウド前提の設計思想がはっきり表れている。

    収録されているテンプレートは3つある。

    • AWS には行政実務用RAGの開発テンプレート
    • Azure にはLLMをセルフデプロイして利用する開発テンプレート。IaCは Bicep
    • Google Cloud には最新の法律条文を参照して回答する法制度AIアプリの実装

    AWS、Azure、Google Cloud それぞれにサンプル実装が整理されているのは、率直にいって気合が入っている。自治体や企業の側では、手持ちのクラウド契約や調達制約によって採用候補が変わる。そこを政府が特定のクラウドに寄せろと押し付けないよう、意図的にフラットな構えで作られている。

    特徴的なOSSポリシー

    冒頭で触れた、PRを受け付けず Issue は致命的案件のみという方針は、従来型のOSSコミュニティの感覚からすると違和感を覚えるものだ。一緒に作ろうではなく、参照してくださいに近い。

    ただ、政府OSSとしては筋が通っている。Pull Request を受け入れるということは、受け取った側が法的責任と運用責任を負うことを意味する。政府基幹システムに組み込まれるコードに、誰が書いたか分からない変更を取り込むのは、たしかに難しい。公開はするが統治は中央で、という姿勢を明示してくれるほうが、使う側としても判断しやすい。

    国産LLM 7モデル選定という布石

    源内のOSS公開と並行して、もうひとつ動いているのが国産LLMの選定プロセスだ。2026年3月、デジタル庁は15件の応募から7モデルを選定したと発表した。内訳は次のとおりで、名前を並べるだけで日本のLLMプレイヤーの現在地が見えてくる。

    • tsuzumi 2(NTTデータ)
    • CC Gov-LLM(カスタマークラウド)
    • Llama-3.1-ELYZA-JP-70B(KDDI・ELYZA共同応募体)
    • Sarashina2 mini(ソフトバンク)
    • cotomi v3(日本電気)
    • Takane 32B(富士通)
    • PLaMo 2.0 Prime(Preferred Networks)

    選定基準は、国内で開発された大規模言語モデルで、開発経緯や開発方法が具体的に説明可能であること、そして行政実務で使える性能を持ち、デジタル庁のテストを通過していることだった。2026年度は無償で試用、2026年8月ごろから源内で順次稼働し、2027年4月以降の政府調達を視野に入れている。

    この動きとOSS公開は無関係ではない。基盤はオープンにし、モデルは国産重視でという二段構えで、ガバメントAI全体の独立性を確保しにいっている。先行して導入された Preferred Networks の PLaMo Translation はすでに政府職員向けの提供が始まっており、翻訳という比較的評価しやすいタスクから実地データを集める段階に入っている。

    海外の政府AIとの温度差

    比較軸として、海外の政府AI事情に目を向けておきたい。国ごとに思想がかなり違っていて面白い。

    シンガポールのPairは、政府職員向けチャットボットの先行事例だ。GovTech が開発し、トライアル開始から2ヶ月で100以上の政府機関、1万1,000人以上が利用したという。建設庁ではレポートレビューの時間を数時間から数分に短縮したと報告されている。源内の80%利用に近い浸透度で、日本とシンガポールはここでは似た立ち位置にいる。

    エストニアのBürokrattは方向性がかなり違う。政府チャットボットでありながら、民間企業が自社サービスを統合できる仕組みを持っており、天候情報などは外部から提供されている。2026年以降は個別職員向けのAIエージェント化と、他国政府との相互接続まで視野に入れている。行政の内側にとどまらず、国境を越えたエコシステムとして設計されている点で、源内とは発想が違う。

    日本の源内はシンガポール型の職員向け内製基盤を起点にしつつ、エストニア型のエコシステム志向を、今回のOSS公開で手前から試みているようにも読める。コードを開くことで、民間SIerや自治体が組み込みやすい状態を作り、少しずつエコシステム寄りに寄せていく狙いが見える。

    民間SIerにとっての変化

    この動きは、行政向けシステム開発を主業にしてきたSIerにとっては無視できない。自治体向けに独自の生成AI基盤を売る余地はこれまで十分にあったが、源内互換を語れるかどうかで、提案の意味が変わってくる。

    現実的な構図は次のように整理できる。

    • フルスクラッチ型の提案は、差別化できる独自性がない限り厳しくなる。自治体側は、源内を参考にしてこの金額なのかと問える立場になる。
    • 源内の運用とカスタマイズ支援は新しい商売になりうる。そのまま動かすには手直しがいる以上、そこを埋めるコンサルや運用委託の需要は出てくる。
    • アプリ層での差別化は十分可能だ。法令、税務、福祉、教育といった業務特化のAIアプリは、政府の汎用基盤だけではカバーしきれない。

    自治体のDX担当者からすれば、源内のこの機能は標準で入っていますが御社の提案には含まれていますか、というフラットな問いを投げられるようになったこと自体が大きい。ブラックボックスのなかで見積もりを受け取る時代から、少しずつ抜け始めている。

    触る前に押さえておきたい注意点

    現実的な落とし穴もいくつかある。

    • 公開されているのはアプリと基盤のコードで、LLM本体は含まれていない。動かすには Azure OpenAI や Amazon Bedrock、あるいは国産LLMを別途用意する必要がある。
    • 政府向けの脅威モデルと民間のそれは同じではない。コードを流用しても政府水準のセキュリティが自動的に引き継がれるわけではない。認証や監査ログ、秘匿情報の扱いは自組織の要件で設計し直すことになる。
    • ライセンスの混在もある。MITが中心だが、一部のファイルは Amazon Software License なので、再配布時はファイル単位で確認を。
    • コミュニティとの付き合い方にも注意がいる。PRが受け付けられない以上、独自拡張は基本的にフォーク側で管理することになる。本家への追随にはそれなりのコストが乗る。

    独自の視点 — 参照実装としての政府OSS

    今回の公開は、典型的なコミュニティ型OSSとも違えば、単なる成果物の公表とも違う、その中間に位置している。PRを受け付けない政府OSSは、参照実装、いわゆる reference implementation に近い姿をしている。自由に使っていいが、正典はあくまで中央にある。

    この形が日本の行政に馴染む理由は、おそらくふたつある。ひとつは、法的責任のラインをぼかさずに済むこと。もうひとつは、自治体や企業が本家に合わせて作り直さなければいけないというプレッシャーから解放されることだ。源内のコードをそのまま使わなくていい。自組織の事情に合わせて切り貼りすればいい。本家は本家で独立して進む。

    このやり方が上手くハマれば、今後ほかの政府OSSも似たポリシーで出てくる可能性は十分ある。法令APIやベース・レジストリの周辺、マイナポータル系のツールチェーン、自治体共通DBの周辺など、公開可能な素性を持つアセットはまだ残っている。源内のOSS公開は、日本的な政府OSSの型を作りにいった一手として捉えた方が、ニュース単体で見るより視野は広がる。

    もうひとつ書いておきたい違和感がある。利用実績レポートの、課長級の半数が利用ゼロという数字は、OSS公開では動かない指標だ。コードを開いても、管理職がAIを触るようになるわけではない。ここは教育や人事評価、権限設計の領域で、技術OSSの射程外になる。源内というソフトウェアが、使える人のところにだけ届き、使えない人のところには届かないというリスクは、自治体に展開しても同じ形で再現されるはずだ。技術の話と組織の話を切り分けずに語ると、源内を入れたのに変わらないという結論に早晩ぶつかる。

    最後に

    公共機関のAIプロジェクトは、これまで外から中身の見えない箱だった。仕様書や調達資料は読めても、実際にどう動いているコードなのかは関係者以外には分からなかった。

    源内のOSS公開は、そこに小さくない穴を開けた。自治体は重複開発から降りる選択肢を手にし、開発者は行政ドメインのRAGやマルチテナント設計を教材として手に取れる。民間SIerは源内互換という新しい会話のレイヤーを持ち、発注側はブラックボックスのなかで見積もりを受け取る状況から一歩抜けられる。

    EastCloudでも、自治体や中堅企業向けの業務システム開発と生成AIの社内実装に日常的に取り組んでいる。源内のリポジトリは、日本の行政ドメインでRAGやマルチテナントをどう設計するかを考える上で、当面は重要な参照点になりそうだ。手元で動かしてみるなら、まず digital-go-jp/genai-web をクローンして docs/ を読むところから始め、次に genai-ai-api 側の AWS、Azure、Google Cloud テンプレートを目的に合わせて触ってみるのが現実的だろう。

    ただし、OSSの公開だけで片付く話ではない。課長級の利用ゼロ問題、セキュリティ要件の再設計、運用人材の不足。ソフトウェアが手に入っても、組織が使いこなせるようになるかは別の話だ。そこが埋まって初めて、源内は政府が作った便利なコードから、日本の行政DXのインフラに昇格する。

    国が作ったAI基盤を、個人や地方自治体が自分たちの土俵に引き込める段階に入った。使いこなせるかどうかは、ここからの動き方にかかってくる。

    参考リンク

  • Claude Codeソースコード流出の全容 — npmソースマップから512,000行が公開状態に

    Claude Codeソースコード流出の全容 — npmソースマップから512,000行が公開状態に

    2026年3月31日、AnthropicのAI開発支援CLI Claude Codeのソースコードがnpmレジストリから流出した。約512,000行、1,900ファイルのTypeScriptがソースマップ経由で丸見えになっていた。

    騒ぎが大きくなった理由はタイミングにもある。わずか5日前の3月26日、Anthropicは未発表モデル Claude Mythos(内部コードネーム Capybara)のブログ下書きをCMSの設定ミスで公開していた。そのとき漏れた約3,000アセットにはベンチマーク情報まで含まれていた。5日間で2度目の漏洩。リリースプロセスそのものの信頼性が問われている。

    何が起きたのか — 59.8MBのソースマップが混入

    セキュリティ研究者 Chaofan Shou氏(@Fried_rice)が、npm上の @anthropic-ai/claude-code v2.1.88に59.8MBのソースマップファイル .map が含まれているのを見つけた。

    ソースマップは、ビルド済みのJavaScriptから元のTypeScriptソースを復元するためのデバッグ用ファイルだ。開発環境でしか使わないはずのもので、公開パッケージに入れてはいけない。それがそのまま混入していた。

    直接原因は.npmignoreの設定漏れ

    Claude CodeのビルドにはBunのバンドラーが使われている。直接の原因と背景は別の話だ。

    直接原因はリリース担当者が.npmignore*.mapを追記し忘れたこと。package.jsonfilesフィールドでの除外もされていなかった。Anthropicは公式声明でリリースパッケージングのヒューマンエラーと認め、顧客データや認証情報は含まれていないとしている。

    一方、背景因子としてBunのIssue #28001がある。2026年3月11日に報告されたもので、development: falseを設定してもBun.serveがソースマップへの参照を出力に残すバグだ。Bunはデフォルトでソースマップを生成するため、明示的に除外しないとパッケージに入ってしまう。

    しかもこれは初めてではない。2025年にもClaude Code v0.2.8とv0.2.28で同様のソースマップ混入が起き、npmレジストリから該当バージョンを削除していた。同じミスの繰り返しだ。

    DMCAは効かなかった — 一度広まったコードは消えない

    流出コードはGitHub上に即座にバックアップされ、ピーク時には41,500以上のフォークがついた。AnthropicはDMCA通知を出し、8,100以上のリポジトリが削除対象になった。

    だが拡散の速度に削除が追いつかなかった。DMCA削除からわずか2時間後にはPythonリライトやRustリライトが出回り、後者は47,000スターを集めている。別言語で書き直すことでDMCAの著作権主張を回避する手法だ。一度広まったコードは消せない。

    流出コードから判明した5つの内部設計

    512,000行のTypeScriptから、Claude Codeの設計思想が見えてきた。

    1. 約40のプラグイン型ツールアーキテクチャ

    Claude Codeはファイル読み込み、Bash実行、Web取得、LSP連携といった機能をそれぞれ独立したツールとして実装している。ツール定義だけで29,000行。すべてにパーミッションゲートがかかっている。

    これらを束ねるクエリエンジンが46,000行。ターミナルUIはReact + Inkで描画し、文字幅計算にはInt32ArrayバッキングのASCIIキャッシュを使って通常の約50倍に高速化している。ゲームエンジンで使われる手法をCLIに転用している。

    2. セッションを超えた記憶 — メモリシステム

    memdir/ というメモリディレクトリが、セッション間のコンテキスト保持を担っている。関連する記憶のスキャン、古い記憶のエージング、チーム間での共有。Claude Codeがコードベースを覚えているように感じるのは、この仕組みのおかげだ。

    3. トークンコストを抑えるキャッシュ最適化

    14個のキャッシュ破棄ベクトルを追跡し、APIコールごとのトークン消費を減らしている。システムプロンプトの変更箇所だけを差分で送るキャッシュ境界管理で、同じプロンプトの再送信を避けている。

    4. マルチエージェント調整

    coordinatorMode.tsがエージェント間のオーケストレーションを受け持つ。プロンプトベースで調整し、「弱い成果物を無批判に承認するな」といった品質管理の指示が埋め込まれている。サブエージェントが並列で動くときの競合回避とマージの制御もここで行う。

    5. 23個のセキュリティ検査を持つBashツール

    Bashツールには23個の検査が組み込まれている。18個のZsh組み込みコマンド制限、ゼロ幅文字のインジェクション防御など。プロンプトインジェクション経由でシステムコマンドが実行されるのを多層で防いでいる。

    未公開機能の発見 — Anthropicの次の一手が見えた

    コードにはまだリリースされていない機能フラグが複数あった。

    KAIROS — 常時稼働する自律デーモンモード

    古代ギリシャ語で「最適なタイミング」を意味するKAIROS。いまのClaude Codeはユーザーの入力を受けてから動くリアクティブなツールだが、KAIROSはバックグラウンドで常時走るデーモンになる。

    • autoDream — ユーザーがアイドル中に、散らばった観測結果を統合し矛盾を除去する「メモリ蒸留」プロセス。/dreamスキルとして実装されている
    • 追記専用ログ — 日次で観測・判断・アクションを記録し、判断根拠をトレースできるようにする
    • GitHubウェブフック購読 — リポジトリのイベントを監視し、PRレビューやIssue対応を自律的にこなす
    • 5分間隔のcronスケジュール — 定期的に<tick>プロンプトを受け取り、能動的に動くかどうかを判断する
    • Briefモード — 常駐アシスタントがターミナルを埋めないよう、極端に短い応答だけ返す出力モード

    実現すれば、コマンドを打って結果を待つ開発スタイルから、AIがバックグラウンドでコードベースを監視し続けるスタイルへ移行する。GitHub CopilotのAgent ModeやCursorのBackground Agentと方向は同じだが、デーモン化はさらに一歩踏み込んでいる。

    Undercover Mode — AI生成を隠すステルス貢献

    undercover.tsは約90行。外部リポジトリでClaude Codeの痕跡を消すモードだ。有効にすると、以下が適用される。

    • Claude Code、Capybara、Tenguなどの内部コードネームを一切出さない
    • 内部Slackチャンネル名やリポジトリ名を出力しない
    • AI生成の痕跡を残さない

    Anthropic社員がオープンソースにClaude Codeで貢献するとき、AIが書いたと分からないようにする仕組みだ。

    この機能にはオフのスイッチがない。コード内のコメントに「There is NO force-OFF」と書かれている。CLAUDE_CODE_UNDERCOVER=1で強制オンはできるが、逆はできない。一方通行の設計になっている。

    AI生成コードの透明性が問われている時期に、AIであることを隠す機能があること自体が論争を呼んでいる。EUのAI Actが求める透明性要件、GitHubのContributor Covenantとの整合性はどうなるのか。そして皮肉なことに、情報漏洩を防ぐためのこのモードが含まれたコード自体が漏れた。

    Anti-Distillation — 競合のモデル蒸留を防ぐ2段構え

    モデル蒸留とは、Claudeの応答を別のモデルの学習データとして転用する行為だ。コードにはこれを防ぐ2系統の仕掛けがあった。

    第1層claude.tsのフラグANTI_DISTILLATION_CCが有効だと、APIリクエストにanti_distillation: ['fake_tools']が付く。サーバー側がこれを受けて偽のツール定義をシステムプロンプトに注入する。競合がトラフィックを記録して学習に使っても、偽ツールの混入で精度が落ちる。GrowthBookのフィーチャーフラグ tengu_anti_distill_fake_tool_injection で制御されている。

    第2層betas.tsのサーバーサイドコネクタがアシスタントの応答テキストを圧縮・署名して返す。傍受しても元の応答を正確に復元できない。

    ネイティブクライアント証明 — DRMライクな正規バイナリ検証

    system.tsで、リクエストのx-anthropic-billing-headercch=00000というプレースホルダーが埋め込まれている。送信直前にBunのネイティブ層、Zig実装のHTTPスタックが計算ハッシュに置き換える。公式バイナリからのリクエストかどうかをサーバーが検証するDRMに近い仕組みだ。

    この技術はオープンソースクローン OpenCode との争いにも絡んでいる。Anthropicは流出の約10日前、3月21日前後に、サブスクリプション料金での内部API不正利用を理由にOpenCodeへ法的脅迫状を送っていた。ネイティブクライアント証明はその技術的な裏付けになっている。

    frustration regex — 正規表現でユーザーの怒りを拾う

    リークの中でSNSが最も盛り上がったのがこれだ。userPromptKeywords.tsに書かれていたユーザーの不満検知用の正規表現。

    /\b(wtf|wth|ffs|omfg|shit(ty|tiest)?|dumbass|horrible|awful|
    piss(ed|ing)? off|piece of (shit|crap|junk)|what the (fuck|hell)|
    fucking? (broken|useless|terrible|awful|horrible)|fuck you|
    screw (this|you)|so frustrating|this sucks|damn it)\b/

    LLMの会社が感情分析を正規表現でやっている。皮肉に見えるが、合理的だ。ユーザーが怒っているかの判定にLLM推論を回すのはコストも遅延も過剰すぎる。正規表現ならマイクロ秒で済む。応答トーンの調整トリガーとしてはこれで十分だろう。

    業界への影響 — コードよりロードマップの流出が痛い

    戦略的サプライズの喪失

    512,000行のコード自体は、時間をかければ再構築できる。だがKAIROSやUndercover Modeのような未発表機能と戦略的方向性は、一度知られたら巻き戻せない。OpenAI、Google、CursorはAnthropicの次の手を見たうえで自社の製品計画を練れるようになった。コードの流出は一時的だが、ロードマップの流出は長く効く。

    npmエコシステムのソースマップ問題

    ソースマップの誤配布はClaude Codeだけで起きた話ではない。ReactやAngularの本番バンドルに混入した例も散発的にある。たとえば2021年、保守系SNS GETTRではソースマップ経由でハードコードされた認証情報が見つかった。Bunのバンドラーがデフォルトでソースマップを生成する仕様は、npmエコシステム全体のビルド設定のもろさを浮き彫りにしている。

    AI生成コードの透明性の議論

    Undercover Modeの発覚は、AIが書いたコードをラベルなしでオープンソースに混ぜることの是非を突きつけた。EUのAI Actは高リスクAIシステムに透明性を求め、GitHubのContributor Covenantも意図的な不正表示を問題視している。AIが書いたコードを人間の手柄に見せる機能を、開発者コミュニティがどう受け止めるか。

    開発者向け再発防止チェックリスト

    Anthropic規模の企業でもソースマップ流出は起きる。npmパッケージを公開するなら、以下の3点は押さえておきたい。

    1. .npmignore*.mapを追記する — ソースマップが公開パッケージに入らないよう明示的に除外する
    2. package.jsonfilesフィールドを指定する — 公開ファイルをホワイトリスト方式で管理する。.npmignoreのブラックリスト方式より確実だ
    3. CI/CDにパッケージ内容チェックを入れるnpm pack --dry-runでパッケージの中身を公開前に確認するステップをパイプラインに追加する

    どれも数分で設定できる。

    まとめ

    項目詳細
    発生日2026年3月31日
    直接原因.npmignoreのソースマップ除外設定漏れ(ヒューマンエラー)
    背景因子Bun Issue #28001(production modeでもソースマップ生成)
    流出規模512,000行 / 1,900ファイル / 59.8MB
    影響顧客データ・認証情報の漏洩なし
    未公開機能KAIROS(自律デーモン)、Undercover Mode、BUDDY、ULTRAPLAN
    GitHub フォークピーク時41,500以上(DMCA対象: 8,100リポジトリ)
    Anthropicの対応ヒューマンエラーと認め、パッケージ差し替え+DMCA通知

    Claude Mythos下書き公開に続く2件目のインシデントで、Anthropicのリリース工程の甘さが露わになった。流出したのはCLIツールの実装であり、AIモデルの重みやトレーニングデータではない。だが未発表のロードマップと内部戦略が競合に筒抜けになったことは、技術面よりビジネス面で響くだろう。

    一方で、流出コードはAIコーディングツールの内部設計を覗ける数少ない実例にもなった。プラグイン型ツール構造、メモリ蒸留、Anti-Distillation。これらの設計パターンは今後のAIツール開発で広く参照されるはずだ。

    よくある質問(FAQ)

    Q. Claude Code流出で自分のデータが漏れた可能性はある?

    A. Anthropicは顧客データや認証情報は含まれていないと明言している。漏れたのはCLIツールのソースコードで、ユーザーのコードやAPIキーは入っていない。ただしClaude Codeの内部動作が明るみに出たことで、プロンプトインジェクション攻撃の精度が上がるリスクは残る。

    Q. ソースマップとは?

    A. .mapファイルのこと。ビルド・圧縮後のJavaScriptから元のTypeScriptなどのソースを復元するための対応表だ。開発時のデバッグ用で、本番環境やパッケージに含めるものではない。

    Q. KAIROSはいつリリースされる?

    A. 公式発表はない。流出コードにフィーチャーフラグとして存在が確認されただけで、開発段階も完成度も分からない。流出を受けて計画自体が変わる可能性もある。

    Q. Undercover Modeは倫理的に問題ないのか?

    A. 見方が割れている。Anthropicの意図は社内ツールの痕跡を外部リポジトリに残さないことであり、悪意のある隠蔽とは限らない。ただしAI生成コードの開示を求める声が強まるなか、AIが書いたことを隠す機能の存在が批判されているのは事実だ。

    Q. 同様のソースマップ流出を防ぐには?

    A. 上の再発防止チェックリストの3点が基本になる。.npmignoreへの*.map追記、package.jsonfilesフィールド指定、CI/CDでのnpm pack --dry-runチェックだ。


    関連記事:

    出典:

  • GPT-5.4はこうしてリークされた — Codex PR・API・スクリーンショット、3つの漏洩と正式リリースまで

    GPT-5.4はこうしてリークされた — Codex PR・API・スクリーンショット、3つの漏洩と正式リリースまで

    2026年2月末、X上で「alpha-gpt-5.4がパブリックモデルのエンドポイントで発見された」という投稿が広まった。削除されたはずのCodex GitHub PRが本物だとも言われていた。そして1週間後の3月5日、OpenAIはGPT-5.4を正式リリースした。リーク情報はほぼ当たっていた。

    GPT-5.4がどのようにして外に漏れ、実際に何が変わったのかを時系列で整理していく。

    GPT-5.4 概要
    出典: DEV Community

    3つのリーク — 偶然が重なった1週間

    GPT-5.4の存在が外部に出たのは、独立した3つの経路からだ。

    1. Codex GitHub PR(2月27日〜3月2日)

    最初のきっかけは、OpenAIのCodexリポジトリに出されたプルリクエストだった。PR #13050には、画像処理の新機能が必要とするモデルバージョンが (5, 4) と記述されていた。つまりGPT-5.4以降でしか動かない機能が実装されていたことになる。

    このPRで実装されていたのは、detail: original パラメータによる画像圧縮スキップ機能だ。従来のモデルでは送信前に画像が自動的に圧縮・リサイズされていたが、このパラメータを指定するとPNG、JPEG、WebPのオリジナルバイトをそのままモデルに渡すことができる。建築図面や医療画像、UI画面のピクセル単位の分析など、圧縮によって劣化が生じると困るユースケースを想定した機能だ。そしてこの機能がGPT-5.4以降でのみ使えるという記述が、新モデルのビジョン能力が大幅に強化されていることを示していた。

    数時間後、その記述は gpt-5.3-codex or newer に書き換えられた。force pushで履歴も消されたが、スクリーンショットはすでに出回っていた。

    3月2日にはPR #13212でも同じことが起きた。gpt-5.4 をモデル引数に指定した関数呼び出しと、「toggle Fast mode for GPT-5.4」というスラッシュコマンドの記述が含まれていた。削除まで約3時間かかったが、そのころにはキャプチャ済みだった。

    2. モデルセレクターのスクリーンショット

    OpenAIの社員Tiboが、CodexアプリのモデルセレクターUIのスクリーンショットを投稿した。GPT-5.3-Codexの隣にGPT-5.4が並んでいる画面だ。投稿はすぐに削除されたが、魚拓はすでに取られていた。

    3. APIエンドポイント

    alpha-gpt-5.4 というラベルが、公開APIの /models エンドポイントに一時的に現れた。X上で複数ユーザーが確認してスクリーンショットを共有し、それが冒頭の投稿の根拠となった。

    3つとも別々のタイミング、別々の人物によるミスだった。1件なら揉み消せたかもしれないが、3件重なれば確定情報として扱われる。

    正式リリース — 3月5日に何が発表されたか

    リークから1週間後の3月5日、OpenAIはGPT-5.4を正式に発表した。位置づけは「プロフェッショナルワーク向けの、最も高性能かつ効率的なフロンティアモデル」だ。

    スペックとベンチマークをまとめる。

    コンテキストウィンドウ

    API版で100万トークンに対応した。OpenAI史上最大のサイズになる。リーク時には200万トークンという情報も出回っていたが、公式発表では100万トークンに落ち着いた。それでもGPT-5の40万トークンから2.5倍の拡張だ。

    ベンチマーク

    指標GPT-5.2GPT-5.4
    GDPval(知識ワーク)83%(過去最高)
    投資銀行モデリング68.4%87.3%
    SWE-Bench Pro57.7%
    OSWorld-Verified47.3%75.0%
    BrowseComp+17%(GPT-5.2比)

    GPT-5.2と比べて、個々の回答に誤情報が混入する確率が33%減った。レスポンス全体のエラー率も18%下がっている。

    GPT-5.4 Capability Stack
    出典: DEV Community

    Computer-Use(コンピュータ操作)

    GPT-5.4は汎用モデルとして初めて、ネイティブのコンピュータ操作機能を搭載した。アプリケーションをまたいだ複雑なワークフローをAIエージェントが直接実行できる。CodexやAPIと組み合わせれば、マルチステップの自律タスクをそのまま動かせる。

    モデルバリエーション

    3つのバリエーションで展開されている。

    • GPT-5.4:標準版。ChatGPT Plus以上で利用可能
    • GPT-5.4 Thinking:推論に特化。Plus、Team、Proプランで利用可能
    • GPT-5.4 Pro:最高性能。ProおよびEnterpriseプラン限定。BrowseCompで89.3%を記録

    トークン効率

    GPT-5.2と同等の問題解決を、より少ないトークン消費で達成できるようになった。つまりAPI利用者にとっては、性能が上がりながらコストも抑えられるアップデートになる。

    GPT-5.4 Model Selection Map
    出典: DEV Community

    API価格とプラン

    GPT-5.4のトークン単価は、GPT-5.2より改善されている。OpenAI公式の発表では、入力・出力ともにコスト効率が向上しており、API経由で使うコストに対して得られる性能の比率が上がっている。

    Thinking版(GPT-5.4 Thinking)は推論トークンの追加コストがかかる構造だ。ただし複雑なタスクでは推論ステップを挟むことで回答の試行回数が減り、リトライや後処理のコストを含めたトータルが下がるケースもある。単純なQ&AにThinkingを使うのは過剰だが、コードレビューや多段階の計算では費用対効果が出やすい。

    プランごとのアクセス範囲は以下のとおりだ。

    プラン月額GPT-5.4GPT-5.4 ThinkingGPT-5.4 Pro
    Plus$20利用可利用可不可
    Team$30/人利用可利用可不可
    Pro$200利用可利用可利用可
    Enterprise要問合せ利用可利用可利用可

    API経由での利用は、ChatGPTのプランとは別に従量課金として請求される。

    リーク情報はどこまで正確だったか

    冒頭の投稿の主張を振り返ると、こうなる。

    • 「alpha-gpt-5.4がエンドポイントで発見された」→ 事実
    • 「Codex GitHub PRリークは100%本物」→ 事実
    • 「来週早々に登場する」→ ほぼ正確(投稿から5日後にリリース)
    • 「盤面をリセットする世代間飛躍」→ ベンチマーク上は大幅な改善。ただし評価は使う人間次第

    唯一外れたのは、コンテキストウィンドウのサイズだ。リーク時の「200万トークン」に対し、公式では「100万トークン」だった。内部のアルファ版でテストされていたスペックが、リリースまでの間に変更されたと考えるのが自然だろう。

    200万から100万への半減には、いくつかの理由が考えられる。まずレイテンシの問題だ。コンテキストが長くなるほど推論時間が伸びる。GPT-5.4はプロフェッショナルワーク向けのモデルとして位置づけられており、応答速度とのバランスを取った結果、100万トークンに落ち着いた可能性が高い。もう一つは段階リリース戦略だ。200万トークン対応を将来のバージョンに温存し、アップデート時の目玉機能として使うという読み方もできる。

    また、リーク時にはもう一つ噂があった。「会話をまたいで状態を保持するステートフルAI」の実装だ。従来のモデルはセッションごとにリセットされるが、ステートフルモードでは記憶や作業状態を持ち越せる。この機能については、正式発表では触れられなかった。開発中ではあるが、プライバシーとコストの問題を解決できていないため別途発表される可能性が残っている。

    GPT-5シリーズの進化ペース

    GPT-5シリーズは2025年8月の初版リリースから、1〜3ヶ月間隔でアップデートを続けてきた。半年で3回のメジャーアップデートというペースはGPT-4時代より明らかに速い。

    バージョンリリース時期主な進化
    GPT-52025年8月初版。推論能力の飛躍
    GPT-5.22025年11月マルチモーダル強化
    GPT-5.32026年1月Codex統合、エージェント強化
    GPT-5.42026年3月Computer-Use、1Mコンテキスト

    競合他社も同様のペースで動いている。AnthropicはClaude 4.5・4.6を2026年に入って相次いでリリースし、GoogleはGemini 2.5でマルチモーダルと長文コンテキストの性能を引き上げた。三社とも3ヶ月以内のサイクルでフラッグシップモデルを更新しており、AI能力のコモディティ化が加速している局面だ。

    computer-useという機能に絞ると、AnthropicはClaude 3.5の段階から先行して提供していた。OSWorldのベンチマークでも一時期Claudeが上位を占めていた。GPT-5.4はOSWorld-Verifiedで75.0%を記録し、GPT-5.2の47.3%から大幅に向上させた。OpenAIが半年かけてchromecast操作からデスクトップ全体の自動化まで対応域を広げた形で、Anthropicの先行優位は縮まっている。

    開発者への影響

    GPT-5.4のスペック変化は、実際に使う開発者にとって具体的な変化をもたらす。

    1Mトークンのコンテキストウィンドウにより、大規模コードベースの一括分析が現実的になった。従来は分割して投入していたファイル群を、リポジトリごとまとめて渡してアーキテクチャ上の問題点を指摘させるといった使い方が成立する。コンテキスト切れによる情報の断絶を避けられる点は、長いセッションを前提とする開発タスクで特に効いてくる。

    Computer-Useの搭載は、エージェントの自律性を一段引き上げる。ブラウザ操作やGUIアプリへの入力など、これまでAPIでは届かなかった領域を自動化できる。Seleniumや専用スクレイパーを用意しなくても、「このサイトにログインしてデータを取ってきて」という指示をそのまま実行できるようになる。

    Codexとの統合という観点では、PRレビューから修正・テスト実行までの一連のフローを自動化する構成が視野に入る。GPT-5.4はCodexのバックエンドとして動き、コードの意図を読んでレビューコメントを付け、修正案を生成し、テストを走らせて結果を確認する。一人の開発者が抱える反復作業の大部分を委任できる構成だ。

    API利用者にとっては、性能とコストの比率が改善している点がシンプルにうれしい。同じ品質の出力をより少ないトークンで得られるなら、月の請求額を変えずにより多くのタスクをこなせる。

    参考リンク

  • Claude Codeを「チャット」から卒業させる — CLAUDE.md・Skills・Hooksによるプロジェクト構造設計

    Claude Codeを「チャット」から卒業させる — CLAUDE.md・Skills・Hooksによるプロジェクト構造設計

    Claude Codeを使い込んでいくと、ある壁にぶつかる。プロジェクトが大きくなるほど、毎回同じ指示を繰り返す羽目になるのだ。コーディング規約、テストの実行方法、避けてほしいパターン。セッションのたびに伝え直すのは時間の無駄で、ミスの温床にもなる。

    こうした問題への答えとして、海外のエンジニアコミュニティで注目されているのがClaude Codeのプロジェクト構造設計というアプローチだ。単発のプロンプトに頼るのをやめ、ソフトウェアエンジニアリングと同じ発想で仕組みを整える。

    CLAUDE.mdをプロジェクトメモリとして設計する

    CLAUDE.mdは、Claude Codeがセッション開始時に自動で読み込むファイルだ。ここに書いた内容が、すべてのやり取りの前提になる。

    ただのメモ帳として使っている人が多いが、それではもったいない。コーディング規約、アーキテクチャの制約、やってはいけないこと。これらをCLAUDE.mdに一元化すると、セッションごとの指示の繰り返しがなくなる。

    具体的には、こういう内容を入れる。

    ## プロジェクト概要
    Tech Stack: Next.js 14, TypeScript, Prisma
    DB: PostgreSQL(Supabase)
    
    ## 開発コマンド
    npm run dev     # 開発サーバー起動
    npm run test    # Vitest実行
    npm run lint    # ESLint + Prettier
    
    ## コーディング規約
    - コンポーネントは関数コンポーネント + TypeScript strict
    - API routeではzodでバリデーション必須
    - main直pushは禁止。必ずブランチを切ってPRを作る
    
    ## やってはいけないこと
    - anyの使用
    - console.logの残存
    - テストなしのマージ

    ここで注意したいのが文量だ。Claudeが確実に従えるのは150〜200行程度の指示で、システムプロンプトが約50行を消費するため、実質100〜150行が上限になる。詰め込みすぎると、ランダムに無視される行が出てくる。

    さらにCLAUDE.mdは階層構造に対応している。~/.claude/CLAUDE.mdにはユーザー全体の設定、プロジェクトルートの./CLAUDE.mdにはチーム共通の規約、各ディレクトリにはパッケージ固有のルールを置く。モノレポ構成でも、各パッケージに適切な文脈を渡せる。

    何を書くべきか、書かなくていいか

    CLAUDE.mdに何でも書けばいいわけではない。コンテキストウィンドウには上限があり、読ませる内容が増えるほど各指示の重みが薄まる。判断基準として使えるのが「この行を消したらClaudeがミスするか?」というリトマス試験だ。答えがYesならば書く価値がある。Noならば不要だ。

    書くべき内容

    • ビルドコマンドやテストの実行方法(プロジェクトごとに異なるため)
    • プロジェクト固有のコード規約(チームで決めたルールであり、標準から外れているもの)
    • 環境の癖(特定のライブラリの制約、外部APIの挙動など)
    • アーキテクチャ上の判断(なぜこの設計を選んだかの背景)

    書かなくていい内容

    • Claudeがコードを読めば分かること(ファイル構造、変数名の意味など)
    • 言語・フレームワークの標準的な慣習(TypeScriptの型付け、Reactのhooks規則など)
    • 頻繁に変わる情報(バージョン番号、一時的なTODOなど)

    「プロジェクト固有かどうか」という軸で判断すると迷いにくい。

    Claude Codeのカスタマイズガイド
    出典: alexop.dev

    Skills — 繰り返す作業を機能として定義する

    Claude Codeにはskillsという仕組みがある。.claude/skills/にSKILL.mdファイルを置くと、タスクの文脈に応じて自動的にロードされる。

    コードレビュー、リファクタリング、テスト生成。こうした繰り返しの作業を、毎回アドホックにプロンプトで指示するのではなく、再利用可能なワークフローとして定義する。プロンプトではなく機能として持たせるという発想だ。

    たとえばコードレビュー用のスキルなら、こう書く。

    ---
    name: code-review
    description: PRのコードレビューを実行する
    ---
    
    以下の観点でコードをレビューしてください。
    1. 型安全性(any, as の使用箇所)
    2. エラーハンドリングの網羅性
    3. テストカバレッジの十分性
    4. パフォーマンス上の懸念
    5. セキュリティリスク(インジェクション、XSS)
    
    問題を見つけたら、修正案をコードブロックで提示すること。

    スキルのディレクトリ構造はシンプルだ。.claude/skills/以下にスキル名のフォルダを作り、SKILL.mdを置くだけでよい。

    .claude/skills/
    ├── code-review/
    │   └── SKILL.md
    ├── test-gen/
    │   └── SKILL.md
    └── refactor/
        └── SKILL.md

    スキルが増えても整理しやすく、チームメンバーが「どんなスキルが使えるか」を把握しやすい構造になる。

    Slash Commandsとの違いも押さえておきたい。コマンドは/reviewのようにユーザーが明示的に呼び出す。一方スキルは、タスクの文脈からClaude側が判断して読み込む。どちらが合うかはユースケース次第だが、定型的なワークフローほどスキル化の恩恵が出やすい。

    Hooks — 出力の品質を仕組みで担保する

    HooksはClaude Codeのライフサイクルイベントに紐づけてシェルスクリプトを自動実行する仕組みだ。CLAUDE.mdのルールが依頼ベースなら、Hooksは強制に相当する。

    ファイル編集後の自動lint、保護ファイルへの書き込みブロック、特定フォーマットへの変換。手動で毎回直していた整形作業を自動化できる。

    {
      "hooks": {
        "PostToolUse": [{
          "matcher": "Edit|Write",
          "hooks": [{
            "type": "command",
            "command": "npx eslint --fix $CLAUDE_FILE_PATH"
          }]
        }]
      }
    }

    この設定では、Claudeがファイルを編集するたびにESLintが自動で走る。lint違反が残ったままコミットされる心配がなくなる。

    使えるイベントはPreToolUsePostToolUseUserPromptSubmitSubagentStopなど。実行モードもシェルコマンドとLLM判断の2種類から選べる。

    Claude Codeのフルスタック構成
    出典: alexop.dev

    .claudeignoreとコンテキスト管理

    Claude Codeはデフォルトでプロジェクト全体を走査しようとするが、それがかえってコンテキストを汚染することがある。node_modules/dist/のようなファイルをClaudeに読ませても意味がない。.claudeignoreファイルでこれらを除外することで、入力トークンを40%以上節約できるケースもある。

    # .claudeignore
    node_modules/
    dist/
    build/
    *.min.js
    coverage/

    .gitignoreと同じ記法で書けるため、既存の設定を流用しやすい。

    コンテキスト管理には/clear/compactも活用したい。/clearはセッション間で会話履歴をリセットし、無関係な文脈が引き継がれるのを防ぐ。/compactはコンテキストを要約して圧縮することで、長時間のセッションでもウィンドウ消費を抑える。

    さらに根本的なアプローチとして、サブエージェントによるコンテキスト分離がある。調査タスクを独立したエージェントウィンドウに委任し、結果だけを受け取る形にすると、メインセッションのコンテキストが調査ログで埋まらない。複雑な調査を並行して走らせつつ、メインの会話を軽く保つことができる。

    関心の分離 — ソフトウェア設計と同じ原則

    ここまで紹介した3つの仕組みは、結局ソフトウェアエンジニアリングの基本原則に行き着く。

    レイヤー仕組み性質実行タイミング
    設定CLAUDE.md助言的(advisory)セッション開始時
    機能Skills文脈駆動(contextual)タスク検出時
    実行Hooks強制的(mandatory)イベント発火時

    CLAUDE.mdはClaudeへの「お願い」だ。書かれていれば従うが、忘れられることもある。Skillsはタスクの内容から自動判断して読み込まれる。HooksはClaudeの意思に関係なく、イベントが発火したら必ず実行される。この3つの性質の違いを理解しておくと、どの仕組みを使うべきか判断しやすくなる。

    これにサブエージェントを組み合わせると、セキュリティ監査、テスト実行、コードレビューといった専門タスクを並列で委任できる。各エージェントは独立したコンテキストウィンドウで動くため、メインの会話が汚染されない。

    EastCloudでも、自社プロダクトの開発にClaude Codeを日常的に使っている。CLAUDE.mdでプロジェクトメモリを持ち、スキルでワークフローを定義し、フックで品質を担保する。この三層構造を導入してから、セッションごとの指示の重複が目に見えて減った。また、新しいメンバーがCLAUDE.mdを読むだけでプロジェクトの制約を把握できるため、属人化の防止にもつながっている。

    プロンプトの書き方を磨くだけでは、いずれ限界が来る。AIエージェントによる開発が広がるなか、次に必要なのは仕組みを整えるフェーズだ。

    参考リンク

  • Claude Code × MCP:自社システムにAIを直結させる完全ガイド

    Claude Code × MCP:自社システムにAIを直結させる完全ガイド

    Claude Code × MCP:自社システムに AI を直結させる完全ガイド

    自分たちのビジネスシステムを AI に直結できたらどうなるか。

    自社の営業支援システムでは、Go + Next.js + PostgreSQL のバックエンドに TypeScript 製の MCP(Model Context Protocol)サーバーを接続して、Claude Code から直接データベースクエリを実行したり API を呼び出したりできるようにした。

    この記事では、その実装の全体像と、再現可能なステップバイステップガイドを共有する。

    MCP とは何か — AI ツール連携の新標準

    まず MCP について整理しておく。

    MCP(Model Context Protocol) は、Anthropic が 2024 年 11 月にオープンソースとして公開した、AI モデルと外部ツール・データソースを接続するための標準プロトコルだ。公開直後から業界全体で採用が進み、2025 年には OpenAI、Google、Microsoft もサポートを表明した。現在は Linux Foundation の標準化プロジェクトとして運用されており、月間 9,700 万回以上の SDK ダウンロード8,200 以上の公開 MCP サーバーが存在する巨大なエコシステムに育っている。

    つまり MCP は一企業の独自仕様ではなく、AI ツール連携のデファクトスタンダードだ。今 MCP に対応した仕組みを作っておけば、将来どの AI モデルを使うことになっても活用できる。

    関連記事:生成AI × 業務効率化:ChatGPTを「使える」ツールにする具体的な方法

    Before / After で見る変化

    導入前と導入後の開発フローを比べてみる。

    Before(MCP なし):

    Developer → pgAdmin → スキーマ確認 → コピペ → Claude Code に貼り付け
               → Postman → API テスト → レスポンスコピー → Claude Code に貼り付け
               → 4つのアプリを行き来するコンテキストスイッチ地獄

    After(MCP サーバー導入):

    Claude Code ← MCP Server → API + Database
        ↓
     自然言語でSQLクエリ実行、データ分析、API操作をワンステップ

    たとえば、こんなことがチャットだけで完結する。

    • “顧客テーブルから売上が100万円以上の企業を抽出して” → SQL 自動実行
    • “最新の営業案件を API 経由で取得して、ステータスごとに集計して” → DB + API 連携
    • “このユーザーのプロフィール画像を更新して” → API PATCH 自動実行 + JWT 自動更新

    では、実際の実装に入っていく。

    技術スタック

    使うものを先に整理しておく。

    コンポーネント技術
    MCP SDK@modelcontextprotocol/sdk(TypeScript)
    言語TypeScript 5.x+
    実行環境Node.js + tsx(TypeScript ランナー)
    DB クライアントpg(node-postgres)
    スキーマ検証Zod
    トランスポートStdio(Claude Code との通信)
    認証JWT(自動リフレッシュ機能付き)

    バージョンは npm install 時点の最新を使えば問題ない。MCP SDK は活発に更新されているので、package.json では ^ 指定にしておくといい。

    実装ステップ — ゼロから動くサーバーを作る

    Step 1: プロジェクトセットアップ

    MCP サーバー用の新しい Node.js プロジェクトを作る。

    mkdir my-mcp-server && cd my-mcp-server
    npm init -y
    npm install @modelcontextprotocol/sdk pg zod
    npm install -D typescript @types/node @types/pg tsx
    npx tsc --init

    package.json はこう設定する。

    {
      "name": "my-mcp-server",
      "version": "1.0.0",
      "description": "Custom MCP Server",
      "type": "module",
      "main": "src/index.ts",
      "scripts": {
        "start": "tsx src/index.ts",
        "build": "tsc",
        "typecheck": "tsc --noEmit"
      }
    }

    tsconfig.json の主要な設定:

    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "ES2022",
        "moduleResolution": "bundler",
        "strict": true,
        "esModuleInterop": true,
        "outDir": "dist",
        "rootDir": "src",
        "skipLibCheck": true,
        "resolveJsonModule": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist"]
    }

    ここまでで土台は完成。次にメインのデータベースツールを実装していく。

    Step 2: データベースツールの実装 — SQL を安全に実行する

    PostgreSQL への接続とクエリ実行を担当するツールを作る。MCP サーバーの核心部分だ。

    2.1 基本的な MCP サーバー構造

    src/index.ts のスケルトンから始める。

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import pg from "pg";
    
    // 環境変数から接続情報を読み込み
    const DATABASE_URL = process.env.DATABASE_URL ||
      "postgresql://user:pass@localhost:5432/mydb";
    
    // ロギング(stderr を使用して stdio との干渉を避ける)
    function log(level: string, message: string, data?: unknown): void {
      const entry = { ts: new Date().toISOString(), level, message, ...(data && { data }) };
      process.stderr.write(JSON.stringify(entry) + "\n");
    }
    
    // データベースプール(遅延初期化)
    let pool: pg.Pool | null = null;
    
    function getPool(): pg.Pool {
      if (!pool) {
        pool = new pg.Pool({
          connectionString: DATABASE_URL,
          max: 5,
          idleTimeoutMillis: 30000,
        });
        pool.on("error", (err) => {
          log("error", "Unexpected pool error", { error: err.message });
        });
      }
      return pool;
    }
    
    // MCP サーバーの作成
    const server = new McpServer({
      name: "my-system",
      version: "1.0.0",
    });
    
    // スタートアップ処理
    async function main(): Promise<void> {
      const transport = new StdioServerTransport();
      await server.connect(transport);
      log("info", "MCP Server started");
    }
    
    main().catch((err) => {
      log("error", "Fatal error", { error: err.message });
      process.exit(1);
    });

    ポイント: ログ出力は必ず stderr に書く。MCP は stdin/stdout を通信に使うため、console.log を使うとプロトコルが壊れる。ここはよくあるハマりどころだ。

    2.2 SQL 安全性チェック — 事故を未然に防ぐ

    データベースを直接触れるツールには安全装置がいる。以下のチェック機能を入れる。

    import { z } from "zod";
    
    // 危険なパターンの定義
    const DANGEROUS_PATTERNS: Array<{ pattern: RegExp; label: string }> = [
      { pattern: /\bDROP\b/i, label: "DROP" },
      { pattern: /\bTRUNCATE\b/i, label: "TRUNCATE" },
      { pattern: /\bALTER\b/i, label: "ALTER" },
      { pattern: /\bCREATE\b/i, label: "CREATE" },
      { pattern: /\bDELETE\b(?!.*\bWHERE\b)/is, label: "DELETE without WHERE" },
      { pattern: /\bINSERT\s+INTO\b.*\bSELECT\b/is, label: "INSERT INTO...SELECT" },
    ];
    
    // SQL の危険性をチェック
    function checkSqlSafety(sql: string): string | null {
      for (const { pattern, label } of DANGEROUS_PATTERNS) {
        if (pattern.test(sql)) {
          return label;
        }
      }
      return null;
    }
    
    // 読み取り専用クエリか判定
    function isReadOnly(sql: string): boolean {
      const trimmed = sql.trim().toUpperCase();
      return (
        trimmed.startsWith("SELECT") ||
        trimmed.startsWith("WITH") ||
        trimmed.startsWith("EXPLAIN") ||
        trimmed.startsWith("SHOW")
      );
    }

    この仕組みがあると、本番の全データをうっかり消すような事故を構造的に防げる。特に DELETE without WHERE の検出には実際に助けられた場面が何度かある。

    2.3 query_db ツール — メインのクエリ実行エンジン

    SQL を実行するメインツールだ。

    server.tool(
      "query_db",
      "Execute a SQL query against the database. By default only SELECT queries " +
        "are allowed. Set allow_write=true to permit INSERT/UPDATE/DELETE.",
      {
        sql: z.string().describe("SQL query to execute"),
        params: z
          .array(z.unknown())
          .optional()
          .describe("Parameterized query values ($1, $2, ...)"),
        allow_write: z
          .boolean()
          .optional()
          .default(false)
          .describe("Allow write operations"),
      },
      async ({ sql, params, allow_write }) => {
        log("info", "query_db", { sql, params, allow_write });
    
        // 読み取り専用モードのチェック
        if (!allow_write && !isReadOnly(sql)) {
          return {
            content: [
              {
                type: "text" as const,
                text: "Blocked: Only SELECT/WITH/EXPLAIN queries in read-only mode.",
              },
            ],
            isError: true,
          };
        }
    
        // 危険なパターンのチェック
        const violation = checkSqlSafety(sql);
        if (violation) {
          return {
            content: [
              {
                type: "text" as const,
                text: `Blocked: Dangerous SQL operation (${violation}).`,
              },
            ],
            isError: true,
          };
        }
    
        try {
          const result = await getPool().query(sql, params ?? []);
    
          if (result.rows && result.rows.length > 0) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: `Query returned ${result.rows.length} row(s):\n\n${JSON.stringify(result.rows, null, 2)}`,
                },
              ],
            };
          }
    
          return {
            content: [
              {
                type: "text" as const,
                text: `Query executed. Rows affected: ${result.rowCount ?? 0}`,
              },
            ],
          };
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          log("error", "query_db failed", { error: message });
          return {
            content: [
              { type: "text" as const, text: `SQL Error: ${message}` },
            ],
            isError: true,
          };
        }
      }
    );

    ここで注目してほしいのは params パラメータだ。$1, $2 形式のパラメータ化クエリが使えるので、SQL インジェクションを根本的に防げる。

    関連記事:プロンプトエンジニアリング入門:AI への指示を最適化する技術

    2.4 list_tables ツール — テーブル一覧の取得

    データベースの全テーブルを一覧表示するツール。開発の最初に「何のテーブルがあるっけ?」と確認するときに重宝する。

    server.tool(
      "list_tables",
      "List all tables with row counts and column counts.",
      {},
      async () => {
        log("info", "list_tables");
    
        const sql = `
          SELECT
            t.table_name,
            (SELECT count(*)::int FROM information_schema.columns c
             WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name)
             AS column_count,
            s.n_live_tup::int AS approx_row_count
          FROM information_schema.tables t
          LEFT JOIN pg_stat_user_tables s
            ON s.schemaname = t.table_schema AND s.relname = t.table_name
          WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE'
          ORDER BY t.table_name;
        `;
    
        try {
          const result = await getPool().query(sql);
          const lines = result.rows.map(
            (r: any) =>
              `${r.table_name.padEnd(40)} ${String(r.column_count).padStart(4)} cols   ~${String(r.approx_row_count ?? 0).padStart(8)} rows`
          );
    
          return {
            content: [
              {
                type: "text" as const,
                text:
                  `Tables (${result.rows.length}):\n\n` +
                  `${"Table".padEnd(40)} Cols   Approx Rows\n` +
                  `${"─".repeat(65)}\n` +
                  lines.join("\n"),
              },
            ],
          };
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          return {
            content: [
              { type: "text" as const, text: `Error: ${message}` },
            ],
            isError: true,
          };
        }
      }
    );

    2.5 describe_table ツール — カラム・制約・外部キーの詳細表示

    特定テーブルの詳細スキーマを取得する。カラム名、データ型、制約、外部キーまで一度に確認できる。

    server.tool(
      "describe_table",
      "Get detailed schema including columns, types, constraints, and foreign keys.",
      {
        table: z.string().describe("Table name to describe"),
      },
      async ({ table }) => {
        log("info", "describe_table", { table });
    
        // テーブル名検証(SQLインジェクション対策)
        if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
          return {
            content: [
              {
                type: "text" as const,
                text: "Invalid table name. Only alphanumeric and underscore allowed.",
              },
            ],
            isError: true,
          };
        }
    
        try {
          // カラム情報取得
          const colSql = `
            SELECT
              c.column_name,
              c.data_type,
              c.character_maximum_length,
              c.is_nullable,
              c.column_default
            FROM information_schema.columns c
            WHERE c.table_schema = 'public' AND c.table_name = $1
            ORDER BY c.ordinal_position;
          `;
          const colResult = await getPool().query(colSql, [table]);
    
          if (colResult.rows.length === 0) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: `Table '${table}' not found.`,
                },
              ],
              isError: true,
            };
          }
    
          // 制約情報取得
          const constraintSql = `
            SELECT
              tc.constraint_name,
              tc.constraint_type,
              kcu.column_name
            FROM information_schema.table_constraints tc
            JOIN information_schema.key_column_usage kcu
              ON tc.constraint_name = kcu.constraint_name
              AND tc.table_schema = kcu.table_schema
            WHERE tc.table_schema = 'public' AND tc.table_name = $1
            ORDER BY tc.constraint_type, tc.constraint_name;
          `;
          const constraintResult = await getPool().query(constraintSql, [table]);
    
          // 外部キー情報取得
          const fkSql = `
            SELECT
              kcu.column_name,
              ccu.table_name AS foreign_table,
              ccu.column_name AS foreign_column
            FROM information_schema.table_constraints tc
            JOIN information_schema.key_column_usage kcu
              ON tc.constraint_name = kcu.constraint_name
              AND tc.table_schema = kcu.table_schema
            JOIN information_schema.constraint_column_usage ccu
              ON ccu.constraint_name = tc.constraint_name
              AND ccu.table_schema = tc.table_schema
            WHERE tc.constraint_type = 'FOREIGN KEY'
              AND tc.table_schema = 'public'
              AND tc.table_name = $1;
          `;
          const fkResult = await getPool().query(fkSql, [table]);
    
          // 出力構築
          const sections: string[] = [];
          sections.push(`Table: ${table}\n`);
    
          // カラムセクション
          sections.push("Columns:");
          for (const col of colResult.rows) {
            let typeStr = col.data_type;
            if (col.character_maximum_length) {
              typeStr += `(${col.character_maximum_length})`;
            }
            const nullable = col.is_nullable === "YES" ? "NULL" : "NOT NULL";
            const def = col.column_default ? ` DEFAULT ${col.column_default}` : "";
            sections.push(
              `  ${col.column_name.padEnd(30)} ${typeStr.padEnd(25)} ${nullable}${def}`
            );
          }
    
          // 制約セクション
          if (constraintResult.rows.length > 0) {
            sections.push("\nConstraints:");
            const grouped = new Map<string, { type: string; columns: string[] }>();
            for (const row of constraintResult.rows) {
              const existing = grouped.get(row.constraint_name);
              if (existing) {
                existing.columns.push(row.column_name);
              } else {
                grouped.set(row.constraint_name, {
                  type: row.constraint_type,
                  columns: [row.column_name],
                });
              }
            }
            for (const [name, info] of grouped) {
              sections.push(
                `  ${info.type.padEnd(15)} ${name} (${info.columns.join(", ")})`
              );
            }
          }
    
          // 外部キーセクション
          if (fkResult.rows.length > 0) {
            sections.push("\nForeign Keys:");
            for (const fk of fkResult.rows) {
              sections.push(
                `  ${fk.column_name} -> ${fk.foreign_table}.${fk.foreign_column}`
              );
            }
          }
    
          return {
            content: [
              { type: "text" as const, text: sections.join("\n") },
            ],
          };
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          return {
            content: [
              { type: "text" as const, text: `Error: ${message}` },
            ],
            isError: true,
          };
        }
      }
    );

    ここまででデータベース操作に必要な 3 つのツールが揃った。次は API 連携機能を追加する。

    Step 3: API ツール実装 — JWT 認証を自動化する

    REST API への認証付きアクセスを提供する。JWT トークンの取得と更新を MCP サーバーが自動で行うので、開発者は認証を意識しなくていい。

    3.1 JWT トークン管理

    let jwtToken: string | null = null;
    let tokenExpiresAt = 0;
    
    const API_BASE_URL = process.env.API_BASE_URL || "http://localhost:8080";
    const API_EMAIL = process.env.API_EMAIL || "[email protected]";
    const API_PASSWORD = process.env.API_PASSWORD || "password";
    
    // API ログイン
    async function apiLogin(): Promise<string> {
      log("info", "API login", { email: API_EMAIL });
    
      const res = await fetch(`${API_BASE_URL}/api/v1/auth/login`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: API_EMAIL, password: API_PASSWORD }),
      });
    
      if (!res.ok) {
        const body = await res.text();
        throw new Error(`Login failed (${res.status}): ${body}`);
      }
    
      const json = (await res.json()) as {
        data?: { token?: { access_token?: string } };
        token?: string;
        access_token?: string;
      };
    
      const token =
        json.data?.token?.access_token ?? json.token ?? json.access_token;
    
      if (!token) {
        throw new Error("API response missing token field");
      }
    
      jwtToken = token;
      // トークン有効期限:1時間。5分前に自動更新
      tokenExpiresAt = Date.now() + 55 * 60 * 1000;
      log("info", "API login successful");
      return token;
    }
    
    // トークン取得(自動リフレッシュ付き)
    async function getToken(): Promise<string> {
      if (!jwtToken || Date.now() >= tokenExpiresAt) {
        return apiLogin();
      }
      return jwtToken;
    }

    JWT(JSON Web Token) は Web API の認証で広く使われるトークン方式だ。サーバーがログイン時にトークンを発行し、クライアントはそれをリクエストに添付してログイン状態を維持する。有効期限があるため、期限切れ前に自動更新する仕組みが欠かせない。

    3.2 api_request ツール — 認証済み API 呼び出し

    server.tool(
      "api_request",
      "Make an authenticated REST API call. Automatically manages JWT authentication.",
      {
        method: z
          .enum(["GET", "POST", "PUT", "PATCH", "DELETE"])
          .describe("HTTP method"),
        path: z
          .string()
          .describe("API path (e.g., /api/v1/users)"),
        body: z
          .record(z.unknown())
          .optional()
          .describe("Request body (JSON)"),
      },
      async ({ method, path, body }) => {
        log("info", "api_request", { method, path });
    
        try {
          const token = await getToken();
          const url = `${API_BASE_URL}${path.startsWith("/") ? path : "/" + path}`;
    
          const headers: Record<string, string> = {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
          };
    
          const fetchOptions: RequestInit = {
            method,
            headers,
          };
    
          if (body && ["POST", "PUT", "PATCH"].includes(method)) {
            fetchOptions.body = JSON.stringify(body);
          }
    
          const res = await fetch(url, fetchOptions);
          const contentType = res.headers.get("content-type") ?? "";
    
          let responseBody: string;
          if (contentType.includes("application/json")) {
            const json = await res.json();
            responseBody = JSON.stringify(json, null, 2);
          } else {
            responseBody = await res.text();
          }
    
          const statusLine = `${res.status} ${res.statusText}`;
    
          if (!res.ok) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: `API Error (${statusLine}):\n\n${responseBody}`,
                },
              ],
              isError: true,
            };
          }
    
          return {
            content: [
              {
                type: "text" as const,
                text: `${method} ${path} -> ${statusLine}\n\n${responseBody}`,
              },
            ],
          };
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          log("error", "api_request failed", { error: message });
          return {
            content: [
              { type: "text" as const, text: `Request failed: ${message}` },
            ],
            isError: true,
          };
        }
      }
    );

    3.3 api_login ツール — 強制再ログイン

    トークンをリセットして新しいものを取得する。認証エラーが起きたときに使う。

    server.tool(
      "api_login",
      "Force re-login to get a fresh JWT token.",
      {},
      async () => {
        log("info", "api_login (forced)");
    
        try {
          jwtToken = null;
          tokenExpiresAt = 0;
          await apiLogin();
          return {
            content: [
              {
                type: "text" as const,
                text: "Successfully logged in. Token refreshed.",
              },
            ],
          };
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          return {
            content: [
              { type: "text" as const, text: `Login failed: ${message}` },
            ],
            isError: true,
          };
        }
      }
    );

    これで 5 つのツール(query_dblist_tablesdescribe_tableapi_requestapi_login)が全て揃った。次は安全に運用するためのセキュリティ設計だ。

    Step 4: セキュリティ — 本番運用に耐える安全設計

    MCP サーバーはデータベースと API に直接アクセスできる。だからこそ、セキュリティ設計が最も重要なステップになる。ここまでの実装に組み込んだ安全機能を整理する。

    4.1 多層防御の考え方

    このサーバーでは 4 つのレイヤーで安全性を確保している。

    レイヤー機能具体的な実装
    1. 読み取り制限デフォルト読み取り専用allow_write=false で UPDATE/DELETE をブロック
    2. パターン検出危険な SQL 検出DROP、TRUNCATE、DELETE without WHERE を自動ブロック
    3. インジェクション対策パラメータ化クエリ$1, $2 プレースホルダーで SQL インジェクションを防止
    4. 入力検証テーブル名ホワイトリスト英数字とアンダースコアのみ許可

    SQL インジェクションとは、悪意のある SQL 文をアプリケーション経由で実行させる攻撃手法だ。パラメータ化クエリを使えば、ユーザー入力がそのまま SQL として実行されることを防げる。

    4.2 環境変数による認証情報管理

    認証情報をコードにハードコーディングするのは厳禁だ。必ず環境変数で管理する。

    export DATABASE_URL="postgresql://user:[email protected]:5432/mydb"
    export API_BASE_URL="https://api.example.com"
    export API_EMAIL="[email protected]"
    export API_PASSWORD="<secure-password>"
    
    npm run start

    4.3 監査ログ

    すべてのクエリと API 呼び出しは stderr にログ出力される。本番環境ではログ集約サービスに転送すれば、不審なアクセスの検出に使える。

    {"ts":"2026-02-25T10:30:00.000Z","level":"info","message":"query_db","data":{"sql":"SELECT * FROM customers","allow_write":false}}
    {"ts":"2026-02-25T10:30:05.000Z","level":"info","message":"api_request","data":{"method":"PATCH","path":"/api/v1/users/42"}}

    4.4 データベースアカウント権限の最小化

    MCP サーバー用の専用 DB ユーザーを作り、必要最小限の権限だけを付与するのがベストプラクティスだ。

    -- 読み取り専用ユーザー
    CREATE USER mcp_readonly WITH PASSWORD 'secure_password';
    GRANT CONNECT ON DATABASE mydb TO mcp_readonly;
    GRANT USAGE ON SCHEMA public TO mcp_readonly;
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;

    Step 5: Claude Code への接続

    最後のステップ。作った MCP サーバーを Claude Code に登録する。

    5.1 Claude Code の MCP 設定

    プロジェクトの .mcp.json(プロジェクト単位)または ~/.claude.json(グローバル)に以下を追加する。

    {
      "mcpServers": {
        "my-system": {
          "command": "tsx",
          "args": [
            "/path/to/my-mcp-server/src/index.ts"
          ],
          "env": {
            "DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb",
            "API_BASE_URL": "http://localhost:8080",
            "API_EMAIL": "[email protected]",
            "API_PASSWORD": "password"
          }
        }
      }
    }

    補足: Claude Code の MCP 設定は claude mcp add コマンドでも追加できる。詳しくは Claude Code 公式ドキュメント を参照。

    5.2 起動確認

    cd my-mcp-server
    npm run start

    stderr に以下のログが出れば接続成功だ。

    {"ts":"2026-02-25T10:30:00.000Z","level":"info","message":"MCP Server started"}

    Claude Code を起動すると、チャット内で query_dblist_tables などのツールが使えるようになる。

    実際の使い方:すぐに試せるユースケース 4 選

    サーバーが動いたら、早速試してみよう。開発で頻繁に使うパターンを 4 つ紹介する。

    ユースケース 1:顧客データの分析

    User: "売上が500万円以上の顧客を全て取得して"
    
    Claude Code:
    > query_db(
        sql: "SELECT * FROM customers WHERE revenue >= 5000000",
        allow_write: false
      )
    
    Result:
    ✓ Query returned 23 row(s):
    [
      { id: 1, name: "ABC Corp", revenue: 6500000, ... },
      { id: 2, name: "XYZ Inc", revenue: 5200000, ... },
      ...
    ]

    pgAdmin で SQL を手書きしていた作業が、自然言語で一発だ。

    ユースケース 2:テーブル構造の確認

    User: "顧客テーブルの構成を教えて"
    
    Claude Code:
    > describe_table(table: "customers")
    
    Result:
    ✓ Table: customers
    
    Columns:
      id                              int8              NOT NULL DEFAULT nextval(...)
      name                            text              NOT NULL
      email                           text              NOT NULL
      revenue                         numeric(12,2)    NULL
      created_at                      timestamp         NOT NULL DEFAULT now()
    
    Constraints:
      PRIMARY KEY pk_customers (id)
      UNIQUE       uq_customers_email (email)
    
    Foreign Keys:
      company_id -> companies.id

    ユースケース 3:API 経由のデータ更新

    User: "user_id=42 のプロフィール画像を更新して"
    
    Claude Code:
    > api_request(
        method: "PATCH",
        path: "/api/v1/users/42",
        body: { profile_image: "https://example.com/avatar.png" }
      )
    
    Result:
    ✓ PATCH /api/v1/users/42 -> 200 OK
    {
      "id": 42,
      "name": "John Doe",
      "profile_image": "https://example.com/avatar.png",
      "updated_at": "2026-02-25T10:35:00Z"
    }

    Postman を起動して URL を入力して認証ヘッダーを設定して……という手順が全部不要になる。

    ユースケース 4:複雑な集計分析

    User: "過去30日間の営業案件をステータスごとに集計して、
    件数と平均金額を出して"
    
    Claude Code:
    > query_db(
        sql: `
          SELECT
            status,
            COUNT(*) as count,
            AVG(amount) as avg_amount
          FROM deals
          WHERE created_at >= NOW() - INTERVAL '30 days'
          GROUP BY status
          ORDER BY count DESC
        `,
        allow_write: false
      )
    
    Result:
    ✓ Query returned 5 row(s):
    [
      { status: "closed_won", count: 47, avg_amount: 1250000 },
      { status: "negotiation", count: 23, avg_amount: 800000 },
      { status: "prospect", count: 15, avg_amount: 500000 },
      ...
    ]

    複雑な SQL もチャットで依頼するだけで実行できる。Claude が SQL を自動生成するので、SQL に詳しくないメンバーでもデータ分析に参加できる。

    関連記事:AI 議事録ツールの選び方:会議の生産性を2倍にする実践ガイド

    既存の MCP サーバーとの違い

    「PostgreSQL 用の MCP サーバーなら既に公開されているのでは?」という疑問はもっともだ。実際、公開レジストリには PostgreSQL 用のサーバーがいくつかある。

    ただ、自社システム専用に作る価値は明確にある。

    観点汎用 MCP サーバー自作 MCP サーバー
    API 連携なし(DB のみ)自社 API に完全対応
    認証汎用的自社の JWT フローに最適化
    セキュリティ汎用ルール自社のポリシーに合わせた制御
    ビジネスロジックなし業務特有のツールを追加可能
    メンテナンス外部依存チーム内で完全制御

    汎用サーバーはデータベースだけ触れればいい場合には十分だが、API 連携、カスタム認証、業務固有のロジックが必要になると自作が圧倒的に有利になる。

    よくある質問(FAQ)

    Q: MCP サーバーは本番環境でも使えますか?

    A: 使えるが、慎重な設計が要る。本番では読み取り専用の DB アカウント、SSL/TLS 通信、アクセスログの監視を必ず設定すること。この記事の「セキュリティ」セクションのベストプラクティスに従えば、安全に運用できる。

    Q: 他の AI ツール(ChatGPT、Gemini など)でも使えますか?

    A: 使える。MCP はオープン標準であり、対応する AI ツールなら何でも接続できる。2025 年以降、OpenAI や Google も MCP サポートを発表しており、一度作った MCP サーバーは複数の AI ツールで再利用可能だ。

    Q: 複雑な JOIN クエリでも大丈夫ですか?

    A: 基本的には問題ない。ただし、何十行にもわたる超複雑なクエリやパフォーマンスチューニングが必要なケースでは、DBeaver のような専用クエリエディタのほうが適している場面もある。MCP は日常の開発作業を効率化するツールであり、すべてを置き換えるものではない。

    Q: セキュリティリスクはありますか?

    A: MCP サーバー自体はローカルで動作し、ネットワークに公開されない。リスクは主に (1) 認証情報の管理ミス、(2) 過剰な DB 権限、(3) 安全チェックの不備 の 3 点。この記事で解説した多層防御(読み取り制限、パターン検出、パラメータ化クエリ、入力検証)をすべて実装すれば、リスクは最小限に抑えられる。

    自社システム × MCP で開発体験を変える

    MCP サーバーを自社システムに統合すると、以下が手に入る。

    • 開発効率の向上 — データベースクエリや API 呼び出しが自然言語で実行できる
    • 堅牢なセキュリティ — 多層防御で、読み取り専用、SQL パターン検出、パラメータ化クエリ、入力検証をカバー
    • 将来性 — MCP はオープン標準なので、一度作ればどの AI ツールでも再利用できる
    • 柔軟な拡張性 — 5 つのツールをベースに、業務に合わせたカスタムツールを足していける

    実装は 600 行程度の TypeScript で完成し、セットアップも半日あれば十分。自社システムを持っているなら、専用の MCP サーバーを作る価値は十分にある。

    この記事のコードをベースに、あなたのシステム専用の MCP サーバーを作ってみてほしい。

  • GitHub運用ガイドを社内標準化した話 — 小規模チーム開発の土台を固める

    GitHub運用ガイドを社内標準化した話 — 小規模チーム開発の土台を固める

    GitHub運用がバラバラだった頃

    弊社EastCloudは受託開発と自社プロダクト開発の両軸で事業を進めている。GitHubは早い段階から導入していたが、運用ルールが定まらないまま開発を続けていた。

    プロジェクトが増え、メンバーが増えてくると、ルールの不在が目に見えて開発効率に影響し始めた。そこで社内のGitHub運用を標準化するガイドを作成した。

    ここではガイド作成の背景、内容のポイント、導入後に確認できた効果をまとめる。

    整備前に抱えていた4つの課題

    ガイド作成にあたって社内の開発フローを棚卸しした。見えてきた課題は4つあった。

    • ブランチ運用の属人化 — 命名規則がなく、誰がどのブランチで何を開発しているか把握できていなかった
    • コミット履歴の不透明さ — コミットメッセージが「修正」「対応」ばかりで、変更の意図を後から追えなかった
    • コードレビュー文化の不在 — PRを経由せずmainブランチへ直接pushする運用が常態化していた
    • 非エンジニアメンバーの疎外 — PMやデザイナーがGitHubの画面を見ても開発状況がまったくわからなかった

    いずれも少人数チームで起こりがちな問題だ。暗黙知で回せる段階はすぐに限界を迎える。メンバーが5人を超えたあたりから、明文化されたルールがないと開発効率は目に見えて落ちる。

    ガイドの全体構成

    作成したガイドは10章+付録の構成にした。エンジニアだけでなくPMやデザイナーも読める内容にすることを重視している。

    内容対象者
    1. ブランチ戦略main/develop/feature/hotfixの運用フローエンジニア
    2. コミットメッセージ規約type付きメッセージのルールエンジニア
    3. Issue運用タスク管理の最小単位としてのIssue活用全員
    4. PR運用PRフロー・タイトル規約・マージ方針エンジニア
    5. コードレビュー観点正確性・セキュリティ・可読性・パフォーマンス・テストエンジニア
    6. GitHub Projectsボード運用カンバンのステータス管理全員
    7. CI/CD方針GitHub Actionsによる自動化エンジニア
    8. 日常の開発フロー一連の作業手順まとめエンジニア
    9. よくある質問コンフリクト対応・誤push対応などエンジニア
    10. 非エンジニア向け基礎GitHubの基本概念と3つのアクションPM・デザイナー
    付録Gitコマンド早見表エンジニア

    では各章のポイントを掘り下げていく。

    ブランチ戦略 — 4種類に絞った理由

    小規模チーム向けのブランチ戦略としてはGitHub Flowがよく推奨される。mainブランチとfeatureブランチだけで回すシンプルなモデルだ。ただ、弊社は受託開発も手がけており、リリースタイミングが顧客との調整で決まる案件が多い。

    GitHub Flowはmainにマージしたらすぐデプロイする前提だ。リリースを溜めてまとめて出す受託開発には合わない。そこでGit Flowをベースに、ブランチを4種類に絞った簡略版を採用した。releaseブランチはリリースフローが複雑化するまで導入しない。

    ブランチ用途命名規則
    main本番リリース済みコード
    develop開発統合ブランチ
    feature/*新機能・改善feature/<issue番号>-<説明>
    hotfix/*本番の緊急修正hotfix/<issue番号>-<説明>

    ブランチ名はケバブケースで統一した。feature/6-authentication-apihotfix/23-fix-login-errorといった形だ。Issue番号を先頭に置くことで、どのタスクに対応するブランチかひと目でわかる。

    運用面で大事にしたのは、mainだけでなくdevelopへの直接コミットも禁止した点だ。すべての変更はfeatureブランチからPRを経由してdevelopにマージする。加えてhotfix/*ブランチはmainから作成し、修正後はmaindevelopの両方にマージして修正漏れを防ぐ。

    ここで欠かせないのがGitHubのBranch Protection Rule(ブランチ保護ルール)だ。mainへの直接pushをシステム的に禁止した。ルールは口頭で伝えるだけでは定着しない。ツールで強制するほうが確実だ。

    なおGitHubは2023年7月にRepository Rulesetsという新しい保護機能をGA(一般提供)している。従来のBranch Protection Ruleと併存しており、Organization全体に統一ルールを適用したい場合はRulesetsのほうが柔軟だ。ただし単一リポジトリの保護なら従来のBranch Protection Ruleで足りるため、今回はそちらを採用した。

    コミットメッセージ規約 — 6つのtypeで変更意図を明確にする

    コミットメッセージの統一にはConventional Commitsの考え方をベースにしたシンプルなフォーマットを採用した。

    <type>: <概要>

    scopeは省いた。小規模チームではモジュール境界がまだ流動的で、scopeの粒度をルール化すると逆に混乱する。概要は日本語でも英語でもよい。

    typeは6つに絞った。

    type用途
    feat新機能feat: ログインAPI追加
    fixバグ修正fix: トークン有効期限の修正
    refactorリファクタリングrefactor: バリデーション処理を分離
    docsドキュメントdocs: API仕様書を追加
    testテストtest: 認証コントローラのテスト追加
    choreビルド・設定・その他chore: docker-compose設定を更新

    Issue番号はコミットメッセージには含めず、PRタイトルで紐付ける運用にした。コミットメッセージは変更の意図だけに集中させ、タスク管理の情報はPRに寄せて役割を分けている。

    Issue運用とGitHub Projectsボード

    Issueをタスク管理の最小単位にする

    ガイドではIssueをタスク管理の最小単位と位置づけた。すべての作業はIssueを起点にする。急ぎのhotfixでも事後でIssueを作成するルールだ。

    IssueにはラベルをつけてProjectsボードと連携する。推奨ラベルとしてbackendfrontendinfrastructurebugdocumentationを用意した。プロジェクトに応じて追加・変更できる。

    Projectsボードのステータス管理

    GitHub Projectsボードのステータスは4つに絞った。

    ステータス説明
    Todo未着手
    In Progress作業中
    In ReviewPR作成済み・レビュー待ち
    Done完了

    Issueに着手したら必ずIn Progressに変更する。PRを作成したらIn Reviewに変更する。さらに週次でボードの棚卸しを行い、停滞しているIssueがないか確認する。この棚卸しがないとボードは簡単に形骸化するため、週次ミーティングの固定アジェンダに組み込んだ。

    PR運用 — タイトル規約とマージ方針

    PRタイトルにIssue番号を必須化

    PRタイトルにはIssue番号を必ず含めるルールにした。フォーマットはこうだ。

    feat[#6]: 認証API実装
    fix[#12]: ユーザー一覧の表示不具合を修正

    type[#Issue番号]: 概要の形式で、コミットメッセージ規約のtypeを流用している。PRタイトルを見ただけで、どのタスクの、どんな種類の変更かわかる。

    PRテンプレートの標準化

    PRテンプレートは.github/PULL_REQUEST_TEMPLATE.mdに配置した。セクションは概要・対応Issue・変更内容・確認事項の4つだ。

    特に効いたのが確認事項のチェックリストだ。「ローカルで動作確認済み」「テスト追加・更新済み」「既存機能への影響なし」の3点をセルフチェックさせることで、レビュアーの負担を下げた。

    加えてPR本文にcloses #<Issue番号>を記載すると、マージ時にIssueが自動クローズされる。閉じ忘れを防ぐ仕組みとして活用している。

    マージ方針の使い分け

    マージ方法は2つを使い分ける。

    種別マージ方法理由
    機能ブランチ → developSquash and merge作業途中の細かいコミットを1つにまとめて履歴を整理する
    developmainMerge commitリリース単位のマージ履歴を明示的に残す

    Squash and mergeを使えば、featureブランチ内で何度コミットしてもdevelopの履歴はPR単位で1コミットに整理される。コミットの粒度を気にせず開発できるため、開発者の心理的なハードルが下がる。

    レビュー方針

    レビューは最低1名のApproveを必須にした。ただし軽微な修正(typo、コメント追記等)はセルフマージを許可している。すべてのPRに厳格なレビューを求めると小規模チームでは回らない。重要な変更にレビューリソースを集中させるための割り切りだ。

    コードレビュー観点 — 5つの視点でチェックする

    レビュアーが何を見ればいいか迷わないよう、コードレビューの観点を5つに分けて明文化した。

    正確性 — ビルドが通ること。Issueの要件を満たしていること。境界値やエッジケースが考慮されていること。

    セキュリティ — SQLインジェクション対策でパラメータバインドを使っているか。XSS対策でユーザー入力値をエスケープしているか。機密情報がハードコードされていないか。CSRF対策が必要な箇所で実装されているか。

    可読性・保守性 — 変数名・関数名がその役割を表しているか。1つの関数が1つの責務に集中しているか。デバッグ用コード(console.log等)が残っていないか。マジックナンバーが定数化されているか。

    パフォーマンス — N+1クエリが発生していないか。ループ内で不要なAPIコールがないか。大量データの処理でページネーションが考慮されているか。

    テスト — 変更に対応するテストが追加されているか。正常系・異常系の両方がテストされているか。

    この5観点をガイドに書いたことで、レビューの属人化を防げた。新しくレビュアーになったメンバーでも、何をチェックすべきか迷わない。

    CI/CD方針 — GitHub Actionsによる自動化

    ガイドにはCI/CD方針も盛り込んだ。GitHub Actionsで以下のタイミングに自動化を走らせる。

    トリガー実行内容
    PR作成・更新時Lint + ビルド + テスト
    developへのマージステージング環境へデプロイ
    mainへのマージ本番環境へデプロイ

    CIのチェック項目はコードフォーマット(Lint)、ビルド成功、ユニットテスト通過の3つ。CIが失敗しているPRはマージしない。Branch Protection RuleでCIパスを必須条件に設定すれば、この運用もツールで強制できる。

    非エンジニア向けGitHub基礎 — 社内で最も反応が良かったセクション

    ガイドの中で社内から最も反応が良かったのがこのセクションだ。

    PMやデザイナーがGitHub上で開発状況を把握できるよう、基本概念をスクリーンショット付きで解説した。技術用語は極力使わず、ブランチはWord文書の変更履歴を別ファイルに分けて管理するイメージ、PRはGoogleドキュメントの提案モードに近い仕組み、という比喩で説明している。

    非エンジニアに覚えてもらった3つのアクション

    1. Issueにコメントする
    仕様の質問や確認をGitHub上で行い、情報を一元管理する。Slackやメールに散らばっていた仕様確認がIssueに集約されるだけで、後からの振り返りが格段に楽になる。

    2. PRのFilesタブを確認する
    コードの詳細はわからなくても、何が変わったかを視覚的に把握できる。緑の行が追加、赤の行が削除。この見方だけ覚えてもらった。

    3. PRにApproveする
    PM承認をGitHub上で行い、口頭承認によるコミュニケーションロスをなくす。PR画面の「Review changes」から「Approve」を選ぶだけで完了する。

    この3つを徹底したことで、開発プロセスの透明性が大幅に上がった。PMが開発状況をリアルタイムで把握できるようになり、チーム全体の連携が目に見えてスムーズになった。Projectsボードの見方もあわせて説明し、PMがタスク進捗を一覧で確認できるようにしている。

    導入後の定量効果

    ガイドを展開してから約1ヶ月後に振り返りを行った。確認できた成果は次の通りだ。

    指標導入前導入後
    mainへの直接push週に数回発生ゼロ
    コミットメッセージ規約の遵守率ほぼ0%約80%
    PRの説明記述率約20%約90%
    非エンジニアのGitHub利用ほぼなしPMがIssueコメントを日常的に利用

    遵守率が100%に届いていないのは正直なところだ。typeの使い分け自体はほぼ100%守られている。ただ概要の書き方にはまだばらつきがある。ここは引き続き改善を進めている。

    定性面で大きかったのは、PMがGitHub上でIssueコメントを行うようになった点だ。以前はSlackでの口頭確認が中心で、仕様の決定経緯がよく流れていた。Issueに記録が残るようになったことで情報の散逸を防げるようになった。

    ガイド作成で意識した3つの原則

    今回のガイド作成を通じて、強く意識した原則が3つある。

    ルールを最小限にすること。 覚えきれないルールは形骸化する。コミットメッセージのtypeは6つ、Projectsボードのステータスは4つ、非エンジニアに覚えてもらうアクションは3つ。小規模チームに必要十分な数に絞り、確実な定着を狙った。

    ツールで強制すること。 Branch Protectionによるmainへの直接push禁止、CIパス必須によるマージ制限、PRテンプレートによる説明欄の標準化。人の注意力に頼るルールはいずれ破綻する。守らざるを得ない環境を作ることが運用定着への最短経路だ。

    非エンジニアを巻き込むこと。 開発プロセスの透明性はチーム全体の生産性に直結する。エンジニアだけが使うツールのままではGitHubの価値は半減する。PMやデザイナーも参加できる設計にしたことで、コミュニケーションコストが下がった。

    よくある質問

    Q. GitHub FlowではなくGit Flowベースにした理由は?

    弊社は受託開発も手がけており、リリースタイミングが顧客調整で決まることが多い。GitHub Flowはmainにマージしたらすぐデプロイする前提だ。リリースを溜めてまとめて出す運用にはフィットしない。developブランチを挟む簡略版Git Flowのほうが実情に合った。

    Q. コミットメッセージにscopeを入れなかった理由は?

    小規模チームではモジュール境界がまだ流動的で、scopeの粒度をルール化すると混乱を招く。typeと概要だけのシンプルなフォーマットにして、覚えやすさと定着率を優先した。

    Q. 非エンジニアにGitHubを使ってもらうのは現実的か?

    弊社ではうまくいった。覚えてもらうアクションをIssueコメント、PRのFilesタブ確認、Approveの3つに絞ったことが大きい。いずれもWebブラウザ上で完結するため学習コストは低い。

    Q. Squash and mergeとMerge commitを使い分ける理由は?

    featureブランチでは試行錯誤で細かいコミットが積まれがちだ。Squash and mergeで1コミットにまとめれば、developの履歴はPR単位で整理される。一方、develop→mainはリリース単位の記録として残したいため、Merge commitでマージ履歴を明示する。

    Q. ガイドの分量はどのくらいが適切?

    今回は本文10章+付録の構成だった。各章を独立して読めるようにしたので、エンジニアは1〜9章、非エンジニアは10章とProjectsボードの章だけ読めば運用を始められる。全部読まなくても始められる設計にしておくのが大事だ。

    Q. Branch Protection RuleとRepository Rulesetsはどちらを使うべき?

    単一リポジトリの保護ならBranch Protection Ruleで十分だ。Organization全体に統一ルールを適用したい場合や、複数リポジトリに同じ保護設定を展開したい場合はRepository Rulesetsが向いている。両者は併存できるため、まずBranch Protection Ruleから始めて必要に応じて移行するのがよい。

    関連記事

    ガイドのダウンロード

    今回作成したガイドを、汎用的に使える形に整理したPDF資料として公開している。自社の状況に合わせてカスタマイズして使ってほしい。

    GitHub運用ガイド(PDF)をダウンロードする

    小規模チームこそ早い段階で開発プロセスを整備する意味がある。人数が増えてからでは、ゼロから構築するよりも遥かにコストがかかる。

  • 情報収集を全自動化する方法 ― RSS・LLMキュレーション・チャット配信の実装ガイド

    情報収集を全自動化する方法 ― RSS・LLMキュレーション・チャット配信の実装ガイド

    エンジニアチームが抱える情報収集の4つの壁

    ソフトウェア開発の世界は変化が激しい。毎日のように新しいツールやフレームワークがリリースされる。とりわけ2024年以降はAI関連の進歩が加速しており、1週間キャッチアップを怠ると動向を見逃しかねない。

    弊社のエンジニアチームも、この問題に長く悩んでいた。

    • 個人依存の情報収集 — 各メンバーが独自のやり方で情報を追い、チーム全体への共有が薄い
    • 時間の問題 — 業務時間中にRSSリーダーやHacker Newsを巡回する余裕がない
    • 英語の壁 — 一次情報の大半は英語。全員が均等にアクセスできるわけではない
    • 情報の偏り — 個人の関心領域に引っ張られ、チームとして押さえるべき技術動向にムラが出る

    毎日15分で技術トレンドをチーム全員がキャッチアップできる仕組みが欲しい。その声をきっかけに、情報収集パイプラインの全自動化に取り組んだ。

    では、RSS収集からLLMキュレーション、チャットツールへの配信までを一気通貫で自動化した仕組みと、数ヶ月の運用で得た知見を順に見ていく。


    自動化パイプラインの全体像

    構築したパイプラインは3つのフェーズで動く。

    [収集フェーズ]          [キュレーションフェーズ]        [配信フェーズ]
    
     RSS Feeds ─────┐
                     ├─→ Markdown ─→ LLM(Claude Haiku) ─→ Discord / Slack
     GitHub Trend ──┘    ファイル      10〜15件に厳選         サマリー配信
           │
        DeepL API
       (英→日翻訳)

    まず、それぞれの役割を整理する。

    1. 収集 (rss_collector.py) — 複数のRSSフィードとGitHubトレンドから記事を取得し、英語記事はDeepL APIで翻訳。Markdownファイルとして出力する
    2. キュレーション (news_distributor.py) — LLMが記事を評価・厳選し、チームにとって価値の高い10〜15件を選ぶ
    3. 配信 — DiscordやSlackに通知なしで投稿。サマリーと個別記事の2段構成にする

    すべてPythonで実装し、日次のスケジュール実行で完全に自動化している。


    収集の仕組み ― 複数ソースからの情報集約

    RSSフィードの選定

    収集元のRSSフィードは、チームの技術スタックと関心領域に合わせて選んでいる。

    # フィード設定の例(実際のコードを簡略化したもの)
    feeds = [
        # 主要テックメディア
        "https://hnrss.org/newest?points=100",         # Hacker News (100pt以上)
        "https://www.infoq.com/feed/",                  # InfoQ
        "https://techcrunch.com/feed/",                 # TechCrunch
    
        # AI/LLM特化
        "https://openai.com/news/rss.xml",              # OpenAI News
        "https://rsshub.app/anthropic/news",            # Anthropic (RSSHub経由)
    
        # 開発ツール・クラウド
        "https://github.blog/feed/",                    # GitHub Blog
        "https://aws.amazon.com/blogs/aws/feed/",       # AWS Blog
    
        # 日本語ソース
        "https://zenn.dev/feed",                        # Zenn
        "https://b.hatena.ne.jp/hotentry/it.rss",       # はてなブックマーク IT
    ]

    ここでのポイントは hnrss.org の活用だ。Hacker News本体にはフィルタ付きRSSがない。だが、hnrss.orgを使えば ?points=100 のようにスコアで足切りでき、ノイズを大幅に減らせる。

    加えて、Anthropicのようにネイティブなフィードを提供していないサイトもある。こうしたケースではRSSHubなどのコミュニティツールを使ってフィードを生成する。フィード収集の安定性を考えると、各ソースのRSS提供状況は定期的に確認したほうがいい。

    GitHubトレンドの収集

    RSSだけでは拾いきれない今まさに注目されているリポジトリを把握するために、GitHubのトレンドページからも情報を引いている。新しいOSSツールやライブラリの早期発見に役立ち、チーム内で面白いリポジトリを見つけたという会話のきっかけにもなる。

    DeepL APIによる翻訳処理

    一次情報の多くは英語で出てくる。チーム全員がストレスなく読めるように、英語記事はDeepL APIで日本語に翻訳している。

    # 翻訳処理の概要
    def translate_if_needed(article):
        if detect_language(article.title) == "en":
            article.title_ja = deepl_translate(article.title, target="JA")
            article.summary_ja = deepl_translate(article.summary, target="JA")
        return article

    DeepL APIを選んだのは、技術文書の翻訳精度が高く専門用語の扱いが自然だからだ。2024年7月に発表されたDeepLの次世代言語モデルは、ブラインドテストでChatGPT-4比1.7倍、Google翻訳比1.3倍の品質評価を得ている。とりわけ日英間の翻訳ではニュアンスの再現に強みがある。

    翻訳にLLMを使う選択肢も検討した。しかしコストとレイテンシの両面でDeepLが勝った。DeepL API Freeプランは月50万文字まで無料で使える。Proプランに移行しても月額基本料5.49ドルに加え、100万文字あたり約25ドルで済む。日次パイプラインの翻訳量ならFreeプランの範囲内で収まるケースも多い。

    Markdownへの出力

    収集した記事はMarkdownファイルとして保存する。日付ごとにファイルが生成され、次のキュレーションフェーズへの入力になる。

    ## 2026-02-17 収集記事一覧
    
    ### [1] Anthropic Releases New Model with Enhanced Coding Capabilities
    - URL: https://example.com/article1
    - ソース: Anthropic
    - 翻訳タイトル: Anthropic、コーディング能力を強化した新モデルを発表
    - サマリー: ...

    では、集めた記事をどう絞り込むか。次のキュレーションフェーズが肝になる。


    LLMキュレーションの実装 ― AIが読むべき記事を選ぶ

    キュレーションが必要な理由

    RSSフィードから集まる記事は、1日あたり数十件から百件を超えることもある。これを全部チャットに流すと、情報過多で誰も読まなくなる。

    実際に試した。キュレーションなしでそのまま流したところ、通知が多すぎて見なくなったというフィードバックが1週間で返ってきた。

    だからといって人間が毎日手作業で選定するのは続かない。単純なキーワードフィルタでは文脈を踏まえた取捨ができない。そこでLLMによるキュレーションを導入した。

    選定基準の設計

    LLMに渡すプロンプトでは、選定基準を明確に記述している。

    curation_prompt = """
    以下の記事リストから、ソフトウェア開発チームにとって価値の高い記事を
    10〜15件選定してください。
    
    ## 選定基準(優先度順)
    
    1. AI/LLM動向: 新モデル、新サービス、実用的なユースケース
    2. 開発ツール: 生産性を向上させるツールやサービスのアップデート
    3. SaaS/クラウド: 主要クラウドプロバイダーの新機能、料金変更
    4. GitHubリポジトリ: 実用的で注目度の高いOSSプロジェクト
    
    ## 除外基準
    
    - 初心者向けチュートリアル
    - 基礎的すぎる内容(チームの技術レベルに合わない)
    - 広告色の強いプレスリリース
    - 古い情報の焼き直し
    
    ## 出力形式
    選定した記事ごとに、選定理由を1行で添えてください。
    """

    ここで重視したのは除外基準の明示だ。LLMは指示がなければ記事を落とすことに慎重になりがちで、件数が膨らむ。除外条件を具体的に書くことで、厳選の精度が上がった。

    Claude Haikuを採用した理由

    キュレーションにはClaude 3 Haikuを使っている。

    • コストが低い — 日次で回すため、ランニングコストの低さは外せなかった。Claude 3 Haikuは入力100万トークンあたり0.25ドル。後継のClaude 3.5 Haikuでも1ドルに収まる
    • タスクに対して十分な精度 — 記事の選定と要約にはHaikuクラスで十分な品質が出る
    • 応答が速い — 収集から配信までのパイプライン全体を高速に回せる

    さらに高性能なモデルを使うことも検討した。ただ、キュレーションは判断の正確さより大外しをしない安定感が求められるタスクだ。コスト対効果でHaikuがベストバランスだった。

    運用で得た知見 ― ハルシネーション対策

    LLMキュレーションで最も注意すべき点がハルシネーションだ。

    弊社の運用でも、ソース情報がURLのみの場合にLLMが記事内容を推測し、実際とは異なるサマリーを生成するケースが発生した。たとえばURLのドメインとパス名だけから、おそらくこういう内容だろうと補完してしまう。

    対策として3つの施策を実施した。

    1. メタデータの事前取得

    URLだけでなく、ページタイトルやog:descriptionを事前にフェッチしてLLMに渡す。これだけでハルシネーション率が大幅に下がった。

    def enrich_article_metadata(article):
        """URLからメタデータを取得し、LLMへの入力を充実させる"""
        metadata = fetch_url_metadata(article.url)
        article.title = metadata.get("og:title", article.title)
        article.description = metadata.get("og:description", "")
        return article

    2. アンチハルシネーション指示の追加

    プロンプトに「提供された情報のみに基づいて要約してください。推測や補完は行わないでください」と明記する。LLMは指示があれば分からないと答えられる。指示がなければ補完しようとする。この差は大きい。

    3. 後処理でのバリデーション

    出力結果に対して、元記事のタイトルやキーワードとの整合性をチェックする。完全な自動検証は難しいが、明らかな逸脱は検出できる。

    この3つの対策を組み合わせたことで、キュレーション結果の信頼性が安定した。では、厳選した記事をどうチームに届けるか。


    チャットツールへの配信設計 ― 業務を邪魔しない情報共有

    通知なし配信という設計判断

    配信先としてDiscordとSlackに対応している。ここで最も大事にした設計判断は通知なしで配信することだ。

    技術ニュースは今すぐ読まなければならない情報ではない。手が空いたときに目を通せばいい。それなのに通知が鳴るたびに集中が途切れては本末転倒だ。

    # Discordへの配信(silent=Trueで通知を抑制)
    async def post_to_discord(channel, message):
        await channel.send(
            content=message,
            suppress_embeds=False,
            silent=True  # SUPPRESS_NOTIFICATIONSフラグを設定
        )

    discord.py 2.2以降では silent=True パラメータが使える。内部的には MessageFlags.SUPPRESS_NOTIFICATIONS フラグが設定され、プッシュ通知やデスクトップ通知を抑制する。チャンネル内のメッセージ自体は通常どおり表示される。

    Slackでも同様に、unfurlは有効にしつつメンションや通知トリガーを含めない形で投稿している。

    メッセージの構成

    配信は2段構成にしている。

    1. サマリーメッセージ

    その日の厳選記事の概要を一覧で示す。忙しい日はこれだけ見ればいい。

    本日のテックニュース(2026/02/17)12件
    
    1. Bun 1.3リリース — GC改善でアイドルCPU消費を100分の1に削減
    2. GitHub Copilot Workspace がGA — AIペアプロの新しい形
    3. AWS Step Functions、Express Workflowsの同時実行上限を5倍に拡張
    4. shadcn/ui にRTLサポートが追加
    ...

    2. 個別記事メッセージ

    各記事について、LLMが生成した要約と元記事のURLを投稿する。

    Bun 1.3リリース — GC改善でアイドルCPU消費を100分の1に削減
    
    JavaScriptランタイムBunの1.3がリリースされた。
    GCをイベントループに統合し、アイドル時のCPU消費を
    100分の1に削減。メモリ使用量も40%減少。
    組み込みSQLクライアントやRedisクライアントも追加された。
    
    https://bun.sh/blog/bun-v1.3

    Discord配信時のレート制限対策

    Discordへの配信では、APIのレート制限に気をつける必要がある。短時間に大量のメッセージを送ると制限に引っかかる。メッセージ間に待機時間を入れて回避している。

    # レート制限対策
    for article in selected_articles:
        await post_article_message(channel, article)
        await asyncio.sleep(0.3)  # 300msの間隔を確保

    0.3秒のインターバルは実運用で安定する最小値だった。10〜15件の配信でも合計5秒以内に完了するため、体感上の遅延はない。


    導入効果 ― 数ヶ月の運用を経て

    パイプラインの運用を始めて数ヶ月が経過した。定量と定性の両面から振り返る。

    定量的な効果

    指標導入前導入後
    チーム全体の情報キャッチアップ率まばら毎日15件を全員が閲覧可能
    情報収集にかかる個人の時間30分〜1時間/日ほぼゼロ(閲覧のみ)
    英語記事へのアクセシビリティ個人差あり全員が日本語で閲覧
    運用コスト日次の手動作業完全自動(月1回程度のメンテ)

    とりわけ情報収集にかかる時間の変化が大きかった。1人あたり毎日30分として、5人チームなら月に約50時間の工数削減になる。

    定性的な効果

    雑談の質が変わった。 昨日のニュースで流れてたあのツール、使ってみた? という会話が自然に生まれるようになった。同じ情報を全員が見ているからこそ起きる変化だ。

    技術選定の引き出しが増えた。 新しいOSSやサービスを早い段階で認知できるため、プロジェクトの技術選定で選択肢が広がった。

    英語記事へのハードルが下がった。 翻訳付きで配信されるので、英語が得意でないメンバーも一次情報に触れられるようになった。結果として、チーム内の情報格差が縮まった。

    受動的な学習効果があった。 自分からは検索しなかっただろう分野の情報にも触れる機会が増えた。知らないことを知らない状態が減り、技術的な視野が広がる実感がある。

    予想外だった効果

    導入前はテック系の情報を追うツールという位置づけだった。ところが運用を続けるうちに、チーム内の技術的な共通言語が増えていくという副次的な変化が見えてきた。全員が同じニュースを読んでいるため、議論の前提をそろえやすい。技術的な意思決定のスピードが上がったのは、この共通基盤のおかげだと感じている。


    構築時のつまずきポイントと対処法

    自動化パイプラインの構築は順調ではなかった。実際にぶつかった問題とその対処法を共有する。

    RSSフィードの不安定さ

    RSSフィードは提供元の都合で突然フォーマットが変わったり、配信が止まったりする。テック企業のブログはサイトリニューアルでURLごと変わることがある。

    そのため、フィードごとにエラーハンドリングを入れ、取得失敗時はスキップして他のフィードの処理を続行する設計にした。加えて、フィードの死活監視を週次で走らせ、応答がないフィードを検出できるようにしている。

    LLMの出力形式のブレ

    LLMの出力は毎回微妙に異なる。JSONで返すよう指示しても、Markdownで返ってきたり余計な前置きが付いたりする。

    ここでは、パース処理に柔軟性を持たせることで対処した。正規表現でJSONブロックを抽出する処理に加え、箇条書き形式もフォールバックとして受け付ける多段パーサーを用意している。

    翻訳の精度とコスト管理

    DeepL APIの翻訳は高品質だが、長文記事を丸ごと翻訳するとコストが膨らむ。タイトルとサマリー、最大300文字程度のみを翻訳対象とすることで、品質を保ちつつコストを抑えた。


    予想FAQ

    Q1. パイプライン全体の構築にどのくらい時間がかかりましたか?

    最初のプロトタイプは2日で動いた。ただし、ハルシネーション対策やレート制限への対応、出力パーサーの改善など、安定稼働させるまでの調整に2週間ほどかかっている。

    Q2. LLMのキュレーション精度はどの程度ですか?

    体感では8割以上の満足度だ。なぜこの記事を選んだ? と思うこともたまにあるが、致命的な選定ミスはほぼない。プロンプトの選定基準を具体的に書くほど精度は上がる傾向にある。

    Q3. DeepL API以外の翻訳手段も検討しましたか?

    Google Cloud Translation APIとLLMによる翻訳を比較した。Google翻訳はコストが安いが技術用語の訳が不自然なケースがあった。LLM翻訳は品質が高いがコストとレイテンシに課題があった。総合的にDeepLのバランスが良かった。

    Q4. フィードの追加や削除はどう管理していますか?

    フィード一覧はYAMLファイルで管理している。追加や削除はYAMLを編集してデプロイするだけで反映される。月に1回程度、フィードの見直しを行い、ノイズが多いフィードの閾値調整や新規フィードの追加を実施している。

    Q5. 他のチャットツールにも対応できますか?

    配信部分はチャットツールごとにアダプタを実装する設計にしている。現在はDiscordとSlackに対応しているが、Microsoft TeamsやGoogle Chatへの対応もアダプタの追加だけで可能だ。

    Q6. 類似のツールやサービスとの違いは?

    RSSリーダーにAIフィルタを組み合わせたサービスは増えている。Feedly AIやInoreaderのAIアシスタントなどが代表的だ。ただ、チーム固有の選定基準をプロンプトで細かく制御できる点、配信先やフォーマットを自由にカスタマイズできる点が自前パイプラインの強みになる。既製品で足りるケースももちろんあるので、チームの規模や要件に合わせて選ぶとよい。


    まとめ

    RSS収集、LLMキュレーション、チャット配信。この3つのフェーズを組み合わせて、情報収集パイプラインを全自動化した。

    技術的に特別なことはしていない。RSSパーサー、翻訳API、LLM API、チャットBot。既存の技術を組み合わせただけだ。ただ、これらを1つのパイプラインとしてつなぎ、日次で安定稼働させることで、チーム全体の情報力を底上げできた。

    今後は収集した情報をナレッジベースとして蓄積する仕組みや、社内のQ&Aシステムとの連携にも取り組んでいく。

  • AppRun入門 共用型・専有型の違いとプライベート環境の構築手順

    AppRun入門 共用型・専有型の違いとプライベート環境の構築手順

    AppRunとは何か

    さくらインターネットが2025年12月9日に正式リリースしたAppRunは、コンテナイメージを渡すだけでアプリを公開できるマネージドサービスだ。裏側ではKubernetesとKnativeが動いている。Knativeは元々Google CloudがCloud Runの基盤として開発し、2021年にCloud Native Computing Foundation(CNCF)へ寄贈されたオープンソースだ。

    ただし利用者がKubernetesやKnativeを直接触る場面はない。具体的には、コンテナイメージとポート番号を指定するだけで、ロードバランシング、オートスケール、TLS終端までAppRunが処理してくれる。つまり、インフラ構築や運用にかける時間を削り、アプリ開発に集中できるわけだ。

    この記事では以下の内容を扱う。

    • 共用型と専有型の料金・機能比較
    • 専有型でSEGを使ったプライベート環境の構築手順
    • apprun-cliによるCI/CDパイプラインへの組み込み方法
    • AWS App RunnerやCloud Runとの違い

    それでは、AppRunが用意している2つのプランから見ていこう。

    この記事の前提条件

    手順を進めるにあたり、以下の準備が必要になる。

    • さくらのクラウドのアカウントを持っていること。コントロールパネルの基本操作に不安がある場合は先にそちらを確認してほしい
    • Docker がローカル環境にインストールされていること
    • コンテナの基本的な概念(イメージ、レジストリ、ポートマッピング)を理解していること
    • 専有型を試す場合はサービスプリンシパルの作成権限が必要。IDポリシーの設定方法についてはこちらの記事で詳しく解説している

    共用型と専有型を比較する

    AppRunには共用型専有型がある。それぞれの違いを以下の表にまとめた。

    項目共用型専有型
    実行基盤共有クラスタ上でコンテナが動作専有VM上でコンテナが動作
    ゼロスケール対応。リクエストがなければインスタンス0になる非対応
    プライベートネットワーク接続非対応SEG経由で対応
    料金目安(税込)月額約3,720円〜(0.5vCPU / 1GiBメモリ)月額約11,000円〜(1vCPU / 2GBメモリ)
    ユースケースWebアプリ、API、検証環境業務システム、DB接続が必要な構成

    共用型はコストを抑えたい場面に向く

    共用型の最大の強みはKnativeベースのゼロスケールだ。アクセスがない時間帯にインスタンスが0になり、その間は課金されない。そのため、個人開発やトラフィックの読めない初期サービスに適している。

    料金体系は1時間単位の従量課金で、最小構成なら約5円/時間だ。月額に直すと約3,720円からスタートできるため、検証用途でも手を出しやすい価格帯といえる。

    とはいえ、ゼロスケールにはデメリットもある。インスタンスが0の状態からリクエストを受けると、コンテナの起動が完了するまで数秒から十数秒の待ち時間(コールドスタート)が発生する。レスポンス速度が求められるサービスでは、最小インスタンス数を1以上に設定しておくほうが安全だ。

    もう1つ注意したいのが、共用型ではプライベートネットワーク接続ができない点だ。クラウド上のデータベースやストレージへの通信はすべてインターネット経由になる。DB接続を含む構成では、次に紹介する専有型を選ぶ必要がある。

    専有型はネットワーク分離が必要な環境向け

    一方で、専有型ではユーザー専用のVMが割り当てられる。他のユーザーとリソースを共有しないため、パフォーマンスが安定しやすい点が特徴だ。

    最小構成は1vCPU/2GBメモリで月額約11,000円、最大8vCPU/8GBメモリまでスケールアップできる。しかし専有型を選ぶ最大の理由は、スペックよりもプライベートネットワーク接続だろう。さくらのクラウドのスイッチにサービスエンドポイントゲートウェイ(SEG)を介して接続することで、クラウド上のデータベースやストレージへインターネットを経由せずプライベートIPで直接通信できる。

    したがって、エンタープライズシステムや自治体業務のように外部へトラフィックを流したくないケースでは、この仕組みが不可欠になる。国産クラウドとしてのセキュリティ基盤についてはさくらのクラウドのSOC2 Type1取得に関する記事でも解説しているので、あわせて確認してほしい。

    専有型でプライベート環境を構築する5ステップ

    ここからは専有型AppRunで、プライベートなコンテナ実行環境を組み立てる流れを解説する。共用型にはない設定が複数あるため、順を追って進めよう。全体像としては、ネットワーク準備 → 認証設定 → クラスター起動 → イメージ準備 → 動作確認という流れになる。

    なお、構築中に詰まりやすいポイントについてはAppRun専有型で起動しない場合の対処法にもまとめているので、トラブル時にはそちらも参照してほしい。

    ステップ1 — スイッチを作成しSEGを有効化する

    まず、さくらのクラウドコントロールパネルでスイッチを作成する。次に、そのスイッチの詳細画面にあるサービスエンドポイントゲートウェイタブからSEGを有効化する。

    SEGはプライベートネットワーク内のリソースから、さくらのクラウドのマネージドサービスへ安全に接続するための仕組みだ。ここでIPアドレスとネットマスクを設定し、コンテナレジストリとAppRun専有型コントロールプレーンへの通信を許可しておく必要がある。

    この設定を忘れると、ワーカーノードがコンテナイメージを取得できなくなる。見落としやすいポイントなので、他の設定より先に済ませておきたい。

    ステップ2 — サービスプリンシパルを作成する

    クラスターの起動には適切な権限が必要だ。そこで、さくらのクラウドのIAM機能でサービスプリンシパルを作成し、「さくらのクラウド 作成・削除」の権限を付与する。

    サービスプリンシパルとは、リソースを自動管理するための認証情報のことだ。具体的には、AppRun専有型がワーカーノードの増減やネットワーク設定を行うときにこの認証情報が使われる。なお、IAMの権限管理に不慣れな場合は、先にIDポリシーの設定方法を確認しておくことをすすめる。

    ステップ3 — クラスターとワーカーノードを起動する

    AppRun専有型の管理画面からクラスターを作成する。続いてオートスケーリンググループを追加し、ワーカーノードを起動する。

    このとき重要なのはNIC設定だ。ステップ1で作成したSEG有効化済みスイッチを選び、ネームサーバにはSEGのIPアドレスを指定する。この接続設定により、ワーカーノードがプライベートネットワーク経由でコンテナレジストリや管理サーバーと通信できるようになる。

    CPUとメモリはアプリの負荷に合わせて選べばよい。初回の検証であれば最小構成で十分だ。

    ステップ4 — コンテナイメージを用意してデプロイする

    現時点でAppRunはさくらのクラウドのコンテナレジストリ専用となっている。そのため、Docker Hubなど外部レジストリから直接pullしてデプロイすることはできない。外部レジストリへの対応はロードマップに入っているものの、具体的な時期は未定とされている。

    外部のベースイメージを使いたい場合は、ローカルでpullしてからさくらのコンテナレジストリに再pushする方法を取る。以下にNode.jsアプリの例を示す。

    FROM node:20-slim
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci --production
    COPY . .
    EXPOSE 8080
    CMD ["node", "server.js"]

    また、AppRunはx86_64アーキテクチャで動作する点にも注意が必要だ。Apple Silicon搭載のMacでビルドする場合は --platform linux/amd64 を明示的に付けなければならない。

    # ビルドしてさくらのコンテナレジストリにpush
    docker build --platform linux/amd64 -t my-app.sakuracr.jp/my-app:latest .
    docker login my-app.sakuracr.jp
    docker push my-app.sakuracr.jp/my-app:latest

    イメージのpushが完了したら、ロードバランサをステップ1のスイッチに接続し、アプリケーションを追加する。コンテナポート8080を外部ポート80にマッピングし、ホスト名を指定すれば準備完了だ。

    ステップ5 — 接続確認と動作検証

    デプロイ後は、プライベートセグメント経由でDBや他のリソースに到達できるか確認する。接続先のプライベートIPは環境変数で渡すと管理しやすい。

    外部DNSサービスでAレコードを登録すれば、指定したホスト名で外部からアクセスできるようになる。

    確認時に起動しない、通信できないといったトラブルが起きた場合は、まずSEGの設定とNIC設定を見直そう。よくある原因と対処法はこちらの記事にまとめている。

    運用で押さえておきたいポイント

    構築が完了したら、運用フェーズで気をつけるべきことを整理しておこう。

    コンテナイメージの更新フロー

    アプリを更新するたびにコンテナレジストリへの再pushが必要になる。手動運用ではミスが起きやすいため、後述するapprun-cliを使ったCI/CDパイプラインの構築を早い段階で検討したい。

    オートスケールの挙動

    専有型でもCPU使用率をベースにしたオートスケールが利用できる。トラフィック増加時には新しいインスタンスが自動的に立ち上がり、負荷を分散してくれる。ただし共用型と異なり、インスタンスを0にするゼロスケールは専有型では動作しない。常に最低1台のインスタンスが稼働し続ける点を理解しておこう。

    監視とログ

    AppRunのコンソールからコンテナのログを確認できる。本番運用では外部の監視ツールと組み合わせ、レスポンスタイムやエラー率を継続的に観測する体制を整えておくと安心だ。

    apprun-cliでデプロイを自動化する

    コントロールパネルでの手動操作に加え、CLIツールのapprun-cliも利用できる。CI/CDパイプラインに組み込む場合はこちらのほうが扱いやすい。

    apprun-cliはfujiwaraさんが開発した非公式ツールで、Homebrewまたはgo installでインストールできる。

    # Homebrewでインストール
    brew install fujiwara/tap/apprun-cli
    
    # または go install
    go install github.com/fujiwara/apprun-cli/cmd/apprun-cli@latest

    このツールはアプリケーション定義をJSONまたはJsonnet形式のファイルで管理する仕組みだ。既存のアプリケーション設定をエクスポートするには、以下のコマンドを実行する。

    # 既存アプリケーション設定をJsonnetファイルに出力
    apprun-cli init --name my_web_app --jsonnet > myapp.jsonnet

    設定ファイルを編集したら、deployコマンドで反映する。

    # 設定ファイルを指定してデプロイ
    apprun-cli deploy --app myapp.jsonnet

    Jsonnet形式を選ぶメリットは、テンプレート機能を活用できる点だ。例えば、must_env() 関数で環境変数を読み込めるため、シークレット情報をファイルに直接書かずに済む。さらに tfstate() でTerraformの状態を参照したり、secret_value() でSecret Managerにアクセスしたりも可能だ。

    GitHub ActionsやGitLab CIのステップに組み込めば、pushのたびにAppRunへ自動デプロイする仕組みが構築できる。なお、アプリケーションが見つからない場合、deployコマンドは新規作成として処理される。つまり、初回も更新も同じコマンドで済む点が運用を楽にしてくれる。

    GitHub Actionsでの設定例

    参考として、GitHub Actionsでapprun-cliを使う場合の基本的なワークフロー構成を示す。

    name: Deploy to AppRun
    on:
      push:
        branches: [main]
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Build and push image
            run: |
              docker build --platform linux/amd64 -t my-app.sakuracr.jp/my-app:${{ github.sha }} .
              echo "${{ secrets.SAKURA_CR_PASSWORD }}" | docker login my-app.sakuracr.jp -u ${{ secrets.SAKURA_CR_USER }} --password-stdin
              docker push my-app.sakuracr.jp/my-app:${{ github.sha }}
    
          - name: Install apprun-cli
            run: |
              go install github.com/fujiwara/apprun-cli/cmd/apprun-cli@latest
    
          - name: Deploy
            env:
              SAKURA_APPRUN_TOKEN: ${{ secrets.SAKURA_APPRUN_TOKEN }}
              IMAGE_TAG: ${{ github.sha }}
            run: |
              apprun-cli deploy --app myapp.jsonnet

    この例ではmainブランチへのpushをトリガーに、イメージのビルド・push・デプロイを自動実行している。must_env() を使えば、Jsonnet内で IMAGE_TAG などの環境変数を安全に参照できる。

    AWS App RunnerやCloud Runとの違い

    名前の似たサービスが海外クラウドにもある。主要な違いを表にまとめた。

    サービス提供元ゼロスケールプライベートネットワーク料金の特徴
    AppRunさくらインターネット共用型のみ対応専有型でSEG経由対応円建て。共用型は月額約3,720円〜
    AWS App RunnerAWS非対応(最低1インスタンス常駐)VPCコネクタで対応ドル建て。待機中もvCPU/メモリ課金あり
    Cloud RunGoogle Cloud対応VPCコネクタ / Direct VPCで対応ドル建て。リクエスト単位の従量課金

    ゼロスケール対応ではAppRun共用型とCloud Runが並ぶ。一方で、AWS App Runnerは最低1インスタンスが常駐するため、待機中もコストが発生し続ける。

    AppRunを選ぶ理由は大きく3つある。国内データセンターで動作すること、円建てで請求が届くこと、そしてさくらのクラウドの既存リソースとプライベート接続できることだ。

    データの国内保管が求められる案件や、ガバメントクラウドを見据えた自治体向けシステムでは、この3点が決定打になる。すでにさくらのクラウドを運用している環境なら、追加のネットワーク設計も最小限で済む。

    ただし、グローバル展開やマルチリージョン構成が必要な場合はAWSやGoogle Cloudのほうが選択肢は多い。結局のところ、用途に応じた使い分けが現実的だ。

    よくある質問

    Q1. 共用型と専有型、最初はどちらを選ぶべきか

    迷ったら共用型から始めるのがよい。ゼロスケールにより待機コストがかからないため、検証や小規模サービスに適している。その後、プライベートネットワーク接続やリソースの分離が必要になった時点で、専有型へ切り替えれば問題ない。

    Q2. 手元のDockerイメージをそのまま使えるか

    直接は使えない。現時点ではさくらのコンテナレジストリのみに対応しているため、手元やDocker Hubのイメージは一度さくらのコンテナレジストリにpushし直す必要がある。加えて、x86_64(linux/amd64)でビルドされている必要があるので、Apple Silicon搭載のMacで作ったイメージはそのままでは動かない。ビルド時に --platform linux/amd64 を付けよう。

    Q3. 独自ドメインで公開できるか

    専有型ではホスト名を指定し、外部DNSサービスでAレコードを登録すれば独自ドメインで公開できる。一方で、共用型にはAppRun単体での独自ドメイン機能がない。ただし、さくらのウェブアクセラレータを前段に置くことで対応は可能だ。その際、リクエスト先をHostヘッダで判別するため、ウェブアクセラレータ側のHostヘッダ設定に注意が必要になる。

    Q4. ゼロスケールからの起動にどのくらいかかるか

    コンテナイメージのサイズや起動処理によって変わるが、目安は数秒から十数秒だ。コールドスタートを避けたいなら最小インスタンス数を1以上に設定すればよい。ただし、その分は常時課金されるので、コストとのトレードオフになる。アクセス頻度やレスポンス要件に応じて判断してほしい。

    Q5. 専有型でもオートスケールは効くか

    効く。CPU使用率をベースにコンテナインスタンスを自動で増減する仕組みが備わっている。トラフィック増加時には新しいインスタンスが自動的に立ち上がり、負荷を分散してくれる。ただし、インスタンスを0にするゼロスケールは専有型では使えない点に注意してほしい。

    Q6. SEGの設定を忘れるとどうなるか

    ワーカーノードがAppRun管理サーバーやコンテナレジストリと通信できなくなる。結果として、コンテナイメージの取得もクラスタの正常運用もできない。専有型を使うなら、SEGの有効化はスキップできない手順だ。

    まとめ

    AppRunはコンテナデプロイの手間を削り、インフラ管理から開発者を解放するサービスだ。共用型のゼロスケールは待機コストをゼロに抑え、専有型のプライベートネットワーク接続はセキュリティ要件の厳しい環境に応える。

    専有型の構築ではSEGの設定が鍵になる。この記事で紹介した5ステップを押さえておけば、プライベートなコンテナ実行環境を迷わず立ち上げられるはずだ。加えて、apprun-cliを導入すればCI/CDパイプラインによる自動デプロイも実現できる。

    さくらのクラウドの他の機能と組み合わせることで、よりセキュアで運用しやすいインフラを構築できる。関連記事もあわせて確認してほしい。

    参考リンク

  • Thesys Agent Builderで始めるGenerative UI:AIがUIを動的生成する仕組み

    Thesys Agent Builderで始めるGenerative UI:AIがUIを動的生成する仕組み

    チャットボットの応答がテキストだけでは足りない場面

    AIチャットボットの返答は、ほとんどテキストだ。Markdownで整えた文章、コードブロック、リンク。それで済む場面もある。ただ、ユーザーが欲しいのは長い説明文ではなく、その場で触れるインターフェースだったりする。

    たとえばフライトを探す質問を投げたとしよう。候補を箇条書きで返されても、別の画面に移って比較しなければならない。チャット内にフィルター付きの検索カードやカレンダーがそのまま出てきたら、操作が一画面で完結する。

    この発想を形にした技術がGenerative UIだ。LLMがテキストの代わりにUIコンポーネントを生成し、ユーザーはチャット画面の中でそのまま操作できる。2025年11月にはGoogleもGenerative UIに関する研究を公開した。LLMが生成したUIは標準的なテキスト出力より強く好まれるという評価結果が示されている。

    では、このGenerative UIをAPIとSDKで手軽に実装できるThesysのAgent Builderを掘り下げていく。

    Thesysとはどんな会社か

    Thesysはサンフランシスコ拠点のスタートアップだ。共同創業者はRabi Shanker GuhaとParikshit Deshmukh。2024年11月にTogether Fundがリードし、8vcが参加する形で400万ドルのシード資金を調達した。300を超えるチームがすでにThesysのツールを本番環境で利用している。

    主力製品のAgent Builderは、AIの応答にインタラクティブなReactコンポーネントを埋め込める。テキストを返すだけでなく、ボタン・フォーム・カード・グラフを動的に生成して返せる点で、従来のチャットボットビルダーとは方向性が違う。加えてノーコードのAgent Builder UIも提供しており、コードを書かずにGenerative UIアプリを構築することも可能だ。

    ThesysはさらにCopilotKitが開発するAG-UIプロトコルのエコシステムにも参加している。AG-UIはAIエージェントとフロントエンドのリアルタイム通信を標準化するオープンソースのプロトコルだ。MCPがツール呼び出しを標準化するのに対し、AG-UIはエージェントからUIへのイベント配信を標準化する。GoogleやLangChain、AWS、Microsoftなども採用しており、エージェントUIの共通基盤として急速に広まっている。

    公式サイト: https://www.thesys.dev/

    Agent Builderを構成する2つの軸

    Agent Builderの核はC1 APIGenUI SDKだ。それぞれの役割を見ていく。

    C1 API ― OpenAI互換で移行コストが低い

    C1 APIはOpenAIのChat Completions APIと互換性がある。すでにOpenAI向けのコードがあるなら、base_urlとモデル名を差し替えるだけで動く。

    import openai
    import os
    
    client = openai.OpenAI(
        api_key=os.environ.get("THESYS_API_KEY"),
        base_url="https://api.thesys.dev/v1/embed"
    )
    
    response = client.chat.completions.create(
        model="c1/anthropic/claude-sonnet-4/v-20250930",
        messages=[
            {"role": "system", "content": "フライト検索アシスタント。結果はカード形式のUIで表示する。"},
            {"role": "user", "content": "東京からニューヨークへの来週のフライトを探して"}
        ]
    )

    ベースURLはhttps://api.thesys.dev/v1/embedを指定する。モデル名はc1/{プロバイダー}/{モデル名}/{バージョン}という命名規則だ。レスポンスにはテキストだけでなく、UIコンポーネントの構造化データも含まれる。APIコール数とLLMトークン消費量がそれぞれ課金対象になる。

    GenUI SDK ― 生成されたUIをReactで描画する

    APIが返したUI記述を画面に表示するのがGenUI SDKの役割だ。内部的にはThesysのオープンソースUIフレームワークCrayonをベースにしている。CrayonはGitHubでMITライセンスのもと公開されており、エージェントUIを構築するための拡張可能なReactコンポーネント群を提供する。

    npm install @thesysai/genui-sdk
    import { C1Chat } from "@thesysai/genui-sdk";
    import "@crayonai/react-ui/styles/index.css";
    
    export default function App() {
      return (
        <C1Chat
          apiUrl="/api/chat"
          agentName="フライト検索アシスタント"
          formFactor="full-page"
        />
      );
    }

    C1Chatコンポーネントを配置するだけで、Generative UI対応のチャット画面が完成する。AIが返したボタン、フォーム、カード、テーブル、グラフがチャット内にそのまま描画される。テーマのカスタマイズにはCrayonのプリセットも使える。

    Generative UIが威力を発揮する3つのシーン

    テキスト応答だけでは体験が不十分になるケースを具体的に見てみよう。

    Eコマースの商品提案

    ユーザーが「赤いスニーカーを探している」と入力すると、商品画像つきのカードが並ぶ。サイズ選択やカート追加のボタンもUI内に表示されるため、チャットを離れずに購入まで完了できる。テキストで商品リストを返される場合と比べると、コンバージョンまでのステップが大幅に減る。

    ダッシュボード型のレポート

    「先月の売上を見せて」と聞けば、グラフやテーブルが直接返ってくる。フィルタを操作して期間やカテゴリを切り替えることもできる。数字の羅列を読み解くより、触れるグラフのほうが判断は速い。意思決定のスピードを上げたい経営層やアナリストに特に響くユースケースだ。

    フォーム入力の段階的ガイド

    問い合わせ対応のAIが、ユーザーの回答に応じてフォームを動的に組み立てる。全項目を一度に見せるのではなく、必要な入力だけを順番に提示する。この段階的な構成は離脱率を下げやすい。

    競合ツールとの違いを整理する

    Agent Builderに近い領域のツールを並べてみた。それぞれ得意分野が異なる。

    ツール特徴UI生成価格帯
    Thesys Agent BuilderGenerative UI。OpenAI互換API。GenUI SDKAIがUIコンポーネントを動的生成無料枠あり($10クレジット付)。従量課金
    Voiceflowノーコードのチャットボットビルダー。フローチャート式設計テンプレートUIのみ無料のStarterプランあり。Pro月額$60〜
    Botpressチャットボットプラットフォーム。オープンソース版ありカスタムUIは手動で構築無料枠あり。Plus月額$79〜
    DifyLLMアプリケーション構築プラットフォームテキスト中心。UI生成は非対応セルフホスト無料。クラウド版Professional $59〜

    Agent Builderが際立つのは、AIが応答のたびにUIを動的に生成する点だ。VoiceflowやBotpressはチャットウィジェットやテキスト応答が中心で、UIコンポーネントの自動生成には対応していない。Difyはワークフロー構築に強いが、フロントエンドのUI生成は守備範囲外になる。

    一方で、Agent Builderはまだ新しいプロダクトだ。エコシステムの厚みやコミュニティ規模ではVoiceflowやBotpressに及ばない。ノーコードで素早くチャットボットを立ち上げたい場合や、CMS連携を優先したい場合は、既存のビルダーのほうが向いている。

    料金体系を確認する

    Agent Builderには無料のEssentialsプランがある。$10分のクレジットが付与され、C1 APIとGenUI SDKの主要機能を試せる。クレジットを使い切った後は従量課金に移行する。

    課金はAPIリクエスト数とLLMトークン消費量の二軸で計算される。APIコールはプランに含まれる分があり、LLMトークンはプロバイダーごとの単価で別途発生する。チーム向けのTeamプランやエンタープライズ向けのプランも用意されている。最新の詳細は公式の料金ページで確認してほしい。

    導入までの最短ステップ

    実際に動かすまでの流れを整理しておく。

    1. Thesysコンソールでアカウントを作成し、APIキーを取得する
    2. バックエンド側でOpenAI SDKを使い、base_urlhttps://api.thesys.dev/v1/embedを設定する
    3. フロントエンド側で@thesysai/genui-sdkをインストールし、C1Chatコンポーネントを配置する
    4. システムプロンプトでUIの出力形式を指示し、レスポンスを確認する

    ノーコードで試したい場合はAgent Builder UIから直接エージェントを作成し、データを接続してPublishするだけで公開できる。

    よくある質問

    Q1. Generative UIとは何か

    LLMがテキストではなく、ボタン・フォーム・カード・テーブル・グラフといったUIコンポーネントを動的に生成して返す技術だ。ユーザーはチャット内でそのUIを直接操作できる。Googleも2025年11月にGemini 3へのGenerative UI実装を発表しており、業界全体で注目が高まっている。

    Q2. React以外のフレームワークでも使えるか

    C1 APIはREST APIなので、バックエンドはどの言語からでも呼べる。ただしUIのレンダリングにはGenUI SDKが必要で、現時点ではReactとNext.jsが前提になる。

    Q3. 日本語のプロンプトや応答に対応しているか

    C1 APIのバックエンドLLMは多言語に対応しており、日本語のプロンプトと応答は動作する。ただし公式ドキュメントは英語のみだ。

    Q4. OpenAIのコードからの移行は簡単か

    base_urlとモデル名を変えるだけで基本的な移行はできる。Generative UI固有の機能を活かすなら、システムプロンプトの調整やUI関連のパラメータ設定が追加で必要になる。

    Q5. セルフホストは可能か

    現時点ではクラウドサービスのみの提供で、セルフホストには対応していない。ただしCrayonフレームワーク自体はMITライセンスのオープンソースとしてGitHubで公開されている。

    Q6. AG-UIプロトコルとの関係は

    AG-UIはCopilotKitが開発したオープンソースのプロトコルで、AIエージェントとフロントエンドのリアルタイム通信を標準化する。ThesysはこのAG-UIプロトコルを統合しており、C1 APIと補完的な関係にある。LangGraph、CrewAI、Mastra、PydanticAIなど多くのフレームワークがAG-UIをサポートしている。

    Q7. Generative UIと従来のチャットUIは何が違うのか

    従来のチャットUIは、あらかじめ定義されたテンプレートの範囲で応答を表示する。Generative UIはLLMがコンテキストに応じてUIの構造自体を動的に決定する。ユーザーの質問内容やデータの性質に合わせて、テーブル、グラフ、フォームなど最適な表現を自動で選べる点が根本的に異なる。

    まとめ

    Generative UIは、チャットボットの応答をテキストからインタラクティブなUIへ押し上げる技術だ。ThesysのAgent Builderは、OpenAI互換のC1 APIとReact向けGenUI SDKでこの技術を実用レベルで提供している。

    無料枠があるので、小さなプロトタイプから試せる。自社のチャットボットにUI生成機能を加えたいなら、選択肢に入れておいて損はない。

    参考リンク

  • Happy Coder導入ガイド — スマホからClaude Codeを操作する方法

    Happy Coder導入ガイド — スマホからClaude Codeを操作する方法

    Happy Coder導入ガイド — スマホからClaude Codeを操作する方法

    対象読者

    • Claude Codeを日常的に使っていて、外出先からもセッションを操作したい開発者
    • AIコーディングアシスタントに興味があり、モバイルワークフローを試したいエンジニア
    • リモートワークやフリーランスで、場所を選ばず開発を続けたい人
    • チームリーダーやテックリードで、移動中にAIエージェントの進捗を確認したい人

    前提知識:ターミナル操作の基本、Node.jsの基礎知識、Claude Codeの基本的な使い方


    Happy Coderとは

    Happy Coderは、Claude CodeやCodexをスマホやWebブラウザから操作できる無料・オープンソースのモバイルクライアントです。Bulka, LLCが開発し、2025年5月にリリースされました。

    GitHubスターは約12,100(2025年6月時点)。App Storeの評価は4.9/5(467件のレビュー)で、短期間のうちに支持を集めています。

    ターミナルで動いているClaude Codeのセッションを、そのままスマホに映し出せるのが基本的な仕組みです。メッセージの送信やファイルの確認、権限の承認まで、すべてモバイルからできます。

    PCの前にいなくてもClaude Codeを使いたい場面

    Claude Codeはターミナルベースのツールなので、PCがないと操作できません。ただ、実際の開発現場ではこういう場面がよくあります。

    • 大規模なリファクタリングをClaude Codeに任せたが、完了通知をスマホで受け取りたい
    • 通勤中の電車で、コードレビューの指示を出したい
    • ミーティング中に、バックグラウンドで動いているエージェントの進捗を確認したい
    • カフェでスマホだけで簡単な修正を指示したい

    Happy Coderはこうした場面をエンドツーエンド暗号化つきでカバーします。ユーザーからは「スマホだけで2本の技術記事を20〜30分で執筆できた」「PCワークフローと同等の速度を実現できる」といった声も上がっています。


    Happy Coderの7つの機能

    1. リアルタイム双方向同期

    PCのClaude Codeセッションとモバイルアプリがリアルタイムで同期します。ターミナルの出力はミリ秒単位でスマホに表示され、スマホから送ったメッセージもすぐにClaude Codeに届きます。

    PCとスマホに主従関係はありません。どちらのデバイスからでも会話を開始し、メッセージの送受信ができます。

    2. エンドツーエンド暗号化

    通信はすべてQRコードで共有した秘密鍵で暗号化されます。AES暗号化と公開鍵認証を組み合わせた方式です。

    中継サーバーは暗号化されたデータを転送するだけ。サーバー側でコードの内容を読み取ることはできません。

    3. マルチセッション管理

    複数のClaude Codeセッションを同時に実行し、それぞれ独立して管理できます。たとえばフロントエンドとバックエンドで別々のセッションを立ち上げ、スマホから切り替えながら操作する使い方が可能です。

    各セッションは独立した状態を持ち、プロジェクトコンテキストや会話履歴も個別に保持されます。アプリを再起動してもセッションは維持されます。

    4. プッシュ通知

    タスクの完了、エラーの発生、権限の確認要求など、重要なイベントをプッシュ通知で受け取れます。長時間のタスクを実行中でも、スマホを見るだけで状況がわかります。

    通知にはディープリンクが含まれており、タップすると該当セッションの該当箇所に直接ジャンプできます。

    5. 音声エージェント

    音声でClaude Codeに指示を出せます。単なる音声認識ではなく、ElevenLabsの音声認識・合成技術を統合しており、AIが音声の意図を解釈してコード実行要求に変換します。

    「テストを全部実行して」と話しかけるだけで、適切なコマンドが実行される仕組みです。

    6. カスタムエージェント・スラッシュコマンド対応

    ~/.claude/agents/ に配置したカスタムエージェントや、独自のスラッシュコマンドもモバイルからそのまま使えます。デスクトップで使える機能はすべてモバイルでも利用可能です。

    MCP(Model Context Protocol)ツールの権限プロンプトにも対応しています。JIRA、GitHub等の外部サービスとの連携時に、モバイル上で許可・拒否を選択できます。

    7. 完全無料・オープンソース

    MITライセンスで公開されており、テレメトリーやトラッキングは一切ありません。ソースコードはGitHubで確認できます。TypeScript 97.7%で構成されており、コミュニティによる活発な開発が続いています。


    仕組み(アーキテクチャ)

    Happy Coderは3つのコンポーネントで構成されています。

    ┌──────────────┐     暗号化データ     ┌──────────────┐     暗号化データ     ┌──────────────┐
    │              │ ──────────────────▶ │              │ ──────────────────▶ │              │
    │   CLI        │                     │  Relay       │                     │  Mobile App  │
    │  (PC上で実行) │ ◀────────────────── │  Server      │ ◀────────────────── │  (スマホ)     │
    │              │                     │ (中継のみ)    │                     │              │
    └──────────────┘                     └──────────────┘                     └──────────────┘
           │                                    │                                    │
      Claude Codeを                     暗号化blobを                          復号して
      監視・暗号化                       保存・転送                           表示・操作

    中継サーバーが必要な理由

    スマホとPCは通常、別のネットワーク上にあるため直接通信できません。中継サーバーが両方のデバイスからの接続を受け付ける役割を果たします。ポート開放やIPアドレスの設定は不要です。

    中継サーバーにはオフラインファースト設計が採用されています。スマホの接続が一時的に途切れても、サーバーが暗号化データを保持し、接続が回復すると見逃した更新分が自動的に同期されます。

    セキュリティの流れ

    1. QRコードスキャンで共有秘密鍵を交換(サーバーには渡らない)
    2. CLIがClaude Codeの出力を秘密鍵で暗号化
    3. 暗号化されたデータを中継サーバーに送信
    4. サーバーはデータを保存・転送(中身は読めない)
    5. スマホが受信し、秘密鍵で復号して表示

    サーバーを経由しても通信内容が漏れることはありません。


    Happy Coderの導入 6ステップ

    前提条件

    • Node.js 20.0.0以上がインストールされていること
    • Claude Code がインストール済みで、認証が完了していること
    • スマートフォン(iOS または Android)

    Step 1:Claude Codeのインストール(未導入の場合)

    Claude Codeをまだ入れていなければ、先にセットアップします。

    macOSの場合はHomebrewが手軽です。

    brew install claude-code

    ほかの環境ではClaude Code公式ドキュメントを参照してください。

    インストール後、初回起動で認証を行います。

    claude

    認証が完了したら Ctrl+C でいったん終了します。

    注意: 従来の npm install -g @anthropic-ai/claude-code は廃止予定です。Homebrewまたは公式インストーラーを使ってください。

    Step 2:Happy Coder CLIのインストール

    npmでグローバルインストールします。

    npm install -g happy-coder

    バージョンが表示されればOKです。

    happy --version

    Step 3:モバイルアプリのインストール

    お使いのスマートフォンにHappy Coderアプリを入れます。

    Webブラウザから使いたい場合は app.happy.engineering にアクセスする方法もあります。ただしプッシュ通知を受け取りたい場合はネイティブアプリがおすすめです。

    Step 4:CLIを起動してQRコードを表示

    ターミナルで以下を実行します。

    happy

    初回起動時に以下が自動的に行われます。

    1. Claude Codeの認証チェック
    2. 暗号化キーペアの生成
    3. QRコードの表示(ターミナル上に出ます)

    ここで表示されるQRコードが、デバイス間の暗号化通信の鍵になります。

    Step 5:QRコードをスキャンしてペアリング

    1. スマホでHappy Coderアプリを開く
    2. 「Connect」または「ペアリング」をタップ
    3. ターミナルに表示されたQRコードをスマホカメラでスキャン
    4. 接続が確立されると、アプリにClaude Codeのセッションが表示される

    この操作で暗号化の秘密鍵がスマホに安全に転送されます。QRコードには接続情報と暗号鍵が含まれており、中継サーバーには一切送信されません。

    注意: QRコードのスクリーンショットは他人に共有しないでください。暗号鍵が含まれているため、第三者にセッションへのアクセス権を与えてしまいます。

    Step 6:動作確認

    接続が完了したら、以下を確認します。

    1. PC側: ターミナルでClaude Codeが通常通り動作していること
    2. スマホ側: アプリにClaude Codeの出力がリアルタイムで表示されていること
    3. 双方向テスト: スマホからメッセージを送信し、PCのClaude Codeに届くこと

    ここまでの所要時間は約10分です。


    基本的な使い方

    セッションの開始

    # Claude Codeセッションを開始(デフォルト)
    happy
    
    # モデルを指定して開始
    happy -m opus
    
    # 権限モードを指定して開始
    happy -p plan
    
    # 環境変数を渡して開始
    happy --claude-env SOPS_AGE_KEY_FILE=keys.txt

    Codexモードで使う

    OpenAI Codexも同様にモバイルから操作できます。

    happy codex

    Geminiモードで使う

    Google Geminiにも対応しています。初回のみ認証が必要です。

    # 初回認証
    happy connect gemini
    
    # セッション開始
    happy gemini
    
    # モデルを指定
    happy gemini model set gemini-2.5-pro

    その他のCLIコマンド

    happy auth      # 認証の管理
    happy doctor    # 診断の実行(問題が起きた場合)
    happy daemon    # バックグラウンドサービスの管理
    happy notify    # プッシュ通知のテスト送信

    Happy Coderの実践シーン

    シーン1:長時間タスクのモニタリング

    大規模なリファクタリングやテスト実行をClaude Codeに任せ、プッシュ通知で完了を知る使い方です。

    あなた: 「このプロジェクトの全テストを実行して、失敗したものを修正して」
    → PCを離れてミーティングへ
    → スマホに「タスク完了」の通知が届く
    → スマホで結果を確認、追加の指示を出す

    CIの待ち時間やビルド時間が長いプロジェクトでは特に役立ちます。

    シーン2:通勤中のコードレビュー

    電車の中でプルリクエストのレビューをClaude Codeに依頼できます。

    スマホから: 「PR #42のコードをレビューして、セキュリティの問題があれば指摘して」
    → Claude Codeがレビューを実行
    → 結果をスマホで確認
    → 修正の指示もスマホから

    シーン3:複数プロジェクトの並行管理

    フロントエンドとバックエンドのセッションを同時に立ち上げ、スマホから切り替えながら指示を出せます。

    # ターミナル1
    cd ~/projects/frontend && happy
    
    # ターミナル2
    cd ~/projects/backend && happy

    スマホのアプリ上でセッションを切り替えて、それぞれに指示を出せます。各セッションのコンテキストは完全に独立しています。

    シーン4:権限承認のリモート対応

    Claude Codeがファイルの書き込みや外部APIの呼び出しなど、権限が必要な操作を要求した場合、スマホに承認プロンプトが表示されます。タップ一つで許可・拒否を選べるので、PCから離れていても作業が止まりません。

    シーン5:音声でのハンズフリー操作

    運転中や料理中など手が離せないときでも、音声でClaude Codeに指示できます。「テストを実行して」「最後のコミットを元に戻して」と話しかけるだけです。


    競合ツールとの比較

    モバイルからClaude Codeを操作する方法はほかにもあります。

    項目Happy CoderTailscale + TermiusClaude Remote
    セットアップ難易度簡単(npm install → QRスキャン)中〜高(VPN設定が必要)中程度
    料金完全無料一部有料不明
    E2E暗号化ありVPNによる暗号化不明
    音声操作ありなしなし
    プッシュ通知ありなしなし
    オフライン対応あり(再接続で自動同期)VPN接続が必要不明
    マルチセッションあり手動で管理不明
    オープンソースあり(MIT)Termiusは非OSS不明

    Happy Coderは設定の手軽さとセキュリティを両立している点、そしてClaude Code以外のツールにも対応している点で差がつきます。


    セキュリティに関する注意点

    Happy Coderはゼロトラスト設計ですが、運用面で気をつけるべきことがあります。

    • 信頼できるデバイスのみでQRコードをスキャンする
    • 公共Wi-Fiでは機密性の高いコマンドの実行を避ける
    • CLIとアプリは常に最新バージョンに保つ
    • QRコードのスクリーンショットを他人に共有しない(暗号鍵が含まれるため)
    • 不要になったデバイスのペアリングは解除する

    よくある質問(FAQ)

    Q1. Happy Coderを使うのに追加料金はかかる?

    かかりません。Happy Coderは完全無料です。ただしClaude Code自体の利用にはAnthropicのAPIキーまたはサブスクリプションが必要です。Happy Coderはクライアントアプリなので、追加のサーバー費用等は発生しません。

    Q2. 中継サーバーにコードが漏れることはない?

    ありません。すべてのデータはQRコードで共有した秘密鍵で暗号化されます。中継サーバーが扱うのは暗号化されたblobのみで、平文のデータにアクセスすることは技術的に不可能です。

    Q3. PCがスリープするとセッションは切れる?

    macOSの場合、Happy CLIはデフォルトで caffeinate を使ってスリープを抑制します。ただし環境変数 HAPPY_DISABLE_CAFFEINATE=true が設定されている場合はスリープします。万が一接続が切れても、オフラインファースト設計により再接続時に自動で同期されます。

    Q4. Claude Code以外のAIツールにも対応している?

    OpenAI Codex(happy codex)とGoogle Gemini(happy gemini)に対応しています。Geminiは happy connect gemini で初回認証を行ってください。

    Q5. 複数のPCからスマホに接続できる?

    できます。それぞれのPCで happy を起動し、スマホアプリでセッションを切り替えることで、複数のマシンを1台のスマホから操作できます。


    トラブルシューティング

    接続に問題がある場合は、まず診断コマンドを実行してください。

    happy doctor

    よくある問題と対処法

    問題対処法
    QRコードが表示されないnode --version でNode.js 20以上か確認
    スマホに出力が表示されないアプリを再起動し、再スキャン
    CLIがクラッシュするnpm update -g happy-coder で最新版に更新
    macOSがスリープするHAPPY_DISABLE_CAFFEINATE 環境変数が未設定か確認
    Gemini接続に失敗するhappy connect gemini で再認証

    まとめ

    Happy Coderを入れると、Claude Codeがスマホから使えるようになります。QRコードを1回スキャンするだけでE2E暗号化接続が確立され、通勤中のコードレビューやカフェからのバグ対応、ミーティング中のバックグラウンドタスク管理まで、PCの前にいなくても開発が止まりません。

    CodexやGemini CLIにも対応しているので、複数のAIコーディングツールを使い分けている場合も1つのアプリで管理できます。MITライセンスで完全無料、GitHubスターは1.2万超。CLIは npm install -g happy-coder、アプリはApp StoreかGoogle Playからどうぞ。

    ※本記事は2026年2月時点の情報に基づいています。最新の情報はHappy Coder公式サイトをご確認ください。


    関連リンク