「チャットで質問に答えてもらうだけでなく、AIに外部のデータを操作させたい」──生成AIの活用が進むにつれ、こうしたニーズが増えています。
例えば、スプレッドシートへのデータ追加や一覧取得といった操作を、自然言語で指示するだけで完了させたいという場面です。
以前の記事では、AIと外部システムを連携させる手段としてMCP(Model Context Protocol)を紹介しました。
今回は、ChatGPT を活用したGPTs Actionsを取り上げます。
「いつ・なぜ GPTs Actions を選ぶのか」を軸に、MCP との使い分けを整理したうえで、GAS(Google Apps Script)を使った実装例を交えて解説します。
GPTs(カスタム GPT)は、ChatGPT を特定の用途向けにカスタマイズできる機能です。
GPT では、Instructions(指示)、Knowledge(参照ファイル)、Capabilities(機能拡張。Custom Actions もここに含まれます)を設定できます。
作成した GPT は、リンク共有やワークスペース内への共有が可能です。
共同編集(Can edit)などの権限管理はワークスペース機能に依存するため、ワークスペース外のユーザーには共同編集権限を付与できない点に注意しましょう。
Actions は、GPTs に外部 API の呼び出し能力を持たせる機能です。
OpenAPI スキーマで API 仕様を定義して GPT に登録すると、ユーザーの入力に応じて GPT がアクションを呼び出してくれます。
つまり、「自然言語インターフェース」+「確定的な API 処理」という組み合わせを実現できるわけです。
たとえば、ユーザーが「現在のタスク状況を一覧表示して」と入力すると、GPT が OpenAPI 定義に沿って API を呼び出し、結果を整形して返してくれます。
実務では、GPTs は以下の3要素を押さえておくとよいでしょう。
| 要素 | 役割 |
|---|---|
| Instructions | GPT の振る舞いや応答スタイルを定義する指示 |
| Knowledge | GPT が参照するアップロードファイル |
| Capabilities | 機能拡張(Web browsing、Code Interpreter、Custom Actions など) |
この中で Custom Actions が、GPT を「情報を返すだけのチャットボット」から「外部システムを操作できるエージェント」へと進化させる要素です。
GPTs Actions は、ChatGPT 上のカスタム GPT から外部 API を呼び出すための仕組みです。
一方の MCP は、AI アプリケーションがローカル・リモート問わずデータソースやツールに接続するためのオープン標準です。
どちらが優れているかではなく、相補的な関係として捉えるのがよいでしょう。
| 観点 | GPTs Actions | MCP |
|---|---|---|
| 利用起点 | ChatGPT の GPT 設定画面から構成 | MCP ホスト(対応クライアント)から接続 |
| 仕様 | OpenAPI ベースでアクションを定義 | MCP プロトコルでツール/リソースを接続 |
| 接続先 | 主に Web API(HTTP) | ローカル/リモート双方(例: ファイル、DB、API) |
| 共有・配布 | GPT の共有設定(リンク/ワークスペース) | サーバー設定やクライアント設定で接続 |
| 適した場面 | 定型業務のチャット操作を展開したいとき | 開発・運用で複数データソースを統合したいとき |
両者の大きな違いのひとつは、操作対象の指定方法です。
GPTs Actions では、バックエンド側に操作対象をあらかじめ埋め込んでおけます。
たとえば、GAS のコード内にスプレッドシート ID を直接記述しておけば、ユーザーは「データを追加して」と言うだけで OK です。
「どのファイルに?」「どのシートに?」といった指定を毎回プロンプトで行う必要がないため、定型業務ではとても扱いやすい設計になります。
一方 MCP は、ツールとリソースを汎用的に公開し、実行時にどの対象を扱うかを指定できる設計です。
柔軟性が高い反面、接続先・権限・公開範囲をきちんと管理する必要があります。
迷ったときは、以下のように考えると整理しやすくなります。
| ケース | 推奨 |
|---|---|
| 非エンジニアがチャットでスプレッドシートを操作したい | GPTs Actions |
| 開発者が複数データソースを統合してAI連携したい | MCP |
| 特定の業務フローをリンク1つでチームに展開したい | GPTs Actions |
| ローカル/社内データを接続対象として柔軟に扱いたい | MCP |
ここからは、GAS(Google Apps Script)を使ってスプレッドシートを操作する Actions を実際に構築していきます。
GPTs Actions のバックエンドには任意の Web API を使えますが、GAS は以下の点で手軽に始められる選択肢です。
| 観点 | 内容 |
|---|---|
| サーバーレスで始めやすい | Apps Script は Google のインフラ上で動くサーバーレス環境なので、サーバー管理が不要です |
| Google サービスとの連携が容易 | SpreadsheetApp などの組み込みサービスから、スプレッドシートを直接操作できます |
| クォータ前提で運用設計できる | 実行回数・実行時間などのクォータ があるため、規模に応じた設計がしやすくなっています |
GAS を Web アプリとして公開する場合、doGet(e) / doPost(e) がエントリーポイントになります。
以下のコードをスプレッドシートに紐づく GAS プロジェクトに記述しましょう。
function getConfig() {
const props = PropertiesService.getScriptProperties();
const spreadsheetId = props.getProperty('SPREADSHEET_ID');
const sheetName = props.getProperty('SHEET_NAME');
const apiKey = props.getProperty('X_API_KEY');
if (!spreadsheetId || !sheetName || !apiKey) {
return null;
}
return { spreadsheetId, sheetName, apiKey };
}
function configError() {
return ContentService
.createTextOutput(JSON.stringify({
error: 'Missing script properties: SPREADSHEET_ID, SHEET_NAME, X_API_KEY'
}))
.setMimeType(ContentService.MimeType.JSON);
}
function parseBody(e) {
if (!e.postData || !e.postData.contents) return {};
try {
return JSON.parse(e.postData.contents);
} catch (error) {
return {};
}
}
function validation(e, body, expectedApiKey) {
const apiKey = e.parameter['x_api_key'] || (body && body['x_api_key']);
if (apiKey !== expectedApiKey) {
return ContentService
.createTextOutput(JSON.stringify({ error: 'Unauthorized' }))
.setMimeType(ContentService.MimeType.JSON);
}
return null;
}
function doGet(e) {
const config = getConfig();
if (!config) return configError();
const authError = validation(e, null, config.apiKey);
if (authError) return authError;
const sheet = SpreadsheetApp.openById(config.spreadsheetId).getSheetByName(config.sheetName);
const data = sheet.getDataRange().getValues();
const headers = data[0];
const rows = data.slice(1).map(row => {
const obj = {};
headers.forEach((h, i) => obj[h] = row[i]);
return obj;
});
return ContentService
.createTextOutput(JSON.stringify({ items: rows, total: data.length - 1 }))
.setMimeType(ContentService.MimeType.JSON);
}
function doPost(e) {
const config = getConfig();
if (!config) return configError();
const body = parseBody(e);
const authError = validation(e, body, config.apiKey);
if (authError) return authError;
const sheet = SpreadsheetApp.openById(config.spreadsheetId).getSheetByName(config.sheetName);
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
const newRow = headers.map(h => body[h] || '');
sheet.appendRow(newRow);
return ContentService
.createTextOutput(JSON.stringify({ status: 'ok', added: body }))
.setMimeType(ContentService.MimeType.JSON);
}
コードのポイントを簡単にまとめます。
| 項目 | 内容 |
|---|---|
| 設定値の管理 | SPREADSHEET_ID / SHEET_NAME / X_API_KEY をスクリプトプロパティから取得するため、値をコードに直書きしません |
| API キー検証 | validation(e, body, expectedApiKey) で API キーを検証し、不一致の場合は 401 Unauthorized 相当のレスポンスを返します(詳細は後述) |
| ボディの安全な解析 | parseBody(e) で POST の JSON を安全にパースし、パース失敗時は空オブジェクトを扱います |
| 件数情報の返却 | レスポンスに total(全件数)を含めることで、GPT がユーザーに残りのデータがあることを伝えられます |
| 追記前の整合性確認 | appendRow の前に「リクエストキーとシートヘッダーが一致しているか」を確認し、不一致時はエラーを返します |
| JSON での応答 | ContentService.createTextOutput(…).setMimeType(…) で JSON 形式のレスポンスを返します |
補足: GPTs Actions にはレスポンスサイズの上限があり、データ量が多いと ResponseTooLargeError が発生する場合があります。件数制御やページングを実装しておくと安定します。
なぜスクリプトプロパティにするのか?
| 理由 | 内容 |
|---|---|
| 1 | API キーや ID をコードに残さず管理できる |
| 2 | 環境ごと(検証/本番)に値を切り替えやすい |
| 3 | 値の更新時にコード差分を増やさず運用できる |

