# Spreadsheet API仕様書
この文書は、将来的なGoogle Apps Script Web AppでのAPI実装のためのOpenAPI仕様を定義します。
```yaml
openapi: 3.0.3
info:
title: Spreadsheet API
description: Google スプレッドシートデータにアクセスするためのAPI
version: 1.0.0
servers:
- url: https://script.google.com/macros/s/{DEPLOYMENT_ID}/exec
description: Google Apps Script Web App URL
paths:
/getSpreadsheetInfo:
get:
summary: スプレッドシートの基本情報を取得する
description: スプレッドシートのメタデータとシート一覧を返します
parameters:
- name: id
in: query
description: スプレッドシートID
required: true
schema:
type: string
- name: apiKey
in: query
description: API認証キー
required: true
schema:
type: string
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/SpreadsheetInfo'
'400':
description: 無効なリクエスト
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 認証エラー
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: スプレッドシートが見つからない
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/getSheetData:
get:
summary: シートのデータを取得する
description: 指定されたシートのデータを二次元配列で返します
parameters:
- name: id
in: query
description: スプレッドシートID
required: true
schema:
type: string
- name: sheetName
in: query
description: シート名
required: true
schema:
type: string
- name: apiKey
in: query
description: API認証キー
required: true
schema:
type: string
- name: maxRows
in: query
description: 取得する最大行数(省略時は全行)
required: false
schema:
type: integer
default: 1000
- name: maxCols
in: query
description: 取得する最大列数(省略時は全列)
required: false
schema:
type: integer
- name: startRow
in: query
description: 取得開始行(0スタート、省略時は0)
required: false
schema:
type: integer
default: 0
- name: startCol
in: query
description: 取得開始列(0スタート、省略時は0)
required: false
schema:
type: integer
default: 0
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/SheetData'
'400':
description: 無効なリクエスト
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: 認証エラー
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: スプレッドシートまたはシートが見つからない
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
SpreadsheetInfo:
type: object
required:
- id
- name
- url
- lastModified
- sheets
properties:
id:
type: string
description: スプレッドシートのID
name:
type: string
description: スプレッドシートの名前
url:
type: string
description: スプレッドシートのURL
lastModified:
type: string
format: date-time
description: 最終更新日時(ISO 8601形式)
sheets:
type: array
description: シート一覧
items:
$ref: '#/components/schemas/SheetInfo'
SheetInfo:
type: object
required:
- id
- name
- index
- rowCount
- columnCount
properties:
id:
type: integer
description: シートのID
name:
type: string
description: シート名
index:
type: integer
description: シートのインデックス(0から始まる)
rowCount:
type: integer
description: 行数
columnCount:
type: integer
description: 列数
SheetData:
type: object
required:
- spreadsheetId
- sheetName
- data
properties:
spreadsheetId:
type: string
description: スプレッドシートのID
sheetName:
type: string
description: シート名
data:
type: array
description: シートデータの二次元配列
items:
type: array
items:
oneOf:
- type: string
- type: number
- type: boolean
- type: 'null'
totalRows:
type: integer
description: シート内の実際の合計行数
totalCols:
type: integer
description: シート内の実際の合計列数
startRow:
type: integer
description: 返却データの開始行(0から始まる)
startCol:
type: integer
description: 返却データの開始列(0から始まる)
Error:
type: object
required:
- error
- message
properties:
error:
type: string
description: エラーコード
message:
type: string
description: エラーメッセージ
```
## Google Apps Script 実装例
以下は、上記API仕様に基づくGoogle Apps Script実装の一例です:
```javascript
// SpreadsheetアクセスのためのAPIを提供するGAS Web App
// 設定
const API_KEY = 'your-api-key-here'; // APIキー
const MAX_ROWS = 1000; // デフォルトの最大行数
/**
* Webアプリのエントリポイント
*/
function doGet(e) {
try {
// APIキー検証
if (!validateApiKey(e.parameter.apiKey)) {
return createErrorResponse(401, 'Invalid API key');
}
const action = e.parameter.action;
// リクエスト処理
if (action === 'getSpreadsheetInfo') {
return handleGetSpreadsheetInfo(e);
} else if (action === 'getSheetData') {
return handleGetSheetData(e);
} else {
return createErrorResponse(400, 'Invalid action parameter');
}
} catch (error) {
return createErrorResponse(500, error.toString());
}
}
/**
* getSpreadsheetInfoリクエストを処理
*/
function handleGetSpreadsheetInfo(e) {
const id = e.parameter.id;
if (!id) {
return createErrorResponse(400, 'Missing required parameter: id');
}
try {
const ss = SpreadsheetApp.openById(id);
const sheets = ss.getSheets();
const result = {
id: id,
name: ss.getName(),
url: ss.getUrl(),
lastModified: ss.getLastUpdated().toISOString(),
sheets: sheets.map(sheet => ({
id: sheet.getSheetId(),
name: sheet.getName(),
index: sheet.getIndex(),
rowCount: sheet.getLastRow(),
columnCount: sheet.getLastColumn()
}))
};
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
if (error.toString().includes('no item with the given ID')) {
return createErrorResponse(404, 'Spreadsheet not found');
}
return createErrorResponse(500, error.toString());
}
}
/**
* getSheetDataリクエストを処理
*/
function handleGetSheetData(e) {
const id = e.parameter.id;
const sheetName = e.parameter.sheetName;
if (!id || !sheetName) {
return createErrorResponse(400, 'Missing required parameters: id and/or sheetName');
}
const maxRows = e.parameter.maxRows ? parseInt(e.parameter.maxRows) : MAX_ROWS;
const maxCols = e.parameter.maxCols ? parseInt(e.parameter.maxCols) : null;
const startRow = e.parameter.startRow ? parseInt(e.parameter.startRow) : 0;
const startCol = e.parameter.startCol ? parseInt(e.parameter.startCol) : 0;
try {
const ss = SpreadsheetApp.openById(id);
const sheet = ss.getSheetByName(sheetName);
if (!sheet) {
return createErrorResponse(404, `Sheet '${sheetName}' not found`);
}
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow === 0 || lastCol === 0) {
// 空のシート
return ContentService.createTextOutput(JSON.stringify({
spreadsheetId: id,
sheetName: sheetName,
data: [],
totalRows: 0,
totalCols: 0,
startRow: startRow,
startCol: startCol
})).setMimeType(ContentService.MimeType.JSON);
}
// データを取得する範囲を計算
const endRow = Math.min(lastRow, startRow + maxRows);
const endCol = maxCols ? Math.min(lastCol, startCol + maxCols) : lastCol;
const numRows = endRow - startRow;
const numCols = endCol - startCol;
if (numRows <= 0 || numCols <= 0) {
// 有効な範囲がない
return createErrorResponse(400, 'Invalid data range');
}
// データ取得
const dataRange = sheet.getRange(startRow + 1, startCol + 1, numRows, numCols);
const data = dataRange.getValues();
const result = {
spreadsheetId: id,
sheetName: sheetName,
data: data,
totalRows: lastRow,
totalCols: lastCol,
startRow: startRow,
startCol: startCol
};
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
if (error.toString().includes('no item with the given ID')) {
return createErrorResponse(404, 'Spreadsheet not found');
}
return createErrorResponse(500, error.toString());
}
}
/**
* APIキーの検証
*/
function validateApiKey(key) {
return key === API_KEY;
}
/**
* エラーレスポンスの作成
*/
function createErrorResponse(code, message) {
const error = {
error: code.toString(),
message: message
};
return ContentService.createTextOutput(JSON.stringify(error))
.setMimeType(ContentService.MimeType.JSON);
}
```
## 統合方法
この仕様に基づくAPIを実装した後、MCPサーバーの`api/spreadsheet.ts`を修正して、モック実装ではなく実際のAPIコールを行うように変更します。以下の手順を行います:
1. 認証情報を設定する(環境変数またはconfigファイル)
2. `getSpreadsheetInfo`と`getSheetData`関数を書き換えて、APIエンドポイントにHTTPリクエストを送信する
3. レスポンス形式をMCPツールの期待する形式に変換する
この変更により、MCPサーバーは実際のスプレッドシートデータにアクセスできるようになります。