Claude × Google Apps Scriptでスプレッドシート業務を自動化する|MCPでは届かない領域の攻め方【2026年7月】
MCP経由でスプレッドシートを操作していると、「自分のPCを開いていないと動かない」という壁に必ずぶつかります。Google Apps Script(GAS)はその壁を越える手段です。この記事ではMCP・API・GASの使い分け、ClaudeにGASを書かせる手順、そして実際に生成させたコードに見つかった二重送信リスクを含むレビュー観点をまとめます。
なぜMCPだけでは足りなくなるのか
ClaudeやCodexからスプレッドシートを操作する方法として、まずMCP接続を試す人が多いはずです。実際それが最短で、対話しながら分析させるには最適です。
ただし運用に乗せようとすると、必ず同じ壁に当たります。MCPはあなたのPC上のClaudeが動いているときにしか働きません。「毎朝9時に集計してSlackに投げる」「入力があったら即座に通知する」といった、人が居ない時間に動いてほしい処理には向いていないのです。
Google Apps Script(GAS)はスプレッドシート側、つまりGoogleのサーバー上で動きます。PCの電源が入っていなくても、あなたが寝ていても実行されます。ここがMCPとの決定的な違いです。
Google Apps ScriptはGoogleが提供する実行環境で、JavaScriptベースの言語でスプレッドシート・Gmail・カレンダー・ドライブを操作できます。サーバーの契約やインストールは不要で、スプレッドシートのメニューから「拡張機能 → Apps Script」を開けばその場でコードを書いて実行できます。時間指定で自動実行する「トリガー」も標準機能です。
MCP・API・GASの使い分け
| やりたいこと | 最適な手段 | 理由 |
|---|---|---|
| データを見ながら分析・相談したい | MCP | 対話しながら試行錯誤できる。導入が最短 |
| 複雑な加工・外部データとの突合を都度やる | MCP または API | AIの判断力が必要な処理はAI側に置く |
| 人が居ない時間に定期実行したい | GAS | Googleのサーバーで動く。PC不要 |
| 入力・編集をきっかけに即座に動かしたい | GAS | 編集トリガー・フォーム送信トリガーがある |
| シート内で使えるオリジナル関数を作りたい | GAS | カスタム関数はGASでしか作れない |
| スプレッドシートにメニューやボタンを付けたい | GAS | UI拡張はGASの領域 |
| 自社システムに組み込みたい | API | Sheets APIを自分のコードから呼ぶ |
整理すると、「AIの判断が毎回必要か」と「人が居ないときに動くべきか」の2軸で決まります。AIの判断が必要ならMCPかAPI、無人で動かすならGAS。両方必要なら、GASからAPI経由でClaudeを呼ぶという組み合わせもあります(後述のFAQ参照)。
GASを使う場合、動いているのはGASのコードであってAIではありません。ClaudeはGASコードを書く役として使い、動かすのはGoogleのサーバーという分担になります。
これはコスト面でも有利です。定期実行のたびにAPI料金が発生する構成と違い、GASの実行は無料(クォータ内)です。「毎日動かすが、やることは決まっている」処理なら、この分担がもっとも安く安定します。
進め方は2つ。多くの人は「コピペ」で足りる
ClaudeにGASを書かせる場合、その先の進め方が2通りあります。ここを最初に決めておくと無駄がありません。
| ブラウザにコピペ | clasp でローカル開発 | |
|---|---|---|
| 準備 | 不要 | Node.js 20以上+clasp導入+API有効化 |
| Gitでバージョン管理 | できない | できる |
| Claude Codeが直接編集 | できない(人がコピペ) | できる |
| 複数人での開発 | 厳しい | 向く |
| 向いている規模 | 数十〜200行程度、1〜2ファイル | それ以上、または継続的に育てる場合 |
結論として、スプレッドシート業務の自動化を1つ2つ作りたいだけなら、ブラウザにコピペで十分です。claspの導入にはNode.jsが必要で、それ自体が目的でない人にとっては余計な障害物になります。
逆に、GASを何本も書く・Gitで履歴を残したい・Claude Codeにコードを直接触らせたい場合はclaspの価値が出ます。導入手順は記事の後半にまとめました。
混同されやすい点です。GASはGoogleのサーバー上の独自ランタイムで動くので、あなたのPCにNode.jsは不要です。Node.jsが必要になるのはclaspというローカル開発ツールを使う場合だけです。ブラウザのエディタで完結するなら何もインストールしなくてよい、というのはGASの大きな利点です。
実践:Claudeに書かせてみた結果
実際にClaude Code(Claude Opus 5)に、よくある業務を投げてみました。使ったプロンプトはこれです。
Google Apps Scriptのコードを書いてください。
要件: シート「注文」のA2:E最終行を読み、E列(ステータス)が「未処理」の行について、
B列のメールアドレス宛に確認メールを送り、E列を「送信済」に更新する。
毎日9時のトリガーで動かす想定。
コードのみ出力し、説明は最小限にしてください。
返ってきたコードの要点部分です(長いので抜粋します)。
function processOrders() {
const lock = LockService.getScriptLock();
if (!lock.tryLock(30 * 1000)) return;
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (!sheet) throw new Error(`シート「${SHEET_NAME}」が見つかりません`);
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const values = sheet.getRange(2, 1, lastRow - 1, 5).getValues();
const failures = [];
values.forEach((row, i) => {
const [orderId, email, name, item, status] = row;
if (String(status).trim() !== STATUS_PENDING) return;
if (!isValidEmail(email)) { failures.push(`${i + 2}行目: アドレス不正`); return; }
try {
MailApp.sendEmail({ to: String(email).trim(), subject: `【ご注文確認】${orderId}`,
body: buildBody({ orderId, name, item }) });
values[i][4] = STATUS_SENT;
} catch (e) {
failures.push(`${i + 2}行目: 送信失敗 (${e.message})`);
}
});
// ステータス列だけをまとめて書き戻す
const statusColumn = values.map(row => [row[4]]);
sheet.getRange(2, 5, statusColumn.length, 1).setValues(statusColumn);
SpreadsheetApp.flush();
if (failures.length) console.warn(failures.join('\n'));
} finally {
lock.releaseLock();
}
}
よく書けていた点
正直なところ、想像より質が高かったです。GAS特有の作法を押さえていました。
LockServiceで同時実行を防いでいる。トリガーと手動実行が重なったときの二重処理を防ぐ定石getValues()/setValues()でまとめて読み書きしている。GASでは1セルずつアクセスすると劇的に遅くなるため、これは重要- 書き戻しをステータス列だけに絞っている(他の列を巻き込まない)
- トリガー作成関数を分け、重複登録を防ぐチェックを入れている
- 失敗した行は「未処理」のまま残し、次回に再試行できるようにしている
- 頼んでいないのに「無料アカウントのメール送信上限は1日100通」と自発的に警告してきた
そして見つかった穴:6分制限による二重送信
GASには1回の実行が6分で強制終了されるという制限があります。このコードはメール送信をループ内で行い、ステータスの書き戻しはループが終わったあとに1回だけ行っています。
つまり、200件を処理中に6分でタイムアウトすると──メールは150件送信済みなのに、シートのステータスは1件も更新されないという状態になります。次回の実行では、その150件がまた「未処理」として拾われ、二重送信されます。
これは「AIが書いたコードだから危ない」という話ではありません。一括書き戻しは正しいGASの作法であり、その作法とタイムアウト制限が衝突しているのです。人間が書いても同じ穴を作ります。だからこそレビュー観点として持っておく価値があります。
対処の方向性
| 方法 | 内容 | 向いている場面 |
|---|---|---|
| 件数を区切る | 1回の実行で処理する行数に上限(例: 50件)を設け、残りは次回に回す | 最も簡単で確実。まずこれ |
| 送信直後に書き戻す | 1件送るごとにそのセルを更新する。遅くなるが取りこぼしがない | 件数が少なく、二重送信が絶対NGな場合 |
| 経過時間を見て自主的に止める | 開始時刻を記録し、4〜5分経ったらループを抜けて書き戻す | 件数が読めない場合 |
3つ目の書き方は次のようになります。時間を見て安全に離脱する形です。
const START = Date.now();
const TIME_LIMIT_MS = 4.5 * 60 * 1000; // 6分制限に対し余裕を持たせる
for (let i = 0; i < values.length; i++) {
if (Date.now() - START > TIME_LIMIT_MS) {
console.log(`時間切れのため${i}件目で中断。残りは次回実行で処理します。`);
break; // ← 抜けてから書き戻しに進む
}
// ...送信処理...
}
// ループを抜けたら必ず書き戻す(breakでもここに来る)
この穴は、依頼時に一文足しておくだけで避けられます。
「GASの6分実行制限を考慮し、途中で中断されても二重処理が起きない設計にしてください。1回の実行で処理する件数に上限を設けてください。」
今回のプロンプトは「毎日9時のトリガーで動かす想定」しか伝えていませんでした。件数の見込みを伝えていないので、AIが件数を想定できなかったのは妥当です。制約を伝えていないものは考慮されないという当たり前の話でもあります。
知らないと設計を間違えるクォータ(無料とWorkspaceで大きく違う)
GASには無料アカウントとGoogle Workspaceで数倍の差がある制限があります。ここを知らずに設計すると、動かしてから詰みます。
| 項目 | 無料アカウント | Google Workspace |
|---|---|---|
| 1回の実行時間 | 6分 | 6分 |
| カスタム関数の実行時間 | 30秒 | 30秒 |
| トリガーの合計実行時間(1日) | 90分 | 6時間 |
| トリガー数(ユーザー/スクリプトあたり) | 20個 | 20個 |
| 同時実行数(ユーザーあたり) | 30 | 30 |
| URL Fetch呼び出し(1日) | 20,000回 | 100,000回 |
| メール送信先(1日) | 100件 | 1,500件 |
| 1通あたりの宛先数 | 50件 | 50件 |
特に注意すべきは太字の3つです。
- トリガーの合計実行時間90分/日:無料アカウントで「5分かかる処理を10分おきに実行」を組むと、1日で余裕なく上限に達します。実行間隔と処理時間の掛け算を必ず確認してください
- メール送信100件/日:一斉通知系の自動化は無料アカウントだとすぐ止まります。今回Claudeが自発的に警告したのはこの点でした
- カスタム関数は30秒:シート内で使う関数に外部API呼び出しを入れると、まず間に合いません
上限に達すると実行が失敗しますが、シートを見ているだけでは気づけません。失敗を検知する仕組みを最初に入れてください。GASのエディタから「トリガー」画面を開くと実行履歴と失敗が確認できますし、トリガーの設定でエラー通知メールの頻度を指定できます。
これらの上限値はGoogleが予告なく変更する可能性があるとも明記されています。設計の前提にする場合は公式ドキュメントで最新値を確認してください。
詰まりどころ4つ
1. 初回実行時の権限承認
最初に実行すると「このアプリはGoogleで確認されていません」という警告が出ます。自分で書いた(書かせた)スクリプトなので「詳細」→「安全ではないページに移動」から進めれば問題ありませんが、初見では不安になるポイントです。
重要なのは、ここで承認する権限の範囲を確認することです。「メールの送信」「スプレッドシートの閲覧・編集」など、コードが使っているサービスに応じた権限が要求されます。要件に対して不自然に広い権限を求められた場合は、コードを見直してください。
2. トリガーが「9時ちょうど」には動かない
時間主導型トリガーで atHour(9) を指定しても、9時00分に実行される保証はありません。その時間帯の中でGoogle側が実行タイミングを決めます。生成コードに nearMinute(0)(0分ごろ)というメソッドが使われていたのは、まさにこの「おおよそ」を表しています。
「9時05分の朝会に間に合わせる」といった分単位の要件がある場合は、余裕を持って8時台に設定してください。
3. トリガーの重複登録
トリガー作成関数を手で2回実行すると、トリガーが2つできて処理が2回走ります。今回の生成コードは既存チェックを入れていましたが、これは必ず確認すべき観点です。
// 作成前に同じ関数のトリガーがないか確認する
const exists = ScriptApp.getProjectTriggers()
.some(t => t.getHandlerFunction() === 'processOrders');
if (exists) return;
4. 誰の権限で動いているのか
トリガーはそれを作成した人の権限で実行されます。担当者が自分のアカウントでトリガーを作り、その人が退職してアカウントが停止されると、自動処理も止まります。メールの送信元もその人のアドレスになります。
業務で使うなら、個人アカウントではなく共用アカウントでトリガーを作るか、少なくとも「誰の権限で動いているか」をドキュメントに残してください。引き継ぎで最も事故が起きる箇所です。
clasp でローカル開発する(必要な人向け)
Claude Codeにコードを直接編集させたい、Gitで履歴を残したい場合はclaspを使います。前述のとおり、小さな自動化なら不要です。
セットアップ
# Node.js 20以上が必要
node --version
# インストール(2026年7月時点の最新は 3.3.0)
npm install -g @google/clasp
# ログイン(ブラウザが開いてGoogleアカウントの認証に進む)
clasp login
これを忘れると clasp push が失敗します。script.google.com/home/usersettings を開き、「Google Apps Script API」をオンにしてください。GCPプロジェクトの設定ではなく、Apps Script側のユーザー設定です。ここでつまずく人が非常に多い箇所です。
既存のスプレッドシートのスクリプトを取り込む
# スクリプトIDはApps Scriptエディタの「プロジェクトの設定」で確認できる
clasp clone <スクリプトID>
# 以降の流れ
clasp pull # リモートの変更をローカルへ
clasp push # ローカルの変更をリモートへ
clasp deploy # バージョンを作成してデプロイ(Webアプリ用)
生成されるファイルは2つです。
.clasp.json:スクリプトIDを保持する。Gitに入れないappsscript.json:マニフェスト。タイムゾーン、使用するサービス、権限スコープなどを定義する。これはGit管理する
認証情報の .clasprc.json も当然ながらGitに入れません。.gitignore に両方を書いてください。
claspはかつてTypeScriptを自動でトランスパイルしていましたが、現在のclaspはTypeScriptの変換を行いません。TypeScriptで書きたい場合は、Rollupなどのバンドラーで自分でJavaScriptに変換したうえで clasp push します。
package.json のスクリプトに "push": "npm run build && clasp push" のように書いておくのが定番です。古い記事の手順をそのまま真似すると動かないので注意してください。
Claude Codeと組み合わせる
claspでローカルにファイルが降りてくれば、あとは通常のコード編集と同じです。Claude Codeにファイルを読ませて修正させ、clasp push で反映します。settings.jsonで権限を絞る方法を使い、clasp push のような反映系コマンドは人が確認してから実行する運用にすると安全です。
よくある質問
Q. GASからClaudeを呼び出すことはできますか?
できます。UrlFetchApp.fetch() でClaude APIを叩く形です。「無人で定期実行したいが、処理内容にAIの判断が必要」というケースの解になります。ただし2点注意があります。ひとつはAPIキーをコード内に直書きしないこと(スクリプトプロパティに保存します)。もうひとつは6分の実行時間制限で、AIの応答待ちは数十秒かかることもあるため、1回の実行で何件も処理する設計にはしにくい点です。件数を区切る前提で組んでください。
Q. MCPで作った処理をGASに移行すべきですか?
「無人実行が必要になったもの」だけ移行してください。全部移す必要はありません。対話しながら分析する用途はMCPのほうが圧倒的に快適です。実務ではMCPで試作してロジックを固め、固まったものをGASに移して自動化するという流れが自然です。移すときは、AIの判断が本当に必要な部分だけを見極めてください。単純な集計や転記なら、AIを呼ばないGASコードで完結します。
Q. AIが書いたGASコードをそのまま本番で動かしていいですか?
メール送信・データ削除・外部への書き込みを含む場合は、必ず先にレビューしてください。今回の検証でも、作法としては正しいコードに二重送信のリスクが潜んでいました。最低限のチェックは、①6分制限で中断されたときに何が起きるか ②処理が2回走ったときに何が起きるか ③失敗した行はどう扱われるかの3点です。この3つを自分の言葉で説明できないコードは、まだ本番に出さないほうがよいです。テスト用のシートを複製して、数件のダミーデータで通してから移行してください。
Q. claspは必須ですか?
必須ではありません。ブラウザのApps Scriptエディタにコピペするだけで動きます。claspが必要なのは、Gitでバージョン管理したい、Claude Codeに直接編集させたい、複数人で開発したい場合です。Node.jsの導入が必要になるため、それ自体が目的でないなら後回しでかまいません。
Q. 無料アカウントでどこまでできますか?
1回の実行時間(6分)は同じなので、処理の複雑さで差は出ません。差が出るのは頻度と量です。トリガーの合計実行時間が1日90分(Workspaceは6時間)、メール送信が1日100件(Workspaceは1,500件)という制限が実質的な天井になります。1日数回の集計処理なら無料で十分です。逆に一斉メール配信や高頻度の実行が要件なら、Workspaceが前提になります。
まとめ
- MCPは対話向き。人が居ない時間に動かすならGASが答えになる
- GASを使う構成では、Claudeは「書く役」で、動かすのはGoogleのサーバー。実行は無料(クォータ内)
- 小さな自動化ならブラウザにコピペで十分。claspはGit管理やClaude Codeでの直接編集が必要になってから
- Claudeが書いたGASコードは作法としては良質だったが、6分制限による二重送信リスクがあった
- 依頼時に「6分制限を考慮し、中断されても二重処理しない設計に」と1文足すだけで防げる
- 無料アカウントはトリガー合計90分/日・メール100件/日が天井。設計前に確認する
- トリガーは作成者の権限で動く。引き継ぎで最も事故が起きる箇所
- claspを使うなら、まずApps Script APIの有効化。TypeScriptは自前でトランスパイルが必要
今日やれる最初の一歩は、いまMCPで毎回手動で実行している処理を1つ選び、「これは人が居なくても動くべきか」を判断することです。答えがイエスなら、その処理の要件をClaudeに伝えてGASコードを書かせてください。そのとき「6分制限を考慮して、途中で止まっても二重処理にならないように」の一文を必ず添えてください。それだけで、レビューの手間が大きく減ります。