各 API の詳細は SpreadsheetApp / Sheet / ContentService の公式リファレンスをご覧ください。
| 手順 | 内容 |
|---|---|
| 1 | GAS エディタの「プロジェクトの設定」から「スクリプト プロパティ」を開く |
| 2 | スクリプトプロパティにキーを追加する |
| 2-1 | SPREADSHEET_ID: 対象スプレッドシートの ID |
| 2-2 | SHEET_NAME: 対象シート名(例: シート1) |
| 2-3 | X_API_KEY: ランダムな API キー(例: openssl rand -hex 32) |
| 3 | GAS エディタで「デプロイ」→「新しいデプロイ」を選択 |
| 4 | 種類として「ウェブアプリ」を選択 |
| 5 | ウェブアプリの公開設定を行う |
| 5-1 | 実行ユーザー(Execute as): 「自分」(Me) |
| 5-2 | アクセス権(Who has access): 「全員」(Anyone) |
| 6 | デプロイ後に表示される URL をコピー(Actions の servers.url で使用) |
なぜ「全員」にする必要があるのか?
GPTs Actions は、OpenAI のサーバーからバックエンド URL を HTTP リクエストで呼び出します。この呼び出し元は Google アカウントによる認証を通過できないため、GAS のアクセス権を「全員」に設定しないとリクエストが到達しません。
デプロイ設定の詳細は Web Apps の公式ドキュメントで確認してください。
アクセス権を「全員」に設定すると、URL を知っている人なら誰でもリクエストを送信できる状態になります。
つまり、URL が第三者に漏れた場合、スプレッドシートのデータを読み取られたり、不正なデータを書き込まれたりするリスクがあります。
この対策として、API キーによる簡易認証をバックエンド側に実装しています。
先ほどの validation(e, body, expectedApiKey) 関数がその役割を担っています。
function validation(e, body, expectedApiKey) {
const apiKey = e.parameter['x_api_key'] || (body && body['x_api_key']);
if (apiKey !== expectedApiKey) {
return ContentService
.createTextOutput(JSON.stringify({ error: 'Unauthorized' }))
.setMimeType(ContentService.MimeType.JSON);
}
return null;
}
| 観点 | 内容 |
|---|---|
| API キーの受け取り | GET リクエストではクエリパラメータから、POST リクエストではリクエストボディから x_api_key を取得します |
| 不一致時の挙動 | スクリプトプロパティの X_API_KEY と一致しない場合はエラーレスポンスを返し、スプレッドシートへのアクセスをブロックします |
| キー値の要件 | X_API_KEY には推測されにくいランダムな文字列を設定してください(例: openssl rand -hex 32 で生成した値など) |
注意: GAS の Web アプリでは HTTP ステータスコードを制御できないため、ステータスは常に 200 で返ります。そのためレスポンスボディの error フィールドでエラーを判定する設計としています。
重要: GAS の doGet(e) / doPost(e) では、リクエストヘッダーの値を直接参照できません。そのため、この実装で API キーを受け取る場所はクエリまたは JSON ボディです。
補足: API キーによる認証は簡易的な対策です。機密性の高いデータを扱う場合は、OAuth 2.0 による認証など、より堅牢な認証方式を検討する必要があり、本構成での実現はできません。
GPT に登録するアクション仕様は、OpenAPI の形式で記述します。
servers.url には、先ほどデプロイした GAS の URL を設定してください。
openapi: 3.1.0
info:
title: スプレッドシート操作 API
version: 1.0.0
description: GAS 経由でスプレッドシートのデータを取得・追加する API
servers:
- url: https://script.google.com/macros/s/あなたのデプロイID
paths:
/exec:
get:
operationId: getItems
summary: スプレッドシートのデータを一覧取得する
description: スプレッドシートの全行を JSON 配列で返します。
parameters:
- name: x_api_key
in: query
required: true
schema:
type: string
description: API キー
responses:
"200":
description: データの一覧
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
post:
operationId: addItem
summary: スプレッドシートにデータを追加する
description: JSON で指定されたデータをスプレッドシートの末尾に1行追加します。
requestBody:
required: true
content:
application/json:
schema:
type: object
description: 追加するデータ(キーはスプレッドシートのヘッダーに対応)
properties:
x_api_key:
type: string
description: API キー
日付:
type: string
pattern: '^\d{4}/\d{2}/\d{2}$'
example: '2026/03/04'
description: 日付(YYYY/MM/DD)
タスク:
type: string
description: タスク
required:
- x_api_key
responses:
"200":
description: 追加成功
content:
application/json:
schema:
type: object
properties:
status:
type: string
added:
type: object
ポイントは以下の通りです。
| 項目 | 内容 |
|---|---|
| operationId | 各操作を一意に識別するための ID です(OpenAPI の Operation Object で定義) |
| summary / description | 具体的に書いておくと、アクションの意図が管理しやすくなります |
| pattern | 入力制約のヒントになりますが、実運用では doPost 側で正規化・検証(–→/ 変換や不正日付判定)も実装しておくと安全です |
トラブルシュート(200 だが保存されない)
| 確認項目 | 内容 |
|---|---|
| レスポンス確認 | GAS は仕様上 HTTP ステータスが常に 200 になるため、レスポンスボディの status / error を確認してください |
| API キー不一致 | x_api_key 不一致の場合、Unauthorized が返って保存されません |
| requestBody のキー名 | requestBody のキー名(例: date, task)とスプレッドシートの1行目ヘッダー名が一致しない場合、値は入りません |
| 手順 | 内容 |
|---|---|
| 1 | ChatGPT の「GPT を作成」画面を開く(2026年3月時点で、GPT の作成には Pro / Plus / Team / Enterprise / Edu プランが必要) |
| 2 | 「構成」タブで設定を行う |
| 2-1 | Instructions: GPT の振る舞いを記述(後述) |
| 2-2 | Actions: 「アクションを作成」から OpenAPI スキーマを登録 |
| 3 | Actions の登録後、認証(Authentication)は None のままにする |
| 4 | x_api_key は OpenAPI 定義の query / requestBody パラメータとして送信し、X_API_KEY と同じ値を渡す |
| 5 | 「プレビュー」でテストする |



