Setup Script¶
scripts/setup.sh is the one-shot automation that takes you from a fresh clone to a working 8-service deployment.
What It Does¶
| Phase | Actions |
|---|---|
| Prerequisites check | Verifies clasp, node, npm, and curl are installed |
| clasp login | Opens browser for Google authentication (once) |
| Build | npm ci && npm run build when package-lock.json is present; compiles shared package and all 8 services |
| Project creation | Reuses an existing project with the expected title, or runs clasp create --type standalone for each missing service |
| Code push | Pushes Auth.gs, Code.gs, Response.gs, and service-specific .gs files to each project |
| Deployment guide | Prints Apps Script editor URLs and instructions for the 8 manual web app deployments |
| Token bootstrap | Collects deployment URLs, posts each setup key as JSON, appends successful token entries to .env |
| Config generator | Prints ready-to-paste opencode.jsonc JSON for all 8 MCP servers under mcp |
| Skill commands | Prints ln -sf commands to install the google-workspace usage skill and workspace-lite-installer operator skill |
Run ./scripts/setup.sh --dry-run to validate prerequisites, install dependencies, build, and preview project/config output without creating Apps Script projects, generating BootstrapSecret.gs, prompting for deployment URLs, or changing .env.
Service Configuration¶
Each service gets its own project with service-specific OAuth scopes:
| Service | Project title | OAuth scopes |
|---|---|---|
| Drive | Google Workspace Proxy - Drive | drive, script.external_request |
| Gmail | Google Workspace Proxy - Gmail | gmail.modify, gmail.settings.basic, script.send_mail, script.external_request |
| Calendar | Google Workspace Proxy - Calendar | calendar, script.external_request |
| Sheets | Google Workspace Proxy - Sheets | spreadsheets, script.external_request |
| Slides | Google Workspace Proxy - Slides | presentations, script.external_request |
| Docs | Google Workspace Proxy - Docs | documents, script.external_request |
| Tasks | Google Workspace Proxy - Tasks | tasks, script.external_request |
| Forms | Google Workspace Proxy - Forms | forms, spreadsheets, script.external_request |
All projects use runtimeVersion: V8, executeAs: USER_DEPLOYING, and access: ANYONE_ANONYMOUS.
Generated Output¶
.env file¶
When token bootstrap succeeds, the script appends export lines to .env at the project root. A complete bootstrap produces all 16 primary environment variables:
# Generated on Fri Jun 13 2026 14:30:00 CDT
export GOOGLE_WORKSPACE_DRIVE_PROXY_URL="https://script.google.com/macros/s/<drive-deployment-id>/exec"
export GOOGLE_WORKSPACE_DRIVE_PROXY_TOKEN="<drive-proxy-token>"
export GOOGLE_WORKSPACE_GMAIL_PROXY_URL="https://script.google.com/macros/s/<gmail-deployment-id>/exec"
export GOOGLE_WORKSPACE_GMAIL_PROXY_TOKEN="<gmail-proxy-token>"
export GOOGLE_WORKSPACE_CALENDAR_PROXY_URL="https://script.google.com/macros/s/<calendar-deployment-id>/exec"
export GOOGLE_WORKSPACE_CALENDAR_PROXY_TOKEN="<calendar-proxy-token>"
export GOOGLE_WORKSPACE_SHEETS_PROXY_URL="https://script.google.com/macros/s/<sheets-deployment-id>/exec"
export GOOGLE_WORKSPACE_SHEETS_PROXY_TOKEN="<sheets-proxy-token>"
export GOOGLE_WORKSPACE_SLIDES_PROXY_URL="https://script.google.com/macros/s/<slides-deployment-id>/exec"
export GOOGLE_WORKSPACE_SLIDES_PROXY_TOKEN="<slides-proxy-token>"
export GOOGLE_WORKSPACE_DOCS_PROXY_URL="https://script.google.com/macros/s/<docs-deployment-id>/exec"
export GOOGLE_WORKSPACE_DOCS_PROXY_TOKEN="<docs-proxy-token>"
export GOOGLE_WORKSPACE_TASKS_PROXY_URL="https://script.google.com/macros/s/<tasks-deployment-id>/exec"
export GOOGLE_WORKSPACE_TASKS_PROXY_TOKEN="<tasks-proxy-token>"
export GOOGLE_WORKSPACE_FORMS_PROXY_URL="https://script.google.com/macros/s/<forms-deployment-id>/exec"
export GOOGLE_WORKSPACE_FORMS_PROXY_TOKEN="<forms-proxy-token>"
If you skip all deployment URL prompts, or if every bootstrap attempt fails, .env is left unchanged. If a service token already exists in .env, setup skips that service instead of consuming bootstrap again.
OpenCode Config¶
The script prints entries for the top-level mcp block in opencode.jsonc:
"google-drive": {
"type": "local",
"command": ["npx", "tsx", "/path/to/packages/drive/src/index.ts"],
"environment": {
"GOOGLE_WORKSPACE_DRIVE_PROXY_URL": "{env:GOOGLE_WORKSPACE_DRIVE_PROXY_URL}",
"GOOGLE_WORKSPACE_DRIVE_PROXY_TOKEN": "{env:GOOGLE_WORKSPACE_DRIVE_PROXY_TOKEN}"
}
},
"google-gmail": { /* ... */ },
"google-calendar": { /* ... */ },
"google-sheets": { /* ... */ },
"google-slides": { /* ... */ },
"google-docs": { /* ... */ },
"google-tasks": { /* ... */ },
"google-forms": { /* ... */ }
On Windows Git Bash/MSYS/Cygwin, setup emits commands that call the local node_modules\\.bin\\tsx.cmd wrapper directly instead of ["npx", "tsx", ...]. This avoids direct-spawn MCP client failures when no shell is used to resolve npx.
Manual Deployment Step¶
The script intentionally pauses for Apps Script editor deployments. For each printed editor URL:
- Open the URL.
- Click Deploy → New deployment.
- Click Select type or the gear icon and choose Web app.
- Set Execute as to Me.
- Set Who has access to Anyone.
- Click Deploy and authorize scopes if prompted.
- Copy the Web app URL ending in
/exec. - Paste that URL back into the setup prompt.
Do not use clasp deploy as a replacement for the first GUI deployment. The CLI can refresh a known deployment later, but the install flow needs a web app deployment with visibly confirmed execute-as, access settings, and OAuth scopes.
If you are not ready to deploy all services, press Enter at the prompt for skipped services. The script leaves .env unchanged for skipped or failed bootstraps, and you can rerun ./scripts/setup.sh later.
If an agent is helping with setup after you complete the GUI deployments, it can recover the /exec URLs with clasp deployments. The correct row is the versioned deployment (@1, @2, etc.), not @HEAD; the URL is https://script.google.com/macros/s/<deployment-id>/exec.
Rerunning for Individual Services¶
The script is idempotent — it skips services that already have a valid .clasp.json with a non-placeholder scriptId, and it reuses existing Apps Script projects with the canonical service title before creating a new project. This keeps aborted setup retries from creating duplicate projects.
Scenario: Add a service you skipped¶
Press Enter for services already configured. Paste the new deployment URL when prompted for the missing service.
Scenario: Re-bootstrap or rotate a token¶
If you lost a token, rerun ./scripts/setup.sh, paste the service /exec URL, and follow the prompt to rotate the primary token with the local setup key. Rotation invalidates the previous primary token and writes the new one to .env.
You can also rotate manually:
- Read the setup key from the untracked
BootstrapSecret.gsfile or regenerate it withscripts/setup.sh. - Call the rotation endpoint:
TOKEN_RESPONSE="$(curl -sL -X POST -H 'Content-Type: application/json' -d '{"setupKey":"<bootstrap-setup-key>","rotate":true}' "https://script.google.com/macros/s/<deployment-id>/exec")"
TOKEN_RESPONSE="$TOKEN_RESPONSE" node -e 'const fs = require("fs"); const r = JSON.parse(process.env.TOKEN_RESPONSE); if (!r.success) throw new Error(r.error?.message || "Bootstrap failed"); fs.appendFileSync(".env", `export GOOGLE_WORKSPACE_<SERVICE>_PROXY_TOKEN=${JSON.stringify(r.data.token)}\n`)'
Update the matching proxy URL in .env if it changed, then re-source.
Scenario: Re-deploy after pushing code changes¶
clasp push updates the Apps Script project source, but a versioned web app deployment keeps serving its pinned version until you refresh it. After the user has created the initial GUI web app deployment, an agent or maintainer can refresh the same deployment URL from the CLI:
cd packages/<service>/apps-script
clasp push --force
VERSION_OUTPUT="$(clasp version "Refresh web app deployment $(date +%F)")"
VERSION="$(printf '%s\n' "$VERSION_OUTPUT" | sed -n 's/.*version \([0-9][0-9]*\).*/\1/p')"
clasp deployments
clasp redeploy "<existing-versioned-deployment-id>" -V "$VERSION" -d "Refresh web app deployment $(date +%F)"
If the code change adds or changes Google OAuth scopes, the user must open the Apps Script editor, review the new scope prompt, authorize it, and then refresh/redeploy. Use a new deployment URL only when intentionally changing web app access or replacing a broken deployment.
Troubleshooting¶
clasp login fails¶
| Symptom | Fix |
|---|---|
| Browser doesn’t open | Run clasp login --no-localhost for a manual auth flow with a code paste |
| “Insufficient permissions” | Ensure the Google account has Apps Script enabled (free for all Google accounts) |
| clasp not found | npm install -g @google/clasp |
Project creation fails¶
| Symptom | Fix |
|---|---|
Could not create project | Verify clasp login succeeded. Try manually: cd packages/drive/apps-script && clasp create --type standalone --title "Test" |
.clasp.json has YOUR_SCRIPT_ID | Delete the file and re-run ./scripts/setup.sh |
Deployment issues¶
| Symptom | Fix |
|---|---|
| “Sorry, unable to open file at this time” | Wait a few seconds after pushing; Apps Script needs time to process |
| Web app not responding | Verify deployment type is Web app, not API executable |
| 403 / 401 from proxy | Check that Execute as: Me is set and Access: Anyone (anonymous) is set |
| Google access denied HTML page | Redeploy from the Apps Script editor with Deploy → New deployment → Web app. Do not use clasp deploy for initial setup. |
| “Script function not found: doGet” | Push failed. Run cd packages/<service>/apps-script && clasp push --force |
Token bootstrap fails¶
| Symptom | Fix |
|---|---|
"FORBIDDEN": Bootstrap already completed | The token was already generated. Rerun setup and accept the rotation prompt if the token is missing from .env |
"UNAUTHORIZED": Invalid or missing bootstrap setup key | POST JSON with {"setupKey":"<bootstrap-setup-key>"} from the untracked BootstrapSecret.gs file |
"BAD_REQUEST": Missing action field | The request was parsed as a normal proxy call. For bootstrap, POST a JSON body containing only setupKey |
| Token not returned | The URL may already be bootstrapped. Check .env for existing tokens |
OpenCode doesn’t see the tools¶
| Symptom | Fix |
|---|---|
| Tools not listed | Verify .env is sourced, OpenCode was restarted, and opencode.jsonc has the top-level mcp block |
GOOGLE_WORKSPACE_* not found | Run source /path/to/.env before starting OpenCode, or add exports to .zshrc |
| Port / STDIO error | The MCP server process failed to start. Check logs with npx tsx packages/drive/src/index.ts directly. On Windows, verify generated OpenCode config uses node_modules\\.bin\\tsx.cmd. |
ERR_MODULE_NOT_FOUND in node_modules | Run npm run check:install; if it reports missing package files, run rm -rf node_modules && npm install from Git Bash. |