The v4 server exposes a minimal MCP API with two tools only:
search- discover available SDK services and operationsexecute- run JavaScript againstsdk.*operations in a sandbox
The runtime provides 47 operations across:
drive(7)sheets(12)forms(4)docs(5)gmail(10)calendar(9)
v4 replaces both:
- operation-routed tools (
{ name: "sheets", operation: "..." }) - import-based code execution examples (
import { readSheet } from ...)
with a single pattern:
- discover via
search - execute via
executeusingsdk.<service>.<operation>(...)
Returns operation metadata from src/sdk/spec.ts.
{
"service": "drive | sheets | forms | docs | gmail | calendar (optional)",
"operation": "string (optional)"
}{}-> returns summary of all services and operation names{ "service": "drive" }-> returns all Drive operation specs{ "service": "drive", "operation": "search" }-> returns one operation spec
{
"name": "search",
"arguments": {
"service": "sheets",
"operation": "readSheet"
}
}Executes JavaScript with sdk available in scope.
{
"code": "const files = await sdk.drive.search({ query: 'budget' }); return files;"
}{
"result": {},
"logs": []
}On failure, returns an error payload (isError: true) with message and captured logs.
- code runs in Node
vmsandbox (src/sdk/sandbox-node.ts) - dangerous globals are blocked (
process,require, timers,fetch, etc.) console.log/info/warn/erroroutput is captured and returned inlogs- use
returnin code to produce structuredresult
{
"name": "search",
"arguments": {
"service": "calendar"
}
}{
"name": "execute",
"arguments": {
"code": "const events = await sdk.calendar.listEvents({ calendarId: 'primary', maxResults: 10 }); return events;"
}
}searchenhancedSearchreadcreateFilecreateFolderupdateFilebatchOperations
listSheetsreadSheetcreateSheetrenameSheetdeleteSheetupdateCellsupdateFormulaformatCellsaddConditionalFormatfreezeRowsColumnssetColumnWidthappendRows
createFormreadFormaddQuestionlistResponses
createDocumentinsertTextreplaceTextapplyTextStyleinsertTable
listMessageslistThreadsgetMessagegetThreadsearchMessagescreateDraftsendMessagesendDraftlistLabelsmodifyLabels
listCalendarsgetCalendarlistEventsgetEventcreateEventupdateEventdeleteEventquickAddcheckFreeBusy
{
"name": "execute",
"arguments": {
"code": "const files = await sdk.drive.search({ query: 'Q4 budget', pageSize: 5 }); if (!files.files.length) return null; const first = files.files[0]; const content = await sdk.drive.read({ fileId: first.id }); return { file: first, contentPreview: content.content.slice(0, 500) };"
}
}{
"name": "execute",
"arguments": {
"code": "return await sdk.sheets.addConditionalFormat({ spreadsheetId: 'YOUR_SHEET_ID', range: 'Sheet1!B2:B100', rule: { condition: { type: 'NUMBER_GREATER', values: ['90'] }, format: { backgroundColor: { red: 0.7, green: 1, blue: 0.7 }, bold: true } } });"
}
}{
"name": "execute",
"arguments": {
"code": "return await sdk.gmail.sendMessage({ to: 'recipient@example.com', subject: 'Status update', body: 'All checks passed.' });"
}
}{
"name": "execute",
"arguments": {
"code": "const now = new Date().toISOString(); const tomorrow = new Date(Date.now() + 24*60*60*1000).toISOString(); return await sdk.calendar.checkFreeBusy({ timeMin: now, timeMax: tomorrow, items: [{ id: 'primary' }] });"
}
}Required OAuth scopes are configured in src/server/transports/stdio.ts:
- Drive:
https://www.googleapis.com/auth/drive - Sheets:
https://www.googleapis.com/auth/spreadsheets - Docs:
https://www.googleapis.com/auth/documents - Forms:
https://www.googleapis.com/auth/forms - Script readonly:
https://www.googleapis.com/auth/script.projects.readonly - Gmail:
https://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/gmail.sendhttps://www.googleapis.com/auth/gmail.composehttps://www.googleapis.com/auth/gmail.modify
- Calendar:
https://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/calendar.events
Inside execute code:
- validate expected input before calling
sdk.* - throw explicit
Errormessages for failed preconditions - return structured objects for expected no-result cases
- use
console.error(...)for debugging context (captured inlogs)
Example:
const result = await sdk.drive.search({ query: "invoice 2026", pageSize: 10 });
if (!result.files.length) {
return { status: "empty", message: "No matching files" };
}
return { status: "ok", files: result.files };- prefer one
executeblock for multi-step workflows instead of many small calls - filter/transform data inside execute code before returning
- use
pageSizeintentionally on list/search operations - Redis caching is optional and configured via
REDIS_URL
When migrating legacy prompts or integrations:
- replace old tool names with
search+execute - replace operation payloads with
sdk.<service>.<operation>calls - replace legacy Sheets names:
updateCellsWithFormula->updateFormulaaddConditionalFormatting->addConditionalFormat
docs/Architecture/ARCHITECTURE.mddocs/Deployment/DOCKER.mddocs/Examples/README.mddocs/Guides/README.md