補足: API キーをヘッダーで秘匿して扱いたい場合は、GAS の前段に Cloud Run / Cloud Functions などのプロキシ API を置き、そこでヘッダー検証する構成にしなければなりません。
Instructions は、GPT がどのように振る舞うかを決める重要な設定です。
Actions と組み合わせる場合、どのアクションをどんな状況で呼び出すかを明確に記述しておくと、GPT の応答精度が向上します。
今回のスプレッドシート操作 GPT であれば、たとえば以下のような Instructions が考えられます。
あなたはスプレッドシートのデータを管理するアシスタントです。
## できること
- データの一覧表示(getItems アクションを使用)
- データの追加(addItem アクションを使用)
## アクション実行時の必須ルール
- getItems を呼び出すときは、query パラメータ `x_api_key` に `<<X_API_KEY>>` を必ず設定してください。
- addItem を呼び出すときは、requestBody に `x_api_key: <<X_API_KEY>>` を必ず含めてください。
- `x_api_key` は内部設定値です。ユーザーに値を尋ねたり、回答文に表示したりしないでください。
## 応答ルール
- ユーザーがデータの確認・表示を求めたら、getItems を呼び出し、結果を見やすい表形式で返してください。
- ユーザーがデータの追加を求めたら、必要な項目を確認したうえで addItem を呼び出してください。
- 必要な項目が不足している場合は、追加前にユーザーに確認してください。
- データの更新・削除はこの GPT では対応していません。依頼された場合はその旨を伝えてください。
- Unauthorized エラーが返った場合は、`x_api_key` の設定不備の可能性を案内し、管理者確認を促してください。
## 注意事項
- スプレッドシートの構造やIDについてユーザーに尋ねる必要はありません
- 機密情報や個人情報の取り扱いには注意し、不明な場合は管理者への確認を促してください。
Instructions を書く際のポイントは以下の通りです。
| ポイント | 内容 |
|---|---|
| アクションとの対応を明示する | 「一覧表示には getItems を使う」のように、ユーザーの意図とアクションの対応関係を書いておくと、GPT が適切にアクションを選択しやすくなります |
| 認証パラメータの扱いを明示する | x_api_key をどの操作でどこに入れるか、ユーザーへ開示しないことまで書いておくと、Unauthorized の発生や情報漏えいリスクを下げられます |
| 対応範囲を明確にする | この GPT で「できること」と「できないこと」を区別しておくと、想定外の操作を防げます |
| 確認フローを組み込む | データ追加のように副作用のある操作では、実行前にユーザーへ確認するよう指示しておくと安全です |
| テスト入力 | 想定結果 |
|---|---|
| 「データを一覧表示して」 | getItems が呼ばれ、スプレッドシートの内容が表示される |
| 「日付: 2026/03/04、タスク: 技術ブログを書く」 | addItem が呼ばれ、行が追加される |






