
Abstract
This article introduces a major update to gas-fakes enabling dynamic loading of Google Apps Script libraries. This enhancement allows developers to build modular, maintainable Model Context Protocol (MCP) servers. We demonstrate this by integrating sophisticated library-based tools with Gemini CLI and Google Antigravity for seamless Google Workspace automation.
Introduction
I recently published an article titled “Power of Google Apps Script: Building MCP Server Tools for Gemini CLI and Google Antigravity in Google Workspace Automation.” In that piece, I demonstrated how to bridge the Model Context Protocol (MCP) with Google Workspace by implementing an MCP server using Google Apps Script (GAS) and gas-fakes. This successfully established a communication channel for sophisticated AI agents—such as the Gemini CLI and Google Antigravity—to interact directly with Workspace data.
However, the initial implementation faced a significant technical hurdle regarding dependency management. As noted in my previous findings, gas-fakes could not dynamically load external GAS libraries at the time. This forced developers to manually inject library source code directly into the main script to ensure execution compatibility. This monolithic approach resulted in high maintenance overhead and reduced code readability, severely limiting the complexity of tools that could be deployed efficiently. This restriction was a major obstacle to achieving true modularization in AI-driven automation.
To overcome this architectural limitation and enable truly Modularizing AI solutions, I have implemented a native function within gas-fakes to support the direct utilization of Google Apps Script libraries. This major enhancement mimics the GAS runtime environment more accurately, allowing developers to maintain modular codebases. It is now possible to execute complex scripts that rely on external libraries without the need for manual code bundling.
In this article, I will introduce practical examples of scripts that leverage this new functionality. We will explore how to build sophisticated and maintainable MCP tools that seamlessly integrate standard GAS libraries, significantly expanding the automation capabilities available to AI agents. The robust, modular MCP server demonstrated below was successfully tested using the Gemini CLI and Google Antigravity.
Prerequisites and Installation
1. Install Gemini CLI
First, install the Gemini CLI using npm:
npm install -g @google/gemini-cli
Next, authorize the CLI by following the instructions in the official documentation. Note: In this article, the Gemini CLI Extension for GAS Development Kit is not used.
2. Install Google Antigravity
Please check the official release and installation guide at https://antigravity.google/.
3. Install gas-fakes CLI
We will use gas-fakes to emulate the GAS environment locally. Install the CLI tool via npm:
npm -g install @mcpher/gas-fakes
4. Authorize Access to Google Services
To allow gas-fakes to interact with your Google services (Drive, Sheets, etc.) for real-world testing, you must authorize the client.
First, create a .env file to store your project configuration. The tool will prompt you for the Project ID (found in your GCP console).
gas-fakes init
Next, run the authorization command. This guides you through the OAuth flow to grant necessary permissions.
gas-fakes auth
Finally, enable the required Google APIs for your project.
gas-fakes enableAPIs
Verification: Run a simple test command to verify configuration. This executes a script in the sandbox to retrieve the root folder name of your Google Drive.
gas-fakes -s "const rootFolder = DriveApp.getRootFolder(); const rootFolderName = rootFolder.getName(); console.log(rootFolderName);"
If the command prints your root folder’s name without errors, your local environment is ready.
Basic Verification: Using Libraries in gas-fakes
Before building the MCP server, let’s verify that gas-fakes can correctly load and execute a library.
1. Create a library file
Create a sample library file named sampleLib.js by copying and pasting the following script.
function function1() {
return "function1";
}
var value1 = "value1";
2. Create a main script
Create a sample script named sample.js that utilizes the library. In this example, the library identifier is defined as LIB.
const res1 = LIB.function1();
const res2 = LIB.value1;
console.log({ res1, res2 });
3. Run the script
Execute the following command to link the library file to the identifier LIB and run the script:
gas-fakes -f sample.js -l LIB@sampleLib.js
Output:
$ gas-fakes -f sample.js -l LIB@sampleLib.js
...using env file in /workspace/.env
...using gasfakes settings file in /workspace/gasfakes.json
[Worker] ...importing Drive API
[Worker] ...importing Sheets API
[Worker] ...importing Slides API
[Worker] ...didnt find /workspace/gasfakes.json ... skipping
[Worker] ...didnt find /workspace/appsscript.json ... skipping
[Worker] ...didnt find /workspace/.clasp.json ... skipping
[Worker] ...cache will be in /tmp/gas-fakes/cache
[Worker] ...properties will be in /tmp/gas-fakes/properties
[Worker] ...writing to /workspace/gasfakes.json
[Worker] ...initializing auth and discovering project ID
[Worker] ...discovered and set projectId to for-testing
...gas-fakes version 1.2.##
{ res1: 'function1', res2: 'value1' }
...terminating worker thread
The output confirms that the function and variable from the library were correctly retrieved.
Building the MCP Server with Libraries
Now we will prepare the actual scripts to be used with the Gemini CLI and Antigravity.
Library Configuration Notes
We will use the following GAS library:
- TableApp: GitHub Repository
- Library Key: 1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt
Important Note on Permissions:
In the updated gas-fakes, the library key can be used directly. However, for this to work via the API, the Google Apps Script project of the library should ideally be a Standalone script type.
- Container-bound scripts require the Google Apps Script API and the scope https://www.googleapis.com/auth/script.projects.readonly.
- Standalone scripts require the Drive API and the scope https://www.googleapis.com/auth/drive.readonly.
Since gas-fakes is already authorized for the Drive API, using a Standalone library avoids additional scope configuration.
Disclaimer: I am not certain if every published library is compatible with gas-fakes. If errors occur, minor modifications to the library script may be required.
Sample Custom Tool Implementation
Create a file named sample.js. This script defines the tools for the MCP server, utilizing TableApp (via library key) and a local helper library TableMng. Replace {your path} with the actual path and test it.
import { z } from "zod";
const tools = [
{
name: "create-sample-google-sheets",
schema: {
description:
"Use this to create a sample Google Spreadsheet including sample data.",
inputSchema: {},
},
func: (object = {}) => {
const values = [
["a1_sample1", "b1_sample2", "c1_sample3"],
["a2_sample4", "b2_sample5", "c2_sample6"],
["a3_sample7", "b3_sample8", "c3_sample9"],
];
const ss = SpreadsheetApp.create("sample");
const sheet = ss.getSheets()[0].setName("Sheet1");
const range = sheet
.getRange(1, 1, values.length, values[0].length)
.setValues(values);
const textStyle = SpreadsheetApp.newTextStyle()
.setBold(true)
.setForegroundColor("red")
.build();
const r = values.map((r) =>
r.map((c) =>
SpreadsheetApp.newRichTextValue()
.setText(c)
.setTextStyle(0, 2, textStyle)
.build()
)
);
range.setRichTextValues(r);
return `Spreadsheet ID of the created Google Spreadsheet is \`${ss.getId()}\`. The data range is \`${range.getA1Notation()}\`.`;
},
},
{
name: "create-table-to-google-sheets",
schema: {
description: "Use this to create a new table to Google Spreadsheet.",
inputSchema: {
spreadsheetId: z
.string()
.describe("The ID of the target Google Sheet."),
tableName: z
.string()
.describe(
"Table name. This name will be used to the created new table."
),
sheetName: z.string().describe("Sheet name."),
range: z
.string()
.describe(
"The cell range of the table as A1Notation. The table will be created to this range."
),
},
},
func: (object = {}) => {
const { spreadsheetId, sheetName, range, tableName } = object;
const table = TableApp.openById(spreadsheetId)
.getSheetByName(sheetName)
.getRange(range)
.create(tableName);
return `Created Table ID: ${table.getId()}`;
},
libraries: [
"TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt",
],
},
{
name: "get-table-to-google-sheets",
schema: {
description: "Use this to get tables in a Google Spreadsheet.",
inputSchema: {
spreadsheetId: z
.string()
.describe("The ID of the target Google Sheet."),
},
},
func: (object = {}) => {
const tables = TableMng.getTables({ ...object, TableApp });
return JSON.stringify(tables);
},
libraries: [
"TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt",
"TableMng@{your path}/tableManager.js",
],
},
{
name: "get-values-from-table-on-google-sheets",
schema: {
description:
"Use this to get values from a table in a Google Spreadsheet.",
inputSchema: {
spreadsheetId: z
.string()
.describe("The ID of the target Google Sheet."),
tableName: z
.string()
.describe(
"Table name in the target Google Sheet. Provide table name or table ID."
)
.optional(),
tableId: z
.string()
.describe(
"Table ID in the target Google Sheet. Provide table name or table ID."
)
.optional(),
},
},
func: (object = {}) => {
const table = TableMng.getTable({ ...object, TableApp });
return JSON.stringify(table.getValues());
},
libraries: [
"TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt",
"TableMng@{your path}/tableManager.js",
],
},
{
name: "reverse-table-on-google-sheets",
schema: {
description: "Use this to reverse a table in a Google Spreadsheet.",
inputSchema: {
spreadsheetId: z
.string()
.describe("The ID of the target Google Sheet."),
tableName: z
.string()
.describe(
"Table name in the target Google Sheet. Provide table name or table ID."
)
.optional(),
tableId: z
.string()
.describe(
"Table ID in the target Google Sheet. Provide table name or table ID."
)
.optional(),
},
},
func: (object = {}) => {
const table = TableMng.getTable({ ...object, TableApp });
const reverseMsg = table.reverse();
return reverseMsg;
},
libraries: [
"TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt",
"TableMng@{your path}/tableManager.js",
],
},
];
Create the helper library file tableManager.js at the path specified in TableMng@{your path}/tableManager.js inside your tool definitions:
function getTables(object) {
const { spreadsheetId, TableApp } = object;
const tables = tableApp.getTables();
return tables;
}
function getTable(object) {
const { spreadsheetId, tableName, tableId, TableApp } = object;
const tableApp = TableApp.openById(spreadsheetId);
let table;
if (tableName) {
table = tableApp.getTableByName(tableName);
} else if (tableId) {
table = tableApp.getTableById(tableId);
} else {
throw new Error("Table name or table ID is required.");
}
return table;
}
Integration Settings
For Gemini CLI
To register these tools with the Gemini CLI, modify your .gemini/settings.json file as follows:
"mcpServers": {
"gas-fakes": {
"command": "gas-fakes",
"args": ["mcp", "--tools", "{your path}/sample.js"]
}
}
After launching the Gemini CLI, run the command /mcp to verify that the custom tools are successfully loaded.

