Gemini 統合を Skills 機能でシンプル化 - MCP からの移行

背景

以前、Gemini MCP サーバーを Claude Code に統合して、Claude のコンテキストを節約しながら Gemini の大規模コンテキストと Google Search を活用していました。

MCP サーバーは Code Assist API を直接呼び出す構成で、OAuth2 認証により安定して動作していました。

その後、Claude Code に Skills 機能が登場しました。Skills は専門知識を発見可能な機能としてパッケージ化する仕組みで、Claude が自律的に判断して使用(model-invoked)する点が特徴です。

Skills の登場により、MCP サーバーを使わずとも、Bash + Node.js スクリプトで直接 API を呼び出す方式で同じ機能を実現できることに気づきました。

Skills とは

Skills は Claude Code の機能拡張の仕組みです。MCP が外部ツールとの統合プロトコルであるのに対し、Skills は専門知識をパッケージ化して Claude に提供します。

Skills には、従来の MCP にはない 3 つの特徴があります。

まず、Model-invoked(モデルが自律判断)という仕組みです。Claude がリクエストのコンテキストと Skill の説明を基に、自律的に使用を判断します。ユーザーが明示的にトリガーする必要はありません。

次に、Progressive disclosure(段階的開示)です。SKILL.md の frontmatter(名前、説明)だけが最初にロードされ、詳細な指示やサポートファイルは必要な時だけロードされます。これによりトークン消費を削減できます。

最後に、Git ベースの配布です。プロジェクトの .claude/skills/ ディレクトリに配置することで、チーム全体で共有できます。

移行の理由

MCP サーバー経由の構成も問題なく動作していましたが、Skills を使った構成には以下のメリットがあり、移行を決めました。

Progressive disclosure によるトークン消費の削減

MCP の場合、すべてのツール定義(名前、説明、パラメータスキーマ)を Claude のコンテキストに含める必要があります。

Skills の場合、SKILL.md の frontmatter(数十トークン程度)だけが最初にロードされ、詳細なガイダンスは Claude が必要と判断した時だけロードされます。

シンプルな実装

MCP サーバーのコードを確認すると、実際には Code Assist API を fetch() で直接呼び出しているだけでした。

// MCP サーバー内部
const response = await fetch(`${CODE_ASSIST_BASE_URL}:generateContent`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify(requestBody),
});

このような単純な API 呼び出しであれば、MCP を経由する必要はありません。Bash ツールで Node.js スクリプトを直接実行する方がシンプルです。

プロセス階層の削減

MCP 構成では 4 層構造でした。

Claude → MCP Client → MCP Server (Node.js) → Code Assist API

Skills を使った構成では 3 層に削減されます。

Claude → Bash → Node.js スクリプト → Code Assist API

MCP Client と Server の間の stdio 通信とプロトコルオーバーヘッドが不要になります。

新しい構成

Skills を使った構成では、SKILL.md で使用方法を定義し、Node.js スクリプトで Code Assist API を直接呼び出します。Claude は Bash ツールでスクリプトを実行し、結果を取得します。

アーキテクチャの変化

Gemini Skill アーキテクチャの変化

上図は MCP サーバー経由の構成(4 層)と Skills を使った構成(3 層)の違いを示しています。MCP Client と Server の間の stdio 通信が不要になり、Claude が Bash ツールで直接 Node.js スクリプトを実行する構成に変更されました。

Code Assist API とは