ここまで GAS のコードと OpenAPI スキーマを紹介しましたが、これらは生成 AI に下書きさせることもできます。
たとえば、ChatGPT に以下のように指示して、ひな形を作らせる運用は実務でも有効です。
| プロンプト例 |
|---|
| 「スプレッドシートのデータを JSON で返す GAS の doGet 関数を書いて」 |
| 「GAS の Web API 用の OpenAPI 3.1 スキーマを作成して」 |
ただし、以下の点は必ず人間が確認・対応してください。
| 確認項目 |
|---|
| GAS のデプロイ設定とアクセス権 |
| スプレッドシート ID とシート構造の整合性 |
| 実データでの取得・追加の動作検証 |
GPTs Actions は、ChatGPT 上で OpenAPI ベースの外部 API 連携を行う仕組みです。GPT の共有機能と組み合わせることで、定型業務をチームへ展開しやすいのが特徴です。
MCP は、ローカル/リモートのデータソースやツールをAIアプリケーションに接続するためのオープン標準です。
そのため、両者は競合というより、用途に応じて使い分ける関係と考えるのが実践的です。
GAS をバックエンドに選べば、比較的少ない構成でスプレッドシート連携のアクションを構築できます。
「AI が自然言語を理解し、確定的な API 操作を実行する」パターンは、業務自動化の現場で再利用しやすい実装方式です。
MCP について詳しく知りたい方は、こちらの記事も参考にしてください。
Tech Funでは、お客様のフェーズに合わせ、生成AI活用に向けた支援を3つのパックでご提供しています。
生成AIに限らず、Web・業務システム開発やインフラ設計など、技術領域を問わずご相談を承っています。「何から始めれば良いか分からない」という段階でも構いませんので、ぜひお気軽にお問い合わせください。