For Google Antigravity
To register these tools with Google Antigravity, modify the file /home/{user_name}/.gemini/antigravity/mcp_config.json:
{
"mcpServers": {
"gas-fakes": {
"command": "gas-fakes",
"args": ["mcp", "--tools", "{your path}/sample.js"]
}
}
}
When you launch Google Antigravity and check the MCP servers, the custom tools should be visible.

Real-world Testing
Test using Gemini CLI
The following section demonstrates the workflow using the Gemini CLI.

1. Create a sample Spreadsheet
Prompt:
Create a new sample Google Spreadsheet using a tool create-sample-google-sheets. Show the URL of the created Google Spreadsheet. Run it without a sandbox.Result:
A new Google Spreadsheet is created successfully.

2. Create a new table
Prompt:
Create the data range of the created Spreadsheet as a new table. The table name is "sampleTable". Use the spreadsheet as whitelistWrite.
Result:
The table is created within the sheet.

Alternative command using gas-fakes CLI:
You can achieve the same result using the CLI directly by passing the TableApp library key:
gas-fakes -s 'const spreadsheetId = "{spreadsheetId}", tableName = "sampleTable", sheetName = "Sheet1", range = "A1:C3"; const table = TableApp.openById(spreadsheetId).getSheetByName(sheetName).getRange(range).create(tableName); console.log(`Created Table ID: ${table.getId()}`);' -l "TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt"
3. Get values from a table
Prompt:
Get values from the table. Use the spreadsheet as whitelistRead.
Result:
The values [["a1_sample1","b1_sample2","c1_sample3"],["a2_sample4","b2_sample5","c2_sample6"],["a3_sample7","b3_sample8","c3_sample9"]] are returned from the “sampleTable”.
Alternative command using gas-fakes CLI:
This command demonstrates loading both a remote library (by key) and a local library (by path):
gas-fakes -s 'const object = {spreadsheetId: "{spreadsheetId}", tableName: "sampleTable", sheetName: "Sheet1", range: "A1:C3"}; const table = TableMng.getTable({ ...object, TableApp }); console.log(JSON.stringify(table.getValues()));' -l "TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt" -l "TableMng@{your path}/tableManager.js"
4. Reverse a table
Prompt:
Reverse the table. Use the spreadsheet as whitelistWrite.
Result:
The table formatting is removed, leaving only the values.

