| name | workspace-lite-installer |
| version | 0.1.0 |
| description | workspace-lite installer/operator: Use when helping a user install, configure, push, deploy, redeploy, or troubleshoot the Workspace Lite Google Workspace MCP servers, Apps Script web apps, clasp, tokens, or OpenCode config. |
| metadata | {"requires":{"bins":["node","npm","clasp","curl"]}} |
Workspace Lite Installer
Use this skill to help a user install, configure, update, or troubleshoot the workspace-lite Google Workspace MCP servers.
Boundaries
- You can run local setup commands, install npm dependencies, build packages, run validation, inspect
.clasp.json, run clasp push, create Apps Script versions, and refresh existing deployments.
- The user must complete Google account browser auth, review OAuth scopes, and confirm web app deployment settings in the Apps Script GUI.
- Never print, commit, or paste
.env, .clasprc.json, .clasp.json, BootstrapSecret.gs, bearer tokens, setup keys, deployment URLs from private installs, or script IDs into public output.
- Ask before rotating tokens, deleting Apps Script properties, creating replacement deployments, or changing OpenCode config.
- Security posture changes are recommend-only: when a user wants to narrow token classes (lockdown profiles in
docs/project/lockdown-spec.md), print the exact steps — mint the class token in the Apps Script editor, set PROXY_AUTH_TOKEN_CLASSES, remove the primary from agent-facing env — and perform a specific mutation only when the user explicitly asks for that mutation. Remember the client falls back to the primary token: a class token next to the primary narrows nothing.
- Supported platforms: macOS, Linux, and Windows through Git Bash or MSYS2. Native PowerShell can run MCP servers after environment variables are persisted, but setup and deploy helper scripts expect bash.
Diagnose Before You Repair
When troubleshooting an existing install, run diagnostics before changing anything — do not guess at redeploys, token rotations, or .env edits:
node packages/cli/dist/index.js doctor
node packages/cli/dist/index.js doctor --live
node packages/cli/dist/index.js doctor --deployments
(Build first with npm run build if packages/cli/dist is missing.) Doctor names the failing service and failure kind — missing env var, unreachable URL, wrong-service wiring, rejected token, or stale deployment — and each failure comes with a remediation hint. For the common drift states, node packages/cli/dist/index.js repair --dry-run proposes guided fixes; without --dry-run it applies safe file writes after per-finding confirmation, prints manual commands for everything else, and never rotates a token without an interactive prompt. Follow the hints; only fall back to the manual procedures below when doctor/repair cannot localize the problem. Interpretation tables live in docs/operations/diagnostics.md.
Doctor output is redaction-safe (env var names, error codes, correlationIds, fingerprinted deployment IDs only) and may be shared with the user or pasted into issues verbatim. Everything else stays private: never paste token values, .env contents, full deployment URLs, or script IDs.
Windows & Shell Context Check
Before running setup or diagnosing MCP startup failures on Windows, verify the shell context:
echo "OS: ${OSTYPE:-unknown}"
echo "Shell: ${SHELL:-unknown}"
If the user is not in Git Bash or MSYS2, explain that setup and deploy helper scripts expect bash. Ask them to open Git Bash and run setup from there.
If OpenCode is launched from a Windows shell that does not inherit the workspace-lite environment, MCP servers can fail with -32000: Connection closed. Prefer launching OpenCode from the shell where .env is sourced, or persist variables with scripts/persist-env.ps1 and restart OpenCode.
For Git Bash login shells, check that ~/.bash_profile sources ~/.bashrc:
grep -q 'bashrc' ~/.bash_profile && echo "OK" || echo "MISSING: add ~/.bashrc sourcing to ~/.bash_profile"
If missing:
printf '\n[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"\n' >> ~/.bash_profile
Services
There are 8 packages and 8 Apps Script web apps. Canonical service order: drive, gmail, calendar, sheets, slides, docs, tasks, forms.
| Service | Package | MCP name | Env prefix |
|---|
| Drive | packages/drive | google-drive | GOOGLE_WORKSPACE_DRIVE |
| Gmail | packages/gmail | google-gmail | GOOGLE_WORKSPACE_GMAIL |
| Calendar | packages/calendar | google-calendar | GOOGLE_WORKSPACE_CALENDAR |
| Sheets | packages/sheets | google-sheets | GOOGLE_WORKSPACE_SHEETS |
| Slides | packages/slides | google-slides | GOOGLE_WORKSPACE_SLIDES |
| Docs | packages/docs | google-docs | GOOGLE_WORKSPACE_DOCS |
| Tasks | packages/tasks | google-tasks | GOOGLE_WORKSPACE_TASKS |
| Forms | packages/forms | google-forms | GOOGLE_WORKSPACE_FORMS |
Apps Script file structure (per service)
Each packages/<service>/apps-script/ contains:
| File | Purpose |
|---|
appsscript.json | Manifest: OAuth scopes, web app config, runtime version |
Auth.gs | Authentication: token classes, bootstrap, rate limiting |
BootstrapSecret.gs | Setup secret used during initial bootstrap (generated by the repo's scripts/setup.sh) |
Code.gs | Entry point: doGet/doPost handler, request routing |
Policy.gs | Authorization policy: checks token class against action required |
Response.gs | Response formatting helpers |
<Service>Service.gs | Service-specific action handlers (e.g. DriveService.gs) |
The shared template lives at shared/apps-script/Auth.gs — when changing DEFAULT_AUTH_TOKEN_CLASSES, update both the shared template AND all 8 service copies.
First-Time Install
The workspace-lite repo ships a scripts/setup.sh that handles initial setup. All scripts/ paths below are relative to the repo root.
- Confirm prerequisites (
node >= 20, npm, clasp, curl):
node --version
npm --version
clasp --version
curl --version
- If
clasp is missing, install it:
npm install -g @google/clasp
- Ask whether the user wants all 8 services or a subset. Setup supports install profiles:
--profile core (drive, gmail, calendar), --profile authoring (drive, docs, sheets, slides), --profile planning (calendar, tasks, docs), --profile forms (drive, sheets, forms), --profile full (all 8, same as no argument)
- or an explicit list:
--services drive,gmail
Only selected services get Apps Script projects, deployment prompts, .env entries, and config snippets. Partial installs are safe: setup is idempotent per service, so rerunning later with a bigger selection adds the missing services, and wslite doctor treats services with no env vars as "not installed" rather than broken.
- Run a dry run first when possible — it prints exactly which services will be touched:
bash ./scripts/setup.sh --dry-run [--profile <name> | --services <csv>]
- Run setup with the same selection:
bash ./scripts/setup.sh [--profile <name> | --services <csv>]
- If
clasp login opens a browser, tell the user to complete Google auth. You cannot approve this for them.
- When setup prints Apps Script editor URLs, tell the user to open each URL and create the initial web app deployment in the GUI:
- Deploy -> New deployment
- Select type -> Web app
- Execute as -> Me
- Who has access -> Anyone
- Review and authorize the requested OAuth scopes
- Copy the
/exec web app URL back into the setup prompt
- If the user completes the GUI deployments but does not paste URLs, retrieve them with
clasp deployments from each service directory. Use the versioned deployment row (@1, @2, etc.), not the @HEAD row, and construct https://script.google.com/macros/s/<deployment-id>/exec.
- After setup writes
.env, persist it (see "Persist Environment Variables" below) and restart OpenCode.
Setup is intentionally idempotent: it reuses local .clasp.json files, reuses existing Apps Script projects with the canonical service titles, and skips service tokens that are already present in .env. If bootstrap was consumed before .env was written, setup prompts before rotating the primary token with the local setup key.
Retrieve Web App URLs After GUI Deployment
Use this when the user has completed Deploy -> New deployment -> Web app in the Apps Script GUI and approved scopes, but the agent needs to recover the /exec URLs for bootstrap.
node --input-type=module -e 'import { execFileSync } from "node:child_process"; import path from "node:path"; const root = process.cwd(); const services = ["drive","gmail","calendar","sheets","slides","docs","tasks","forms"]; for (const s of services) { const cwd = path.join(root, "packages", s, "apps-script"); const out = execFileSync("clasp", ["deployments"], { cwd, encoding: "utf8" }); const v = out.split("\n").find((l) => /^-\s+\S+\s+@\d+/.test(l)); if (!v) throw new Error(s + ": no versioned deployment"); const id = v.match(/^-\s+(\S+)\s+@\d+/)?.[1]; console.log(s + ": https://script.google.com/macros/s/" + id + "/exec"); }'
To feed those URLs back through the setup script:
node --input-type=module -e 'import { execFileSync, spawnSync } from "node:child_process"; import path from "node:path"; const root = process.cwd(); const services = ["drive","gmail","calendar","sheets","slides","docs","tasks","forms"]; const urls = []; for (const s of services) { const cwd = path.join(root, "packages", s, "apps-script"); const out = execFileSync("clasp", ["deployments"], { cwd, encoding: "utf8" }); const v = out.split("\n").find((l) => /^-\s+\S+\s+@\d+/.test(l)); if (!v) throw new Error(s + ": no versioned deployment"); const id = v.match(/^-\s+(\S+)\s+@\d+/)?.[1]; urls.push("https://script.google.com/macros/s/" + id + "/exec"); } spawnSync("./scripts/setup.sh", { cwd: root, input: urls.join("\n") + "\n", encoding: "utf8", stdio: ["pipe", "inherit", "inherit"] });'
Rules:
- Do not use
@HEAD as the web app URL. Use the versioned deployment ID row.
- Keep service order as
drive, gmail, calendar, sheets, slides, docs, tasks, forms.
- If a service has no versioned deployment row, the user has not completed the GUI web app deployment for that service yet.
- Do not print the generated
.env or token values.
Agent-Assisted Deploy Updates
Use this when code has changed after the user already created and authorized the initial web app deployment.
Helper scripts (no mistakes)
The skill ships three hardened scripts. Use these instead of running raw clasp commands:
| Script | Purpose |
|---|
scripts/deploy-all.sh <repo-path> "<message>" | Push, version, and redeploy all 8 services to the .env deployment IDs |
scripts/deploy-single.sh <repo-path> <service> "<message>" | Push, version, and redeploy one service |
scripts/verify-deployments.sh <repo-path> | Check that every .env deployment URL matches clasp state |
Always run verify-deployments.sh after deploying to confirm no deployment ID mismatch occurred. When packages/cli is built, the script also runs wslite doctor (env presence only; never prints token values). For a deeper post-deploy check, run wslite doctor --live --deployments — it verifies the live proxies answer, tokens are accepted, and no .env URL points at a stale version.
Manual steps (fallback)
If the scripts can't be used, do this per service. Note: sed variant shown works on macOS, Linux, and Git Bash.
REPO=/path/to/workspace-lite
SVC=<service-name>
MSG="Deploy $(date +%Y-%m-%d): brief description"
source "$REPO/.env"
cd "$REPO/packages/$SVC/apps-script"
clasp push --force
V=$(clasp version "$MSG" 2>&1 | sed -n 's/Created version \([0-9][0-9]*\)/\1/p')
if [ -z "$V" ]; then echo "ERROR: version creation failed"; exit 1; fi
env_var="GOOGLE_WORKSPACE_$(echo $SVC | tr '[:lower:]' '[:upper:]')_PROXY_URL"
env_url="${!env_var}"
env_id=$(echo "$env_url" | sed -n 's/.*macros\/s\/\(AKfy[a-zA-Z0-9_-]*\).*/\1/p')
if [ -z "$env_id" ]; then echo "ERROR: could not extract deployment ID from $env_var"; exit 1; fi
clasp redeploy "$env_id" -V "$V" -d "$MSG"
Push vs. deploy: the two-step update cycle
Apps Script has two separate states:
clasp push uploads local files to the script project's working copy (@HEAD in clasp deployments).
clasp version plus clasp redeploy pins that source as an immutable numbered version and updates the existing /exec deployment ID.
The MCP PROXY_URL uses the versioned /exec URL, so pushed code is not visible to MCP traffic until you redeploy:
cd packages/<service>/apps-script
clasp push --force
V=$(clasp version "brief deploy message" 2>&1 | sed -n 's/Created version \([0-9][0-9]*\)/\1/p')
clasp redeploy "<deployment-id-from-.env>" -V "$V" -d "brief deploy message"
The /dev URL serves @HEAD and requires a browser session as the deploying user. It can be useful for manual debugging, but never set an MCP PROXY_URL to /dev because MCP server processes do not have browser auth.
Critical: Always redeploy to the .env deployment ID
Use clasp redeploy <deployment-id> for in-place refreshes. clasp deploy -i can create a new deployment ID instead of updating the existing one on some clasp/API paths. After redeploying, always verify with the helper script or manually:
source "$REPO/.env"
for svc in drive gmail calendar sheets slides docs tasks forms; do
cd "$REPO/packages/$svc/apps-script"
env_var="GOOGLE_WORKSPACE_$(echo $svc | tr '[:lower:]' '[:upper:]')_PROXY_URL"
expected_id=$(echo "${!env_var}" | sed -n 's/.*macros\/s\/\(AKfy[a-zA-Z0-9_-]*\).*/\1/p')
actual=$(clasp deployments 2>&1 | grep "$expected_id")
echo "$svc: $actual"
done
If a service shows the wrong version number, redeploy to the .env deployment ID. If .env has a deployment ID that doesn't appear in clasp deployments, something went wrong — run clasp deployments to inspect all deployments and redeploy to the correct ID.
Recovery patches
If you temporarily add an HTTP handler for recovery or debugging, the safe cycle is:
- Edit locally.
clasp push --force.
- Create a version and
clasp redeploy <deployment-id> -V <version> -d "temporary recovery".
- Hit the temporary endpoint.
- Revert the local patch.
- Push, version, and redeploy clean code to the same deployment ID.
Without the redeploy steps, /exec still serves the previous pinned version; without the cleanup redeploy, the temporary patch can remain live.
Token class upgrades
If write/destructive/share/send operations fail with ACTION_NOT_ALLOWED, the token classes need upgrading. The DEFAULT_AUTH_TOKEN_CLASSES constant in packages/<service>/apps-script/Auth.gs controls which action classes the primary token gets:
| Constant value | Allowed operations |
|---|
read,draft | Read-only (initial default) |
read,draft,write | Read + write (create/edit files, events, tasks, etc.) |
read,draft,write,destructive | Above + trash/delete |
read,draft,write,destructive,share | Above + sharing/permissions |
read,draft,write,destructive,share,send | All operations including email send |
Update ALL 8 service Auth.gs files + the shared template, then run scripts/deploy-all.sh.
Understanding clasp deployments output
- AKfycbEXAMPLEheadEXAMPLEheadEXAMPLEheadEXAMPLE @HEAD
- AKfycbEXAMPLEversion4aEXAMPLEversion4aEXAMPLEversion4aEXAMPLE1 @4 - Enable all token classes
- AKfycbEXAMPLEversion4bEXAMPLEversion4bEXAMPLEversion4bEXAMPLE2 @4 - Enable all token classes
@HEAD row is the live code at the script editor URL (always latest push). Never use this for MCP PROXY_URL.
- Versioned rows (
@1, @2, @3, @4) are pinned to immutable code snapshots. MCP servers use these.
- Stray deployments (leftover from failed
-i redeploys) are safe to leave but clutter the list. Remove with clasp undeploy <deployment-id>.
Initial GUI Step Is Still Required
clasp redeploy is useful for refreshing an existing deployment, but it is not a full replacement for the initial Apps Script GUI deployment flow. The user needs the GUI to verify:
- Deployment type is Web app.
- Execute-as is Me /
USER_DEPLOYING.
- Access is Anyone.
- OAuth scopes match the service and are acceptable to the user.
If a deployment returns a Google access-denied HTML page, ask the user to re-open the Apps Script editor and verify those settings.
Client Config (OpenCode, Claude Code/Desktop, Cursor)
The setup script prints an OpenCode mcp block scoped to the selected services, generated by scripts/client-config.mjs. Merge it into the active OpenCode config (e.g. ~/.config/opencode-general/opencode.jsonc) and restart OpenCode.
For other MCP clients, generate mcpServers-style config with the same tool:
node scripts/client-config.mjs --client claude-code --services drive,gmail
Those entries launch a bash wrapper that sources the repo .env and runs the built server (npm run build required) — no token values ever appear in client config files. Full insertion instructions per client are in docs/getting-started/client-config.md.
Persist Environment Variables
Prefer sourcing .env from the shell startup file instead of copying tokens. This keeps secrets in one ignored install file.
zsh (~/.zshrc):
grep -q 'workspace-lite/.env' ~/.zshrc || cat >> ~/.zshrc <<'EOF'
[ -f /path/to/workspace-lite/.env ] && source /path/to/workspace-lite/.env
EOF
bash (~/.bashrc or ~/.bash_profile):
grep -q 'workspace-lite/.env' ~/.bashrc || cat >> ~/.bashrc <<'EOF'
[ -f /path/to/workspace-lite/.env ] && . /path/to/workspace-lite/.env
EOF
fish (~/.config/fish/config.fish):
if test -f /path/to/workspace-lite/.env
for line in (cat /path/to/workspace-lite/.env | string match -r '^export')
eval (string replace 'export ' 'set -gx ' $line)
end
end
Replace /path/to/workspace-lite with the actual repo path. If older export GOOGLE_WORKSPACE_* lines exist in shell config, remove or comment them out.
For the current shell:
source /path/to/workspace-lite/.env
Then restart OpenCode so MCP processes inherit the new environment.
Windows PowerShell user environment:
powershell -ExecutionPolicy Bypass -File .\skills\workspace-lite-installer\scripts\persist-env.ps1 -EnvFile .\.env
This persists variables at the Windows User scope so native OpenCode, PowerShell, and cmd can inherit them after restart. The deployment helper scripts normalize CRLF when reading .env; still prefer leaving .env as LF to keep manual source .env behavior predictable.
On Windows, the generated OpenCode config should point directly at the local tsx.cmd wrapper, not ["npx", "tsx", ...]. The setup script handles this automatically in Git Bash/MSYS/Cygwin contexts. If diagnosing a hand-written config, prefer:
"command": ["C:\\path\\to\\workspace-lite\\node_modules\\.bin\\tsx.cmd", "C:\\path\\to\\workspace-lite\\packages\\drive\\src\\index.ts"]
This avoids direct-spawn resolution failures where OpenCode does not invoke a shell.
Repair Broken npm Installs
On Windows with some npm versions, a successful-looking incremental install can still leave missing .js files inside node_modules, causing MCP startup errors such as ERR_MODULE_NOT_FOUND for zod or ajv paths.
Run the integrity check:
npm run check:install
If it fails, do a full clean reinstall. Incremental reinstalls may not repair the missing files:
rm -rf node_modules
npm install
Installing Repo Skills into OpenCode
mkdir -p ~/.config/opencode/skills
ln -sfn "$(pwd)/skills/google-workspace" ~/.config/opencode/skills/google-workspace
ln -sfn "$(pwd)/skills/workspace-lite-installer" ~/.config/opencode/skills/workspace-lite-installer
On Windows, native symlinks require Developer Mode or an elevated/admin shell. If symlink creation fails, copy the two skill directories into OpenCode's skills directory instead.
Validation
Use the smallest relevant checks:
npm run build
npm run typecheck
For MCP schema startup issues, list tools through the MCP client or start one server directly after sourcing .env:
npx tsx packages/drive/src/index.ts
For web app health, use the /exec URL without a token:
curl -sL "https://script.google.com/macros/s/<deployment-id>/exec"
Expected response is a JSON health envelope with success:true.