Google Gemini Code Assist API (https://cloudcode-pa.googleapis.com/v1internal) は、Gemini モデルにアクセスするための API です。通常の Gemini API (generativelanguage.googleapis.com) とは異なるインフラで動作し、OAuth2 認証を使用します。

主な特性として、OAuth2 認証によるアクセストークン取得が必要で、Gemini Code Assist for individuals として動作します。

実装

Skill の作成

~/.claude/skills/gemini-api/SKILL.md を作成し、Code Assist API の使い方を Claude に教えます。

SKILL.md には以下の内容を記載しました。

  • YAML フロントマター(Skill のメタデータ、名前、説明、使用可能なツール)
  • 基本的な使い方(コマンド構文とモデルの選択)
  • 使用判断フロー(いつ Code Assist API を使うべきかの判断基準)
  • コマンドパターン(具体的な実行例)

主要なコマンドパターンは次の通りです。

# SwiftUI のベストプラクティス調査
~/.claude/skills/gemini-api/dist/index.js -p "SwiftUI ベストプラクティス 2025 パフォーマンス最適化" -m gemini-2.5-flash

# HTML の内容を要約(curl で取得後)
# curl で HTML 取得 → gemini-api で要約(パイプ経由)
curl -s https://example.com | ~/.claude/skills/gemini-api/dist/index.js -p "以下の HTML を日本語で要約してください" -m gemini-2.5-flash

# 技術選定の比較検討
~/.claude/skills/gemini-api/dist/index.js -p "MVVM vs VIPER vs Clean Architecture iOS アプリ開発 2025" -m gemini-2.5-pro

OAuth 認証

Code Assist API は OAuth2 認証を使用します。初回実行時の認証フローは以下の通りです。

まず、スクリプトが ~/.gemini/oauth_creds.json から認証情報を読み込みます。アクセストークンが期限切れの場合、自動的にリフレッシュします。新しいトークンをファイルに保存します。

認証情報は Gemini CLI と共有されるため、Gemini CLI を事前にセットアップしておく必要があります。

# 初回のみ Gemini CLI で OAuth 認証
npm install -g @google/gemini-cli
gemini  # ブラウザで Google アカウント認証

# 以降は gemini-api が自動的にトークンを取得・更新
~/.claude/skills/gemini-api/dist/index.js -p "テスト"

認証情報は ~/.gemini/oauth_creds.json に保存され、access_tokenrefresh_tokenexpiry_date を含みます。プロジェクト ID は Code Assist API から自動取得されます(例:empirical-crawler-7k0xs)。

CLAUDE.md での優先順位設定

Claude が自動的に gemini-api Skill を使用するように、~/.claude/CLAUDE.md に設定を追加します。

情報検索・調査は gemini-api を最優先し、URL の内容取得は curl → gemini-api の組み合わせ(パイプ経由)を使用します。

情報検索の場合、Claude は情報検索が必要と判断すると、Skill ツールで gemini-api を invoke し、Bash ツールで ~/.claude/skills/gemini-api/dist/index.js コマンドを実行します。

URL 要約の場合、curl で HTML を取得し、パイプ経由で gemini-api に渡して要約します。

正しい実行例は以下の通りです。

# ユーザー: "https://example.com の内容を要約して"
# 正しい行動:
# curl で HTML 取得 → パイプで gemini-api に渡す
Skill(command: "gemini-api")
→ curl -s "https://example.com" | ~/.claude/skills/gemini-api/dist/index.js -p "以下の HTML を日本語で要約してください" -m gemini-2.5-flash

Code Assist API の制限と curl の活用

運用を進める中で、Code Assist API には重要な制限があることが判明しました。

URL コンテキストツールは非サポート

一般の Gemini API (generativelanguage.googleapis.com) では、URL コンテキストツールを使って Web ページの内容を直接取得できます。

// 一般の Gemini API(consumer 向け)
const result = await model.generateContent({
  contents: [{ role: "user", parts: [{ text: "https://example.com を要約" }] }],
  tools: [{ urlContext: {} }],  // URL コンテキストツールを有効化
});

しかし、Code Assist API はこの機能をサポートしていません。

検証プロセス

URL コンテキストツールの動作を検証するため、3 つのテストを実施しました。

まず、URL を直接プロンプトに含めるテストです。

~/.claude/skills/gemini-api/dist/index.js \
  -p "https://example.com/article の内容を要約" \
  -m gemini-2.5-flash

結果は、URL の内容ではなく、全く異なる内容が返されました。

次に、tools パラメータで URL コンテキストを有効化するテストです。Code Assist API に tools: [{ url_context: {} }] を追加しました。

const response = await client.generateContent({
  model,
  contents: [{ role: "user", parts: [{ text: prompt }] }],
  tools: [{ url_context: {} }],  // URL コンテキスト有効化を試みる
});

結果は、urlContextMetadata が存在せず、依然として誤った内容が返されました。

最後に、curl で HTML 取得後、gemini-api で要約するテストです。

# 1. curl で HTML を取得
curl -s "https://example.com/article" > /tmp/article.html

# 2. gemini-api で要約
~/.claude/skills/gemini-api/dist/index.js \
  -p "以下の HTML を要約: $(cat /tmp/article.html)" \
  -m gemini-2.5-flash

結果は、正しく記事内容が要約されました。

curl との組み合わせ

Code Assist API は URL の直接フェッチをサポートしていないため、curl で HTML を取得してパイプで gemini-api に渡す方式を採用しました。

ユーザーが「https://example.com を要約して」と依頼した場合、Claude は以下のように処理します。

curl -s "https://example.com" | ~/.claude/skills/gemini-api/dist/index.js \
  -p "以下の HTML を日本語で要約してください" \
  -m gemini-2.5-flash

この方式には 3 つのメリットがあります。curl は標準的なツールで安定して動作する確実性があり、パイプで標準入力に渡すだけのシンプルさがあり、中間ファイルが不要な効率性を兼ね備えています。

gemini-api スクリプトに標準入力からの読み取り機能を追加しました。

async function readStdin(): Promise<string> {
  // 標準入力がパイプから来ているかチェック
  if (process.stdin.isTTY) {
    return "";
  }

  const chunks: Buffer[] = [];
  for await (const chunk of process.stdin) {
    chunks.push(chunk);
  }

  return Buffer.concat(chunks).toString("utf-8");
}

async function main() {
  const { prompt, model } = parseArgs();
  const stdinContent = await readStdin();

  // 標準入力がある場合は、それをプロンプトの前に追加
  const fullPrompt = stdinContent
    ? `${stdinContent}\n\n${prompt}`
    : prompt;

  // API 呼び出し
  const response = await client.generateContent({
    model,
    contents: [{ role: "user", parts: [{ text: fullPrompt }] }],
  }, randomUUID());
}

SKILL.md に URL 要約のセクションを追加しました。Code Assist API は URL の直接フェッチをサポートしていないため、curl で HTML を取得してから gemini-api に渡す方式を推奨しています。

Claude が自動的に以下のパターンで処理します。curl で URL の HTML を取得し、gemini-api で HTML 内容を要約します。

実行例は以下の通りです。

curl -s https://example.com | ~/.claude/skills/gemini-api/dist/index.js \
  -p "以下の HTML を日本語で3つのポイントにまとめてください" \
  -m gemini-2.5-flash

API の違いまとめ

機能Code Assist APIGemini API (consumer)
URL コンテキストツール❌ 非サポート✅ サポート
Grounding with Google Search❌ 不明✅ サポート
認証方式OAuth 2.0API Key
用途コード生成・開発支援汎用 AI タスク

Code Assist API はコード生成に最適化されており、Web スクレイピング機能は範囲外と考えられます。

まとめ

Claude Code の Skills 機能により、専門知識をパッケージ化して Claude が自律的に使用できる仕組みが実現しました。MCP サーバー経由の構成から、Bash + Node.js による直接 API 呼び出しへ移行し、いくつかの改善を達成しました。

Progressive disclosure により SKILL.md の frontmatter のみ常時ロードし、詳細は必要時のみロードすることでトークンを削減しました。Model-invoked の仕組みにより、Claude がコンテキストに応じて自律的に Skill を使用します。プロセス階層も 4 層から 3 層へシンプル化されました。

最終的なアーキテクチャは、情報検索・技術調査は Code Assist API を直接呼び出し、URL の内容取得には curl と gemini-api の組み合わせ(パイプ経由)を使用し、コード生成・レビューは Claude が直接処理するという構成になりました。

MCP は外部ツールとの統合プロトコルとして強力ですが、単純な API 呼び出しであれば、Skills + Bash の方がシンプルです。Skills の Progressive disclosure により、トークン消費を抑えながら専門知識を提供できます。

Code Assist API の特性(URL コンテキスト非サポート)を理解し、curl との組み合わせで補完することで、柔軟な情報取得を実現しました。

技術的な詳細

実装構成

~/.claude/skills/gemini-api/
├── SKILL.md                  # Skill の定義と使用方法
├── package.json              # 依存関係
├── tsconfig.json             # TypeScript 設定
├── src/
│   ├── index.ts             # CLI エントリーポイント
│   ├── oauth.ts             # OAuth2 認証処理
│   ├── codeAssistClient.ts  # Code Assist API クライアント
│   └── types.ts             # 型定義
└── dist/
    ├── index.js             # ビルド済み CLI
    ├── oauth.js
    ├── codeAssistClient.js
    └── types.js

コアロジック

// OAuth2 認証
const oauth2Client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
const savedCreds = await loadCredentials(); // ~/.gemini/oauth_creds.json
oauth2Client.setCredentials(savedCreds);
const accessToken = await getAccessToken(oauth2Client);

// Code Assist API 呼び出し
const requestBody = {
  model: "gemini-2.5-flash",
  project: projectId,  // Code Assist API から取得
  user_prompt_id: randomUUID(),
  request: {
    contents: [
      {
        role: "user",
        parts: [{ text: prompt }],
      },
    ],
  },
};

const response = await fetch(`https://cloudcode-pa.googleapis.com/v1internal:generateContent`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify(requestBody),
});

const data = await response.json();
const text = data.response.candidates[0].content.parts[0].text;
console.log(text);

Claude は Bash ツールでこのスクリプトを実行し、stdout から結果を取得します。OAuth2 認証、API 呼び出し、レスポンス解析をすべて Node.js スクリプト内で完結させます。

参考リンク

関連する技術スタック

Code Assist API は https://cloudcode-pa.googleapis.com/v1internal を使用し、OAuth2 認証には google-auth-library パッケージを使用しています。Gemini CLI で OAuth 認証情報の初期セットアップを実施します。Claude Code Skills は専門知識をパッケージ化し、Claude が自律的に使用できる機能拡張です。