Alternative command using gas-fakes CLI:
gas-fakes -s 'const object = {spreadsheetId: "{spreadsheetId}", tableName: "sampleTable"}; const table = TableMng.getTable({ ...object, TableApp }); const reverseMsg = table.reverse(); console.log(reverseMsg);' -l "TableApp@1G4RVvyLtwPjQl6x_p8j3X65-yYVU3w2dMXxHDuzCgorucjs8P3Clv5Qt" -l "TableMng@{your path}/tableManager.js"
Test using Google Antigravity
For this test, I configured Antigravity with the rule: Use the MCP server preferentially by understanding the prompt. This ensured that tasks were routed smoothly to the MCP tools.

The testing process for Creating a Spreadsheet, Creating a Table, Getting Values, and Reversing a Table yielded identical results to the Gemini CLI tests. This confirms that the MCP server functionality is consistent across different AI agent environments when powered by the updated gas-fakes library support.
Summary
- Overcoming Limitations: Previous versions of gas-fakes required monolithic scripts because they lacked dynamic library loading, which hindered code maintainability.
- Library Integration: The updated gas-fakes now supports native loading of Google Apps Script libraries (both remote keys and local files), enabling true modularity.
- Enhanced MCP Servers: Developers can now build sophisticated MCP servers that leverage existing GAS libraries (TableApp, etc.) without manually bundling code.
- Seamless Automation: The demonstrated scripts successfully bridge the gap between AI agents and Google Workspace, allowing for complex operations like table management in Sheets.
- Verified Compatibility: The solution was rigorously tested and proven to work identically on both the Gemini CLI and Google Antigravity platforms.
Modularizing AI Agents: Integrating Google Apps Script Libraries with Gemini CLI and Antigravity was originally published in Google Cloud – Community on Medium, where people are continuing the conversation by highlighting and responding to this story.
Source Credit: https://medium.com/google-cloud/modularizing-ai-agents-integrating-google-apps-script-libraries-with-gemini-cli-and-antigravity-7ba0416b6fef?source=rss—-e52cf94d98af—4
