diff --git a/bun.lock b/bun.lock index 6945c2e6..bba650de 100644 --- a/bun.lock +++ b/bun.lock @@ -558,6 +558,14 @@ "version": "1.0.0", "dependencies": { "@decocms/runtime": "^1.2.6", + "google-calendar": "workspace:*", + "google-docs": "workspace:*", + "google-drive": "workspace:*", + "google-forms": "workspace:*", + "google-gmail": "workspace:*", + "google-meet": "workspace:*", + "google-sheets": "workspace:*", + "google-slides": "workspace:*", "zod": "^4.0.0", }, "devDependencies": { @@ -4141,7 +4149,7 @@ "google-big-query/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "google-calendar/@decocms/runtime": ["@decocms/runtime@1.3.0", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@cloudflare/workers-types": "^4.20250617.0", "@decocms/bindings": "^1.0.7", "@modelcontextprotocol/sdk": "1.27.1", "hono": "^4.10.7", "jose": "^6.0.11", "zod": "^4.0.0" }, "peerDependencies": { "ai": ">=6.0.0" } }, "sha512-xRFTV9gqrdsUCAkJ3xoBOeDGXps6moZeZB/NNL+XZ7LNa4qzfmL3BGWG42n8xbFyEpprfEfOtOD+tVgWhkPDxQ=="], + "google-calendar/@decocms/runtime": ["@decocms/runtime@1.6.0", "", { "dependencies": { "@ai-sdk/provider": "^3.0.0", "@cloudflare/workers-types": "^4.20250617.0", "@decocms/bindings": "^1.0.7", "@modelcontextprotocol/sdk": "1.27.1", "hono": "^4.10.7", "jose": "^6.0.11", "zod": "^4.0.0" }, "peerDependencies": { "ai": ">=6.0.0" } }, "sha512-iVRp3yUvPd5x1nQ+WbsTOHA3KKBRMq6kGnJLoV1oyjIFm1uyxK69IihnYp1B49sQxlWAfPAJco3AZ8TuvF3T6A=="], "google-calendar/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], diff --git a/google-docs/package.json b/google-docs/package.json index c977d732..aeec4ded 100644 --- a/google-docs/package.json +++ b/google-docs/package.json @@ -4,6 +4,10 @@ "description": "Google Docs MCP Server - Create and edit documents", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-drive/package.json b/google-drive/package.json index 598059c8..1bd97805 100644 --- a/google-drive/package.json +++ b/google-drive/package.json @@ -4,6 +4,10 @@ "description": "Google Drive MCP Server - Manage files, folders and permissions", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-forms/package.json b/google-forms/package.json index ef93d5f6..ac005635 100644 --- a/google-forms/package.json +++ b/google-forms/package.json @@ -4,6 +4,10 @@ "description": "Google Forms MCP Server - Create forms and collect responses", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-gmail/package.json b/google-gmail/package.json index 052ff03b..067df77d 100644 --- a/google-gmail/package.json +++ b/google-gmail/package.json @@ -4,6 +4,10 @@ "description": "Google Gmail MCP Server - Read, send and manage emails", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bunx wrangler dev", "check": "tsc --noEmit", diff --git a/google-gmail/server/tools/index.ts b/google-gmail/server/tools/index.ts index 4009283f..2d9ff8d2 100644 --- a/google-gmail/server/tools/index.ts +++ b/google-gmail/server/tools/index.ts @@ -8,10 +8,16 @@ import { labelTools } from "./labels.ts"; import { draftTools } from "./drafts.ts"; import { triggers } from "../lib/trigger-store.ts"; -export const tools = [ +/** + * Core Gmail tools without the webhook/trigger machinery. Safe to import from + * other MCPs (e.g. google-workspace) that don't have the Workers KV binding + * required by `triggers.tools()`. + */ +export const basicTools = [ ...messageTools, ...threadTools, ...labelTools, ...draftTools, - ...triggers.tools(), ]; + +export const tools = [...basicTools, ...triggers.tools()]; diff --git a/google-meet/package.json b/google-meet/package.json index f810a2ca..c1ac4836 100644 --- a/google-meet/package.json +++ b/google-meet/package.json @@ -4,6 +4,10 @@ "description": "Google Meet MCP Server - Create and manage meetings", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-sheets/package.json b/google-sheets/package.json index 18db122e..3cc35c21 100644 --- a/google-sheets/package.json +++ b/google-sheets/package.json @@ -4,6 +4,10 @@ "description": "Google Sheets MCP Server - Read, write and manage spreadsheets", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-slides/package.json b/google-slides/package.json index b06cf5e1..15fe77dd 100644 --- a/google-slides/package.json +++ b/google-slides/package.json @@ -4,6 +4,10 @@ "description": "Google Slides MCP Server - Create and edit presentations", "private": true, "type": "module", + "exports": { + "./tools": "./server/tools/index.ts", + "./constants": "./server/constants.ts" + }, "scripts": { "dev": "bun run --hot server/main.ts", "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", diff --git a/google-workspace/README.md b/google-workspace/README.md index 5a0ef506..9e87c36c 100644 --- a/google-workspace/README.md +++ b/google-workspace/README.md @@ -1,118 +1,80 @@ # google-workspace -One OAuth login that fans out to Google's official MCP servers — Calendar, Chat, Drive, Gmail and People — exposed as a single deco MCP. After consenting once, the user gets ~33 tools prefixed `calendar_*`, `chat_*`, `drive_*`, `gmail_*`, `people_*`. +One OAuth login that exposes the Google productivity stack — **Calendar, Gmail, Drive, Docs, Sheets, Slides, Forms and Meet** — under a single connection. After consenting once, the user gets ~160 tools prefixed `calendar_*`, `gmail_*`, `drive_*`, `docs_*`, `sheets_*`, `slides_*`, `forms_*`, `meet_*`. -See [`TOOLS.md`](./TOOLS.md) for the full tool catalog (auto-generated). - -## Built-in prompts - -Two flavors, both exposed through the standard `prompts/list` and `prompts/get` calls. - -### Agent guides (no arguments) - -Long-form references the agent pulls on demand instead of stuffing every tool description into the system prompt: - -- `GOOGLE_WORKSPACE_AGENT_GUIDE` — entry point covering all 5 services, the tool naming convention, time/timezone handling, destructive-action rules, and pagination. -- `GOOGLE_WORKSPACE_CALENDAR_GUIDE` — Calendar tool selection, the `primary` calendar convention, scheduling workflows, and pitfalls around recurring events. -- `GOOGLE_WORKSPACE_GMAIL_GUIDE` — Gmail search syntax (`from:`, `is:unread`, `newer_than:7d`, …), thread vs. message ops, system-label IDs, and the **drafts-only** caveat. -- `GOOGLE_WORKSPACE_DRIVE_GUIDE` — Drive structured query syntax, MIME types for Docs/Sheets/Slides, content vs. metadata vs. permissions. -- `GOOGLE_WORKSPACE_CHAT_GUIDE` — Spaces vs. DMs, threading, send-message confirmation patterns. -- `GOOGLE_WORKSPACE_PEOPLE_GUIDE` — directory (Workspace-only) vs. personal contacts, looking up the user themselves. - -### User templates (with arguments) - -Slash-command-style entries the user picks from the prompt menu in their MCP client. Each one expands into a user message that drives the agent through a common day-to-day workflow: - -| Template | Arguments | Does | -|---|---|---| -| `morning_briefing` | — | Calendar + important unread email + recent chat activity, in three short sections. | -| `prep_for_meeting` | `lookahead?` | Pulls attendees, last emails with them, and shared docs for the next meeting. | -| `whats_on_calendar` | `when?` | Summarizes the schedule for any free-form window. | -| `find_meeting_time` | `attendees`, `duration?`, `when?` | Finds slots when everyone is free; asks before booking. | -| `block_focus_time` | `duration`, `when?`, `title?` | Reserves a private focus block; confirms before creating. | -| `inbox_triage` | `timeframe?` | Classifies unread email (Reply / FYI / Action / Noise) and surfaces what's urgent. | -| `draft_reply` | `thread_query`, `instruction` | Finds a thread and creates a draft reply (user must click Send). | -| `find_files` | `query` | Translates natural-language to Drive structured query and searches. | -| `summarize_doc` | `document` | Finds a doc by name and produces an exec summary + key points. | -| `catch_up_chat` | `space`, `timeframe?` | Summarizes a Chat space as Decisions / Action items / Open threads / FYI. | -| `find_person` | `name` | Looks up contact info across directory + personal contacts. | - -Source lives in [`server/prompts.ts`](./server/prompts.ts). +See [`TOOLS.md`](./TOOLS.md) for the catalog. ## How it works -Google's MCP endpoints (`calendarmcp.googleapis.com/mcp/v1`, etc.) don't accept Dynamic Client Registration, so they can't be added to mesh as a generic custom MCP today. This package wraps them: it holds the Google OAuth client ID/secret server-side and proxies JSON-RPC `tools/call` to the right backend with the user's Bearer token. +Rather than duplicating logic, this MCP composes our existing per-service Google MCPs: ``` -mesh client ──► google-workspace MCP ──► calendarmcp.googleapis.com/mcp/v1 - │ - ├──► chatmcp.googleapis.com/mcp/v1 - ├──► drivemcp.googleapis.com/mcp/v1 - ├──► gmailmcp.googleapis.com/mcp/v1 - └──► people.googleapis.com/mcp/v1 +google-workspace +├─ google-calendar/tools → calendar_* +├─ google-gmail/tools → gmail_* (basicTools — without webhook triggers) +├─ google-drive/tools → drive_* +├─ google-docs/tools → docs_* +├─ google-sheets/tools → sheets_* +├─ google-slides/tools → slides_* +├─ google-forms/tools → forms_* +└─ google-meet/tools → meet_* ``` -The OAuth flow is the standard `createGoogleOAuth` from `@decocms/mcps-shared` — PKCE with refresh-token rotation. The full union of scopes is sent at consent time, so a single approval covers every service. +`server/lib/prefix-tool.ts` clones each tool factory's output with a service-prefixed id, so collisions across services (e.g. multiple `list_*` tools) are resolved without touching the upstream packages. `createGoogleOAuth({ scopes })` from `@decocms/mcps-shared/google-oauth` runs the standard PKCE flow with the union of scopes from all eight services. -## Tool definitions are committed snapshots +The composition is type-safe: each child MCP's tool factory is invoked with the workspace's `Env`, which is structurally compatible because every Google MCP reads the access token from the same `MESH_REQUEST_CONTEXT.authorization` slot. -Tool names, descriptions, JSON schemas and PRM scopes for each backend live in `server/tools/generated/.json`. These are committed to the repo for reproducible builds. +## What's intentionally NOT here -> **Important:** Anytime Google updates one of their MCP servers (new tools, changed schemas, additional scopes), you need to refresh the snapshots manually: -> -> ```sh -> bun run generate-tools -> ``` -> -> Commit the diff in `server/tools/generated/*.json`, the regenerated `TOOLS.md`, and re-deploy. The script needs no Google credentials — `tools/list` and the RFC 9728 PRM endpoint are both public. +- **Chat / People** — Google ships official MCP servers for these (`chatmcp.googleapis.com`, `people.googleapis.com`), but their scopes need extra OAuth-consent-screen verification we haven't completed. They'll come back in a separate `google-workspace-official` MCP that wraps the upstream Google MCPs once verification lands. +- **Send mail** — there is no `gmail_send_message` tool here. Gmail can only create drafts; the user clicks Send themselves. This is the same constraint as the standalone `google-gmail` MCP. -The generator also writes `TOOLS.md` so you can preview the catalog from GitHub or the registry without booting the server. +## Built-in prompts -### Why snapshot instead of fetching at boot? +Two flavors, both via the standard `prompts/list` and `prompts/get`. -- **Reproducibility:** the bundle is deterministic; an upstream change can't silently flip behavior in production. -- **Cold-start cost:** no extra round-trips to Google before the worker can serve requests. -- **Offline safety:** if Google is down at boot, the MCP still starts and returns cached tool definitions; only `tools/call` fails through to the upstream. +### Agent guides (no arguments) -The trade-off is the manual refresh step above. +Long-form references the agent pulls on demand instead of stuffing every tool description into the system prompt: -## Adding another Google service +- `GOOGLE_WORKSPACE_AGENT_GUIDE` — entry point covering all 8 services, naming, time/timezone rules, destructive actions, pagination. +- `GOOGLE_WORKSPACE_CALENDAR_GUIDE`, `..._GMAIL_GUIDE`, `..._DRIVE_GUIDE`, `..._DOCS_GUIDE`, `..._SHEETS_GUIDE`, `..._SLIDES_GUIDE`, `..._FORMS_GUIDE`, `..._MEET_GUIDE` — per-service cheat sheets, pitfalls, and (where applicable) query syntax. -When Google ships a new official MCP (e.g. `tasksmcp.googleapis.com`): +### User templates (with arguments) -1. Add the entry to `BACKEND_MCPS` in `server/constants.ts`. -2. Update the `GoogleService` union type in the same file. -3. Add the corresponding `import` for `./tools/generated/.json` and an entry in `TOOL_SNAPSHOTS`. -4. Run `bun run generate-tools` — the new service's snapshot will be created and added to `TOOLS.md`. -5. `bun run check && bun run build` to verify, then commit and PR. +Slash-command-style entries the user picks from the prompt menu in their MCP client: -Existing users get a re-consent prompt the next time they authenticate, since the union of scopes changes. +| Template | Arguments | Does | +|---|---|---| +| `morning_briefing` | — | Calendar + important unread email + recent Drive activity | +| `prep_for_meeting` | `lookahead?` | Attendees, last emails with them, shared docs | +| `whats_on_calendar` | `when?` | Schedule for any free-form window | +| `find_meeting_time` | `attendees`, `duration?`, `when?` | Multi-attendee free/busy; confirms before booking | +| `block_focus_time` | `duration`, `when?`, `title?` | Reserves a private block; confirms before creating | +| `inbox_triage` | `timeframe?` | Classifies unread as Reply/FYI/Action/Noise | +| `draft_reply` | `thread_query`, `instruction` | Finds thread, creates draft (user clicks Send) | +| `find_files` | `query` | NL → Drive structured query | +| `summarize_doc` | `document` | Finds doc by name, exports content, summarizes | +| `new_deck_from_outline` | `title`, `outline` | Creates Slides deck from a bullet outline | +| `create_form` | `title`, `questions` | Creates a Form from a question list | +| `create_meet_for_event` | `event_query` | Creates a Meet space and attaches it to a calendar event | + +Source lives in [`server/prompts.ts`](./server/prompts.ts). -## Local development +## Local dev ```sh bun install -bun run generate-tools # First time, or after Google updates -bun run check # tsc --noEmit -bun run dev # Hot-reload server on PORT (default 8001) +bun run check +bun run dev # PORT defaults to 8001 ``` -To exercise the OAuth flow locally you need: - -1. A Web OAuth client created in [Google Cloud Console](https://console.cloud.google.com/apis/credentials). -2. The redirect URI configured to match the mesh callback used in dev (e.g. `http://localhost:4000/api/auth/callback/...`). -3. `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` exported in your environment. - -Once running, the MCP exposes: - -- `POST /mcp` — JSON-RPC entry. `tools/list` works without auth (handy for previewing what the user will get); `tools/call` requires the Bearer token from the OAuth flow. -- `GET /.well-known/oauth-protected-resource` — RFC 9728 metadata pointing at `accounts.google.com` and the union of scopes. +For the full OAuth flow you need a Google Cloud OAuth client (Web type) with all the scopes listed in `server/constants.ts` declared on its consent screen, plus the redirect URI of your dev or prod worker. ## Files - `server/main.ts` — `withRuntime` wiring with `createGoogleOAuth` and the aggregated tool list. -- `server/constants.ts` — backend URLs, snapshot imports, scope union. -- `server/lib/mcp-proxy.ts` — JSON-RPC fetcher that injects the Bearer token and surfaces 401/403 with re-auth hints. -- `server/lib/json-schema-to-zod.ts` — small converter that turns the JSON Schema returned by Google into Zod for `createPrivateTool`. -- `server/lib/wrap-tool.ts` — turns a snapshot entry into a deco tool factory. -- `server/scripts/generate-tools.ts` — refreshes snapshots and `TOOLS.md`. +- `server/constants.ts` — union of OAuth scopes across the 8 services. +- `server/tools/index.ts` — imports and prefixes each child MCP's tools. +- `server/lib/prefix-tool.ts` — small helper that clones a tool with a prefixed id. +- `server/prompts.ts` — agent guides + user templates. diff --git a/google-workspace/TOOLS.md b/google-workspace/TOOLS.md index 62e6d246..fe05b3f4 100644 --- a/google-workspace/TOOLS.md +++ b/google-workspace/TOOLS.md @@ -1,106 +1,20 @@ # Google Workspace — Tool Catalog -_Auto-generated by `bun run generate-tools`. Do not edit by hand._ +162 tools across 8 services, gated behind a single OAuth login. -33 tools across 5 services, gated behind 26 OAuth scopes. +Tool ids are prefixed with the service name (e.g. `calendar_list_events`, `gmail_search_threads`). The full per-tool metadata lives in each child MCP's source — this catalog is just an at-a-glance count. -Tool names below are listed without the service prefix. The MCP exposes them as `_` (e.g. `calendar_list_events`). +| Service | Prefix | Tool count | Source | +|---|---|---|---| +| Calendar | `calendar_*` | 20 | [`google-calendar`](../google-calendar) | +| Gmail | `gmail_*` | 26 | [`google-gmail`](../google-gmail) (basic tools, no webhook triggers) | +| Drive | `drive_*` | 15 | [`google-drive`](../google-drive) | +| Docs | `docs_*` | 13 | [`google-docs`](../google-docs) | +| Sheets | `sheets_*` | 55 | [`google-sheets`](../google-sheets) | +| Slides | `slides_*` | 12 | [`google-slides`](../google-slides) | +| Forms | `forms_*` | 9 | [`google-forms`](../google-forms) | +| Meet | `meet_*` | 12 | [`google-meet`](../google-meet) | -## calendar +To browse the actual tool list at runtime, hit `POST /mcp` on the deployed worker with a `tools/list` JSON-RPC request (no authentication required for the listing — only `tools/call` needs the bearer token). -**Endpoint:** `https://calendarmcp.googleapis.com/mcp/v1` - -**Scopes:** -- `https://www.googleapis.com/auth/calendar` -- `https://www.googleapis.com/auth/calendar.app.created` -- `https://www.googleapis.com/auth/calendar.events` -- `https://www.googleapis.com/auth/calendar.events.readonly` -- `https://www.googleapis.com/auth/calendar.events.freebusy` -- `https://www.googleapis.com/auth/calendar.events.owned` -- `https://www.googleapis.com/auth/calendar.events.owned.readonly` -- `https://www.googleapis.com/auth/calendar.events.public.readonly` -- `https://www.googleapis.com/auth/calendar.readonly` - -**Tools (8):** -- `list_events` — Lists calendar events in a given calendar satisfying the given conditions. -- `get_event` — Returns a single event from a given calendar. -- `list_calendars` — Returns the calendars on the user's calendar list. -- `suggest_time` — Suggests time periods across one or more calendars. To access the primary calendar, add 'primary' in the attendee_emails field. -- `create_event` — Creates a calendar event. -- `update_event` — Updates a calendar event. -- `delete_event` — Deletes a calendar event. -- `respond_to_event` — Responds to an event. - -## chat - -**Endpoint:** `https://chatmcp.googleapis.com/mcp/v1` - -**Scopes:** -- `https://www.googleapis.com/auth/chat.spaces` -- `https://www.googleapis.com/auth/chat.spaces.readonly` -- `https://www.googleapis.com/auth/chat.memberships` -- `https://www.googleapis.com/auth/chat.memberships.readonly` -- `https://www.googleapis.com/auth/chat.messages` -- `https://www.googleapis.com/auth/chat.messages.readonly` - -**Tools (4):** -- `list_messages` — Retrieves messages from a specified Google Chat conversation (Space, direct message (DM) or group DM). Allows filtering by thread, time range, and number of messages. Additionally, the next page of messages can be retrieved to allow for more context. Private messages (messages only visible to a single user) are filtered out. -- `search_conversations` — Searches for Google Chat conversations by display name. -- `search_messages` — Searches for Google Chat messages using keywords and filters. Works across all spaces the user has access to, or can be scoped to a specific conversation. -- `send_message` — Sends a Google Chat message to a conversation. - -## drive - -**Endpoint:** `https://drivemcp.googleapis.com/mcp/v1` - -**Scopes:** -- `https://www.googleapis.com/auth/drive` -- `https://www.googleapis.com/auth/drive.readonly` -- `https://www.googleapis.com/auth/drive.file` - -**Tools (8):** -- `copy_file` — Call this tool to copy an existing File in Google Drive. -- `create_file` — Call this tool to create or upload a File to Google Drive. -- `download_file_content` — Call this tool to download the content of a Drive file as a base64 encoded string. -- `get_file_metadata` — Call this tool to find general metadata about a user's Drive file. -- `get_file_permissions` — Call this tool to list the permissions of a Drive File. -- `list_recent_files` — Call this tool to find recent files for a user specified a sort order. Default sort order is `recency`. -- `read_file_content` — Call this tool to fetch a natural language representation of a Drive file. -- `search_files` — Search for Drive files using a structured query (synatax: `query_term operator values`). - -## gmail - -**Endpoint:** `https://gmailmcp.googleapis.com/mcp/v1` - -**Scopes:** -- `https://mail.google.com/` -- `https://www.googleapis.com/auth/gmail.modify` -- `https://www.googleapis.com/auth/gmail.compose` -- `https://www.googleapis.com/auth/gmail.readonly` -- `https://www.googleapis.com/auth/gmail.metadata` - -**Tools (10):** -- `create_draft` — Creates a new draft email in the authenticated user's Gmail account. -- `list_drafts` — Lists draft emails from the authenticated user's Gmail account. -- `get_thread` — Retrieves a specific email thread from the authenticated user's Gmail account, including a list of its messages. -- `search_threads` — Lists email threads from the authenticated user's Gmail account. -- `label_thread` — Adds labels to an entire thread in the authenticated user's Gmail account. This operation affects all messages currently in the thread and any future messages added to it. -- `unlabel_thread` — Removes labels from an entire thread in the authenticated user's Gmail account. If unsure of the thread ID, use the `search_threads` tool first. If unsure of a user label's ID, use the `list_labels` tool first. -- `list_labels` — Lists all user-defined labels available in the authenticated user's Gmail account. Use this tool to discover the `id` of a user label before calling `label_thread`, `unlabel_thread`, `label_message`, or `unlabel_message`. System labels are not returned by this tool but can be used with their well-known IDs: 'INBOX', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'CHAT', 'DRAFT', 'SENT'. -- `label_message` — Adds one or more labels to a specific message in the authenticated user's Gmail account. -- `unlabel_message` — Removes one or more labels from a specific message in the authenticated user's Gmail account. To find the message ID, use tools like `search_threads` or `get_thread`. If unsure of a user label's ID, use the `list_labels` tool first to discover available labels and their IDs. -- `create_label` — Creates a new label in the authenticated user's Gmail account. - -## people - -**Endpoint:** `https://people.googleapis.com/mcp/v1` - -**Scopes:** -- `https://www.googleapis.com/auth/directory.readonly` -- `https://www.googleapis.com/auth/userinfo.profile` -- `https://www.googleapis.com/auth/contacts.readonly` - -**Tools (3):** -- `search_directory_people` — Search for people within your organization's Google Workspace directory. This feature is exclusively for Google Workspace accounts (used by businesses, schools, and other organizations) and is not available for personal Google accounts. -- `search_contacts` — Search user's contacts. -- `get_user_profile` — Get profile info about yourself (name and email). +For agent-facing usage notes, retrieve the prompt named `GOOGLE_WORKSPACE_AGENT_GUIDE` (entry point) or any of the per-service guides via `prompts/get`. diff --git a/google-workspace/app.json b/google-workspace/app.json index 8be823c9..3c87e417 100644 --- a/google-workspace/app.json +++ b/google-workspace/app.json @@ -6,7 +6,7 @@ "type": "HTTP", "url": "https://sites-google-workspace.decocache.com/mcp" }, - "description": "One login for the entire Google Workspace. Calendar, Chat, Drive, Gmail and People — all powered by Google's official MCP servers.", + "description": "One login for the entire Google Workspace. Calendar, Gmail, Drive, Docs, Sheets, Slides, Forms and Meet under a single connection.", "icon": "https://assets.decocache.com/mcp/b5fffe71-647a-461c-aa39-3da07b86cc96/Google-Meets.svg", "unlisted": false, "metadata": { @@ -18,11 +18,14 @@ "calendar", "gmail", "drive", - "chat", - "people", + "docs", + "sheets", + "slides", + "forms", + "meet", "productivity" ], - "short_description": "Single OAuth login fanning out to Google's official Calendar, Chat, Drive, Gmail and People MCP servers.", - "mesh_description": "The Google Workspace MCP unifies Google's official MCP servers (Calendar, Chat, Drive, Gmail, People) behind a single OAuth login. Authenticate once with Google and gain access to ~35 tools spanning event management, email, file storage, team chat, and contact directory. The MCP proxies JSON-RPC calls to the upstream Google MCP endpoints, so tool definitions stay in sync with Google's official catalog. Adding a new Google service in the future only requires registering its endpoint and scopes — no UI changes, no extra logins." + "short_description": "Single OAuth login covering Calendar, Gmail, Drive, Docs, Sheets, Slides, Forms and Meet.", + "mesh_description": "The Google Workspace MCP gives an agent access to the full Google productivity suite behind a single OAuth login: Calendar (events, scheduling, free/busy), Gmail (read, label, draft), Drive (files, folders, permissions), Docs (create/edit documents), Sheets (read/write spreadsheets), Slides (create/edit presentations), Forms (create forms, read responses) and Meet (create/manage meeting spaces). Tools are namespaced per service (e.g. `calendar_list_events`, `gmail_search_threads`, `drive_list_files`, `docs_get_document`). Built by composing our existing per-service Google MCPs so the underlying integrations are battle-tested in production." } } diff --git a/google-workspace/package.json b/google-workspace/package.json index c1c1cea4..8eeb7488 100644 --- a/google-workspace/package.json +++ b/google-workspace/package.json @@ -1,7 +1,7 @@ { "name": "google-workspace", "version": "1.0.0", - "description": "Google Workspace MCP — one OAuth login fanning out to Google's official Calendar, Chat, Drive, Gmail and People MCP servers", + "description": "Google Workspace MCP — one OAuth login covering Calendar, Gmail, Drive, Docs, Sheets, Slides, Forms and Meet", "private": true, "type": "module", "scripts": { @@ -9,8 +9,7 @@ "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", "build": "bun run build:server", "publish": "cat app.json | deco registry publish -w /shared/deco -y", - "check": "tsc --noEmit", - "generate-tools": "bun run server/scripts/generate-tools.ts" + "check": "tsc --noEmit" }, "exports": { "./tools": "./server/tools/index.ts", @@ -18,6 +17,14 @@ }, "dependencies": { "@decocms/runtime": "^1.2.6", + "google-calendar": "workspace:*", + "google-docs": "workspace:*", + "google-drive": "workspace:*", + "google-forms": "workspace:*", + "google-gmail": "workspace:*", + "google-meet": "workspace:*", + "google-sheets": "workspace:*", + "google-slides": "workspace:*", "zod": "^4.0.0" }, "devDependencies": { diff --git a/google-workspace/server/constants.ts b/google-workspace/server/constants.ts index 3dcf9586..b542609c 100644 --- a/google-workspace/server/constants.ts +++ b/google-workspace/server/constants.ts @@ -1,55 +1,54 @@ /** - * Google Workspace MCP — backend endpoints and OAuth scopes. + * Google Workspace MCP — bundled OAuth scopes. * - * To add a new Google service: - * 1. Add an entry to BACKEND_MCPS below - * 2. Run `bun run generate-tools` to fetch its tools/list and PRM scopes - * 3. The generator will rewrite server/tools/generated/.json - * and the runtime will pick it up automatically + * The Workspace MCP composes tool factories from our existing Google REST-based + * MCPs (google-calendar, google-gmail, google-drive, google-docs, google-sheets, + * google-slides, google-forms, google-meet). Each child MCP advertises its own + * scope set; this file is the **union** of those, deduped, sent to Google's + * authorization endpoint as a single consent screen. + * + * Keep narrow: only the broadest scope per service is needed because Google's + * scope hierarchy means `calendar` covers `calendar.events`/`calendar.readonly`, + * `drive` covers `drive.file`/`drive.readonly`, etc. Sub-scopes are listed only + * when they aren't implied by a broader scope already on the list. */ -export type GoogleService = "calendar" | "chat" | "drive" | "gmail" | "people"; - -export const BACKEND_MCPS: Record = { - calendar: "https://calendarmcp.googleapis.com/mcp/v1", - chat: "https://chatmcp.googleapis.com/mcp/v1", - drive: "https://drivemcp.googleapis.com/mcp/v1", - gmail: "https://gmailmcp.googleapis.com/mcp/v1", - people: "https://people.googleapis.com/mcp/v1", -}; - -import calendarSnap from "./tools/generated/calendar.json" with { type: "json" }; -import chatSnap from "./tools/generated/chat.json" with { type: "json" }; -import driveSnap from "./tools/generated/drive.json" with { type: "json" }; -import gmailSnap from "./tools/generated/gmail.json" with { type: "json" }; -import peopleSnap from "./tools/generated/people.json" with { type: "json" }; - -export const TOOL_SNAPSHOTS: Record = { - calendar: calendarSnap as BackendSnapshot, - chat: chatSnap as BackendSnapshot, - drive: driveSnap as BackendSnapshot, - gmail: gmailSnap as BackendSnapshot, - people: peopleSnap as BackendSnapshot, -}; - -export interface BackendToolDefinition { - name: string; - description?: string; - inputSchema?: Record; - outputSchema?: Record; - annotations?: Record; -} - -export interface BackendSnapshot { - service: GoogleService; - scopes: string[]; - tools: BackendToolDefinition[]; -} +export const GOOGLE_WORKSPACE_SCOPES: string[] = [ + // Calendar + "https://www.googleapis.com/auth/calendar", + // Gmail + "https://www.googleapis.com/auth/gmail.readonly", + "https://www.googleapis.com/auth/gmail.send", + "https://www.googleapis.com/auth/gmail.modify", + "https://www.googleapis.com/auth/gmail.labels", + // Drive (full read/write — also satisfies the .file scope used by Docs/Sheets/Slides/Forms) + "https://www.googleapis.com/auth/drive", + // Docs + "https://www.googleapis.com/auth/documents", + // Sheets + "https://www.googleapis.com/auth/spreadsheets", + // Slides + "https://www.googleapis.com/auth/presentations", + // Forms + "https://www.googleapis.com/auth/forms.body", + "https://www.googleapis.com/auth/forms.responses.readonly", + // Meet + "https://www.googleapis.com/auth/meetings.space.created", + "https://www.googleapis.com/auth/meetings.space.readonly", +]; /** - * Union of every scope advertised by every backend's PRM. - * Sent to Google's authorization endpoint at consent time. + * Tool prefix → human label, used to namespace tool ids and produce TOOLS.md. */ -export const GOOGLE_WORKSPACE_SCOPES: string[] = Array.from( - new Set(Object.values(TOOL_SNAPSHOTS).flatMap((snap) => snap.scopes)), -).sort(); +export const SERVICE_PREFIXES = { + calendar: "Calendar", + gmail: "Gmail", + drive: "Drive", + docs: "Docs", + sheets: "Sheets", + slides: "Slides", + forms: "Forms", + meet: "Meet", +} as const; + +export type ServicePrefix = keyof typeof SERVICE_PREFIXES; diff --git a/google-workspace/server/lib/json-schema-to-zod.ts b/google-workspace/server/lib/json-schema-to-zod.ts deleted file mode 100644 index 4c3f5f0f..00000000 --- a/google-workspace/server/lib/json-schema-to-zod.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Minimal JSON Schema → Zod converter, scoped to the shapes Google MCP servers - * return in tools/list. Anything we don't recognize falls back to z.unknown(), - * letting the request through for the upstream backend to validate. - */ - -import { z } from "zod"; - -type JsonSchema = { - type?: string | string[]; - description?: string; - properties?: Record; - required?: string[]; - items?: JsonSchema; - enum?: unknown[]; - oneOf?: JsonSchema[]; - anyOf?: JsonSchema[]; - default?: unknown; -}; - -export function jsonSchemaToZod(schema: unknown): z.ZodTypeAny { - if (!schema || typeof schema !== "object") return z.unknown(); - const s = schema as JsonSchema; - - // Tagged unions / polymorphism — pass through; backend validates. - if (Array.isArray(s.oneOf) || Array.isArray(s.anyOf)) { - return z.unknown().describe(s.description ?? ""); - } - - // Some Google schemas use type as an array, e.g. ["string", "null"]. Pick the - // first non-null type and rely on the backend for nullability. - const type = Array.isArray(s.type) - ? (s.type.find((t) => t !== "null") ?? "unknown") - : s.type; - - switch (type) { - case "object": - return objectSchema(s); - case "string": - return stringSchema(s); - case "integer": - case "number": - return numberSchema(s); - case "boolean": - return withDescription(z.boolean(), s.description); - case "array": - return withDescription( - z.array(jsonSchemaToZod(s.items ?? {})), - s.description, - ); - default: - return withDescription(z.unknown(), s.description); - } -} - -function objectSchema(s: JsonSchema): z.ZodTypeAny { - const props = s.properties ?? {}; - const required = new Set(s.required ?? []); - const shape: Record = {}; - for (const [key, propSchema] of Object.entries(props)) { - const child = jsonSchemaToZod(propSchema); - shape[key] = required.has(key) ? child : child.optional(); - } - // passthrough() lets the backend accept fields we may not know about. - return withDescription(z.object(shape).passthrough(), s.description); -} - -function stringSchema(s: JsonSchema): z.ZodTypeAny { - if (Array.isArray(s.enum) && s.enum.length > 0) { - const values = s.enum.filter((v): v is string => typeof v === "string"); - if (values.length === s.enum.length && values.length > 0) { - return withDescription( - z.enum(values as [string, ...string[]]), - s.description, - ); - } - } - return withDescription(z.string(), s.description); -} - -function numberSchema(s: JsonSchema): z.ZodTypeAny { - return withDescription(z.number(), s.description); -} - -function withDescription( - schema: T, - description: string | undefined, -): z.ZodTypeAny { - return description ? schema.describe(description) : schema; -} diff --git a/google-workspace/server/lib/mcp-proxy.ts b/google-workspace/server/lib/mcp-proxy.ts deleted file mode 100644 index 36f843bf..00000000 --- a/google-workspace/server/lib/mcp-proxy.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * JSON-RPC proxy for Google's official MCP servers. - * - * Each Google MCP endpoint (calendarmcp.googleapis.com, gmailmcp..., etc.) speaks - * JSON-RPC 2.0 over HTTP and authenticates via Bearer tokens. We forward the user's - * access token and pass through the result/error. - */ - -interface JsonRpcSuccess { - jsonrpc: "2.0"; - id: number | string; - result: unknown; -} - -interface JsonRpcError { - jsonrpc: "2.0"; - id: number | string; - error: { code: number; message: string; data?: unknown }; -} - -type JsonRpcResponse = JsonRpcSuccess | JsonRpcError; - -export async function proxyMcpCall( - backendUrl: string, - toolName: string, - args: unknown, - accessToken: string, -): Promise { - const body = { - jsonrpc: "2.0" as const, - id: crypto.randomUUID(), - method: "tools/call", - params: { name: toolName, arguments: args ?? {} }, - }; - - const res = await fetch(backendUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json, text/event-stream", - Authorization: `Bearer ${accessToken}`, - }, - body: JSON.stringify(body), - }); - - if (res.status === 401) { - throw new Error( - `Google ${backendUrl} returned 401 Unauthorized. The access token has been revoked or has expired — please re-authenticate.`, - ); - } - - if (res.status === 403) { - const text = await res.text(); - throw new Error( - `Google ${backendUrl} returned 403 Forbidden. The token is missing the required scope. Re-authenticate to grant the new permission. Body: ${text}`, - ); - } - - if (!res.ok) { - const text = await res.text(); - throw new Error( - `Google MCP backend error ${res.status} on ${toolName}: ${text}`, - ); - } - - const json = (await res.json()) as JsonRpcResponse; - - if ("error" in json) { - throw new Error( - `Google MCP JSON-RPC error on ${toolName}: ${json.error.message} (code ${json.error.code})`, - ); - } - - return json.result; -} diff --git a/google-workspace/server/lib/prefix-tool.ts b/google-workspace/server/lib/prefix-tool.ts new file mode 100644 index 00000000..4bda3fa6 --- /dev/null +++ b/google-workspace/server/lib/prefix-tool.ts @@ -0,0 +1,32 @@ +/** + * Wrap a tool factory imported from another MCP package so the produced tool's + * id is namespaced with a service prefix. + * + * Each child MCP's tools array is `Array<(env) => Tool>`. We can't change the + * factory signature, but we can clone the resulting tool with a new id. + * + * The Env types between MCPs differ structurally (each has its own deco.gen.ts) + * but the only field tools actually read is `MESH_REQUEST_CONTEXT.authorization`, + * which is identical everywhere. The cast below is therefore safe in practice. + */ + +// biome-ignore lint/suspicious/noExplicitAny: child Env types differ but are +// structurally compatible at runtime — see comment above. +type AnyToolFactory = (env: any) => { id: string; [k: string]: unknown }; + +export function prefixToolFactory( + factory: AnyToolFactory, + prefix: string, +): AnyToolFactory { + return (env) => { + const tool = factory(env); + return { ...tool, id: `${prefix}_${tool.id}` }; + }; +} + +export function prefixToolFactories( + factories: ReadonlyArray, + prefix: string, +): AnyToolFactory[] { + return factories.map((f) => prefixToolFactory(f, prefix)); +} diff --git a/google-workspace/server/lib/wrap-tool.ts b/google-workspace/server/lib/wrap-tool.ts deleted file mode 100644 index 9d6a84e0..00000000 --- a/google-workspace/server/lib/wrap-tool.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createPrivateTool } from "@decocms/runtime/tools"; -import { z } from "zod"; -import type { Env } from "../../shared/deco.gen.ts"; -import { - BACKEND_MCPS, - type BackendToolDefinition, - type GoogleService, -} from "../constants.ts"; -import { jsonSchemaToZod } from "./json-schema-to-zod.ts"; -import { getGoogleAccessToken } from "./env.ts"; -import { proxyMcpCall } from "./mcp-proxy.ts"; - -/** - * Build a deco tool factory from a Google MCP backend tool definition. - * The returned tool, when executed, proxies tools/call to the upstream - * backend with the user's Bearer token. - */ -export function wrapBackendTool( - service: GoogleService, - def: BackendToolDefinition, -) { - const id = `${service}_${def.name}`; - const inputSchema = jsonSchemaToZod(def.inputSchema ?? {}); - // The upstream content can be anything — we forward whatever the backend - // returns under the `content` field per the MCP spec. - const outputSchema = z.unknown(); - - return (env: Env) => - createPrivateTool({ - id, - description: def.description ?? `${service} ${def.name}`, - inputSchema, - outputSchema, - execute: async ({ context }) => { - const accessToken = getGoogleAccessToken(env); - const result = await proxyMcpCall( - BACKEND_MCPS[service], - def.name, - context, - accessToken, - ); - return result; - }, - }); -} diff --git a/google-workspace/server/prompts.ts b/google-workspace/server/prompts.ts index 4932f015..697ac1c9 100644 --- a/google-workspace/server/prompts.ts +++ b/google-workspace/server/prompts.ts @@ -1,19 +1,13 @@ /** * Google Workspace MCP Prompts * - * Two kinds of prompts: + * Two kinds: * * 1. Agent guides (no arguments) — long-form references the agent pulls on - * demand: tool catalog, search syntax, pitfalls. Exposed via `prompts/get` - * when the agent decides it needs the docs for a service. - * - * 2. User templates (arguments) — slash-command-style entries the user picks - * from the prompt menu in their MCP client. Each one expands into a user - * message that drives the agent through a common day-to-day workflow. - * - * The tool catalog itself comes from Google's upstream MCPs and lives in - * `server/tools/generated/`. Re-run `bun run generate-tools` whenever Google - * ships a new tool — the prompts below should stay hand-curated. + * demand. Cover tool selection, query syntax, and pitfalls per service. + * 2. User templates (with arguments) — slash-command-style entries the user + * picks from the prompt menu. Each expands into a user message that drives + * the agent through a common workflow. */ import { createPrompt } from "@decocms/runtime"; @@ -23,7 +17,7 @@ const agentGuidePrompt = createPrompt({ name: "GOOGLE_WORKSPACE_AGENT_GUIDE", title: "Google Workspace Agent — Main Instructions", description: - "Entry-point system prompt for an agent using the Google Workspace MCP. Lists the wrapped services, the tool naming convention, the auth model, and points at per-service guides.", + "Entry-point prompt covering all 8 services in this MCP, the tool naming convention, time/timezone handling, destructive-action rules and re-auth signals.", execute: () => ({ messages: [ { @@ -39,43 +33,43 @@ const agentGuidePrompt = createPrompt({ type: "text" as const, text: `# Google Workspace Agent — Main Instructions -You are an agent backed by the **Google Workspace MCP**, a single connection that fans out to five of Google's official MCP servers: +You are an agent backed by the **Google Workspace MCP**, a single connection covering eight Google productivity services through one OAuth login: -| Service | Backend | Tool prefix | +| Service | Tool prefix | What's there | |---|---|---| -| Calendar | \`calendarmcp.googleapis.com\` | \`calendar_*\` | -| Chat | \`chatmcp.googleapis.com\` | \`chat_*\` | -| Drive | \`drivemcp.googleapis.com\` | \`drive_*\` | -| Gmail | \`gmailmcp.googleapis.com\` | \`gmail_*\` | -| People | \`people.googleapis.com\` | \`people_*\` | +| Calendar | \`calendar_*\` | Events, scheduling, free/busy | +| Gmail | \`gmail_*\` | Read, label, draft (no send via MCP — drafts only) | +| Drive | \`drive_*\` | Files, folders, permissions | +| Docs | \`docs_*\` | Create, read, edit documents | +| Sheets | \`sheets_*\` | Read/write spreadsheets, formulas, formatting | +| Slides | \`slides_*\` | Create and edit presentations | +| Forms | \`forms_*\` | Build forms, read responses | +| Meet | \`meet_*\` | Create and manage meeting spaces | -A single OAuth login covers every service — when the user authenticates, you get access to all 33 tools at once. +A single OAuth flow grants access to every service. --- ## 1. Picking a service -| User intent | Service to use | +| User intent | Service | |---|---| -| "What's on my calendar?", "schedule a meeting", "block 30 min" | **calendar** | -| "Send/list/search emails", "label this thread", "create draft" | **gmail** | -| "Find file X", "share doc", "list recent files" | **drive** | -| "Post to space Y", "search messages in chat" | **chat** | -| "Look up person", "what's my email", "find colleague" | **people** | +| "What's on my calendar?", "schedule meeting" | calendar | +| "Find/triage/draft email", "label thread" | gmail | +| "Find file X", "list recent files", "share/copy" | drive | +| "Edit doc", "summarize document" | docs (read content) or drive (find first) | +| "Read/write spreadsheet", "calculate" | sheets | +| "Create presentation" | slides | +| "Build a form", "see responses" | forms | +| "Create a Meet link" | meet | -When in doubt, retrieve the per-service prompt below for the exact tool set: - -- \`GOOGLE_WORKSPACE_CALENDAR_GUIDE\` -- \`GOOGLE_WORKSPACE_GMAIL_GUIDE\` -- \`GOOGLE_WORKSPACE_DRIVE_GUIDE\` -- \`GOOGLE_WORKSPACE_CHAT_GUIDE\` -- \`GOOGLE_WORKSPACE_PEOPLE_GUIDE\` +When in doubt, retrieve the per-service guide via \`prompts/get\`. --- ## 2. Time and timezone handling -Calendar, Gmail and Chat tools accept ISO 8601 timestamps. **Always include an explicit timezone offset** (\`-03:00\`, \`Z\`, etc.) when the user names a wall-clock time — never silently assume UTC. +Tools that accept timestamps want ISO 8601 **with explicit offset**. Never assume UTC silently. \`\`\`text ✅ 2026-04-24T14:00:00-03:00 @@ -83,45 +77,55 @@ Calendar, Gmail and Chat tools accept ISO 8601 timestamps. **Always include an e ❌ 2026-04-24T14:00:00 (naive — backend may misinterpret) \`\`\` -Date-only inputs (\`2026-04-24\`) are usually treated as UTC midnight by the backends. +For wall-clock times, attach the user's timezone offset. --- ## 3. Identity defaults -- The user's **own** identifier in most tools is implicit (e.g. Calendar's "primary" calendar, Gmail's authenticated account). You do **not** need to look up the user's email before calling those tools. -- When you do need it (e.g. for a meeting body, an attendee list, a signature), call \`people_get_user_profile\` — cheap, returns name + email. +- The user's **own** identifier is implicit in most tools (Calendar's \`primary\`, Gmail's authenticated account, Drive's "my files"). +- For meetings/emails that need the user's email (e.g. signature, attendee list, body of an event), look it up via \`drive_get_about\` or one of the user-info tools — don't ask the user. --- ## 4. Destructive actions: confirm first -These tools mutate or delete user data. Confirm with the user before invoking unless they explicitly authorized the change in this turn: +These tools mutate or delete user data. Confirm before invoking unless the user explicitly authorized the change in the same turn: -- \`calendar_delete_event\`, \`calendar_update_event\`, \`calendar_respond_to_event\` -- \`gmail_label_thread\`, \`gmail_unlabel_thread\`, \`gmail_label_message\`, \`gmail_unlabel_message\`, \`gmail_create_label\`, \`gmail_create_draft\` -- \`drive_create_file\`, \`drive_copy_file\` -- \`chat_send_message\` +- Calendar: \`calendar_create_event\`, \`calendar_update_event\`, \`calendar_delete_event\`, \`calendar_quick_add_event\`, advanced ops (move/duplicate) +- Gmail: \`gmail_create_draft\`, \`gmail_label_thread\`/\`unlabel_thread\`, \`gmail_create_label\` +- Drive: \`drive_create_*\`, \`drive_copy_*\`, \`drive_delete_*\`, \`drive_share_*\` +- Docs/Sheets/Slides/Forms: any \`create_*\`, \`update_*\`, \`delete_*\` (most of these mutate) +- Meet: \`meet_create_space\`, \`meet_end_active_conference\` -> **Gmail caveat**: there is **no \`gmail_send\` tool** — only \`create_draft\`. Composed emails land in the user's drafts folder for them to review and send manually. Make sure the user knows this before promising "I'll send the email." +> **Gmail caveat:** there is no \`gmail_send\` tool — only \`gmail_create_draft\`. Composed emails land in the user's drafts folder. Never claim "I sent the email." --- ## 5. Pagination -Tools that return lists (\`list_events\`, \`search_threads\`, \`list_recent_files\`, \`list_messages\`, \`search_directory_people\`, …) typically expose a \`pageSize\` and a page-token field (\`pageToken\` / \`nextPageToken\`). Default page sizes are small (often 10–50). For "show me everything" requests, loop with the returned token until empty — but cap the loops to avoid runaway fan-out. +Tools that return lists (\`list_events\`, \`search_threads\`, \`list_recent_files\`, ...) typically expose \`pageSize\` and a token field (\`pageToken\` / \`nextPageToken\`). Default page sizes are small (10–50). For "show me everything" requests, loop with the returned token until empty — but cap loops to avoid runaway fan-out. --- ## 6. Errors and re-auth -If a call returns \`401 Unauthorized\` or \`The access token has been revoked\`, the user needs to re-authenticate. Surface that explicitly — don't retry. \`403 Forbidden\` usually means the token is missing a scope; ask the user to reconnect to grant the new permission. +If a call returns \`401 Unauthorized\` or "the access token has been revoked", the user must re-authenticate. Surface that explicitly — don't retry. \`403 Forbidden\` usually means the token is missing a scope; ask the user to reconnect to grant the new permission. --- ## 7. Further reading -Pull the per-service guide before doing real work in any one service. Each guide includes a tool cheat sheet, common workflows, query syntax (when applicable), and pitfalls. Retrieve via the standard MCP \`prompts/get\` request. +Pull the per-service guide before doing real work in any one service. Each guide has a tool cheat sheet, common workflows, query syntax (when applicable), and pitfalls. Available: + +- \`GOOGLE_WORKSPACE_CALENDAR_GUIDE\` +- \`GOOGLE_WORKSPACE_GMAIL_GUIDE\` +- \`GOOGLE_WORKSPACE_DRIVE_GUIDE\` +- \`GOOGLE_WORKSPACE_DOCS_GUIDE\` +- \`GOOGLE_WORKSPACE_SHEETS_GUIDE\` +- \`GOOGLE_WORKSPACE_SLIDES_GUIDE\` +- \`GOOGLE_WORKSPACE_FORMS_GUIDE\` +- \`GOOGLE_WORKSPACE_MEET_GUIDE\` `, }, }, @@ -129,240 +133,128 @@ Pull the per-service guide before doing real work in any one service. Each guide }), }); -const calendarGuidePrompt = createPrompt({ - name: "GOOGLE_WORKSPACE_CALENDAR_GUIDE", - title: "Google Calendar — Tool Guide", - description: - "Practical guide for the calendar_* tools: tool selection, scheduling workflows, time-range syntax, the primary calendar convention, and pitfalls.", - execute: () => ({ - messages: [ - { - role: "user" as const, - content: { - type: "text" as const, - text: "How do I use the calendar_* tools?", +/** + * Per-service guide factory — keeps the per-service prompts short and + * uniform: tool list + key pitfalls. Exhaustive cheat sheets are intentionally + * left to each tool's own description (which the agent already sees). + */ +function serviceGuide(opts: { + name: string; + title: string; + prefix: string; + description: string; + body: string; +}) { + return createPrompt({ + name: opts.name, + title: opts.title, + description: opts.description, + execute: () => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: `How do I use the ${opts.prefix}_* tools?`, + }, }, - }, - { - role: "assistant" as const, - content: { - type: "text" as const, - text: `# Google Calendar — Tool Guide - -## Tools (8) - -| Tool | When to use | -|---|---| -| \`calendar_list_calendars\` | Discover which calendars the user has — primary, secondary, shared. | -| \`calendar_list_events\` | "What's on my schedule from X to Y?" Most common read tool. | -| \`calendar_get_event\` | Fetch a single event by ID (after a list/search). | -| \`calendar_suggest_time\` | "Find a 30-min slot when A, B and C are all free next week." | -| \`calendar_create_event\` | Schedule a new meeting. | -| \`calendar_update_event\` | Move, rename, change attendees on an existing event. | -| \`calendar_delete_event\` | Remove an event (destructive — confirm first). | -| \`calendar_respond_to_event\` | Accept / decline / tentative an invitation. | - -## Calendar IDs - -- The user's main calendar is identified as **\`"primary"\`** — use this as the default for any "my calendar" request. -- Secondary calendars use email-shaped IDs (e.g. \`team@group.calendar.google.com\`). Get them via \`calendar_list_calendars\`. - -## Time syntax - -All times must be ISO 8601 **with an explicit offset**: - -\`\`\`json -{ - "calendarId": "primary", - "startTime": "2026-04-24T09:00:00-03:00", - "endTime": "2026-04-24T18:00:00-03:00" + { + role: "assistant" as const, + content: { type: "text" as const, text: opts.body }, + }, + ], + }), + }); } -\`\`\` - -Don't compute epoch ms by hand — pass the ISO string and let Google's backend resolve it. - -## Common workflows -### "What's on my calendar tomorrow?" -\`\`\`json -{ "tool": "calendar_list_events", - "input": { "calendarId": "primary", - "startTime": "", - "endTime": "" } } -\`\`\` - -### "Find 30 min next week with Bob and Alice" -\`\`\`json -{ "tool": "calendar_suggest_time", - "input": { "attendee_emails": ["primary","bob@x.com","alice@y.com"], - "duration_minutes": 30, - "earliest_start_time": "...", - "latest_end_time": "..." } } -\`\`\` -Note: \`primary\` works as a special attendee value to include the user themselves. - -### "Reschedule the standup to 10am" -1. \`calendar_list_events\` to find the standup → grab its \`id\`. -2. \`calendar_update_event\` with the new \`startTime\` / \`endTime\`. - -### "Decline that meeting" -\`calendar_respond_to_event\` with \`responseStatus: "declined"\`. +const calendarGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_CALENDAR_GUIDE", + title: "Google Calendar — Tool Guide", + prefix: "calendar", + description: + "Calendar tool selection, the `primary` calendar convention, scheduling workflows and recurring-event pitfalls.", + body: `# Google Calendar — Tool Guide + +## Core tools +- \`calendar_list_calendars\` — discover the user's calendars (primary, secondary, shared). +- \`calendar_list_events\` — most common read; takes time window + optional search. +- \`calendar_get_event\` — fetch one event by ID after a list/search. +- \`calendar_get_freebusy\` — busy/free intervals for one or more calendars. +- \`calendar_quick_add_event\` — natural-language one-liner ("Lunch tomorrow at noon"). +- \`calendar_create_event\` / \`calendar_update_event\` / \`calendar_delete_event\` — explicit CRUD. +- \`calendar_find_available_slots\` — multi-attendee slot finder. +- \`calendar_move_event\` / \`calendar_duplicate_event\` — advanced. + +## Conventions +- Use \`"primary"\` as the default calendar id. +- Times are ISO 8601 **with timezone offset**; pass the user's tz unless they say otherwise. +- Recurring events are a single object — updating "the standup" updates every instance unless you target a specific instance id. ## Pitfalls - -1. **Recurring events are a single object.** Updating "the standup" updates **every** instance unless you target the specific instance ID. Make sure the user wants that. -2. **\`updateEvent\` overwrites fields you pass.** Read first if you're only changing one thing — partial updates require sending the whole event. -3. **Timezones are sticky.** If the user's calendar timezone is \`America/Sao_Paulo\` but you create with \`+00:00\`, the event will appear at the wrong wall-clock hour. Match the user's tz unless they say otherwise. -4. **\`primary\` is implicit.** Don't ask "which calendar?" if the user just says "my calendar" — use \`primary\`. -5. **\`suggest_time\` only works inside the user's organization.** Free/busy data for outside emails won't be available. -`, - }, - }, - ], - }), +- Free/busy for outside-the-org emails returns empty data. +- \`update_event\` overwrites the fields you pass; read first if you're only changing one thing. +- \`quick_add\` ignores attendees — use \`create_event\` when you need to invite people.`, }); -const gmailGuidePrompt = createPrompt({ +const gmailGuidePrompt = serviceGuide({ name: "GOOGLE_WORKSPACE_GMAIL_GUIDE", title: "Gmail — Tool Guide", + prefix: "gmail", description: - "Practical guide for the gmail_* tools: tool selection, Gmail search query syntax, label vs thread vs message, and pitfalls. Important: this MCP cannot send mail — only create drafts.", - execute: () => ({ - messages: [ - { - role: "user" as const, - content: { - type: "text" as const, - text: "How do I use the gmail_* tools?", - }, - }, - { - role: "assistant" as const, - content: { - type: "text" as const, - text: `# Gmail — Tool Guide - -> **Up front: this MCP cannot send email.** There is no \`gmail_send_message\`. The closest tool is \`gmail_create_draft\`, which leaves the message in the user's drafts folder for them to review and click "Send" themselves. Always set this expectation before promising to "send" anything. - -## Tools (10) - -| Tool | When to use | -|---|---| -| \`gmail_search_threads\` | "Find emails about X", "show me unread from Bob". The main read tool. | -| \`gmail_get_thread\` | After a search, fetch the full conversation by thread ID. | -| \`gmail_create_draft\` | Compose a reply or new email — leaves it in Drafts for the user. | -| \`gmail_list_drafts\` | Show what's in Drafts. | -| \`gmail_list_labels\` | Discover label IDs (you must look up an ID before label/unlabel ops). | -| \`gmail_create_label\` | New custom label. | -| \`gmail_label_thread\` / \`gmail_unlabel_thread\` | Apply/remove labels at the thread level. | -| \`gmail_label_message\` / \`gmail_unlabel_message\` | Same, at the single-message level. | + "Gmail tool selection, search syntax, thread vs message ops, and the drafts-only caveat (this MCP cannot send mail).", + body: `# Gmail — Tool Guide -## Gmail search query syntax +> **No send tool.** This MCP only creates drafts. The user clicks Send themselves. Never promise to "send" an email. -\`gmail_search_threads\` accepts the **same query syntax as the Gmail web UI search box**. - -| Operator | Example | Meaning | -|---|---|---| -| \`from:\` | \`from:bob@example.com\` | From that sender | -| \`to:\` | \`to:me\` | Sent to that address | -| \`subject:\` | \`subject:"Q1 review"\` | Subject contains phrase | -| \`label:\` | \`label:starred\` | Has that label (system or custom) | -| \`is:\` | \`is:unread\`, \`is:important\`, \`is:starred\` | State | -| \`has:\` | \`has:attachment\` | Filter by attachment / link / etc. | -| \`after:\` / \`before:\` | \`after:2026/04/01 before:2026/04/15\` | Date range (note: \`yyyy/mm/dd\`) | -| \`newer_than:\` | \`newer_than:7d\` | Relative time (\`d\`, \`m\`, \`y\`) | -| \`OR\` / \`-\` | \`from:bob OR from:alice -label:archived\` | Boolean / exclusion | -| \`{}\` | \`{from:bob from:alice}\` | OR shorthand | -| \`""\` | \`"connection refused"\` | Phrase match | - -Combine freely: +## Core tools +- \`gmail_search_messages\` / \`gmail_search_threads\` — Gmail's search syntax (see below). +- \`gmail_get_message\` / \`gmail_get_thread\` — fetch full content by id. +- \`gmail_create_draft\` — compose a draft (reply if you pass \`threadId\`). +- \`gmail_list_drafts\`, \`gmail_get_draft\`, \`gmail_update_draft\`, \`gmail_delete_draft\`. +- \`gmail_list_labels\`, \`gmail_create_label\`, \`gmail_label_thread\`, \`gmail_unlabel_thread\`, \`gmail_label_message\`, \`gmail_unlabel_message\`. +## Search syntax cheat sheet \`\`\`text -from:billing@vendor.com after:2026/03/01 has:attachment -label:archived -is:unread label:inbox -from:noreply@ -\`\`\` - -## System label IDs - -These are well-known and work without calling \`list_labels\`: - -\`INBOX\`, \`SENT\`, \`DRAFT\`, \`TRASH\`, \`SPAM\`, \`STARRED\`, \`UNREAD\`, \`IMPORTANT\`, \`CHAT\`. - -For **custom labels**, call \`gmail_list_labels\` first to get the ID — the label name alone won't work. - -## Common workflows - -### "Find emails about the renewal from billing@vendor.com last 30 days" -\`\`\`json -{ "tool": "gmail_search_threads", - "input": { "query": "from:billing@vendor.com renewal newer_than:30d" } } +from:bob@example.com +to:me +subject:"Q1 review" +label:starred (or label:INBOX, label:UNREAD, etc.) +is:unread, is:important, is:starred +has:attachment, has:link +after:2026/04/01 (yyyy/mm/dd, NOT ISO 8601) +newer_than:7d (d, m, y) +from:bob OR from:alice -label:archived +"connection refused" (phrase) \`\`\` -### "Reply to the latest message in that thread" -1. \`gmail_get_thread\` → grab the latest message's \`id\` and \`Message-ID\` header. -2. \`gmail_create_draft\` with \`threadId\` to keep it in the conversation. -3. **Tell the user** the draft is in Drafts — they need to send it. - -### "Star and label these threads as 'follow-up'" -1. \`gmail_list_labels\` → find the \`follow-up\` label ID (or call \`gmail_create_label\` first if it doesn't exist). -2. \`gmail_label_thread\` with both \`STARRED\` and the custom label ID. - -### "Archive everything from this newsletter" -1. \`gmail_search_threads\` with \`from:newsletter@x.com\`. -2. For each thread → \`gmail_unlabel_thread\` removing \`INBOX\` (Gmail's "archive" is just removing the INBOX label). +## System label ids +\`INBOX\`, \`SENT\`, \`DRAFT\`, \`TRASH\`, \`SPAM\`, \`STARRED\`, \`UNREAD\`, \`IMPORTANT\`, \`CHAT\` — use these directly. For custom labels, call \`gmail_list_labels\` first to resolve the id. ## Pitfalls - -1. **No send tool.** Repeat: this MCP creates drafts only. Never claim an email was sent. -2. **Threads vs messages.** A thread is a conversation; a message is a single email. Most operations should target the thread (\`label_thread\`, \`get_thread\`) unless the user is editing one specific reply. -3. **Custom labels need IDs, not names.** A user-friendly label like "Receipts" has an internal ID like \`Label_1234567890\`. Always resolve via \`gmail_list_labels\` before label/unlabel ops. -4. **Date filters use \`yyyy/mm/dd\`.** Not ISO 8601, not US format. \`after:2026/04/01\`, not \`2026-04-01\`. -5. **\`is:unread\` is per-message, not per-thread.** A thread can be partially unread. \`gmail_search_threads\` returns the thread; check the message states inside. -6. **Drafts in a thread.** When replying, pass \`threadId\` to keep the draft attached to the conversation; otherwise it becomes a new thread. -`, - }, - }, - ], - }), +- Threads vs. messages — most ops should target the thread. +- Custom labels need ids, not display names. +- "Archive" = remove the \`INBOX\` label. +- Drafts in a thread require the \`threadId\` to stay attached to the conversation.`, }); -const driveGuidePrompt = createPrompt({ +const driveGuidePrompt = serviceGuide({ name: "GOOGLE_WORKSPACE_DRIVE_GUIDE", title: "Google Drive — Tool Guide", + prefix: "drive", description: - "Practical guide for the drive_* tools: tool selection, search query syntax, content vs metadata, MIME types, and pitfalls.", - execute: () => ({ - messages: [ - { - role: "user" as const, - content: { - type: "text" as const, - text: "How do I use the drive_* tools?", - }, - }, - { - role: "assistant" as const, - content: { - type: "text" as const, - text: `# Google Drive — Tool Guide - -## Tools (8) - -| Tool | When to use | -|---|---| -| \`drive_search_files\` | "Find a file matching X". Structured query syntax — see below. | -| \`drive_list_recent_files\` | "What did I work on lately?" Sorted by recency / viewed time / modified. | -| \`drive_get_file_metadata\` | Title, owner, size, MIME, parents — without downloading. | -| \`drive_read_file_content\` | Natural-language extraction of the file (works for Docs, Sheets, PDFs, etc.). Use this for "summarize this doc". | -| \`drive_download_file_content\` | Raw bytes as base64. Use for binary files or when you need the original format. | -| \`drive_get_file_permissions\` | "Who has access to this?" | -| \`drive_create_file\` | Upload / create a new file (destructive — confirm). | -| \`drive_copy_file\` | Duplicate an existing file (destructive — confirm). | - -## Search query syntax - -\`drive_search_files\` uses Google Drive's structured query language: - + "Drive tool selection, structured query syntax, MIME types, and content vs. metadata vs. permissions.", + body: `# Google Drive — Tool Guide + +## Core tools +- \`drive_list_files\` — search via Drive's structured query language. +- \`drive_get_file\` — metadata + URLs for a single file. +- \`drive_export_file\` — natural-language extraction (Docs/Sheets/PDFs/etc.). +- \`drive_download_file\` — raw bytes (base64). +- \`drive_create_file\`, \`drive_copy_file\`, \`drive_update_file\`, \`drive_delete_file\`. +- \`drive_list_folders\`, \`drive_create_folder\`, \`drive_move_file\`. +- \`drive_list_permissions\`, \`drive_create_permission\`, \`drive_delete_permission\`. + +## Query syntax \`\`\`text name contains 'budget' mimeType = 'application/pdf' @@ -370,203 +262,148 @@ modifiedTime > '2026-04-01T00:00:00' '' in parents trashed = false sharedWithMe and modifiedTime > '2026-04-01T00:00:00' +fullText contains 'security incident' \`\`\` -Combine with \`and\` / \`or\`: - -\`\`\`text -name contains 'invoice' and mimeType = 'application/pdf' and trashed = false -fullText contains 'security incident' and modifiedTime > '2026-03-01T00:00:00' -\`\`\` - -## Common MIME types - -| Workspace type | MIME | +## MIME types you'll need +| Type | MIME | |---|---| -| Google Docs | \`application/vnd.google-apps.document\` | -| Google Sheets | \`application/vnd.google-apps.spreadsheet\` | +| Google Doc | \`application/vnd.google-apps.document\` | +| Google Sheet | \`application/vnd.google-apps.spreadsheet\` | | Google Slides | \`application/vnd.google-apps.presentation\` | -| Google Forms | \`application/vnd.google-apps.form\` | +| Google Form | \`application/vnd.google-apps.form\` | | Folder | \`application/vnd.google-apps.folder\` | -| Shortcut | \`application/vnd.google-apps.shortcut\` | | PDF | \`application/pdf\` | -## Common workflows - -### "Find PDFs about Q1 budget from this year" -\`\`\`json -{ "tool": "drive_search_files", - "input": { "query": "name contains 'budget' and mimeType = 'application/pdf' and modifiedTime > '2026-01-01T00:00:00'" } } -\`\`\` +## Pitfalls +- \`name contains\` is a substring match (case-insensitive). Use \`fullText\` for content. +- Add \`trashed = false\` to skip the bin. +- Prefer \`drive_export_file\` over \`drive_download_file\` for "summarize this" — it returns clean text. Download only when you need raw bytes. +- For deeper editing of Docs/Sheets/Slides/Forms, hand off to the matching service-specific tools instead of \`drive_update_file\`.`, +}); -### "Summarize this Google Doc" -1. \`drive_search_files\` (or use the file ID if the user has it). -2. \`drive_read_file_content\` to get the natural-language extraction. -3. Summarize the returned text. +const docsGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_DOCS_GUIDE", + title: "Google Docs — Tool Guide", + prefix: "docs", + description: + "Tools for creating and editing Google Docs (titles, body content, batch updates).", + body: `# Google Docs — Tool Guide -### "What's in folder X?" -1. Resolve folder ID via \`drive_search_files\` (\`mimeType = 'application/vnd.google-apps.folder' and name = 'X'\`). -2. \`drive_search_files\` with \`'' in parents and trashed = false\`. +## Core tools +- \`docs_create_document\` — start a new doc with a title. +- \`docs_get_document\` — read full structure (paragraphs, tables, lists, embedded objects). +- \`docs_batch_update\` — apply one or many text/format/table operations atomically. -### "Who has access to this doc?" -\`drive_get_file_permissions\` with the file ID. +## When to use Drive vs. Docs +- "Find a doc" / "summarize a doc" → \`drive_list_files\` then \`drive_export_file\`. +- "Edit a doc" / "insert text" / "format" → \`docs_get_document\` then \`docs_batch_update\`. ## Pitfalls - -1. **\`read_file_content\` ≠ \`download_file_content\`.** Use \`read\` for "summarize this", \`download\` for "give me the bytes". Reading a Google Doc as raw bytes returns Drive's internal export format — usually not what you want. -2. **Trashed files match search by default.** Add \`trashed = false\` unless the user wants the bin too. -3. **\`'' in parents\` requires the literal folder ID string with quotes.** Not the folder name. -4. **\`drive.file\` scope is sandboxed.** If the OAuth client has only \`drive.file\` (not full \`drive\`), the agent can only see files it created — listings will look empty for everything else. -5. **Download is base64.** Decode before writing; large files will hit token limits in the conversation. For >1MB files, prefer \`read_file_content\` (extracts text) or stream them outside the LLM. -6. **\`name contains\` is case-insensitive but exact substring.** It does not tokenize — \`name contains 'invoices'\` won't match \`Invoice 042\`. Use \`fullText contains\` for content search. -`, - }, - }, - ], - }), +- \`docs_batch_update\` uses **1-indexed positions** referring to the doc's structural index. Read the doc with \`get_document\` first to find the right insertion index — guessing produces shifted text. +- Edits are atomic per request: if any sub-operation fails, the entire batch is rolled back.`, }); -const chatGuidePrompt = createPrompt({ - name: "GOOGLE_WORKSPACE_CHAT_GUIDE", - title: "Google Chat — Tool Guide", +const sheetsGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_SHEETS_GUIDE", + title: "Google Sheets — Tool Guide", + prefix: "sheets", description: - "Practical guide for the chat_* tools: spaces vs DMs, message search, sending messages, and pitfalls.", - execute: () => ({ - messages: [ - { - role: "user" as const, - content: { - type: "text" as const, - text: "How do I use the chat_* tools?", - }, - }, - { - role: "assistant" as const, - content: { - type: "text" as const, - text: `# Google Chat — Tool Guide + "Tools for reading/writing spreadsheets — values, ranges, formulas, formatting, batch updates.", + body: `# Google Sheets — Tool Guide -## Tools (4) +## Core areas +- Read values: \`sheets_get_values\`, \`sheets_batch_get_values\`. +- Write values: \`sheets_update_values\`, \`sheets_append_values\`, \`sheets_clear_values\`, \`sheets_batch_update_values\`. +- Spreadsheet meta: \`sheets_get_spreadsheet\`, \`sheets_create_spreadsheet\`, \`sheets_batch_update\`. +- Sheet ops (tabs): add, delete, copy, rename via \`sheets_batch_update\` requests. +- Formatting and formulas via \`sheets_batch_update\` request types. -| Tool | When to use | -|---|---| -| \`chat_search_conversations\` | Find a Space or DM by display name. The first step of most workflows. | -| \`chat_list_messages\` | Read messages from a specific conversation, time range, or thread. | -| \`chat_search_messages\` | Find messages by keyword across all accessible conversations. | -| \`chat_send_message\` | Post a message (destructive — confirm before posting). | +## Conventions +- Ranges use A1 notation: \`Sheet1!A1:B10\`, \`'My Tab'!A:A\`. +- For appends, set \`valueInputOption: "USER_ENTERED"\` so formulas like \`=SUM(...)\` are evaluated; use \`"RAW"\` to write literal strings. +- For multi-sheet operations, batch them in a single \`sheets_batch_update\` rather than serial calls. -## Conversation types - -Google Chat has three kinds of conversations: +## Pitfalls +- Empty trailing cells are not returned in value reads — pad client-side if you need a fixed shape. +- Sheet names with spaces/special chars must be quoted in A1 ranges. +- Formulas referencing external sheets require the user to have access to the other file.`, +}); -- **Space**: a named multi-person room (think Slack channel). -- **Group DM**: ad-hoc multi-person message thread, no name. -- **DM**: 1-on-1 direct message. +const slidesGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_SLIDES_GUIDE", + title: "Google Slides — Tool Guide", + prefix: "slides", + description: "Tools for creating and editing Google Slides presentations.", + body: `# Google Slides — Tool Guide -\`chat_search_conversations\` works for **named** ones (Spaces). DMs are usually addressed by the other person's identifier — flow through People MCP to look up the user. +## Core tools +- \`slides_create_presentation\` — new presentation with a title. +- \`slides_get_presentation\` — read all slides, layouts and elements. +- \`slides_get_page\` — read one slide. +- \`slides_batch_update\` — apply create/replace/style operations atomically. ## Common workflows - -### "What's been said in #engineering today?" -1. \`chat_search_conversations\` with \`displayName: "engineering"\` → grab the space ID. -2. \`chat_list_messages\` with the space ID and a 24h time window. - -### "Find messages about the outage" -\`\`\`json -{ "tool": "chat_search_messages", - "input": { "query": "outage", "scope": "all" } } -\`\`\` - -### "Reply 'yes' in the thread Bob mentioned" -1. \`chat_search_messages\` with the keyword — grab the \`threadId\` of Bob's message. -2. \`chat_send_message\` with the same \`threadId\` to keep the reply threaded. -3. **Confirm with the user before posting** — Chat messages can't be unsent silently. +- New deck from scratch: create_presentation → batch_update with createSlide / insertText / createShape requests. +- Replace placeholder text across slides: batch_update with \`replaceAllText\`. +- Insert images: \`createImage\` referencing a Drive file or public URL. ## Pitfalls - -1. **Private (1:1) DMs may not be indexed.** \`chat_list_messages\` excludes messages visible to a single user only — search will not surface them. -2. **\`send_message\` is irreversible.** A posted message is visible immediately; editing/deleting requires a separate flow that this MCP doesn't expose. Always confirm. -3. **Threading matters.** Pass \`threadId\` (or \`threadKey\`) to reply inside an existing thread — without it, the message starts a new thread in the same space. -4. **Scope: read vs modify.** If the OAuth grant has only \`chat.messages.readonly\` / \`chat.spaces.readonly\`, \`send_message\` will fail with 403. Re-auth with the full \`chat.messages\` / \`chat.spaces\` scopes if so. -5. **No bot identity.** Messages are sent **as the authenticated user** — they will see "you" posted it in their Chat history. Make sure the user wants that. -`, - }, - }, - ], - }), +- Element IDs are required for most updates — get them via \`get_presentation\` first. +- Layouts (\`TITLE\`, \`TITLE_AND_BODY\`, etc.) define placeholders; use the placeholder id when inserting text rather than creating fresh shapes.`, }); -const peopleGuidePrompt = createPrompt({ - name: "GOOGLE_WORKSPACE_PEOPLE_GUIDE", - title: "People — Tool Guide", +const formsGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_FORMS_GUIDE", + title: "Google Forms — Tool Guide", + prefix: "forms", description: - "Practical guide for the people_* tools: directory vs personal contacts, looking up the user themselves, and pitfalls.", - execute: () => ({ - messages: [ - { - role: "user" as const, - content: { - type: "text" as const, - text: "How do I use the people_* tools?", - }, - }, - { - role: "assistant" as const, - content: { - type: "text" as const, - text: `# People — Tool Guide - -## Tools (3) + "Tools for creating Google Forms, configuring questions, and reading responses.", + body: `# Google Forms — Tool Guide -| Tool | When to use | -|---|---| -| \`people_get_user_profile\` | "What's my name and email?" — info about the authenticated user themselves. | -| \`people_search_directory_people\` | Find a colleague by name within the user's Google Workspace organization. | -| \`people_search_contacts\` | Find someone in the user's personal address book (people they've emailed before, manually-added contacts). | +## Core tools +- \`forms_create_form\` — empty form with a title; questions added via batch_update. +- \`forms_get_form\` — read the structure (sections, items, options). +- \`forms_batch_update\` — add/edit/remove items, reorder, change settings. +- \`forms_list_responses\`, \`forms_get_response\` — read submissions. -## Two namespaces +## Conventions +- Questions are \`item\` objects with a \`questionItem\` payload. Types: choice, text, scale, date, time, file upload, etc. +- For multi-page forms, use \`pageBreakItem\`. +- Quizzes set \`isQuiz\` on the form and \`grading\` on individual questions. -People MCP queries two distinct sources: +## Pitfalls +- Once responses exist, certain edits (like changing answer options on a question with submitted answers) may invalidate prior data — read existing responses before destructive edits. +- The form's "edit URL" and "respond URL" are different; surface the right one when sharing.`, +}); -| Source | Tool | Available when | -|---|---|---| -| **Workspace directory** | \`search_directory_people\` | The user has a Google Workspace account (work / school). Personal \`@gmail.com\` accounts return nothing here. | -| **Personal contacts** | \`search_contacts\` | Always available, but only contains people the user has interacted with or saved. | +const meetGuidePrompt = serviceGuide({ + name: "GOOGLE_WORKSPACE_MEET_GUIDE", + title: "Google Meet — Tool Guide", + prefix: "meet", + description: + "Tools for creating and managing Google Meet spaces and conferences.", + body: `# Google Meet — Tool Guide -For "find Maria from the design team", \`search_directory_people\` is the right call. For "find my dentist's email", \`search_contacts\` is the right call. +## Core tools +- \`meet_create_space\` — new persistent meeting space (returns the meeting URL and \`name\`). +- \`meet_get_space\`, \`meet_update_space\` — inspect / change access settings. +- \`meet_end_active_conference\` — kick everyone out of the current conference in a space. +- \`meet_list_conference_records\`, \`meet_get_conference_record\` — historical metadata for past conferences. +- \`meet_list_participants\`, \`meet_list_recordings\`, \`meet_list_transcripts\` — post-meeting artifacts. ## Common workflows - -### "Who am I?" -\`people_get_user_profile\` — returns name + email. Useful before composing emails / events that need the user's identity in the body. - -### "Find Bob's email" -1. Try \`people_search_directory_people\` first if the user is on Workspace. -2. Fall back to \`people_search_contacts\` if no match. - -### "Schedule a meeting with the engineering team" -1. \`people_search_directory_people\` for each name → resolve to email addresses. -2. Pass the emails into \`calendar_create_event\` as attendees. +- "Schedule a meeting with Bob": call \`meet_create_space\` to get a URL, then \`calendar_create_event\` with that URL in the description / conferenceData. +- "What did we cover in yesterday's call?" → conference records + transcripts (only if recording/transcription was on). ## Pitfalls - -1. **Directory search returns nothing on personal accounts.** Don't suggest "let me look up your colleague" if the user signed in with \`@gmail.com\` — only \`@\` accounts can read the directory. -2. **\`search_contacts\` is incomplete.** It only knows about people the user has saved or interacted with. A first-time recipient won't appear. -3. **Don't loop name lookups.** If the user types out an email already, just use it — don't make a People search call for verification. -4. **Privacy.** Surface as little PII as the task needs. If the user asks "what's Bob's email?", reply with the email and don't dump the entire directory entry. -`, - }, - }, - ], - }), +- Spaces are persistent; the same space can host many conferences over time. Don't conflate "space" with "meeting". +- Recording/transcript availability depends on the user's Workspace tier and admin policy.`, }); // ============================================================================ // User templates — picked from the prompt menu, accept arguments // ============================================================================ -// -// Each template returns a single user-role message instructing the agent. -// The agent then orchestrates calls across the 5 services to fulfill it. const userMessage = (text: string) => ({ messages: [ @@ -581,14 +418,14 @@ const morningBriefing = createPrompt({ name: "morning_briefing", title: "Morning briefing", description: - "Summarize the day ahead — calendar, important unread email, and recent chat activity — in one shot.", + "Summarize the day ahead — calendar, important unread email, and recent Drive activity — in one shot.", execute: () => userMessage( - `Give me a morning briefing for today. Use the Google Workspace MCP and combine three sources: + `Give me a morning briefing for today. Combine three sources: -1. **Calendar** — call \`calendar_list_events\` on \`primary\` from now until end of day in my timezone. Group as: meetings I'm hosting, meetings I'm attending, blocked focus time. -2. **Email** — call \`gmail_search_threads\` with \`is:unread is:important newer_than:1d\`. Show subject, sender, one-line summary. Cap at 10. -3. **Chat** — call \`chat_search_messages\` for the last 24h that mention me. Surface the spaces with the most activity. +1. **Calendar** — \`calendar_list_events\` on \`primary\` from now until end of day in my timezone. Group as: meetings I'm hosting, meetings I'm attending, blocked focus time. +2. **Email** — \`gmail_search_threads\` with \`is:unread is:important newer_than:1d\`. Show subject, sender, one-line summary. Cap at 10. +3. **Drive activity** — \`drive_list_files\` for \`modifiedTime > '<24h ago>' and trashed = false\`, top 5. Surface anything shared with me or that I last touched. Format as three short sections with bullets. End with one sentence: "What should I tackle first?"`, ), @@ -598,7 +435,7 @@ const prepForMeeting = createPrompt({ name: "prep_for_meeting", title: "Prep for next meeting", description: - "Pull together context for the next meeting on the calendar — attendees, recent emails with them, and shared docs.", + "Pull together context for the next meeting on the calendar — attendees, recent emails with them, shared docs.", argsSchema: { lookahead: z .string() @@ -610,14 +447,14 @@ const prepForMeeting = createPrompt({ execute: ({ args }) => { const lookahead = args.lookahead ?? "4h"; return userMessage( - `Prep me for my next meeting in the next ${lookahead}. Steps: + `Prep me for my next meeting in the next ${lookahead}. 1. \`calendar_list_events\` on \`primary\` from now until ${lookahead} from now. Pick the next event with at least one attendee besides me. -2. For each attendee email, \`gmail_search_threads\` with \`from: OR to: newer_than:14d\` and pull the latest 3 threads each. Highlight any open questions or commitments. -3. \`drive_search_files\` for files modified by those people in the last 30 days OR shared in those email threads. List up to 5. -4. \`gmail_search_threads\` with the meeting title as a query — surface any prior meeting notes / agenda emails. +2. For each attendee email, \`gmail_search_threads\` with \`from: OR to: newer_than:14d\` and pull the latest 3 threads each. Highlight open questions or commitments. +3. \`drive_list_files\` for files modified by those people in the last 30 days OR shared in those email threads. List up to 5. +4. \`gmail_search_threads\` with the meeting title — surface any prior agenda emails / meeting notes. -Output: meeting title and time, attendee list with role hints (org, last interaction), 5 bullets of context, suggested talking points.`, +Output: meeting title and time, attendee list, 5 bullets of context, suggested talking points.`, ); }, }); @@ -631,7 +468,7 @@ const whatsOnCalendar = createPrompt({ .string() .optional() .describe( - "Window to summarize. Free-form, e.g. 'today', 'tomorrow', 'this week', 'next Monday', '2026-04-24'. Default 'today'.", + "Window to summarize. e.g. 'today', 'tomorrow', 'this week', '2026-04-24'. Default 'today'.", ), }, execute: ({ args }) => { @@ -639,10 +476,10 @@ const whatsOnCalendar = createPrompt({ return userMessage( `Summarize what's on my calendar for: ${when}. -1. Resolve the window into ISO 8601 timestamps using my local timezone (always include offset — never naive). If the user said something ambiguous like "this week", interpret as Monday–Sunday in my tz. -2. Call \`calendar_list_events\` on \`primary\` for that window. +1. Resolve the window to ISO 8601 timestamps in my local timezone (always include offset). +2. \`calendar_list_events\` on \`primary\` for that window. 3. Group by day. For each event: time, title, attendee count, location/Meet link. Flag conflicts. -4. End with: total meeting hours and longest free block per day.`, +4. End with: total meeting hours and the longest free block per day.`, ); }, }); @@ -651,12 +488,12 @@ const findMeetingTime = createPrompt({ name: "find_meeting_time", title: "Find a time to meet", description: - "Find a slot when given attendees are all free — across the user's organization.", + "Find a slot when the given attendees are all free, within the user's organization.", argsSchema: { attendees: z .string() .describe( - "Comma-separated emails (or names — agent will look them up via people_search_directory_people). Always include me as 'primary'.", + "Comma-separated emails. Always include 'primary' to represent me.", ), duration: z .string() @@ -666,7 +503,7 @@ const findMeetingTime = createPrompt({ .string() .optional() .describe( - "Time window to search. e.g. 'today', 'tomorrow', 'this week', 'next 5 business days'. Default 'next 5 business days'.", + "Time window to search. e.g. 'today', 'this week', 'next 5 business days'. Default 'next 5 business days'.", ), }, execute: ({ args }) => { @@ -675,11 +512,10 @@ const findMeetingTime = createPrompt({ return userMessage( `Find a ${duration} meeting slot when these people are all free during ${when}: ${args.attendees}. -1. For any names that aren't emails, resolve via \`people_search_directory_people\`. Always include \`primary\` to represent me. -2. Resolve the time window into ISO 8601 with my timezone. -3. Call \`calendar_suggest_time\` with the attendee_emails list, duration, and window. -4. Return up to 5 candidate slots (start time in my tz). For each, note which attendees confirmed availability. -5. Ask me which slot to book — do NOT call \`calendar_create_event\` until I confirm.`, +1. Resolve the time window into ISO 8601 with my timezone offset. +2. \`calendar_find_available_slots\` (or \`calendar_get_freebusy\` if the former isn't available) with the attendee emails, duration, and window. +3. Return up to 5 candidate slots in my tz. For each, note any caveats (e.g. attendee outside org → no free/busy data). +4. Ask which slot to book — do NOT call \`calendar_create_event\` until I confirm.`, ); }, }); @@ -687,8 +523,7 @@ const findMeetingTime = createPrompt({ const blockFocusTime = createPrompt({ name: "block_focus_time", title: "Block focus time", - description: - "Reserve a focus block on the user's primary calendar. Confirms before creating.", + description: "Reserve a focus block on the user's primary calendar.", argsSchema: { duration: z .string() @@ -697,7 +532,7 @@ const blockFocusTime = createPrompt({ .string() .optional() .describe( - "When to block. e.g. 'tomorrow morning', 'today 14:00 GMT-3', 'next free slot'. Default 'next free slot today'.", + "When to block. e.g. 'tomorrow morning', 'today 14:00 GMT-3', 'next free slot today'. Default 'next free slot today'.", ), title: z.string().optional().describe("Block title. Default 'Focus time'."), }, @@ -707,13 +542,9 @@ const blockFocusTime = createPrompt({ return userMessage( `Block ${args.duration} of focus time on my calendar for: "${title}". When: ${when}. -1. Resolve "${when}" into a concrete start time (ISO 8601 with my timezone offset). If it says "next free slot", first call \`calendar_list_events\` for the rest of today and pick the earliest gap of at least ${args.duration}. -2. Show me the proposed start/end times and ask for confirmation. -3. **Only after I confirm**, call \`calendar_create_event\` on \`primary\` with: - - summary: "${title}" - - the resolved start/end - - visibility: "private" - - no attendees +1. Resolve "${when}" into a concrete start (ISO 8601 with my timezone). For "next free slot", \`calendar_list_events\` first and pick the earliest gap of at least ${args.duration}. +2. Show me proposed start/end and ask for confirmation. +3. **After I confirm**, \`calendar_create_event\` on \`primary\` with summary "${title}", visibility "private", no attendees. 4. Confirm the event was created and show me the link.`, ); }, @@ -722,8 +553,7 @@ const blockFocusTime = createPrompt({ const inboxTriage = createPrompt({ name: "inbox_triage", title: "Triage my inbox", - description: - "Summarize unread email by importance and suggest what needs a reply.", + description: "Summarize unread email by importance and what needs a reply.", argsSchema: { timeframe: z .string() @@ -741,14 +571,14 @@ const inboxTriage = createPrompt({ return userMessage( `Triage my Gmail inbox for the last ${timeframe}. -1. \`gmail_search_threads\` with: \`is:unread ${newerThan} -category:promotions -category:social\`. -2. For each thread, get the latest message via \`gmail_get_thread\` and classify it as: +1. \`gmail_search_threads\` with \`is:unread ${newerThan} -category:promotions -category:social\`. +2. For each thread, \`gmail_get_thread\` and classify as: - **Reply needed** — direct question or @-mention - **FYI** — informational, no action - **Action item** — task assigned to me - - **Noise** — newsletters, automated, etc. -3. Output a table with columns: From, Subject, Class, One-line gist. -4. End with: how many need a reply, and which 1–3 are the most time-sensitive. Do NOT draft replies yet — just the triage.`, + - **Noise** — newsletters, automated +3. Output a table: From, Subject, Class, One-line gist. +4. End with: how many need a reply, and which 1–3 are most time-sensitive. Don't draft replies — just triage.`, ); }, }); @@ -772,14 +602,14 @@ const draftReply = createPrompt({ userMessage( `Draft a reply to a Gmail thread. -1. \`gmail_search_threads\` with query: \`${args.thread_query}\`. If multiple match, pick the most recent and tell me which one. -2. \`gmail_get_thread\` to read the latest message in that thread. +1. \`gmail_search_threads\` with: \`${args.thread_query}\`. If multiple match, pick the most recent and tell me which. +2. \`gmail_get_thread\` to read the latest message. 3. Compose a reply that says: ${args.instruction} - - Match the existing tone and language. + - Match the existing tone. - Keep it concise unless the original was long. - - Sign off with my first name (look it up via \`people_get_user_profile\` if you don't already know it). + - Sign off with my first name. 4. \`gmail_create_draft\` with the threadId so it stays in the conversation. -5. Tell me where to find the draft. **Remember: this MCP cannot send mail — the user must click Send themselves.**`, +5. Tell me where to find the draft. **Remember: this MCP cannot send mail — I must click Send.**`, ), }); @@ -791,21 +621,20 @@ const findFiles = createPrompt({ query: z .string() .describe( - "Natural-language description of what to find. e.g. 'PDFs about Q1 budget from this year', 'spreadsheets shared by Alice last month'.", + "Natural-language description. e.g. 'PDFs about Q1 budget from this year', 'spreadsheets shared by Alice last month'.", ), }, execute: ({ args }) => userMessage( `Find files in my Drive matching: ${args.query}. -1. Translate the request into Drive structured query syntax. Examples: +1. Translate the request to Drive structured query syntax. Examples: - PDFs → \`mimeType = 'application/pdf'\` - Sheets → \`mimeType = 'application/vnd.google-apps.spreadsheet'\` - "this year" → \`modifiedTime > 'YYYY-01-01T00:00:00'\` - - "shared by X" → resolve email via \`people_search_directory_people\` then \`'' in owners\` - - Always add \`trashed = false\` unless the user wants the bin. -2. Call \`drive_search_files\` with the structured query. -3. List up to 10 results: title, type (Doc/Sheet/PDF/…), owner, last modified. Include the file ID for follow-ups.`, + - Always add \`trashed = false\`. +2. \`drive_list_files\` with the structured query. +3. List up to 10 results: title, type, owner, last modified. Include the file id.`, ), }); @@ -818,75 +647,120 @@ const summarizeDoc = createPrompt({ document: z .string() .describe( - "Document name or keyword. The agent searches Drive and confirms the match before summarizing.", + "Document name or keyword. The agent confirms before summarizing.", ), }, execute: ({ args }) => userMessage( `Summarize a document from my Drive: ${args.document}. -1. \`drive_search_files\` with \`name contains '${args.document}' and trashed = false\`. If multiple results, list the top 3 with last-modified dates and ask me to pick. -2. \`drive_read_file_content\` (NOT download) on the chosen file — this returns natural-language extraction that handles Docs/Sheets/PDFs. -3. Produce: - - 3-sentence executive summary - - Bullet list of key points (max 7) - - Open questions / TODOs flagged in the doc +1. \`drive_list_files\` with \`name contains '${args.document}' and trashed = false\`. If multiple, list the top 3 with last-modified dates and ask me to pick. +2. \`drive_export_file\` on the chosen file (preferred over download — handles Docs/Sheets/PDFs). +3. Produce: 3-sentence executive summary, bullet list of key points (max 7), open questions/TODOs. 4. End with the doc URL.`, ), }); -const catchUpChat = createPrompt({ - name: "catch_up_chat", - title: "Catch up on a Chat space", - description: "Summarize recent activity in a Google Chat space or DM.", +const newDeck = createPrompt({ + name: "new_deck_from_outline", + title: "Create a slide deck from an outline", + description: + "Generate a Google Slides deck from a bullet outline. Confirms before mutating.", argsSchema: { - space: z + title: z.string().describe("Deck title."), + outline: z .string() .describe( - "Space display name or keyword. e.g. 'engineering', 'design-team'.", + "Bullet outline. One slide per top-level bullet; nested bullets are speaker notes.", ), - timeframe: z - .string() - .optional() - .describe("How far back to read. e.g. '24h', '3d'. Default '24h'."), }, - execute: ({ args }) => { - const timeframe = args.timeframe ?? "24h"; - return userMessage( - `Catch me up on the "${args.space}" Google Chat space — last ${timeframe}. - -1. \`chat_search_conversations\` with displayName containing "${args.space}". If multiple match, pick the one with the most recent activity and tell me which. -2. \`chat_list_messages\` on that space, scoped to the last ${timeframe}. -3. Summarize as: - - **Decisions** — anything that sounds like a conclusion or commitment - - **Action items** — tasks called out, with owner if mentioned - - **Open threads** — questions still hanging - - **FYI** — links, announcements -4. Highlight any messages that mention me (use my email from \`people_get_user_profile\` if needed).`, - ); + execute: ({ args }) => + userMessage( + `Create a Google Slides deck titled "${args.title}" from this outline: + +${args.outline} + +Steps: +1. \`slides_create_presentation\` with the title. +2. Parse the outline: each top-level bullet = one slide; nested bullets = speaker notes. +3. \`slides_batch_update\` with createSlide + insertText requests in one batch. +4. Show me the deck URL when done. + +Confirm with me before step 3 if the outline is ambiguous (e.g. inconsistent indentation).`, + ), +}); + +const createForm = createPrompt({ + name: "create_form", + title: "Build a form from a question list", + description: + "Create a Google Form with the given title and questions. Confirms before mutating.", + argsSchema: { + title: z.string().describe("Form title."), + questions: z + .string() + .describe( + "One question per line. Prefix with type: 'short:', 'long:', 'choice:', 'multi:', 'scale:1-5'. Default short text.", + ), }, + execute: ({ args }) => + userMessage( + `Build a Google Form titled "${args.title}" with these questions: + +${args.questions} + +Steps: +1. \`forms_create_form\` with the title. +2. Parse each line into the right item type: + - \`short:Q\` → SHORT_ANSWER + - \`long:Q\` → PARAGRAPH + - \`choice:Q | A | B | C\` → RADIO with three options + - \`multi:Q | A | B | C\` → CHECKBOX + - \`scale:1-5 Q\` → linear scale 1–5 + - bare line → SHORT_ANSWER +3. \`forms_batch_update\` with all items in one batch. +4. Show me the form's edit URL and respond URL. + +Confirm before step 3 if any line is ambiguous.`, + ), }); -const findPerson = createPrompt({ - name: "find_person", - title: "Find someone's contact info", +const meetingLink = createPrompt({ + name: "create_meet_for_event", + title: "Add a Meet link to a calendar event", description: - "Look up a person across the Workspace directory and personal contacts.", + "Create a Google Meet space and attach it to an existing or new calendar event.", argsSchema: { - name: z.string().describe("Name or partial name to search for."), + event_query: z + .string() + .describe( + "Either 'new' to create an event, or a calendar search to find one (e.g. 'Q1 sync this Friday').", + ), }, execute: ({ args }) => userMessage( - `Find contact info for: ${args.name}. + `Create a Google Meet space and attach it to a calendar event: ${args.event_query}. -1. Try \`people_search_directory_people\` first — this hits the user's Workspace org directory. -2. If no match (or the user has a personal account), fall back to \`people_search_contacts\`. -3. Return: full name, email, job title (if available), department/team. If multiple matches, list up to 5. -4. Don't dump every directory field — only what's useful for "send them an email" or "schedule a meeting".`, +1. \`meet_create_space\` to get a Meet URL. +2. If "${args.event_query}" is "new": + - Ask me for title, attendees, time. Then \`calendar_create_event\` with the Meet URL in the description. +3. Otherwise, \`calendar_list_events\` with the query, pick the matching event, then \`calendar_update_event\` to add the Meet URL. +4. Confirm the event was updated and show me the Meet URL.`, ), }); -const userPrompts = [ +export const prompts = [ + // Agent guides + agentGuidePrompt, + calendarGuidePrompt, + gmailGuidePrompt, + driveGuidePrompt, + docsGuidePrompt, + sheetsGuidePrompt, + slidesGuidePrompt, + formsGuidePrompt, + meetGuidePrompt, + // User templates morningBriefing, prepForMeeting, whatsOnCalendar, @@ -896,18 +770,7 @@ const userPrompts = [ draftReply, findFiles, summarizeDoc, - catchUpChat, - findPerson, -]; - -export const prompts = [ - // Agent guides - agentGuidePrompt, - calendarGuidePrompt, - gmailGuidePrompt, - driveGuidePrompt, - chatGuidePrompt, - peopleGuidePrompt, - // User templates - ...userPrompts, + newDeck, + createForm, + meetingLink, ]; diff --git a/google-workspace/server/scripts/generate-tools.ts b/google-workspace/server/scripts/generate-tools.ts deleted file mode 100644 index c17f98f9..00000000 --- a/google-workspace/server/scripts/generate-tools.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Refresh per-service snapshots in server/tools/generated/ and the - * human-readable TOOLS.md preview. - * - * Fetches `tools/list` from each Google MCP backend and the PRM scopes_supported - * from each backend's RFC 9728 metadata. Both endpoints are reachable without - * authentication, so this script needs no Google credentials. - * - * Run: `bun run generate-tools` - */ - -import { writeFile, mkdir } from "node:fs/promises"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { BACKEND_MCPS, type GoogleService } from "../constants.ts"; - -const HERE = dirname(fileURLToPath(import.meta.url)); -const OUT_DIR = join(HERE, "..", "tools", "generated"); -const PACKAGE_ROOT = join(HERE, "..", ".."); -const TOOLS_MD_PATH = join(PACKAGE_ROOT, "TOOLS.md"); - -interface JsonRpcResponse { - jsonrpc: "2.0"; - id: number | string; - result?: { tools: unknown[] }; - error?: { code: number; message: string }; -} - -interface ProtectedResourceMetadata { - resource?: string; - authorization_servers?: string[]; - scopes_supported?: string[]; - bearer_methods_supported?: string[]; -} - -async function fetchToolsList(backendUrl: string): Promise { - const res = await fetch(backendUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json, text/event-stream", - }, - body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), - }); - if (!res.ok) { - throw new Error( - `${backendUrl} tools/list HTTP ${res.status}: ${await res.text()}`, - ); - } - const json = (await res.json()) as JsonRpcResponse; - if (json.error) { - throw new Error(`${backendUrl} tools/list error: ${json.error.message}`); - } - return json.result?.tools ?? []; -} - -async function fetchScopes(backendUrl: string): Promise { - const u = new URL(backendUrl); - const prmUrl = `${u.origin}/.well-known/oauth-protected-resource${u.pathname}`; - const res = await fetch(prmUrl, { headers: { Accept: "application/json" } }); - if (!res.ok) { - throw new Error(`${prmUrl} HTTP ${res.status}: ${await res.text()}`); - } - const prm = (await res.json()) as ProtectedResourceMetadata; - return prm.scopes_supported ?? []; -} - -interface BackendTool { - name: string; - description?: string; -} - -function renderToolsMarkdown( - snapshots: Record, -): string { - const services = Object.keys(snapshots) as GoogleService[]; - const total = services.reduce( - (acc, svc) => acc + new Set(snapshots[svc].tools.map((t) => t.name)).size, - 0, - ); - const allScopes = Array.from( - new Set(services.flatMap((svc) => snapshots[svc].scopes)), - ).sort(); - - const lines: string[] = []; - lines.push("# Google Workspace — Tool Catalog"); - lines.push(""); - lines.push( - "_Auto-generated by `bun run generate-tools`. Do not edit by hand._", - ); - lines.push(""); - lines.push( - `${total} tools across ${services.length} services, gated behind ${allScopes.length} OAuth scopes.`, - ); - lines.push(""); - lines.push( - "Tool names below are listed without the service prefix. The MCP exposes them as `_` (e.g. `calendar_list_events`).", - ); - lines.push(""); - - for (const service of services) { - const snap = snapshots[service]; - const seen = new Set(); - const unique = snap.tools.filter((t) => { - if (seen.has(t.name)) return false; - seen.add(t.name); - return true; - }); - - lines.push(`## ${service}`); - lines.push(""); - lines.push(`**Endpoint:** \`${BACKEND_MCPS[service]}\``); - lines.push(""); - lines.push("**Scopes:**"); - for (const scope of snap.scopes) lines.push(`- \`${scope}\``); - lines.push(""); - lines.push(`**Tools (${unique.length}):**`); - for (const tool of unique) { - const firstLine = (tool.description ?? "").split("\n")[0].trim(); - lines.push(`- \`${tool.name}\`${firstLine ? ` — ${firstLine}` : ""}`); - } - lines.push(""); - } - - return lines.join("\n"); -} - -async function main() { - await mkdir(OUT_DIR, { recursive: true }); - - const services = Object.keys(BACKEND_MCPS) as GoogleService[]; - const indexEntries: Record< - string, - { scopes: string[]; toolNames: string[] } - > = {}; - const snapshotsForMarkdown: Record< - GoogleService, - { scopes: string[]; tools: BackendTool[] } - > = {} as never; - - for (const service of services) { - const url = BACKEND_MCPS[service]; - process.stdout.write(`fetching ${service} (${url})... `); - const [tools, scopes] = await Promise.all([ - fetchToolsList(url), - fetchScopes(url), - ]); - const snap = { service, scopes, tools }; - await writeFile( - join(OUT_DIR, `${service}.json`), - JSON.stringify(snap, null, 2) + "\n", - ); - indexEntries[service] = { - scopes, - toolNames: tools.map((t) => (t as { name: string }).name), - }; - snapshotsForMarkdown[service] = { - scopes, - tools: tools as BackendTool[], - }; - console.log(`${tools.length} tools, ${scopes.length} scopes`); - } - - await writeFile( - join(OUT_DIR, "_index.json"), - JSON.stringify(indexEntries, null, 2) + "\n", - ); - - await writeFile(TOOLS_MD_PATH, renderToolsMarkdown(snapshotsForMarkdown)); - console.log(`\nWrote ${services.length} snapshots to ${OUT_DIR}`); - console.log(`Wrote tool catalog to ${TOOLS_MD_PATH}`); -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/google-workspace/server/tools/generated/_index.json b/google-workspace/server/tools/generated/_index.json deleted file mode 100644 index d2d6df91..00000000 --- a/google-workspace/server/tools/generated/_index.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "calendar": { - "scopes": [ - "https://www.googleapis.com/auth/calendar", - "https://www.googleapis.com/auth/calendar.app.created", - "https://www.googleapis.com/auth/calendar.events", - "https://www.googleapis.com/auth/calendar.events.readonly", - "https://www.googleapis.com/auth/calendar.events.freebusy", - "https://www.googleapis.com/auth/calendar.events.owned", - "https://www.googleapis.com/auth/calendar.events.owned.readonly", - "https://www.googleapis.com/auth/calendar.events.public.readonly", - "https://www.googleapis.com/auth/calendar.readonly" - ], - "toolNames": [ - "list_events", - "get_event", - "list_calendars", - "suggest_time", - "create_event", - "update_event", - "delete_event", - "respond_to_event" - ] - }, - "chat": { - "scopes": [ - "https://www.googleapis.com/auth/chat.spaces", - "https://www.googleapis.com/auth/chat.spaces.readonly", - "https://www.googleapis.com/auth/chat.memberships", - "https://www.googleapis.com/auth/chat.memberships.readonly", - "https://www.googleapis.com/auth/chat.messages", - "https://www.googleapis.com/auth/chat.messages.readonly" - ], - "toolNames": [ - "list_messages", - "search_conversations", - "list_messages", - "search_messages", - "search_conversations", - "send_message" - ] - }, - "drive": { - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file" - ], - "toolNames": [ - "copy_file", - "create_file", - "download_file_content", - "get_file_metadata", - "get_file_permissions", - "list_recent_files", - "read_file_content", - "search_files" - ] - }, - "gmail": { - "scopes": [ - "https://mail.google.com/", - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/gmail.compose", - "https://www.googleapis.com/auth/gmail.readonly", - "https://www.googleapis.com/auth/gmail.metadata" - ], - "toolNames": [ - "create_draft", - "list_drafts", - "get_thread", - "search_threads", - "label_thread", - "unlabel_thread", - "list_labels", - "label_message", - "unlabel_message", - "create_label" - ] - }, - "people": { - "scopes": [ - "https://www.googleapis.com/auth/directory.readonly", - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/contacts.readonly" - ], - "toolNames": [ - "search_directory_people", - "search_contacts", - "get_user_profile" - ] - } -} diff --git a/google-workspace/server/tools/generated/calendar.json b/google-workspace/server/tools/generated/calendar.json deleted file mode 100644 index f0e1b4c6..00000000 --- a/google-workspace/server/tools/generated/calendar.json +++ /dev/null @@ -1,1864 +0,0 @@ -{ - "service": "calendar", - "scopes": [ - "https://www.googleapis.com/auth/calendar", - "https://www.googleapis.com/auth/calendar.app.created", - "https://www.googleapis.com/auth/calendar.events", - "https://www.googleapis.com/auth/calendar.events.readonly", - "https://www.googleapis.com/auth/calendar.events.freebusy", - "https://www.googleapis.com/auth/calendar.events.owned", - "https://www.googleapis.com/auth/calendar.events.owned.readonly", - "https://www.googleapis.com/auth/calendar.events.public.readonly", - "https://www.googleapis.com/auth/calendar.readonly" - ], - "tools": [ - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Lists calendar events in a given calendar." - }, - "description": "Lists calendar events in a given calendar satisfying the given conditions.\n\nKey Features:\n\n * Any Calendar ID, which can be user's primary calendar or others.\n * Time range filtering.\n * Retrieves ALL events matching the time constraints.\n\nIf available, use search_events tool instead for searches on the user's primary calendar if:\n\n * You are querying for events matching a specific topic, category, or intent (e.g., 'lunch meetings', 'project syncs').\n * You need to find the (top K) most relevant events rather than all events satisfying the constraints.\n * You need keyword or semantic search capabilities.\n\nUse this tool for queries like:\n\n * What's on my calendar tomorrow?\n * What's on my calendar for July 14th 2025?\n * What are my meetings next week?\n * Do I have any conflicts this afternoon?\n\n * What meetings does John have tomorrow?\n\nExample:\n\n list_events(\n startTime='2024-09-17T06:00:00',\n endTime='2024-09-17T12:00:00',\n pageSize=10\n )\n # Returns up to 10 calendar events between 6:00 AM and 12:00 PM on September 17, 2024 from the user's primary calendar.\n", - "inputSchema": { - "properties": { - "calendarId": { - "description": "Optional. The calendar ID to list events from. The default is the user's primary calendar.", - "type": "string" - }, - "endTime": { - "description": "Optional. Upper bound (exclusive) for an event's start time. Only events starting strictly before this time are returned (i.e., the end of the time window to search). If specified, must be greater than or equal to `start_time`. Must be an ISO 8601 timestamp. For example, 2026-06-03T10:00:00-07:00, 2026-06-03T10:00:00Z, or 2026-06-03T10:00:00. Milliseconds may be provided but are ignored.", - "type": "string" - }, - "eventTypeFilter": { - "description": "Optional. The event types to return. Possible values are: * `default` - Regular events (default). * `outOfOffice` - Out of office events. * `focusTime` - Focus time events. * `workingLocation` - Working location events. * `birthday` - Birthday events. * `fromGmail` - Events from Gmail. If empty, only the following event types are returned: `default`, `outOfOffice`, `focusTime`, `fromGmail`", - "items": { - "type": "string" - }, - "type": "array" - }, - "fullText": { - "description": "Optional. Free-form search query to search across title, description, location and attendees.", - "type": "string" - }, - "orderBy": { - "description": "Optional. The order in which events should be returned. Possible values are: * `default` - Unspecified, but deterministic ordering (default). * `startTime` - Order by start time ascending. * `startTimeDesc` - Order by start time descending. * `lastModified` - Order by last modification time ascending.", - "type": "string" - }, - "pageSize": { - "description": "Optional. Maximum number of events returned on one result page. The number of events in the resulting page may be less than this value, or none at all, even if there are more events matching the query. Incomplete pages can be detected by a non-empty `next_page_token` field in the response. By default the value is 250 events. The page size can never be larger than 2500 events.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. Token specifying which result page to return.", - "type": "string" - }, - "startTime": { - "description": "Optional. Lower bound (exclusive) for an event's end time. Only events ending strictly after this time are returned (i.e., the start of the time window to search). Defaults to the current time if neither `start_time` nor `end_time` is provided. If specified, must be less than or equal to `end_time`. Must be an ISO 8601 timestamp. For example, 2026-06-03T10:00:00-07:00, 2026-06-03T10:00:00Z, or 2026-06-03T10:00:00. Milliseconds may be provided but are ignored.", - "type": "string" - }, - "timeZone": { - "description": "Optional. Time zone used in the response and to resolve timezone-less dates in the request (formatted as an IANA Time Zone Database name, e.g. `Europe/Zurich`). The default is the time zone of the calendar.", - "type": "string" - } - }, - "type": "object" - }, - "name": "list_events", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Event": { - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "accessRole": { - "description": "The user's access role for this calendar. Read-only. Possible values are: * `none` - The user has no access. * `freeBusyReader` - The user has read access to free/busy information. * `reader` - The user has read access to the calendar. Private events will appear to users with reader access, but event details will be hidden. * `writer` - The user has read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible. * `owner` - The user has manager access to the calendar. This role has all of the permissions of the writer role with the additional ability to see and modify access levels of other users. Important: the owner role is different from the calendar's data owner. A calendar has a single data owner, but can have multiple users with owner role.", - "type": "string" - }, - "defaultReminders": { - "description": "The default reminders on the calendar for the authenticated user. These reminders apply to all events on this calendar that do not explicitly override them (i.e. do not have reminders.useDefault set to True).", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "description": { - "description": "Description of the calendar.", - "type": "string" - }, - "events": { - "description": "List of events on the calendar.", - "items": { - "$ref": "#/$defs/Event" - }, - "type": "array" - }, - "nextPageToken": { - "description": "Token used to access the next page of this result. Omitted if no further results are available.", - "type": "string" - }, - "summary": { - "description": "Title of the calendar.", - "type": "string" - }, - "timeZone": { - "description": "The time zone of the calendar.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the calendar (as a ISO 8601 timestamp).", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Returns a single event on the specified calendar." - }, - "description": "Returns a single event from a given calendar.\n\nUse this tool for queries like:\n\n * Get details for the team meeting.\n * Show me the event with id event123 on my calendar.\n\nExample:\n\n get_event(\n eventId='event123'\n )\n # Returns the event details for the event with id `event123` on the user's primary calendar.\n", - "inputSchema": { - "properties": { - "calendarId": { - "description": "Optional. The calendar ID to get the event from. The default is the user's primary calendar.", - "type": "string" - }, - "eventId": { - "description": "Required. The ID of the event to get.", - "type": "string" - } - }, - "required": [ - "eventId" - ], - "type": "object" - }, - "name": "get_event", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Returns the calendars on the user's calendar list." - }, - "description": "Returns the calendars on the user's calendar list.\n\nUse this tool for queries like:\n\n * What are all my calendars?\n\nExample:\n\n list_calendars()\n # Returns all calendars the authenticated user has access to.\n", - "inputSchema": { - "properties": { - "pageSize": { - "description": "Optional. Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. Token specifying which result page to return.", - "type": "string" - } - }, - "type": "object" - }, - "name": "list_calendars", - "outputSchema": { - "$defs": { - "Calendar": { - "description": "A calendar is a collection of related events, along with additional metadata such as summary, default time zone, etc. Each calendar is identified by an ID, which is an email address. Calendars can be shared with others. Primary calendars are owned by their associated user account, other calendars are owned by a single data owner.", - "properties": { - "description": { - "description": "Description of the calendar. Optional. Read-only.", - "type": "string" - }, - "id": { - "description": "Identifier. Identifier of the calendar.", - "type": "string", - "x-google-identifier": true - }, - "summary": { - "description": "Title of the calendar. Read-only.", - "type": "string" - }, - "timeZone": { - "description": "The time zone of the calendar. Optional. Read-only.", - "type": "string" - } - }, - "type": "object" - } - }, - "properties": { - "calendars": { - "description": "List of calendars on the calendar list.", - "items": { - "$ref": "#/$defs/Calendar" - }, - "type": "array" - }, - "nextPageToken": { - "description": "Token used to access the next page of this result. Omitted if no further results are available.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Suggests time periods across one or more calendars." - }, - "description": "Suggests time periods across one or more calendars. To access the primary calendar, add 'primary' in the attendee_emails field.\n\nUse this tool for queries like:\n\n * When are all of us free for a meeting?\n * Find a 30 minute slot where we are both available.\n * Check if jane.doe@google.com is free on Monday morning.\n\nExample:\n\n suggest_time(\n attendeeEmails=['joedoe@gmail.com', 'janedoe@gmail.com'],\n startTime='2024-09-10T00:00:00',\n endTime='2024-09-17T00:00:00',\n durationMinutes=60,\n preferences={\n 'startHour': '09:00',\n 'endHour': '17:00',\n 'excludeWeekends': True\n }\n )\n # Returns up to 5 suggested time slots where both users are available for at least one hour between 9:00 AM and 5:00 PM on weekdays from September 10 through September 16, 2024.\n", - "inputSchema": { - "$defs": { - "Preferences": { - "description": "Preferences for the suggested time slots.", - "properties": { - "endHour": { - "description": "The preferred end hour of day (e.g., `17:00`).", - "type": "string" - }, - "excludeWeekends": { - "description": "Whether to exclude weekends.", - "type": "boolean" - }, - "pageSize": { - "description": "Maximum number of time slots to return. Default is 5.", - "format": "int32", - "type": "integer" - }, - "startHour": { - "description": "The preferred start hour of day (e.g., `09:00`).", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Request message for SuggestTime.", - "properties": { - "attendeeEmails": { - "description": "Required. The attendee emails to find free time for.", - "items": { - "type": "string" - }, - "type": "array" - }, - "durationMinutes": { - "description": "Optional. Minimum duration of a free time slot in minutes. The default is 30 minutes.", - "format": "int32", - "type": "integer" - }, - "endTime": { - "description": "Required. The end of the interval for the query formatted as per ISO 8601.", - "type": "string" - }, - "preferences": { - "$ref": "#/$defs/Preferences", - "description": "The preferences to find suggested time for." - }, - "startTime": { - "description": "Required. The start of the interval for the query formatted as per ISO 8601.", - "type": "string" - }, - "timeZone": { - "description": "Optional. Time zone used for the time values. This field accepts IANA Time Zone database names, e.g., `America/Los_Angeles`. The default is the time zone of the user's primary calendar.", - "type": "string" - } - }, - "required": [ - "attendeeEmails", - "startTime", - "endTime" - ], - "type": "object" - }, - "name": "suggest_time", - "outputSchema": { - "$defs": { - "TimeSlot": { - "description": "A time slot is a period of time that is available for an event.", - "properties": { - "durationMinutes": { - "description": "The duration of the free time slot in minutes.", - "format": "int32", - "type": "integer" - }, - "endTime": { - "description": "The end time of the free time slot as an ISO 8601 formatted timestamp.", - "type": "string" - }, - "startTime": { - "description": "The start time of the free time slot as an ISO 8601 formatted timestamp.", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for SuggestTime.", - "properties": { - "timeSlots": { - "description": "List of suggested time slots.", - "items": { - "$ref": "#/$defs/TimeSlot" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Creates a calendar event." - }, - "description": "Creates a calendar event.\n\nUse this tool for queries like:\n\n * Create an event on my calendar for tomorrow at 2pm called 'Meeting with Jane'.\n * Schedule a meeting with john.doe@google.com next Monday from 10am to 11am.\n\nExample:\n\n create_event(\n summary='Meeting with Jane',\n startTime='2024-09-17T14:00:00',\n endTime='2024-09-17T15:00:00'\n )\n # Creates an event on the primary calendar for September 17, 2024 from 2pm to 3pm called 'Meeting with Jane'.\n", - "inputSchema": { - "$defs": { - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "required": [ - "method", - "minutes" - ], - "type": "object" - } - }, - "description": "Request message for CreateEvent.", - "properties": { - "addGoogleMeetUrl": { - "description": "Optional. Allows to create a Google Meet url for the event. By default, no Google Meet url is created. No Google Meet url is created if Meet is disabled for the user, but the event creation will succeed.", - "type": "boolean" - }, - "allDay": { - "description": "Optional. Whether the event is an all-day event. The default is False. If true, the start and end time must be set to midnight UTC.", - "type": "boolean" - }, - "attendeeEmails": { - "description": "Optional. The additional attendees of the event, as email addresses. For events that are created on the user's primary calendar with at least one other attendee, the current user will automatically be added as an attendee if not already included in this list.", - "items": { - "type": "string" - }, - "type": "array" - }, - "calendarId": { - "description": "Optional. The calendar ID to create the event on. The default is the user's primary calendar.", - "type": "string" - }, - "colorId": { - "description": "Optional. The color of the event. This is an ID referring to an entry in the calendar's color palette. Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato.", - "type": "string" - }, - "description": { - "description": "Optional. Description of the event. Can contain HTML.", - "type": "string" - }, - "endTime": { - "description": "Required. The end time of the event formatted as per ISO 8601.", - "type": "string" - }, - "googleMeetUrl": { - "description": "Optional. Allows attaching an existing Google Meet URL or meeting ID to the event. If set, this URL will be attached to the event instead of generating a new Google Meet room, even if `add_google_meet_url` is set to `true`.", - "type": "string" - }, - "location": { - "description": "Optional. Geographic location of the event as free-form text.", - "type": "string" - }, - "notificationLevel": { - "description": "Optional. Which email notification should be sent for this event update. Possible values are: * `NONE` - No email notifications are sent (default). * `EXTERNAL_ONLY` - Only external (non-Calendar) attendees receive email notifications. * `ALL` - All event attendees receive email notifications.", - "enum": [ - "NOTIFICATION_LEVEL_UNSPECIFIED", - "NONE", - "EXTERNAL_ONLY", - "ALL" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Default value. Will be treated as NONE.", - "No email notifications are sent.", - "Only external (non-Calendar) attendees receive email notifications.", - "All event attendees receive email notifications." - ] - }, - "overrideReminders": { - "description": "Optional. Reminders defined for this event, overriding the default reminders for the calendar. If not set, the event will use the default reminders of the calendar.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrenceData": { - "description": "Optional. The recurrence data of the event as `RRULE`, `RDATE` or `EXDATE` as per RFC 5545. Use this field to create a recurring event.", - "items": { - "type": "string" - }, - "type": "array" - }, - "startTime": { - "description": "Required. The start time of the event formatted as per ISO 8601.", - "type": "string" - }, - "summary": { - "description": "Required. Title of the event.", - "type": "string" - }, - "timeZone": { - "description": "Optional. Time zone of the event (formatted as an IANA Time Zone Database name, e.g. \"Europe/Zurich\"). Optional, but recommended to provide. It is also used to resolve timezone-less dates in the request. The default is the time zone of the calendar.", - "type": "string" - }, - "visibility": { - "description": "Optional. Visibility of the event. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details.", - "type": "string" - } - }, - "required": [ - "summary", - "startTime", - "endTime" - ], - "type": "object" - }, - "name": "create_event", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Updates a calendar event." - }, - "description": "Updates a calendar event.\n\nUse this tool for queries like:\n\n * Update the event 'Meeting with Jane' to be one hour later.\n * Add john.doe@google.com to the meeting tomorrow.\n\nExample:\n\n update_event(\n eventId='event123',\n summary='Meeting with Jane and John'\n )\n # Updates the summary of event with id 'event123' on the primary calendar to 'Meeting with Jane and John'.\n", - "inputSchema": { - "$defs": { - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "required": [ - "method", - "minutes" - ], - "type": "object" - } - }, - "description": "Request message for UpdateEvent.", - "properties": { - "addGoogleMeetUrl": { - "description": "Optional. Allows to create or update a Google Meet url for the event. By default, no Google Meet url is created or updated. No Google Meet url is created or updated if Meet is disabled for the user, but the event update will succeed.", - "type": "boolean" - }, - "addedAttendeeEmails": { - "description": "Optional. The additional attendees of the event, as email addresses.", - "items": { - "type": "string" - }, - "type": "array" - }, - "calendarId": { - "description": "Optional. The calendar ID of the event to update. The default is the user's primary calendar.", - "type": "string" - }, - "colorId": { - "description": "Optional. New color ID of the event. Will not be updated if not set. Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato.", - "type": "string" - }, - "description": { - "description": "Optional. The new description of the event. Will not be updated if not set.", - "type": "string" - }, - "endTime": { - "description": "Optional. The new end time of the event formatted as per ISO 8601. Will not be updated if not set.", - "type": "string" - }, - "eventId": { - "description": "Required. The ID of the event to update.", - "type": "string" - }, - "googleMeetUrl": { - "description": "Optional. Allows attaching an existing Google Meet URL or meeting ID to the event. If set, this URL will be attached to the event instead of generating a new Google Meet room, even if `add_google_meet_url` is set to `true`.", - "type": "string" - }, - "location": { - "description": "Optional. The new location of the event. Will not be updated if not set.", - "type": "string" - }, - "notificationLevel": { - "description": "Optional. Which email notification should be sent for this event update. Possible values are: * `NONE` - No email notifications are sent (default). * `EXTERNAL_ONLY` - Only external (non-Calendar) attendees receive email notifications. * `ALL` - All event attendees receive email notifications.", - "enum": [ - "NOTIFICATION_LEVEL_UNSPECIFIED", - "NONE", - "EXTERNAL_ONLY", - "ALL" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Default value. Will be treated as NONE.", - "No email notifications are sent.", - "Only external (non-Calendar) attendees receive email notifications.", - "All event attendees receive email notifications." - ] - }, - "overrideReminders": { - "description": "Optional. Reminders defined for this event, overriding any existing reminders and the default reminders for the calendar. If set, this will replace all existing reminders on the event. If not set, reminders will not be updated.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "removedAttendeeEmails": { - "description": "Optional. The attendees of the event to remove, as email addresses.", - "items": { - "type": "string" - }, - "type": "array" - }, - "startTime": { - "description": "Optional. The new start time of the event formatted as per ISO 8601. Will not be updated if not set.", - "type": "string" - }, - "summary": { - "description": "Optional. The new title of the event. Will not be updated if not set.", - "type": "string" - }, - "visibility": { - "description": "Optional. New visibility of the event. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details.", - "type": "string" - } - }, - "required": [ - "eventId" - ], - "type": "object" - }, - "name": "update_event", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": true, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Deletes a calendar event." - }, - "description": "Deletes a calendar event.\n\nUse this tool for queries like:\n\n * Delete the event with id event123 on my calendar.\n\nTo cancel or decline an event, use the respond_to_event tool instead.\n\nExample:\n\n delete_event(\n eventId='event123'\n )\n # Deletes the event with id 'event123' on the user's primary calendar.\n", - "inputSchema": { - "description": "Request message for DeleteEvent.", - "properties": { - "calendarId": { - "description": "Optional. The calendar ID of the event to delete. The default is the user's primary calendar.", - "type": "string" - }, - "eventId": { - "description": "Required. The ID of the event to delete.", - "type": "string" - }, - "notificationLevel": { - "description": "Optional. Which email notification should be sent for this event update. Possible values are: * `NONE` - No email notifications are sent (default). * `EXTERNAL_ONLY` - Only external (non-Calendar) attendees receive email notifications. * `ALL` - All event attendees receive email notifications.", - "enum": [ - "NOTIFICATION_LEVEL_UNSPECIFIED", - "NONE", - "EXTERNAL_ONLY", - "ALL" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Default value. Will be treated as NONE.", - "No email notifications are sent.", - "Only external (non-Calendar) attendees receive email notifications.", - "All event attendees receive email notifications." - ] - } - }, - "required": [ - "eventId" - ], - "type": "object" - }, - "name": "delete_event", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Responds to an event." - }, - "description": "Responds to an event.\n\nUse this tool for queries like:\n\n * Accept the event with id event123 on my calendar.\n * Decline the meeting with Jane.\n * Cancel my next meeting.\n * Tentatively accept the planing meeting.\n\nExample:\n\n respond_to_event(\n eventId='event123',\n responseStatus='accepted'\n )\n # Responds with status 'accepted' to the event with id 'event123' on the user's primary calendar.\n", - "inputSchema": { - "description": "Request message for RespondToEvent.", - "properties": { - "calendarId": { - "description": "Optional. The calendar ID of the event to respond to. The default is the user's primary calendar.", - "type": "string" - }, - "eventId": { - "description": "Required. The ID of the event to respond to.", - "type": "string" - }, - "notificationLevel": { - "description": "Optional. Which email notification should be sent for this event update. Possible values are: * `NONE` - No email notifications are sent (default). * `EXTERNAL_ONLY` - Only external (non-Calendar) attendees receive email notifications. * `ALL` - All event attendees receive email notifications.", - "enum": [ - "NOTIFICATION_LEVEL_UNSPECIFIED", - "NONE", - "EXTERNAL_ONLY", - "ALL" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Default value. Will be treated as NONE.", - "No email notifications are sent.", - "Only external (non-Calendar) attendees receive email notifications.", - "All event attendees receive email notifications." - ] - }, - "responseComment": { - "description": "Optional. The user's comment attached to the response.", - "type": "string" - }, - "responseStatus": { - "description": "Required. The new user's response status of the event. Possible values are: * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - } - }, - "required": [ - "eventId", - "responseStatus" - ], - "type": "object" - }, - "name": "respond_to_event", - "outputSchema": { - "$defs": { - "Attendee": { - "properties": { - "additionalGuests": { - "description": "Number of additional guests. Optional. The default is 0.", - "format": "int32", - "type": "integer" - }, - "comment": { - "description": "The attendee's response comment. Optional.", - "type": "string" - }, - "displayName": { - "description": "The attendee's name, if available. Optional.", - "type": "string" - }, - "email": { - "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322. Required when adding an attendee.", - "type": "string" - }, - "id": { - "description": "The attendee's Profile ID, if available.", - "type": "string" - }, - "optionalAttendee": { - "description": "Whether this is an optional attendee. Optional. The default is False.", - "type": "boolean" - }, - "organizer": { - "description": "Whether the attendee is the organizer of the event. Read-only. The default is False.", - "type": "boolean" - }, - "resource": { - "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.", - "type": "boolean" - }, - "responseStatus": { - "description": "The attendee's response status. Possible values are: * `needsAction` - The attendee has not responded to the invitation (recommended for new events). * `declined` - The attendee has declined the invitation. * `tentative` - The attendee has tentatively accepted the invitation. * `accepted` - The attendee has accepted the invitation.", - "type": "string" - }, - "self": { - "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "DateOrDateTime": { - "description": "A date or date-time with optional timezone. Only one of the fields `date` or `date_time` must ever be set, never both.", - "properties": { - "date": { - "description": "An ISO 8601 formatted date at midnight UTC such as `2019-11-20T00:00:00Z`. If this field is set, `date_time` must not be set.", - "type": "string" - }, - "dateTime": { - "description": "An ISO 8601 formatted timestamp such as `2019-11-20T08:19:06-07:00` or `2019-11-20T08:19:06Z`. If this field is set, `date` must not be set.", - "type": "string" - }, - "timeZone": { - "description": "TZDB timezone name if available.", - "type": "string" - } - }, - "type": "object" - }, - "Principal": { - "properties": { - "displayName": { - "description": "The principal's name, if available.", - "type": "string" - }, - "email": { - "description": "Email address of the principal (calendar).", - "type": "string" - }, - "self": { - "description": "Whether this principal corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.", - "type": "boolean" - } - }, - "type": "object" - }, - "Reminder": { - "description": "An event reminder.", - "properties": { - "method": { - "description": "Required. How the reminder is delivered to the user. Possible values are: * `email` - Reminders are sent via email. * `popup` - Reminders are sent via a UI popup. Required when adding a reminder.", - "type": "string" - }, - "minutes": { - "description": "Required. Number of minutes in advance that the reminder should be delivered. Required when adding a reminder.", - "format": "int32", - "type": "integer" - } - }, - "type": "object" - } - }, - "properties": { - "attendees": { - "description": "The attendees of the event.", - "items": { - "$ref": "#/$defs/Attendee" - }, - "type": "array" - }, - "colorId": { - "description": "Event color ID (string `1`-`11`): * 1: Lavender * 2: Sage * 3: Grape * 4: Flamingo * 5: Banana * 6: Tangerine * 7: Peacock * 8: Graphite * 9: Blueberry * 10: Basil * 11: Tomato. In Google Calendar, event colors function as categories — settable per-event or per-series. Users may assign custom labels to colors in the web UI (e.g., `1:1s`, `Break`), but the API only exposes numeric IDs, not those labels. Only affects your own calendar view — each attendee controls their own event color.", - "type": "string" - }, - "conferenceUrl": { - "description": "The Google Meet link for the event.", - "type": "string" - }, - "created": { - "description": "Creation time of the event (as a ISO 8601 formatted timestamp). Read-only.", - "type": "string" - }, - "creator": { - "$ref": "#/$defs/Principal", - "description": "The creator of the event. Read-only." - }, - "description": { - "description": "Description of the event. Can contain HTML. Optional.", - "type": "string" - }, - "end": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance." - }, - "eventType": { - "description": "Specific type of the event. This cannot be modified after the event is created. Possible values are: * `birthday` - A special all-day event with an annual recurrence. * `default` - A regular event or not further specified. * `focusTime` - A focus-time event. * `fromGmail` - An event from Gmail. This type of event cannot be created. * `outOfOffice` - An out-of-office event. * `workingLocation` - A working location event.", - "type": "string" - }, - "htmlLink": { - "description": "An absolute link to this event in the Google Calendar Web UI. Read-only.", - "type": "string" - }, - "id": { - "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: * characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 * the length of the ID must be between 5 and 1024 characters * the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122. If you do not specify an ID, it will be automatically generated by the server. Note that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.", - "type": "string" - }, - "location": { - "description": "Geographic location of the event as free-form text. Optional.", - "type": "string" - }, - "organizer": { - "$ref": "#/$defs/Principal", - "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. Read-only." - }, - "originalStartTime": { - "$ref": "#/$defs/DateOrDateTime", - "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. It uniquely identifies the instance within the recurring event series even if the instance was moved to a different time. Immutable." - }, - "overrideReminders": { - "description": "Reminders defined for this event, overriding the default reminders for the calendar. If not set, the default reminders on the calendar are used.", - "items": { - "$ref": "#/$defs/Reminder" - }, - "type": "array" - }, - "recurrence": { - "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.", - "items": { - "type": "string" - }, - "type": "array" - }, - "recurringEventId": { - "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable.", - "type": "string" - }, - "start": { - "$ref": "#/$defs/DateOrDateTime", - "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance." - }, - "status": { - "description": "Status of the event. Optional. Possible values are: * `confirmed` - The event is confirmed. This is the default status. * `tentative` - The event is tentatively confirmed. * `cancelled` - The event is cancelled (deleted). The list method returns cancelled events only on incremental sync (when syncToken or updatedMin are specified) or if the showDeleted flag is set to true. The get method always returns them. A cancelled status represents two different states depending on the event type: 1. Cancelled exceptions of an uncancelled recurring event indicate that this instance should no longer be presented to the user. Clients should store these events for the lifetime of the parent recurring event.Cancelled exceptions are only guaranteed to have values for the id, recurringEventId and originalStartTime fields populated. The other fields might be empty. 2. All other cancelled events represent deleted events. Clients should remove their locally synced copies. Such cancelled events will eventually disappear, so do not rely on them being available indefinitely. Deleted events are only guaranteed to have the id field populated. On the organizer's calendar, cancelled events continue to expose event details (summary, location, etc.) so that they can be restored (undeleted). Similarly, the events to which the user was invited and that they manually removed continue to provide details. However, incremental sync requests with showDeleted set to false will not return these details. If an event changes its organizer (for example via the move operation) and the original organizer is not on the attendee list, it will leave behind a cancelled event where only the id field is guaranteed to be populated.", - "type": "string" - }, - "summary": { - "description": "Title of the event.", - "type": "string" - }, - "transparency": { - "description": "Whether the event blocks time on the calendar. Optional. Possible values are: * `opaque` - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. * `transparent` - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.", - "type": "string" - }, - "updated": { - "description": "Last modification time of the main event data (as a ISO 8601 formatted timestamp). Updating event reminders will not cause this to change. Read-only.", - "type": "string" - }, - "visibility": { - "description": "Visibility of the event. Optional. Possible values are: * `default` - Uses the default visibility for events on the calendar. This is the default value. * `public` - The event is public and event details are visible to all readers of the calendar. * `private` - The event is private and only event attendees may view event details. * `confidential` - The event is private. This value is provided for compatibility reasons.", - "type": "string" - } - }, - "type": "object" - } - } - ] -} diff --git a/google-workspace/server/tools/generated/chat.json b/google-workspace/server/tools/generated/chat.json deleted file mode 100644 index 64510ae6..00000000 --- a/google-workspace/server/tools/generated/chat.json +++ /dev/null @@ -1,977 +0,0 @@ -{ - "service": "chat", - "scopes": [ - "https://www.googleapis.com/auth/chat.spaces", - "https://www.googleapis.com/auth/chat.spaces.readonly", - "https://www.googleapis.com/auth/chat.memberships", - "https://www.googleapis.com/auth/chat.memberships.readonly", - "https://www.googleapis.com/auth/chat.messages", - "https://www.googleapis.com/auth/chat.messages.readonly" - ], - "tools": [ - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Retrieves messages from a specified Google Chat conversation (Space, direct message (DM) or group DM). Allows filtering by thread, time range, and number of messages. Additionally, the next page of messages can be retrieved to allow for more context. Private messages (messages only visible to a single user) are filtered out.\n", - "inputSchema": { - "description": "Request message for ListMessages RPC.", - "properties": { - "conversationId": { - "description": "Required. The ID of the conversation. A conversation can either be a space, direct message (DM) or group DM/Chat. Format: spaces/{space}", - "type": "string" - }, - "endTime": { - "description": "Optional. ISO 8601 timestamp to filter messages. Only messages created before this time will be returned.", - "type": "string" - }, - "pageSize": { - "description": "Optional. The maximum number of messages to return. The service may return fewer than this value. If unspecified, defaults to 20. The maximum allowed value is 50.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A page token, received from a previous list_messages call. Provide this to retrieve the subsequent page.", - "type": "string" - }, - "startTime": { - "description": "Optional. ISO 8601 timestamp to filter messages. Only messages created after this time will be returned.", - "type": "string" - }, - "threadId": { - "description": "Optional. The ID of a specific thread within the conversation. If provided, only messages from this thread will be returned. If omitted, messages from all threads in the conversation are considered. Format: spaces/{space}/threads/{thread}", - "type": "string" - } - }, - "required": [ - "conversationId" - ], - "type": "object" - }, - "name": "list_messages", - "outputSchema": { - "$defs": { - "ChatAttachmentMetadata": { - "description": "Metadata for a user-uploaded attachment in a chat message.", - "properties": { - "attachmentId": { - "description": "Resource name of the attachment. Format: spaces/{space}/messages/{message}/attachments/{attachment}.", - "type": "string" - }, - "filename": { - "description": "Name of the attachment.", - "type": "string" - }, - "mimeType": { - "description": "Content type (MIME type).", - "type": "string" - }, - "source": { - "description": "The source of the attachment.", - "enum": [ - "SOURCE_UNSPECIFIED", - "DRIVE_FILE", - "UPLOADED_CONTENT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Reserved.", - "The file is a Google Drive file.", - "The file is uploaded to Chat." - ] - } - }, - "type": "object" - }, - "ChatMessage": { - "description": "Represents a single message in a chat conversation.", - "properties": { - "attachments": { - "description": "Attachments included in the message.", - "items": { - "$ref": "#/$defs/ChatAttachmentMetadata" - }, - "type": "array" - }, - "createTime": { - "description": "Output only. Timestamp when the message was created.", - "readOnly": true, - "type": "string" - }, - "messageId": { - "description": "Resource name of the message. Format: spaces/{space}/messages/{message}", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body of the message.", - "type": "string" - }, - "reactionSummaries": { - "description": "The emoji reactions summary included in the message.", - "items": { - "$ref": "#/$defs/ReactionSummary" - }, - "type": "array" - }, - "sender": { - "$ref": "#/$defs/User", - "description": "The sender of the message." - }, - "threadId": { - "description": "The thread this message belongs to. This will be empty if the message is unthreaded. Format: spaces/{space}/threads/{thread}", - "type": "string" - }, - "threadedReply": { - "description": "Whether message is a thread reply.", - "type": "boolean" - } - }, - "type": "object" - }, - "ReactionSummary": { - "description": "Summary of emoji reactions on a chat message.", - "properties": { - "count": { - "description": "The total number of reactions using the associated emoji.", - "format": "int32", - "type": "integer" - }, - "emoji": { - "description": "The emoji unicode string or custom emoji name.", - "type": "string" - } - }, - "type": "object" - }, - "User": { - "description": "Represents a Google Chat user.", - "properties": { - "displayName": { - "description": "The display name of a Chat user.", - "type": "string" - }, - "email": { - "description": "The email address of the user. This field is only populated when the user type is HUMAN.", - "type": "string" - }, - "userId": { - "description": "Resource name of a Chat user. Format: users/{user}.", - "type": "string" - }, - "userType": { - "description": "The type of the user.", - "enum": [ - "USER_TYPE_UNSPECIFIED", - "HUMAN", - "APP" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "Human user.", - "App user." - ] - } - }, - "type": "object" - } - }, - "description": "Response message for ListMessages RPC.", - "properties": { - "messages": { - "description": "List of messages retrieved, in reverse chronological order (newest first).", - "items": { - "$ref": "#/$defs/ChatMessage" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token, which can be sent as `page_token` in a subsequent `ListMessagesRequest` to retrieve the next page of messages. If this field is empty, there are no more pages.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "readOnlyHint": true - }, - "description": "Searches for Google Chat conversations by display name.\n\nIf only participants are provided, this tool finds 1:1 direct messages (if one participant is provided) or group chats (if multiple participants are provided) that include the specified participants and the calling user.\n\nIf only a query is provided, this tool searches for conversations where the query is a case-insensitive substring of the conversation's display name.\n\nIf both participants and query are provided, this tool finds conversations by participants and then filters them by display name.\n\nIf neither participants nor query are provided, this tool lists all conversations the calling user is a member of.\n\nThis tool only lists conversations the calling user is a member of.\n\nIMPORTANT: An empty 'conversations' list does not mean there are no more results overall. If 'next_page_token' is present, more pages can be fetched. If you get an empty list but a 'next_page_token', ask the user if you should continue the searching.\n", - "inputSchema": { - "description": "Request message for FindConversations RPC.", - "properties": { - "pageSize": { - "description": "Optional. The maximum number of spaces to return. The service may return fewer than this value. If unspecified, at most 100 spaces will be returned. The maximum value is 1000; values above 1000 will be coerced to 1000.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A page token, received from a previous `search_conversations` call. Provide this to retrieve the subsequent page.", - "type": "string" - }, - "participants": { - "description": "Optional. List of email addresses of the participants to filter the conversations, excluding the caller.", - "items": { - "type": "string" - }, - "type": "array" - }, - "spaceNameQuery": { - "description": "Optional. The text to search for within the space display names.", - "type": "string" - } - }, - "type": "object" - }, - "name": "search_conversations", - "outputSchema": { - "$defs": { - "Conversation": { - "description": "Represents a Chat conversation.", - "properties": { - "conversationId": { - "description": "The ID of the conversation (e.g., \"spaces/AAAAAAAAA\").", - "type": "string" - }, - "conversationType": { - "description": "The type of conversation (DIRECT_MESSAGE, GROUP_CHAT, or NAMED_SPACE).", - "enum": [ - "CONVERSATION_TYPE_UNSPECIFIED", - "NAMED_SPACE", - "GROUP_CHAT", - "DIRECT_MESSAGE" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "A named space.", - "A group chat between 3 or more people.", - "A direct message between two humans, or a human and a Chat app." - ] - }, - "displayName": { - "description": "The display name of the conversation.", - "type": "string" - }, - "lastActiveTimestamp": { - "description": "The last active time of the conversation in ISO 8601 format.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for FindConversations RPC.", - "properties": { - "conversations": { - "description": "List of conversation objects that match the search criteria.", - "items": { - "$ref": "#/$defs/Conversation" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token that can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Retrieves messages from a specified Google Chat conversation (Space, direct message (DM) or group DM). Allows filtering by thread, time range, and number of messages. Additionally, the next page of messages can be retrieved to allow for more context. Private messages (messages only visible to a single user) are filtered out.\n", - "inputSchema": { - "description": "Request message for ListMessages RPC.", - "properties": { - "conversationId": { - "description": "Required. The ID of the conversation. A conversation can either be a space, direct message (DM) or group DM/Chat. Format: spaces/{space}", - "type": "string" - }, - "endTime": { - "description": "Optional. ISO 8601 timestamp to filter messages. Only messages created before this time will be returned.", - "type": "string" - }, - "pageSize": { - "description": "Optional. The maximum number of messages to return. The service may return fewer than this value. If unspecified, defaults to 20. The maximum allowed value is 50.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A page token, received from a previous list_messages call. Provide this to retrieve the subsequent page.", - "type": "string" - }, - "startTime": { - "description": "Optional. ISO 8601 timestamp to filter messages. Only messages created after this time will be returned.", - "type": "string" - }, - "threadId": { - "description": "Optional. The ID of a specific thread within the conversation. If provided, only messages from this thread will be returned. If omitted, messages from all threads in the conversation are considered. Format: spaces/{space}/threads/{thread}", - "type": "string" - } - }, - "required": [ - "conversationId" - ], - "type": "object" - }, - "name": "list_messages", - "outputSchema": { - "$defs": { - "ChatAttachmentMetadata": { - "description": "Metadata for a user-uploaded attachment in a chat message.", - "properties": { - "attachmentId": { - "description": "Resource name of the attachment. Format: spaces/{space}/messages/{message}/attachments/{attachment}.", - "type": "string" - }, - "filename": { - "description": "Name of the attachment.", - "type": "string" - }, - "mimeType": { - "description": "Content type (MIME type).", - "type": "string" - }, - "source": { - "description": "The source of the attachment.", - "enum": [ - "SOURCE_UNSPECIFIED", - "DRIVE_FILE", - "UPLOADED_CONTENT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Reserved.", - "The file is a Google Drive file.", - "The file is uploaded to Chat." - ] - } - }, - "type": "object" - }, - "ChatMessage": { - "description": "Represents a single message in a chat conversation.", - "properties": { - "attachments": { - "description": "Attachments included in the message.", - "items": { - "$ref": "#/$defs/ChatAttachmentMetadata" - }, - "type": "array" - }, - "createTime": { - "description": "Output only. Timestamp when the message was created.", - "readOnly": true, - "type": "string" - }, - "messageId": { - "description": "Resource name of the message. Format: spaces/{space}/messages/{message}", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body of the message.", - "type": "string" - }, - "reactionSummaries": { - "description": "The emoji reactions summary included in the message.", - "items": { - "$ref": "#/$defs/ReactionSummary" - }, - "type": "array" - }, - "sender": { - "$ref": "#/$defs/User", - "description": "The sender of the message." - }, - "threadId": { - "description": "The thread this message belongs to. This will be empty if the message is unthreaded. Format: spaces/{space}/threads/{thread}", - "type": "string" - }, - "threadedReply": { - "description": "Whether message is a thread reply.", - "type": "boolean" - } - }, - "type": "object" - }, - "ReactionSummary": { - "description": "Summary of emoji reactions on a chat message.", - "properties": { - "count": { - "description": "The total number of reactions using the associated emoji.", - "format": "int32", - "type": "integer" - }, - "emoji": { - "description": "The emoji unicode string or custom emoji name.", - "type": "string" - } - }, - "type": "object" - }, - "User": { - "description": "Represents a Google Chat user.", - "properties": { - "displayName": { - "description": "The display name of a Chat user.", - "type": "string" - }, - "email": { - "description": "The email address of the user. This field is only populated when the user type is HUMAN.", - "type": "string" - }, - "userId": { - "description": "Resource name of a Chat user. Format: users/{user}.", - "type": "string" - }, - "userType": { - "description": "The type of the user.", - "enum": [ - "USER_TYPE_UNSPECIFIED", - "HUMAN", - "APP" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "Human user.", - "App user." - ] - } - }, - "type": "object" - } - }, - "description": "Response message for ListMessages RPC.", - "properties": { - "messages": { - "description": "List of messages retrieved, in reverse chronological order (newest first).", - "items": { - "$ref": "#/$defs/ChatMessage" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token, which can be sent as `page_token` in a subsequent `ListMessagesRequest` to retrieve the next page of messages. If this field is empty, there are no more pages.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Searches for Google Chat messages using keywords and filters. Works across all spaces the user has access to, or can be scoped to a specific conversation.\n", - "inputSchema": { - "$defs": { - "SearchParameters": { - "description": "Configures the parameters used for searching messages.", - "properties": { - "conversationId": { - "description": "Optional. Scopes the search to a specific conversation identifier, as returned from the search_conversations tool. Format: `spaces/{ID}`.", - "type": "string" - }, - "conversationIncludesUser": { - "description": "Optional. Filter for messages in DMs and group chats that include the specific user email or ID.", - "type": "string" - }, - "endTime": { - "description": "Optional. Filter for messages created before this time. Format: ISO 8601 timestamp.", - "type": "string" - }, - "hasLink": { - "description": "Optional. Filter for messages containing at least one URL.", - "type": "boolean" - }, - "isUnread": { - "description": "Optional. Filter for messages that have not been read by the calling user.", - "type": "boolean" - }, - "keywords": { - "description": "Optional. A set of keywords which are used to filter the results.", - "items": { - "type": "string" - }, - "type": "array" - }, - "mentionsMe": { - "description": "Optional. Filter for messages that explicitly mention the calling user.", - "type": "boolean" - }, - "sender": { - "description": "Optional. Filter for messages from a specific user. Either the email or resource name of the sender can be used. User resource names are formatted as `users/{ID}`, where `{ID}` can be a person ID or their email address.", - "type": "string" - }, - "spaceDisplayNames": { - "description": "Optional. Filter by a list of space names; space display names are partially matched. Note: Only the top 5 matches are returned.", - "items": { - "type": "string" - }, - "type": "array" - }, - "startTime": { - "description": "Optional. Filter for messages created after this time. Format: ISO 8601 timestamp.", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Request to search for Google Chat messages using keywords and filters. Works across all spaces the user has access to, or can be scoped to a specific conversation.", - "properties": { - "orderBy": { - "description": "Optional. Specifies the order in which the results should be returned. Supported values: `CREATE_TIME_DESC`, `CREATE_TIME_ASC`, or `RELEVANCE_DESC`. NOTE: `RELEVANCE_DESC` cannot be used when the is_unread filter is used. By default, `RELEVANCE_DESC` is used if `is_unread` is not set to true, otherwise `CREATE_TIME_DESC` is used.", - "enum": [ - "ORDER_BY_UNSPECIFIED", - "CREATE_TIME_DESC", - "RELEVANCE_DESC" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Default value.", - "Order by create time in descending order.", - "Order by relevance in descending order." - ] - }, - "pageSize": { - "description": "Optional. The maximum number of results to return (max up to 100). If unspecified, at most 25 are returned.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A page token, received from a previous `search_messages` call. Provide this to retrieve the subsequent page.", - "type": "string" - }, - "searchParameters": { - "$ref": "#/$defs/SearchParameters", - "description": "Required. The search parameters to use for the search." - } - }, - "required": [ - "searchParameters" - ], - "type": "object" - }, - "name": "search_messages", - "outputSchema": { - "$defs": { - "ChatAttachmentMetadata": { - "description": "Metadata for a user-uploaded attachment in a chat message.", - "properties": { - "attachmentId": { - "description": "Resource name of the attachment. Format: spaces/{space}/messages/{message}/attachments/{attachment}.", - "type": "string" - }, - "filename": { - "description": "Name of the attachment.", - "type": "string" - }, - "mimeType": { - "description": "Content type (MIME type).", - "type": "string" - }, - "source": { - "description": "The source of the attachment.", - "enum": [ - "SOURCE_UNSPECIFIED", - "DRIVE_FILE", - "UPLOADED_CONTENT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Reserved.", - "The file is a Google Drive file.", - "The file is uploaded to Chat." - ] - } - }, - "type": "object" - }, - "ChatMessage": { - "description": "Represents a single message in a chat conversation.", - "properties": { - "attachments": { - "description": "Attachments included in the message.", - "items": { - "$ref": "#/$defs/ChatAttachmentMetadata" - }, - "type": "array" - }, - "createTime": { - "description": "Output only. Timestamp when the message was created.", - "readOnly": true, - "type": "string" - }, - "messageId": { - "description": "Resource name of the message. Format: spaces/{space}/messages/{message}", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body of the message.", - "type": "string" - }, - "reactionSummaries": { - "description": "The emoji reactions summary included in the message.", - "items": { - "$ref": "#/$defs/ReactionSummary" - }, - "type": "array" - }, - "sender": { - "$ref": "#/$defs/User", - "description": "The sender of the message." - }, - "threadId": { - "description": "The thread this message belongs to. This will be empty if the message is unthreaded. Format: spaces/{space}/threads/{thread}", - "type": "string" - }, - "threadedReply": { - "description": "Whether message is a thread reply.", - "type": "boolean" - } - }, - "type": "object" - }, - "ReactionSummary": { - "description": "Summary of emoji reactions on a chat message.", - "properties": { - "count": { - "description": "The total number of reactions using the associated emoji.", - "format": "int32", - "type": "integer" - }, - "emoji": { - "description": "The emoji unicode string or custom emoji name.", - "type": "string" - } - }, - "type": "object" - }, - "User": { - "description": "Represents a Google Chat user.", - "properties": { - "displayName": { - "description": "The display name of a Chat user.", - "type": "string" - }, - "email": { - "description": "The email address of the user. This field is only populated when the user type is HUMAN.", - "type": "string" - }, - "userId": { - "description": "Resource name of a Chat user. Format: users/{user}.", - "type": "string" - }, - "userType": { - "description": "The type of the user.", - "enum": [ - "USER_TYPE_UNSPECIFIED", - "HUMAN", - "APP" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "Human user.", - "App user." - ] - } - }, - "type": "object" - } - }, - "description": "Response to search for Google Chat messages. If next_page_token is populated, call SearchMessages can be called again with that token to retrieve the next page of results.", - "properties": { - "messages": { - "description": "List of message objects that match the search criteria, ordered according to the `order_by` request parameter.", - "items": { - "$ref": "#/$defs/ChatMessage" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token that can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "readOnlyHint": true - }, - "description": "Searches for Google Chat conversations by display name.\n\nIf only participants are provided, this tool finds 1:1 direct messages (if one participant is provided) or group chats (if multiple participants are provided) that include the specified participants and the calling user.\n\nIf only a query is provided, this tool searches for conversations where the query is a case-insensitive substring of the conversation's display name.\n\nIf both participants and query are provided, this tool finds conversations by participants and then filters them by display name.\n\nIf neither participants nor query are provided, this tool lists all conversations the calling user is a member of.\n\nThis tool only lists conversations the calling user is a member of.\n\nIMPORTANT: An empty 'conversations' list does not mean there are no more results overall. If 'next_page_token' is present, more pages can be fetched. If you get an empty list but a 'next_page_token', ask the user if you should continue the searching.\n", - "inputSchema": { - "description": "Request message for FindConversations RPC.", - "properties": { - "pageSize": { - "description": "Optional. The maximum number of spaces to return. The service may return fewer than this value. If unspecified, at most 100 spaces will be returned. The maximum value is 1000; values above 1000 will be coerced to 1000.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A page token, received from a previous `search_conversations` call. Provide this to retrieve the subsequent page.", - "type": "string" - }, - "participants": { - "description": "Optional. List of email addresses of the participants to filter the conversations, excluding the caller.", - "items": { - "type": "string" - }, - "type": "array" - }, - "spaceNameQuery": { - "description": "Optional. The text to search for within the space display names.", - "type": "string" - } - }, - "type": "object" - }, - "name": "search_conversations", - "outputSchema": { - "$defs": { - "Conversation": { - "description": "Represents a Chat conversation.", - "properties": { - "conversationId": { - "description": "The ID of the conversation (e.g., \"spaces/AAAAAAAAA\").", - "type": "string" - }, - "conversationType": { - "description": "The type of conversation (DIRECT_MESSAGE, GROUP_CHAT, or NAMED_SPACE).", - "enum": [ - "CONVERSATION_TYPE_UNSPECIFIED", - "NAMED_SPACE", - "GROUP_CHAT", - "DIRECT_MESSAGE" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "A named space.", - "A group chat between 3 or more people.", - "A direct message between two humans, or a human and a Chat app." - ] - }, - "displayName": { - "description": "The display name of the conversation.", - "type": "string" - }, - "lastActiveTimestamp": { - "description": "The last active time of the conversation in ISO 8601 format.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for FindConversations RPC.", - "properties": { - "conversations": { - "description": "List of conversation objects that match the search criteria.", - "items": { - "$ref": "#/$defs/Conversation" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token that can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true, - "readOnlyHint": false, - "title": "Sends a Google Chat message to a conversation" - }, - "description": "Sends a Google Chat message to a conversation.\n\nThis tool uses a conversation ID, an optional thread ID, and a message text as inputs.\nConversation ID's can be found using the search_conversations tool.\nIt returns the created message.\n", - "inputSchema": { - "description": "Request to send a message to a Google Chat conversation.", - "properties": { - "conversationId": { - "description": "Required. The ID of the conversation (e.g., 'spaces/AAAA...') to send the message to.", - "type": "string" - }, - "messageText": { - "description": "Required. The main content of the message. Basic formatting can be added using a subset of Markdown. For information about how to format messages, see [Format messages](https://developers.google.com/workspace/chat/format-messages). The following formatting is supported: * **Bold:** `*text*` * **Italic:** `_text_` * **Strikethrough:** `~text~` * **Monospace:** `text` * **Monospace block:** ``` line 1 line 2 ``` * **Bulleted list:** * item 1 * item 2 * **Block quote:** `> quoted text` * **Hyperlink:** `` * **Mention user:** ``", - "type": "string" - }, - "threadId": { - "description": "Optional. The ID of the thread (e.g., 'spaces/AAAA.../threads/BBBB...') to send the message to. If not set, the message will be sent to a new thread.", - "type": "string" - } - }, - "required": [ - "conversationId", - "messageText" - ], - "type": "object" - }, - "name": "send_message", - "outputSchema": { - "$defs": { - "ChatAttachmentMetadata": { - "description": "Metadata for a user-uploaded attachment in a chat message.", - "properties": { - "attachmentId": { - "description": "Resource name of the attachment. Format: spaces/{space}/messages/{message}/attachments/{attachment}.", - "type": "string" - }, - "filename": { - "description": "Name of the attachment.", - "type": "string" - }, - "mimeType": { - "description": "Content type (MIME type).", - "type": "string" - }, - "source": { - "description": "The source of the attachment.", - "enum": [ - "SOURCE_UNSPECIFIED", - "DRIVE_FILE", - "UPLOADED_CONTENT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Reserved.", - "The file is a Google Drive file.", - "The file is uploaded to Chat." - ] - } - }, - "type": "object" - }, - "ChatMessage": { - "description": "Represents a single message in a chat conversation.", - "properties": { - "attachments": { - "description": "Attachments included in the message.", - "items": { - "$ref": "#/$defs/ChatAttachmentMetadata" - }, - "type": "array" - }, - "createTime": { - "description": "Output only. Timestamp when the message was created.", - "readOnly": true, - "type": "string" - }, - "messageId": { - "description": "Resource name of the message. Format: spaces/{space}/messages/{message}", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body of the message.", - "type": "string" - }, - "reactionSummaries": { - "description": "The emoji reactions summary included in the message.", - "items": { - "$ref": "#/$defs/ReactionSummary" - }, - "type": "array" - }, - "sender": { - "$ref": "#/$defs/User", - "description": "The sender of the message." - }, - "threadId": { - "description": "The thread this message belongs to. This will be empty if the message is unthreaded. Format: spaces/{space}/threads/{thread}", - "type": "string" - }, - "threadedReply": { - "description": "Whether message is a thread reply.", - "type": "boolean" - } - }, - "type": "object" - }, - "ReactionSummary": { - "description": "Summary of emoji reactions on a chat message.", - "properties": { - "count": { - "description": "The total number of reactions using the associated emoji.", - "format": "int32", - "type": "integer" - }, - "emoji": { - "description": "The emoji unicode string or custom emoji name.", - "type": "string" - } - }, - "type": "object" - }, - "User": { - "description": "Represents a Google Chat user.", - "properties": { - "displayName": { - "description": "The display name of a Chat user.", - "type": "string" - }, - "email": { - "description": "The email address of the user. This field is only populated when the user type is HUMAN.", - "type": "string" - }, - "userId": { - "description": "Resource name of a Chat user. Format: users/{user}.", - "type": "string" - }, - "userType": { - "description": "The type of the user.", - "enum": [ - "USER_TYPE_UNSPECIFIED", - "HUMAN", - "APP" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Unspecified.", - "Human user.", - "App user." - ] - } - }, - "type": "object" - } - }, - "description": "Response to sending a message to a Google Chat conversation.", - "properties": { - "message": { - "$ref": "#/$defs/ChatMessage", - "description": "The message that was sent." - } - }, - "type": "object" - } - } - ] -} diff --git a/google-workspace/server/tools/generated/drive.json b/google-workspace/server/tools/generated/drive.json deleted file mode 100644 index 88edc817..00000000 --- a/google-workspace/server/tools/generated/drive.json +++ /dev/null @@ -1,702 +0,0 @@ -{ - "service": "drive", - "scopes": [ - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file" - ], - "tools": [ - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true, - "readOnlyHint": false - }, - "description": "Call this tool to copy an existing File in Google Drive.\nThe tool allows specifying a new title and a parent folder for the copy.\nIf the tile is not specified, the copy title will be 'Copy of {original title}'If the parent folder is not specified, the copy will be created in the same folder as the original file, unless the requesting user does not have write access to that folder, in which case the copy will be created in the user's root folder.Returns the newly created File object upon successful copying.\n", - "inputSchema": { - "description": "Request to copy a file.", - "properties": { - "fileId": { - "description": "Required. The ID of the file to copy.", - "type": "string" - }, - "parentId": { - "description": "The parent id of the newly created file. If empty, the file will be created with the same parent as the original file.", - "type": "string" - }, - "title": { - "description": "The title of the newly created file. If empty, the title will be 'Copy of [original file title]'.", - "type": "string" - } - }, - "required": [ - "fileId" - ], - "type": "object" - }, - "name": "copy_file", - "outputSchema": { - "description": "A file resource.", - "properties": { - "contentSnippet": { - "description": "Generated snippet about the content of the file.", - "type": "string" - }, - "createdTime": { - "description": "The time that the file was created.", - "format": "date-time", - "type": "string" - }, - "description": { - "description": "The description of the file.", - "type": "string" - }, - "fileExtension": { - "description": "The original file extension of the file, this is only populated for files with content stored in Drive.", - "type": "string" - }, - "fileSize": { - "description": "The size in bytes of the file.", - "format": "int64", - "type": "string" - }, - "id": { - "description": "The id of the file that was fetched.", - "type": "string" - }, - "mimeType": { - "description": "The mime type of the file.", - "type": "string" - }, - "modifiedTime": { - "description": "The most recent time at which the file was modified.", - "format": "date-time", - "type": "string" - }, - "owner": { - "description": "The email address of the owner of the file.", - "type": "string" - }, - "parentId": { - "description": "The (optional) id of the parent of the file.", - "type": "string" - }, - "sharedWithMeTime": { - "description": "The time that the file was shared with the requester.", - "format": "date-time", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - }, - "viewUrl": { - "description": "The URL to view the file.", - "type": "string" - }, - "viewedByMeTime": { - "description": "The most recent time at which the file was viewed by requester.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": true, - "readOnlyHint": false - }, - "description": "Call this tool to create or upload a File to Google Drive.\n\nIf uploading content, prefer \"text_content\" for text content. For non-UTF8 contents, use the \"base64_content\" field and base64 encode the data to set on that field.\n\nReturns a single File object upon successful creation.\n\nThe following Google Drive first-party mime types can be created without providing content:\n\n - `application/vnd.google-apps.document` \n - `application/vnd.google-apps.spreadsheet` \n - `application/vnd.google-apps.presentation` \n\nBy default, the following conversions will be made for the following mime types:\n\n - `text/plain` to `application/vnd.google-apps.document` \n - `text/csv` to `application/vnd.google-apps.spreadsheet` \n\nTo disable conversions for first-party mime types, set `disable_conversion_to_google_type` to true.\n\nFolders can be created by setting the mime type to `application/vnd.google-apps.folder`.\n\nWhen uploading content, the `content_mime_type` field is required and should match the type of the content being uploaded.\n", - "inputSchema": { - "description": "Request to upload a file.", - "properties": { - "base64Content": { - "description": "Optional. The base64 encoded content to upload. It's an error to set this and text_content.", - "type": "string" - }, - "content": { - "description": "The content of the file encoded as base64. The content field should always be base64 encoded regardless of the mime type of the file. DEPRECATED. Use base64_content or text_content instead.", - "type": "string" - }, - "contentMimeType": { - "description": "The mime type of the content being uploaded. Required when any type of content is provided.", - "type": "string" - }, - "disableConversionToGoogleType": { - "description": "Set to true to retain the passed in content mime type and not convert to a Google type. For example, without this a text/plain content mime type will be converted to to an application/vnd.google-apps.document. Has no effect for types that do not have a Google equivalent.", - "type": "boolean" - }, - "mimeType": { - "description": "DEPRECATED. DO NOT USE!! Set content_mime_type instead.", - "type": "string" - }, - "parentId": { - "description": "The parent id of the file.", - "type": "string" - }, - "textContent": { - "description": "Optional. The (UTF-8) text content to upload. It's an error to set this and base64_content.", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - } - }, - "type": "object" - }, - "name": "create_file", - "outputSchema": { - "description": "A file resource.", - "properties": { - "contentSnippet": { - "description": "Generated snippet about the content of the file.", - "type": "string" - }, - "createdTime": { - "description": "The time that the file was created.", - "format": "date-time", - "type": "string" - }, - "description": { - "description": "The description of the file.", - "type": "string" - }, - "fileExtension": { - "description": "The original file extension of the file, this is only populated for files with content stored in Drive.", - "type": "string" - }, - "fileSize": { - "description": "The size in bytes of the file.", - "format": "int64", - "type": "string" - }, - "id": { - "description": "The id of the file that was fetched.", - "type": "string" - }, - "mimeType": { - "description": "The mime type of the file.", - "type": "string" - }, - "modifiedTime": { - "description": "The most recent time at which the file was modified.", - "format": "date-time", - "type": "string" - }, - "owner": { - "description": "The email address of the owner of the file.", - "type": "string" - }, - "parentId": { - "description": "The (optional) id of the parent of the file.", - "type": "string" - }, - "sharedWithMeTime": { - "description": "The time that the file was shared with the requester.", - "format": "date-time", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - }, - "viewUrl": { - "description": "The URL to view the file.", - "type": "string" - }, - "viewedByMeTime": { - "description": "The most recent time at which the file was viewed by requester.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Call this tool to download the content of a Drive file as a base64 encoded string.\n\nIf the file is a Google Drive first-party mime type, the `exportMimeType` field is required and will determine the format of the downloaded file.\n\nIf the file is not found, try using other tools like `search_files` to find the file the user is requesting.\n\nIf the user wants a natural language representation of their Drive content, use the `read_file_content` tool (`read_file_content` should be smaller and easier to parse).\n", - "inputSchema": { - "description": "Defines a request to download a file's content.", - "properties": { - "exportMimeType": { - "description": "Optional. For Google native files, the MIME type to export the file to, ignored otherwise. Defaults to text if not specified.", - "type": "string" - }, - "fileId": { - "description": "Required. The ID of the file to retrieve.", - "type": "string" - } - }, - "required": [ - "fileId" - ], - "type": "object" - }, - "name": "download_file_content", - "outputSchema": { - "description": "The response for a file download request.", - "properties": { - "content": { - "description": "The base64 encoded content of the file.", - "type": "string" - }, - "id": { - "description": "The ID of the file.", - "type": "string" - }, - "mimeType": { - "description": "The MIME type of the file.", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Call this tool to find general metadata about a user's Drive file.\n\nIf the file is not found, try using other tools like `search_files` to find the file the user is requesting.\n", - "inputSchema": { - "description": "Request to get the file.", - "properties": { - "excludeContentSnippets": { - "description": "If true, the content snippet will be excluded from the response.", - "type": "boolean" - }, - "fileId": { - "description": "Required. The ID of the file to retrieve.", - "type": "string" - } - }, - "required": [ - "fileId" - ], - "type": "object" - }, - "name": "get_file_metadata", - "outputSchema": { - "description": "A file resource.", - "properties": { - "contentSnippet": { - "description": "Generated snippet about the content of the file.", - "type": "string" - }, - "createdTime": { - "description": "The time that the file was created.", - "format": "date-time", - "type": "string" - }, - "description": { - "description": "The description of the file.", - "type": "string" - }, - "fileExtension": { - "description": "The original file extension of the file, this is only populated for files with content stored in Drive.", - "type": "string" - }, - "fileSize": { - "description": "The size in bytes of the file.", - "format": "int64", - "type": "string" - }, - "id": { - "description": "The id of the file that was fetched.", - "type": "string" - }, - "mimeType": { - "description": "The mime type of the file.", - "type": "string" - }, - "modifiedTime": { - "description": "The most recent time at which the file was modified.", - "format": "date-time", - "type": "string" - }, - "owner": { - "description": "The email address of the owner of the file.", - "type": "string" - }, - "parentId": { - "description": "The (optional) id of the parent of the file.", - "type": "string" - }, - "sharedWithMeTime": { - "description": "The time that the file was shared with the requester.", - "format": "date-time", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - }, - "viewUrl": { - "description": "The URL to view the file.", - "type": "string" - }, - "viewedByMeTime": { - "description": "The most recent time at which the file was viewed by requester.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Call this tool to list the permissions of a Drive File.\n", - "inputSchema": { - "description": "Request to get file permissions.", - "properties": { - "fileId": { - "description": "Required. The ID of the file to get permissions for.", - "type": "string" - } - }, - "required": [ - "fileId" - ], - "type": "object" - }, - "name": "get_file_permissions", - "outputSchema": { - "$defs": { - "Permission": { - "description": "A permission for a file.", - "properties": { - "displayName": { - "description": "Output only. The \"pretty\" name of the value of the permission. The following is a list of examples for each type of permission: * `user` - User's full name, as defined for their Google Account, such as \"Dana A.\" * `group` - Name of the Google Group, such as \"The Company Administrators.\" * `domain` - String domain name, such as \"cymbalgroup.com.\" * `anyone` - No `displayName` is present.", - "type": "string" - }, - "emailAddress": { - "description": "The email address of the user or group to which this permission refers.", - "type": "string" - }, - "role": { - "description": "The role of the grantee for the file. The possible roles include: * `owner` * `organizer` * `fileOrganizer` * `writer` * `commenter` * `reader`", - "type": "string" - }, - "type": { - "description": "The type of the grantee. Supported values include: * `user` * `group` * `domain` * `anyone`", - "type": "string" - }, - "view": { - "description": "Specifies the view to which this permission applies, if any. Supported values include: * `published` * `metadata`", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response to get file permissions.", - "properties": { - "permissions": { - "description": "The list of permissions.", - "items": { - "$ref": "#/$defs/Permission" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Call this tool to find recent files for a user specified a sort order. Default sort order is `recency`.\n\nSupported sort orders are:\n\n - `recency`: The most recent timestamp from the file's date-time fields.\n - `lastModified`: The last time the file was modified by anyone.\n - `lastModifiedByMe`: The last time the file was modified by the user.\n\nThe default page size is 10. Utilize `next_page_token` to paginate through the results.\n", - "inputSchema": { - "description": "Request to list files.", - "properties": { - "excludeContentSnippets": { - "description": "If true, the content snippet will be excluded from the response.", - "type": "boolean" - }, - "orderBy": { - "description": "The sort order for the files.", - "type": "string" - }, - "pageSize": { - "description": "The maximum number of files to return.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "The page token to use for pagination.", - "type": "string" - } - }, - "type": "object" - }, - "name": "list_recent_files", - "outputSchema": { - "$defs": { - "File": { - "description": "A file resource.", - "properties": { - "contentSnippet": { - "description": "Generated snippet about the content of the file.", - "type": "string" - }, - "createdTime": { - "description": "The time that the file was created.", - "format": "date-time", - "type": "string" - }, - "description": { - "description": "The description of the file.", - "type": "string" - }, - "fileExtension": { - "description": "The original file extension of the file, this is only populated for files with content stored in Drive.", - "type": "string" - }, - "fileSize": { - "description": "The size in bytes of the file.", - "format": "int64", - "type": "string" - }, - "id": { - "description": "The id of the file that was fetched.", - "type": "string" - }, - "mimeType": { - "description": "The mime type of the file.", - "type": "string" - }, - "modifiedTime": { - "description": "The most recent time at which the file was modified.", - "format": "date-time", - "type": "string" - }, - "owner": { - "description": "The email address of the owner of the file.", - "type": "string" - }, - "parentId": { - "description": "The (optional) id of the parent of the file.", - "type": "string" - }, - "sharedWithMeTime": { - "description": "The time that the file was shared with the requester.", - "format": "date-time", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - }, - "viewUrl": { - "description": "The URL to view the file.", - "type": "string" - }, - "viewedByMeTime": { - "description": "The most recent time at which the file was viewed by requester.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response to list files.", - "properties": { - "files": { - "description": "The list of files.", - "items": { - "$ref": "#/$defs/File" - }, - "type": "array" - }, - "nextPageToken": { - "description": "The next page token.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Call this tool to fetch a natural language representation of a Drive file.\n\nThe file content may be incomplete for very large files. The text representation will change over time, so don't make assumptions about the particular format of the text returned by this tool.\n\nSupported Mime Types:\n\n - `application/vnd.google-apps.document` \n - `application/vnd.google-apps.presentation` \n - `application/vnd.google-apps.spreadsheet` \n - `application/pdf` \n - `application/msword` \n - `application/vnd.openxmlformats-officedocument.wordprocessingml.document` \n - `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` \n - `application/vnd.openxmlformats-officedocument.presentationml.presentation` \n - `application/vnd.oasis.opendocument.spreadsheet` \n - `application/vnd.oasis.opendocument.presentation` \n - `application/x-vnd.oasis.opendocument.text` \n - `image/png` \n - `image/jpeg` \n - `image/jpg` \n\nIf the file is not found, try using other tools like `search_files` to find the file the user is requesting using keywords.\n", - "inputSchema": { - "description": "Request to read file content.", - "properties": { - "fileId": { - "description": "Required. The ID of the file to retrieve.", - "type": "string" - } - }, - "required": [ - "fileId" - ], - "type": "object" - }, - "name": "read_file_content", - "outputSchema": { - "description": "Response to read file content.", - "properties": { - "fileContent": { - "description": "Drive file content returned in text format.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Search for Drive files using a structured query (synatax: `query_term operator values`).\nCombine clauses with `and`, `or`, `not`, and parentheses. String values must be single-quoted; escape embedded quotes as `\\'`. \n\nQuery terms & operators:\n - `title` (ops: contains, =, !=) — file title\n - `fullText` (ops: contains) — title or body text\n - `mimeType` (ops: contains, =, !=) — MIME type\n - `modifiedTime`, `viewedByMeTime`, `createdTime` (ops: `<=`, `<`, `=`, `!=`, `>`, `>=`). Use RFC 3339 UTC, e.g., `2012-06-04T12:00:00-08:00`. Date types not comparable.\n - `parentId` (ops: `=`, `!=`). Use `'root'` for the user's \"My Drive\".\n - `owner` (ops: `=`, `!=`). Use `'me'` for the requesting user.\n - `sharedWithMe` (ops: `=`, `!=`). Values: `true` or `false`.\nOther operators: `and`, `or`, `not`.\nExamples:\n - `title contains 'hello' and title contains 'goodbye'`\n - `modifiedTime > '2024-01-01T00:00:00Z' and (mimeType contains 'image/' or mimeType contains 'video/')`\n - `parentId = '1234567'`\n - `fullText contains 'hello'`\n - `owner = 'test@example.org'`\n - `sharedWithMe = true`\n - `owner = 'me'` (for files owned by the user)\n\nUse `next_page_token` to paginate. An empty response means no more results.\n", - "inputSchema": { - "description": "Request to search files.", - "properties": { - "excludeContentSnippets": { - "description": "If true, the content snippet will be excluded from the response.", - "type": "boolean" - }, - "pageSize": { - "description": "The maximum number of files to return in each page.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "The page token to use for pagination.", - "type": "string" - }, - "query": { - "description": "The search query.", - "type": "string" - } - }, - "type": "object" - }, - "name": "search_files", - "outputSchema": { - "$defs": { - "File": { - "description": "A file resource.", - "properties": { - "contentSnippet": { - "description": "Generated snippet about the content of the file.", - "type": "string" - }, - "createdTime": { - "description": "The time that the file was created.", - "format": "date-time", - "type": "string" - }, - "description": { - "description": "The description of the file.", - "type": "string" - }, - "fileExtension": { - "description": "The original file extension of the file, this is only populated for files with content stored in Drive.", - "type": "string" - }, - "fileSize": { - "description": "The size in bytes of the file.", - "format": "int64", - "type": "string" - }, - "id": { - "description": "The id of the file that was fetched.", - "type": "string" - }, - "mimeType": { - "description": "The mime type of the file.", - "type": "string" - }, - "modifiedTime": { - "description": "The most recent time at which the file was modified.", - "format": "date-time", - "type": "string" - }, - "owner": { - "description": "The email address of the owner of the file.", - "type": "string" - }, - "parentId": { - "description": "The (optional) id of the parent of the file.", - "type": "string" - }, - "sharedWithMeTime": { - "description": "The time that the file was shared with the requester.", - "format": "date-time", - "type": "string" - }, - "title": { - "description": "The title of the file.", - "type": "string" - }, - "viewUrl": { - "description": "The URL to view the file.", - "type": "string" - }, - "viewedByMeTime": { - "description": "The most recent time at which the file was viewed by requester.", - "format": "date-time", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response to search files.", - "properties": { - "files": { - "description": "Output only. The list of files.", - "items": { - "$ref": "#/$defs/File" - }, - "readOnly": true, - "type": "array" - }, - "nextPageToken": { - "description": "The next page token.", - "type": "string" - } - }, - "type": "object" - } - } - ] -} diff --git a/google-workspace/server/tools/generated/gmail.json b/google-workspace/server/tools/generated/gmail.json deleted file mode 100644 index 99d00eea..00000000 --- a/google-workspace/server/tools/generated/gmail.json +++ /dev/null @@ -1,663 +0,0 @@ -{ - "service": "gmail", - "scopes": [ - "https://mail.google.com/", - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/gmail.compose", - "https://www.googleapis.com/auth/gmail.readonly", - "https://www.googleapis.com/auth/gmail.metadata" - ], - "tools": [ - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Creates a new draft email in the authenticated user's Gmail account." - }, - "description": "Creates a new draft email in the authenticated user's Gmail account.\n\nThis tool takes recipient addresses, a subject, and body content as inputs. It returns the ID of the created Gmail draft.\n", - "inputSchema": { - "description": "Request message for CreateDraft RPC.", - "properties": { - "bcc": { - "description": "Optional. The blind carbon copy recipients of the email draft. Each string MUST be a valid email address (e.g., \"user@example.com\"). The \"Name \" format is NOT supported by this tool.", - "items": { - "type": "string" - }, - "type": "array" - }, - "body": { - "description": "Optional. The main body content of the email draft. If html_body is also provided, this field is treated as the plain-text alternative.", - "type": "string" - }, - "cc": { - "description": "Optional. The carbon copy recipients of the email draft. Each string MUST be a valid email address (e.g., \"user@example.com\"). The \"Name \" format is NOT supported by this tool.", - "items": { - "type": "string" - }, - "type": "array" - }, - "htmlBody": { - "description": "The HTML content of the email draft. If provided, this will be used as the rich-text version of the email.", - "type": "string" - }, - "replyToMessageId": { - "description": "Optional. The ID of the message to reply to. If provided, this will be used as the reply-to message ID for the email draft, and the `body` and `html_body` will be appended to the original message body.", - "type": "string" - }, - "subject": { - "description": "Optional. The subject line of the email. Defaults to empty if not provided.", - "type": "string" - }, - "to": { - "description": "Required. The primary recipients of the email draft. Each string MUST be a valid email address (e.g., \"user@example.com\"). The \"Name \" format is NOT supported by this tool.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "name": "create_draft", - "outputSchema": { - "description": "Details of a draft.", - "properties": { - "bccRecipients": { - "description": "List of 'Bcc' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - }, - "ccRecipients": { - "description": "List of 'Cc' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - }, - "date": { - "description": "Date of the draft in ISO 8601 format (YYYY-MM-DD).", - "type": "string" - }, - "id": { - "description": "The unique identifier of the draft resource.", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body content, if available.", - "type": "string" - }, - "subject": { - "description": "The subject line of the draft message.", - "type": "string" - }, - "threadId": { - "description": "The ID of the thread this draft belongs to.", - "type": "string" - }, - "toRecipients": { - "description": "List of 'To' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Lists draft emails from the authenticated user's Gmail account." - }, - "description": "Lists draft emails from the authenticated user's Gmail account.\n\nThis tool can filter drafts based on a query string and supports pagination. It returns a list of drafts, including their IDs and subjects.\n", - "inputSchema": { - "description": "Request message for ListDrafts RPC.", - "properties": { - "pageSize": { - "description": "Optional. The maximum number of drafts to return. If unspecified, defaults to 20. The maximum allowed value is 50.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. A token received from a previous list_drafts call to retrieve the next page of results. Leave empty to fetch the first page.", - "type": "string" - }, - "query": { - "description": "Optional. A query string to filter the drafts, using the same format as the Gmail search bar. If omitted, all drafts (excluding spam and trash by default) are listed. Key Operators: from: - Messages from a specific sender. to: - Messages sent to a specific recipient. subject: - Messages with specific words in the subject. - Messages containing specific words in the body or subject. is:unread - Unread messages. is:starred - Starred messages. has:attachment - Messages with attachments. after:YYYY/MM/DD - Messages sent after a date. before:YYYY/MM/DD - Messages sent before a date. newer_than: - Messages newer than a duration (e.g., 7d for 7 days). AND / OR - Combine terms (must be uppercase). -\"\" - Exclude messages containing these words. \"\" - Search for an exact phrase. Examples: \"subject:OneMCP Update\" \"from:gduser1@workspacesamples.dev\" \"to:gduser2@workspacesamples.dev AND newer_than:7d\" \"project proposal has:attachment\" \"is:unread\"", - "type": "string" - } - }, - "type": "object" - }, - "name": "list_drafts", - "outputSchema": { - "$defs": { - "Draft": { - "description": "Details of a draft.", - "properties": { - "bccRecipients": { - "description": "List of 'Bcc' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - }, - "ccRecipients": { - "description": "List of 'Cc' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - }, - "date": { - "description": "Date of the draft in ISO 8601 format (YYYY-MM-DD).", - "type": "string" - }, - "id": { - "description": "The unique identifier of the draft resource.", - "type": "string" - }, - "plaintextBody": { - "description": "Plain text body content, if available.", - "type": "string" - }, - "subject": { - "description": "The subject line of the draft message.", - "type": "string" - }, - "threadId": { - "description": "The ID of the thread this draft belongs to.", - "type": "string" - }, - "toRecipients": { - "description": "List of 'To' recipient email addresses extracted from headers.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "description": "Response message for ListDrafts RPC.", - "properties": { - "drafts": { - "description": "List of drafts.", - "items": { - "$ref": "#/$defs/Draft" - }, - "type": "array" - }, - "nextPageToken": { - "description": "A token that can be used in a subsequent call to retrieve the next page of drafts. Present only if there are more results.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Retrieves a specific email thread from the authenticated user's Gmail account." - }, - "description": "Retrieves a specific email thread from the authenticated user's Gmail account, including a list of its messages.\n", - "inputSchema": { - "description": "Request message for GetThread RPC.", - "properties": { - "messageFormat": { - "description": "Optional. Specifies the format of the messages returned within the thread. Defaults to FULL_CONTENT.", - "enum": [ - "MESSAGE_FORMAT_UNSPECIFIED", - "MINIMAL", - "FULL_CONTENT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Defaults to FULL_CONTENT.", - "Returns message snippets and key headers (Subject, From, To, Cc, Date).", - "Returns all information in \"MINIMAL\" plus the full body content of each message." - ] - }, - "threadId": { - "description": "Required. The unique identifier of the thread to fetch.", - "type": "string" - } - }, - "type": "object" - }, - "name": "get_thread", - "outputSchema": { - "$defs": { - "Message": { - "description": "Message within a thread.", - "properties": { - "attachmentIds": { - "description": "Output only. The attachment ids, only populated if MessageFormat was FULL_CONTENT.", - "items": { - "type": "string" - }, - "readOnly": true, - "type": "array" - }, - "ccRecipients": { - "description": "CC recipient email addresses.", - "items": { - "type": "string" - }, - "type": "array" - }, - "date": { - "description": "Date of the message in ISO 8601 format (YYYY-MM-DD).", - "type": "string" - }, - "id": { - "description": "The unique identifier of the message.", - "type": "string" - }, - "plaintextBody": { - "description": "Full body content, only populated if MessageFormat was FULL_CONTENT.", - "type": "string" - }, - "sender": { - "description": "Sender email address.", - "type": "string" - }, - "snippet": { - "description": "Snippet of the message body.", - "type": "string" - }, - "subject": { - "description": "The message subject extracted from headers:", - "type": "string" - }, - "toRecipients": { - "description": "To recipient email addresses.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "description": "Thread containing a list of messages.", - "properties": { - "id": { - "description": "The unique identifier of the thread.", - "type": "string" - }, - "messages": { - "description": "A list of messages in the thread, ordered chronologically.", - "items": { - "$ref": "#/$defs/Message" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Searches for email threads from the authenticated user's Gmail account." - }, - "description": "Lists email threads from the authenticated user's Gmail account.\n\nThis tool can filter threads based on a query string and supports pagination. It returns a list of threads, including their IDs and related messages. Each related message contains details like a snippet of the message body, the subject, the sender, the recipients etc. Note that the full message bodies are not returned by this tool; use the 'get_thread' tool with a thread ID to fetch the full message body if needed.\n", - "inputSchema": { - "description": "Request message for SearchThreads RPC.", - "properties": { - "includeTrash": { - "description": "Optional. Include drafts from TRASH in the results. Defaults to false.", - "type": "boolean" - }, - "pageSize": { - "description": "Optional. The maximum number of threads to return. If unspecified, defaults to 20. The maximum allowed value is 50.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. Page token to retrieve a specific page of results in the list. Leave empty to fetch the first page.", - "type": "string" - }, - "query": { - "description": "Optional. A query string to filter the threads, using the same format as the Gmail search bar. If omitted, all threads (excluding spam and trash by default) are listed. Key Operators: from: - Messages from a specific sender. to: - Messages sent to a specific recipient. subject: - Messages with specific words in the subject. - Messages containing specific words in the body or subject. is:unread - Unread messages. is:starred - Starred messages. has:attachment - Messages with attachments. after:YYYY/MM/DD - Messages sent after a date. before:YYYY/MM/DD - Messages sent before a date. newer_than: - Messages newer than a duration (e.g., 7d for 7 days). AND / OR - Combine terms (must be uppercase). -\"\" - Exclude messages containing these words. \"\" - Search for an exact phrase. Examples: \"subject:OneMCP Update\" \"from:gduser1@workspacesamples.dev\" \"to:gduser2@workspacesamples.dev AND newer_than:7d\" \"project proposal has:attachment\" \"is:unread\"", - "type": "string" - } - }, - "type": "object" - }, - "name": "search_threads", - "outputSchema": { - "$defs": { - "Message": { - "description": "Message within a thread.", - "properties": { - "attachmentIds": { - "description": "Output only. The attachment ids, only populated if MessageFormat was FULL_CONTENT.", - "items": { - "type": "string" - }, - "readOnly": true, - "type": "array" - }, - "ccRecipients": { - "description": "CC recipient email addresses.", - "items": { - "type": "string" - }, - "type": "array" - }, - "date": { - "description": "Date of the message in ISO 8601 format (YYYY-MM-DD).", - "type": "string" - }, - "id": { - "description": "The unique identifier of the message.", - "type": "string" - }, - "plaintextBody": { - "description": "Full body content, only populated if MessageFormat was FULL_CONTENT.", - "type": "string" - }, - "sender": { - "description": "Sender email address.", - "type": "string" - }, - "snippet": { - "description": "Snippet of the message body.", - "type": "string" - }, - "subject": { - "description": "The message subject extracted from headers:", - "type": "string" - }, - "toRecipients": { - "description": "To recipient email addresses.", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "Thread": { - "description": "Thread containing a list of messages.", - "properties": { - "id": { - "description": "The unique identifier of the thread.", - "type": "string" - }, - "messages": { - "description": "A list of messages in the thread, ordered chronologically.", - "items": { - "$ref": "#/$defs/Message" - }, - "type": "array" - } - }, - "type": "object" - } - }, - "description": "Response message for SearchThreads RPC.", - "properties": { - "nextPageToken": { - "description": "A token that can be used in a subsequent call to retrieve the next page of threads. Present only if there are more results.", - "type": "string" - }, - "threads": { - "description": "List of thread summaries.", - "items": { - "$ref": "#/$defs/Thread" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Adds labels to a thread." - }, - "description": "Adds labels to an entire thread in the authenticated user's Gmail account. This operation affects all messages currently in the thread and any future messages added to it.\n\nIf unsure of the thread ID, use the `search_threads` tool first.\n\nIf unsure of a user label's ID, use the `list_labels` tool first to discover available labels and their IDs.\n", - "inputSchema": { - "description": "Request message for LabelThread RPC.", - "properties": { - "labelIds": { - "description": "Required. The unique identifiers of the labels to add. Can be a system label ID (e.g., 'INBOX', 'TRASH', 'SPAM', 'STARRED', 'UNREAD') or a user-defined label ID.", - "items": { - "type": "string" - }, - "type": "array" - }, - "threadId": { - "description": "Required. The unique identifier of the thread to add labels to.", - "type": "string" - } - }, - "type": "object" - }, - "name": "label_thread", - "outputSchema": { - "description": "Response message for LabelThread RPC.", - "properties": {}, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": true, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Removes labels from a thread." - }, - "description": "Removes labels from an entire thread in the authenticated user's Gmail account. If unsure of the thread ID, use the `search_threads` tool first. If unsure of a user label's ID, use the `list_labels` tool first.", - "inputSchema": { - "description": "Request message for UnlabelThread RPC.", - "properties": { - "labelIds": { - "description": "Required. The unique identifiers of the labels to remove.", - "items": { - "type": "string" - }, - "type": "array" - }, - "threadId": { - "description": "Required. The unique identifier of the thread to remove labels from.", - "type": "string" - } - }, - "type": "object" - }, - "name": "unlabel_thread", - "outputSchema": { - "description": "Response message for UnlabelThread RPC.", - "properties": {}, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true, - "title": "Lists user labels." - }, - "description": "Lists all user-defined labels available in the authenticated user's Gmail account. Use this tool to discover the `id` of a user label before calling `label_thread`, `unlabel_thread`, `label_message`, or `unlabel_message`. System labels are not returned by this tool but can be used with their well-known IDs: 'INBOX', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'CHAT', 'DRAFT', 'SENT'.", - "inputSchema": { - "description": "Request message for ListLabels RPC.", - "properties": { - "pageSize": { - "description": "Optional. The maximum number of labels to return.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Optional. Page token to retrieve a specific page of results in the list.", - "type": "string" - } - }, - "type": "object" - }, - "name": "list_labels", - "outputSchema": { - "$defs": { - "Label": { - "description": "Details of a label.", - "properties": { - "labelId": { - "description": "The unique identifier of the label.", - "type": "string" - }, - "name": { - "description": "The human-readable display name of the label.", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for ListLabels RPC.", - "properties": { - "labels": { - "description": "List of user labels in the user's account.", - "items": { - "$ref": "#/$defs/Label" - }, - "type": "array" - }, - "nextPageToken": { - "description": "Token to retrieve the next page of results in the list.", - "type": "string" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Adds labels to a message." - }, - "description": "Adds one or more labels to a specific message in the authenticated user's Gmail account.\n\nTo find the message ID, use tools like `search_threads` or `get_thread`. If unsure of a user label's ID, use the `list_labels` tool first to discover available labels and their IDs.\n", - "inputSchema": { - "description": "Request message for LabelMessage RPC.", - "properties": { - "labelIds": { - "description": "Required. The IDs of the labels to add.", - "items": { - "type": "string" - }, - "type": "array" - }, - "messageId": { - "description": "Required. The ID of the message to add the labels to.", - "type": "string" - } - }, - "type": "object" - }, - "name": "label_message", - "outputSchema": { - "description": "Response message for LabelMessage RPC.", - "properties": {}, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": true, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Removes labels from a message." - }, - "description": "Removes one or more labels from a specific message in the authenticated user's Gmail account. To find the message ID, use tools like `search_threads` or `get_thread`. If unsure of a user label's ID, use the `list_labels` tool first to discover available labels and their IDs.", - "inputSchema": { - "description": "Request message for UnlabelMessage RPC.", - "properties": { - "labelIds": { - "description": "Required. The IDs of the labels to remove.", - "items": { - "type": "string" - }, - "type": "array" - }, - "messageId": { - "description": "Required. The ID of the message to remove the labels from.", - "type": "string" - } - }, - "type": "object" - }, - "name": "unlabel_message", - "outputSchema": { - "description": "Response message for UnlabelMessage RPC.", - "properties": {}, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": false, - "openWorldHint": false, - "readOnlyHint": false, - "title": "Creates a new label." - }, - "description": "Creates a new label in the authenticated user's Gmail account.", - "inputSchema": { - "description": "Request message for CreateLabel RPC.", - "properties": { - "displayName": { - "description": "Required. The display name of the label to create.", - "type": "string" - } - }, - "type": "object" - }, - "name": "create_label", - "outputSchema": { - "description": "Details of a label.", - "properties": { - "labelId": { - "description": "The unique identifier of the label.", - "type": "string" - }, - "name": { - "description": "The human-readable display name of the label.", - "type": "string" - } - }, - "type": "object" - } - } - ] -} diff --git a/google-workspace/server/tools/generated/people.json b/google-workspace/server/tools/generated/people.json deleted file mode 100644 index be112190..00000000 --- a/google-workspace/server/tools/generated/people.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "service": "people", - "scopes": [ - "https://www.googleapis.com/auth/directory.readonly", - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/contacts.readonly" - ], - "tools": [ - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Search for people within your organization's Google Workspace directory. This feature is exclusively for Google Workspace accounts (used by businesses, schools, and other organizations) and is not available for personal Google accounts.\n\n**IMPORTANT RULES TO FOLLOW:**\n\n* If this tool returns multiple results, you should present the results to the user and prompt the user for clarification on which result to use before proceeding.\n\n* You are strictly forbidden from passing the output of this tool into another tool (e.g., sending an email, creating a draft, creating an event, etc.) without explicit user confirmation.\n\n* Even if only one person result is found, you must present the found person's details to the user and prompt the user to verify that this is the intended person before proceeding with further steps.\n\n* If this tool returns no results, fall back to using the `search_contacts` tool.\n", - "inputSchema": { - "description": "Request message for SearchDirectoryPeople.", - "properties": { - "pageSize": { - "description": "Page size. The default is 10 and the maximum allowed value is 500.", - "format": "int32", - "type": "integer" - }, - "pageToken": { - "description": "Page token.", - "type": "string" - }, - "query": { - "description": "Query string to search for.", - "type": "string" - }, - "sources": { - "description": "Directory sources to return. Defaults to DOMAIN_PROFILE if not set.", - "items": { - "enum": [ - "DOMAIN_PROFILE", - "DOMAIN_CONTACT" - ], - "type": "string", - "x-google-enum-descriptions": [ - "Google Workspace domain profile.", - "Google Workspace domain shared contact." - ] - }, - "type": "array" - } - }, - "type": "object" - }, - "name": "search_directory_people", - "outputSchema": { - "$defs": { - "SearchDirectoryResult": { - "description": "A person result from search directory people.", - "properties": { - "email": { - "description": "The person's account email address.", - "type": "string" - }, - "name": { - "description": "The person's display name.", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for SearchDirectoryPeople.", - "properties": { - "nextPageToken": { - "description": "A token to retrieve the next page of results.", - "type": "string" - }, - "results": { - "description": "The list of people that matched the query.", - "items": { - "$ref": "#/$defs/SearchDirectoryResult" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Search user's contacts.\n\n**IMPORTANT RULES TO FOLLOW:**\n\n* If this tool returns multiple results, you should present the results to the user and prompt the user for clarification on which result to use before proceeding.\n\n* You are strictly forbidden from passing the output of this tool into another tool (e.g., sending an email, creating a draft, creating an event, etc.) without explicit user confirmation.\n\n* Even if only one person result is found, you must present the found person's details to the user and prompt the user to verify that this is the intended person before proceeding with further steps.\n", - "inputSchema": { - "description": "Request message for SearchContacts.", - "properties": { - "maxResults": { - "description": "Max number of results. The default is 10 and the maximum allowed value is 30.", - "format": "int32", - "type": "integer" - }, - "query": { - "description": "Query string to search for.", - "type": "string" - } - }, - "type": "object" - }, - "name": "search_contacts", - "outputSchema": { - "$defs": { - "SearchContactsResult": { - "description": "A contact result from search contacts.", - "properties": { - "email": { - "description": "The contact's account email address.", - "type": "string" - }, - "name": { - "description": "The contact's display name.", - "type": "string" - } - }, - "type": "object" - } - }, - "description": "Response message for SearchContacts.", - "properties": { - "results": { - "description": "The list of contacts that matched the query.", - "items": { - "$ref": "#/$defs/SearchContactsResult" - }, - "type": "array" - } - }, - "type": "object" - } - }, - { - "annotations": { - "destructiveHint": false, - "idempotentHint": true, - "openWorldHint": false, - "readOnlyHint": true - }, - "description": "Get profile info about yourself (name and email).", - "inputSchema": { - "description": "Request message for GetUserProfile.", - "type": "object" - }, - "name": "get_user_profile", - "outputSchema": { - "description": "Response message for GetUserProfile.", - "properties": { - "emailAddress": { - "description": "The user's account email address.", - "type": "string" - }, - "name": { - "description": "The user's display name.", - "type": "string" - } - }, - "type": "object" - } - } - ] -} diff --git a/google-workspace/server/tools/index.ts b/google-workspace/server/tools/index.ts index 082ac6a1..2eac1217 100644 --- a/google-workspace/server/tools/index.ts +++ b/google-workspace/server/tools/index.ts @@ -1,23 +1,30 @@ /** - * Aggregates tool factories for every Google service we proxy. Each backend - * MCP's tools/list snapshot lives in ./generated/.json — re-run - * `bun run generate-tools` to refresh. + * Google Workspace tools = the union of our existing Google REST-based MCPs, + * each tool's id namespaced with the service prefix to avoid collisions. * - * Some Google MCP backends (notably chat) return duplicate tool entries from - * tools/list. Dedupe by prefixed name to keep the runtime registration sane. + * We pull from the productivity-focused MCPs that share the same OAuth flow. + * Chat and People are intentionally excluded — Google's official MCP servers + * for them require additional scope verification we haven't completed. */ -import { TOOL_SNAPSHOTS } from "../constants.ts"; -import { wrapBackendTool } from "../lib/wrap-tool.ts"; +import { tools as calendarTools } from "google-calendar/tools"; +import { basicTools as gmailTools } from "google-gmail/tools"; +import { tools as driveTools } from "google-drive/tools"; +import { tools as docsTools } from "google-docs/tools"; +import { tools as sheetsTools } from "google-sheets/tools"; +import { tools as slidesTools } from "google-slides/tools"; +import { tools as formsTools } from "google-forms/tools"; +import { tools as meetTools } from "google-meet/tools"; -const seen = new Set(); -export const tools = Object.entries(TOOL_SNAPSHOTS).flatMap(([service, snap]) => - snap.tools - .filter((def) => { - const id = `${service}_${def.name}`; - if (seen.has(id)) return false; - seen.add(id); - return true; - }) - .map((def) => wrapBackendTool(service as keyof typeof TOOL_SNAPSHOTS, def)), -); +import { prefixToolFactories } from "../lib/prefix-tool.ts"; + +export const tools = [ + ...prefixToolFactories(calendarTools, "calendar"), + ...prefixToolFactories(gmailTools, "gmail"), + ...prefixToolFactories(driveTools, "drive"), + ...prefixToolFactories(docsTools, "docs"), + ...prefixToolFactories(sheetsTools, "sheets"), + ...prefixToolFactories(slidesTools, "slides"), + ...prefixToolFactories(formsTools, "forms"), + ...prefixToolFactories(meetTools, "meet"), +];