#!/usr/bin/env python3 """Load Zapier MCP function reference into zapier.platform_reference. Each hosted meta-tool becomes kind=mcp_function with typed inputs, documented outputs, internals, related tools, and call examples. Also upserts the build-new-connector start guide (Grok must begin there). """ from __future__ import annotations import json import os import sys from datetime import datetime, timezone from pathlib import Path ROOT = Path(os.environ.get("ZAPIER_RESEARCH_ROOT") or Path(__file__).resolve().parents[1]) SCHEMA_PATH = ROOT / "raw" / "site-extras" / "zapier-mcp-tools-gumloop.json" NOW = datetime.now(timezone.utc).isoformat() # Hosted server is closed source. Internals below are reconstructed from # docs.zapier.com/mcp, the live tool descriptions, and the parallel Zapier SDK. ENRICH = { "discover_zapier_actions": { "category": "action-management", "official_14": True, "safety": "read", "bills_tasks": False, "title": "Discover apps and actions in the Zapier catalog", "high_level": ( "Catalog search. Finds apps and their actions that this MCP server " "can enable. Call this before claiming an app is unavailable." ), "internals": ( "Hits Zapier's public app/action catalog (the same directory behind " "docs/SDK `listApps`/`listActions`), not the user's enabled toolset. " "Returns `selected_api` identifiers (e.g. `GoogleMailV2CLIAPI`) that " "must be copied verbatim into enable/list/manage/execute. Does not " "open OAuth or mutate the server. Omit `app` to get popular apps." ), "outputs": { "type": "object", "description": "Search hits. Exact envelope is server-generated; fields used by later tools:", "properties": { "apps": { "type": "array", "items": { "type": "object", "properties": { "app": {"type": "string", "description": "Display name, e.g. Gmail. Pass as app_display_name later."}, "selected_api": {"type": "string", "description": "Canonical API id, e.g. GoogleMailV2CLIAPI. Never invent this."}, "actions": { "type": "array", "description": "Available action keys/labels (read + write) for this app.", }, }, }, } }, }, "related": [ "enable_zapier_action", "inspect_zapier_actions", "manage_zapier_connections", "write_code_action", ], "sdk_equivalent": ["zapier.listApps({search})", "zapier.listActions({app})", "zapier-sdk list-apps --search"], "examples": [ { "title": "Find Gmail in the catalog", "call": {"app": "gmail"}, "note": "Use the returned selected_api (often GoogleMailV2CLIAPI, not GmailCLIAPI).", }, { "title": "Popular apps (no query)", "call": {}, }, ], "code": '''# MCP tools/call { "name": "discover_zapier_actions", "arguments": { "app": "slack" } } # TypeScript MCP client const found = await client.callTool({ name: "discover_zapier_actions", arguments: { app: "google calendar" }, }); # Parallel Zapier SDK (not MCP — same catalog) const apps = zapier.listApps({ search: "gmail" }); ''', }, "enable_zapier_action": { "category": "action-management", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Enable an app action on this MCP server", "high_level": ( "Adds one action (or all actions) for a catalog app to this server's " "callable set so execute_* can run it." ), "internals": ( "Mutates the MCP server configuration stored at mcp.zapier.com. " "Looks up the app by `selected_api` from discover. If the user has " "no Zapier connection for that app, the response includes an auth URL " "(same OAuth dance as manage_zapier_connections). Enabling does not " "run the partner API. `action` omitted or `*` enables the app's " "actions; prefer enabling only what you need." ), "outputs": { "type": "object", "properties": { "actions": {"type": "array", "description": "Enabled action keys plus which execute tool to use (read vs write)."}, "parameters": {"type": "object", "description": "Initial parameter hints; still call inspect before execute."}, "auth_url": {"type": "string", "description": "Present when the app has no usable connection yet."}, "selected_api": {"type": "string"}, "app_display_name": {"type": "string"}, }, }, "related": [ "discover_zapier_actions", "inspect_zapier_actions", "disable_zapier_action", "manage_zapier_connections", "execute_zapier_read_action", "execute_zapier_write_action", ], "sdk_equivalent": ["N/A — SDK has no per-server enable; it calls any action the user is allowed to run"], "examples": [ { "title": "Enable Slack send-channel-message only", "call": { "selected_api": "SlackCLIAPI", "app_display_name": "Slack", "action": "send_channel_message", }, }, { "title": "Enable every Gmail action", "call": { "selected_api": "GoogleMailV2CLIAPI", "app_display_name": "Gmail", "action": "*", }, }, ], "code": '''await client.callTool({ name: "enable_zapier_action", arguments: { selected_api: "GoogleMailV2CLIAPI", // from discover_zapier_actions app_display_name: "Gmail", action: "find_email", }, }); ''', }, "disable_zapier_action": { "category": "action-management", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Disable an enabled action or an entire app", "high_level": "Removes actions from this server so the agent can no longer execute them.", "internals": ( "Inverse of enable. Deletes the action binding from the server config. " "Does not revoke the user's Zapier app connection and does not delete " "history. Omit `action` to remove every action for `selected_api`." ), "outputs": { "type": "object", "properties": { "disabled": {"type": "array", "description": "Action keys that were removed."}, "selected_api": {"type": "string"}, }, }, "related": ["inspect_zapier_actions", "enable_zapier_action"], "sdk_equivalent": ["N/A"], "examples": [ { "title": "Remove one write action", "call": { "selected_api": "SlackCLIAPI", "app_display_name": "Slack", "action": "send_channel_message", }, } ], "code": '''await client.callTool({ name: "disable_zapier_action", arguments: { selected_api: "SlackCLIAPI", app_display_name: "Slack", action: "send_channel_message", }, }); ''', }, "inspect_zapier_actions": { "category": "action-management", "official_14": True, "safety": "read", "bills_tasks": False, "title": "Inspect enabled actions and resolve dynamic fields", "high_level": ( "The schema tool. Lists what is enabled and, when called again with " "`tool_name` / `enum_property` / `params`, resolves dropdowns and " "dynamic input fields. Always call before execute." ), "internals": ( "Reads this server's enabled-action table, then (when `tool_name` or " "`enum_property` is set) calls the same Zapier Platform input-field " "and dynamic-dropdown endpoints the visual builder uses: " "`is_dynamic_enum` → choices (paginated via `enum_cursor` / " "`enum_search`); `dynamic_properties_depends_on` → extra fields after " "parent params are known. Does not run the partner action. Never " "guess action keys — they are not human names." ), "outputs": { "type": "object", "properties": { "apps": { "type": "array", "items": { "type": "object", "properties": { "app": {"type": "string"}, "selected_api": {"type": "string"}, "connections": { "type": "object", "properties": { "default": {"description": "Default account this app's actions will run against."} }, }, "actions": { "type": "array", "items": { "type": "object", "properties": { "action": {"type": "string"}, "tool_name": {"type": "string", "description": "Collision-safe id to pass into execute."}, "execute_tool": {"type": "string", "enum": ["execute_zapier_read_action", "execute_zapier_write_action"]}, "parameters": {"type": "object", "description": "JSON Schema-like fields; may include is_dynamic_enum, dynamic_properties_depends_on."}, "dynamic_enum_values": {"type": "array", "description": "Present when enum_property was set."}, "dynamic_properties_schema": {"type": "object", "description": "Present after parent params are settled."}, }, }, }, }, }, } }, }, "related": [ "execute_zapier_read_action", "execute_zapier_write_action", "discover_zapier_actions", "list_zapier_connections", "manage_zapier_connections", "enable_zapier_action", ], "sdk_equivalent": [ "zapier.getActionInputFieldsSchema", "zapier.listActionInputFields", "zapier-sdk list-action-input-fields", "zapier-sdk list-action-input-field-choices", ], "examples": [ {"title": "List everything enabled", "call": {}}, { "title": "Schema for one action", "call": {"tool_name": "SlackCLIAPI.send_channel_message"}, }, { "title": "Resolve a dynamic dropdown (Slack channel)", "call": { "tool_name": "SlackCLIAPI.send_channel_message", "enum_property": "channel", "enum_search": "launches", }, }, { "title": "Load fields that depend on a parent (e.g. spreadsheet → worksheet)", "call": { "tool_name": "GoogleSheetsV2CLIAPI.lookup_spreadsheet_row", "params": {"spreadsheet": "1AbC..."}, }, }, ], "code": '''// 1) inventory await client.callTool({ name: "inspect_zapier_actions", arguments: {} }); // 2) resolve Slack channel id before write await client.callTool({ name: "inspect_zapier_actions", arguments: { tool_name: "SlackCLIAPI.send_channel_message", enum_property: "channel", enum_search: "launches", }, }); ''', }, "execute_zapier_read_action": { "category": "execution", "official_14": True, "safety": "read", "bills_tasks": True, "title": "Run a search / lookup / get action", "high_level": ( "Read-only perform. Finds emails, rows, events, contacts, issues. " "No user confirmation required by Zapier's safety model." ), "internals": ( "Resolves (`selected_api`, `action` or `tool_name`) to a Zapier " "Platform search/read operation, injects the user's connection " "(default or `connection_id`), maps `params` onto `bundle.inputData`, " "and runs the same perform path as `zapier.apps..search.*` / " "`runAction({actionType:'search'|'read'})`. Successful calls cost " "**2 Zapier tasks**. Failed calls are free. Results are whatever " "the partner action returns (usually an array of objects). " "Does not auto-create records." ), "outputs": { "type": "object", "description": "Partner payload, typically an array of records. Shape is action-specific.", "properties": { "results": {"type": "array", "description": "Matching records (id + display fields). Empty array = not found, not an error."}, "data": {"type": "array", "description": "Some responses use data[] (same as the SDK)."}, "error": {"type": "string", "description": "Present on failure (401 = reconnect via manage_zapier_connections)."}, }, }, "related": [ "inspect_zapier_actions", "discover_zapier_actions", "enable_zapier_action", "list_zapier_connections", "execute_zapier_write_action", ], "sdk_equivalent": [ "zapier.runAction({actionType:'search'|'read'})", "zapier-sdk run-action search ", "repos/sdk/examples/by-app/*/find-*.ts", ], "examples": [ { "title": "Find a Gmail thread", "call": { "selected_api": "GoogleMailV2CLIAPI", "action": "find_email", "tool_name": "GoogleMailV2CLIAPI.find_email", "params": {"query": "from:sarah@acme.com newer_than:7d"}, }, } ], "code": '''// Always inspect first — never invent action keys. await client.callTool({ name: "inspect_zapier_actions", arguments: { selected_api: "GoogleMailV2CLIAPI" } }); const emails = await client.callTool({ name: "execute_zapier_read_action", arguments: { selected_api: "GoogleMailV2CLIAPI", action: "find_email", tool_name: "GoogleMailV2CLIAPI.find_email", params: { query: "from:sarah@acme.com" }, }, }); // SDK twin const { data } = await zapier.runAction({ appKey: "GoogleMailV2CLIAPI", actionType: "search", actionKey: "find_email", connection: connection.id, inputs: { query: "from:sarah@acme.com" }, }); ''', }, "execute_zapier_write_action": { "category": "execution", "official_14": True, "safety": "write", "bills_tasks": True, "title": "Run a create / update / send action", "high_level": ( "Write perform. Sends messages, creates records, updates fields. " "Always confirm with the user first — side effects are real." ), "internals": ( "Same runtime as execute_zapier_read_action but targets create/write " "operations (`runAction({actionType:'write'})`). Params must uniquely " "identify recipients/channels/records — resolve IDs via inspect " "dynamic enums or a prior read. Costs **2 tasks** on success. " "Irreversible partner-side effects (sent email, created ticket) are " "not rolled back by Zapier." ), "outputs": { "type": "object", "properties": { "results": {"type": "array", "description": "Usually one created/updated object."}, "data": {"type": "array"}, "error": {"type": "string"}, }, }, "related": [ "inspect_zapier_actions", "execute_zapier_read_action", "list_zapier_connections", "enable_zapier_action", ], "sdk_equivalent": [ "zapier.runAction({actionType:'write'})", "zapier-sdk run-action write ", "repos/sdk/examples/by-app/gmail/send-email.ts", "repos/sdk/examples/by-app/slack (channel message)", ], "examples": [ { "title": "Send Slack after resolving channel id", "call": { "selected_api": "SlackCLIAPI", "action": "send_channel_message", "params": {"channel": "C01234567", "text": "Release shipped, monitoring now"}, }, } ], "code": '''// 1) confirm with the user // 2) resolve channel via inspect (enum_property: "channel") // 3) write await client.callTool({ name: "execute_zapier_write_action", arguments: { selected_api: "SlackCLIAPI", action: "send_channel_message", tool_name: "SlackCLIAPI.send_channel_message", params: { channel: "C01234567", text: "Release shipped, monitoring now" }, }, }); // SDK twin — repos/sdk/examples/by-app/gmail/send-email.ts await gmail.write.message({ inputs: { subject: "Quarterly Update", body: "Hello team", body_type: "plain" }, }); ''', }, "list_zapier_connections": { "category": "connections", "official_14": False, "safety": "read", "bills_tasks": False, "title": "List authenticated accounts for an app", "high_level": ( "Lists Zapier connections (OAuth grants) the user owns for one app. " "Use the returned `connection_id` on execute when the default account is wrong." ), "internals": ( "Reads Zapier's connection store for `selected_api`. Default is " "owner=me only; `include_shared` adds connections others shared " "(do not surface those unless the user asks). Paginated (`cursor`, " "`limit` 1–100, default 20). Same objects the SDK's " "`findFirstConnection` / `listConnections` return." ), "outputs": { "type": "object", "properties": { "connections": { "type": "array", "items": { "type": "object", "properties": { "connection_id": {"type": ["string", "integer"]}, "title": {"type": "string", "description": "Account label, e.g. work@company.com"}, "owner": {"type": "string"}, "is_default": {"type": "boolean"}, "expired": {"type": "boolean"}, }, }, }, "next_cursor": {"type": "string"}, }, }, "related": [ "manage_zapier_connections", "inspect_zapier_actions", "execute_zapier_read_action", "execute_zapier_write_action", ], "sdk_equivalent": ["zapier.listConnections", "zapier.findFirstConnection", "zapier-sdk find-first-connection"], "examples": [ { "title": "Own Gmail accounts", "call": {"selected_api": "GoogleMailV2CLIAPI"}, }, { "title": "Include shared (only if user asked)", "call": {"selected_api": "SlackCLIAPI", "include_shared": True, "limit": 50}, }, ], "code": '''const conns = await client.callTool({ name: "list_zapier_connections", arguments: { selected_api: "GoogleMailV2CLIAPI" }, }); // pick connection_id → pass to execute_* or manage_zapier_connections ''', }, "manage_zapier_connections": { "category": "connections", "official_14": False, "safety": "config", "bills_tasks": False, "title": "Connect a new account or set the default", "high_level": ( "Returns an `auth_url` for the user to complete OAuth, and/or sets " "which connection is the default for every execute on this app." ), "internals": ( "An app cannot execute until it has a default connection. This tool " "mints a Zapier-hosted OAuth start URL (same as SDK " "`get-connection-start-url`) and optionally writes " "`default_connection_id`. Never invent `selected_api` — Gmail is " "`GoogleMailV2CLIAPI`, not `GmailCLIAPI`. After the user finishes " "OAuth, call list_zapier_connections, then call this again with " "`default_connection_id` if needed." ), "outputs": { "type": "object", "properties": { "auth_url": {"type": "string", "description": "Open in a browser; human must authorize."}, "default_connection_id": {"type": ["string", "integer"]}, "selected_api": {"type": "string"}, }, }, "related": ["list_zapier_connections", "discover_zapier_actions", "enable_zapier_action"], "sdk_equivalent": [ "zapier-sdk create-connection", "zapier-sdk get-connection-start-url", "zapier-sdk wait-for-new-connection", ], "examples": [ { "title": "Get an auth URL", "call": {"selected_api": "GoogleMailV2CLIAPI", "app_display_name": "Gmail"}, }, { "title": "Set default after user connects", "call": { "selected_api": "GoogleMailV2CLIAPI", "app_display_name": "Gmail", "default_connection_id": 123456, }, }, ], "code": '''const start = await client.callTool({ name: "manage_zapier_connections", arguments: { selected_api: "GoogleMailV2CLIAPI", app_display_name: "Gmail" }, }); // show start.auth_url to the user, wait for "done" const listed = await client.callTool({ name: "list_zapier_connections", arguments: { selected_api: "GoogleMailV2CLIAPI" }, }); ''', }, "auto_provision_mcp": { "category": "action-management", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Auto-enable actions from existing Zapier connections", "high_level": ( "One-shot setup. Finds apps the user already connected in Zapier, " "enables actions, binds auth. Runs automatically on OAuth connect." ), "internals": ( "Scans the signed-in user's own Zapier connections (not connections " "shared by teammates), enables a default action set per app, and " "returns top Zap titles so the agent can suggest Skills. Idempotent " "enough to re-run on a fresh/empty server. Does not pull other " "users' apps in a shared workspace." ), "outputs": { "type": "object", "properties": { "enabled_apps": {"type": "array"}, "enabled_actions": {"type": "array"}, "zap_titles": {"type": "array", "description": "User's popular Zap names — seed for create_zapier_skill."}, }, }, "related": ["inspect_zapier_actions", "enable_zapier_action", "list_zapier_skills", "create_zapier_skill"], "sdk_equivalent": ["N/A — MCP-server bootstrap only"], "examples": [{"title": "Empty server", "call": {}}], "code": '''await client.callTool({ name: "auto_provision_mcp", arguments: {} }); await client.callTool({ name: "inspect_zapier_actions", arguments: {} }); ''', }, "get_configuration_url": { "category": "configuration", "official_14": True, "safety": "read", "bills_tasks": False, "title": "Get this server's mcp.zapier.com config URL", "high_level": "Returns the dashboard URL where a human can add/edit/remove tools in the UI.", "internals": ( "Looks up the current MCP server id for this auth session and " "returns https://mcp.zapier.com/… for that server. No mutation. " "Used by the zapier-demo / zapier-onboard skills when in-chat " "enable is not enough (manual field locks, tool bundles)." ), "outputs": { "type": "object", "properties": {"url": {"type": "string", "description": "Absolute https://mcp.zapier.com/… URL."}}, }, "related": ["enable_zapier_action", "auto_provision_mcp"], "sdk_equivalent": ["N/A"], "examples": [{"title": "Hand the user the dashboard", "call": {}}], "code": '''const { url } = (await client.callTool({ name: "get_configuration_url", arguments: {}, })).structuredContent ?? {}; ''', }, "list_zapier_skills": { "category": "skills", "official_14": True, "safety": "read", "bills_tasks": False, "title": "List saved Zapier Skills", "high_level": "Catalog of reusable Markdown workflow instructions, including packaged `zapier:onboarding`.", "internals": ( "Reads the Skills store for this MCP account (also visible in the " "Skills tab at mcp.zapier.com). Names are case-insensitive. Catalog " "is dynamic — always re-list. Pair with get_zapier_skill to load body." ), "outputs": { "type": "object", "properties": { "skills": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "description": {"type": "string"}, }, }, } }, }, "related": ["get_zapier_skill", "create_zapier_skill", "update_zapier_skill", "delete_zapier_skill"], "sdk_equivalent": ["N/A — MCP Skills, not Platform CLI skills"], "examples": [{"title": "See what is saved", "call": {}}], "code": '''await client.callTool({ name: "list_zapier_skills", arguments: {} }); ''', }, "get_zapier_skill": { "category": "skills", "official_14": True, "safety": "read", "bills_tasks": False, "title": "Load a Skill's full Markdown", "high_level": "Returns the skill definition plus any appended execution instructions. Follow it.", "internals": ( "Fetches one skill by exact `name`. Packaged skill " "`zapier:onboarding` walks first-run setup (official first-workflow " "tutorial). Body may contain `ZapierAction[app:action](params)` " "references the agent should execute via inspect + execute_*." ), "outputs": { "type": "object", "properties": { "name": {"type": "string"}, "description": {"type": "string"}, "skillDefinition": {"type": "string", "description": "Full Markdown."}, "execution_instructions": {"type": "string"}, }, }, "related": ["list_zapier_skills", "execute_zapier_read_action", "execute_zapier_write_action"], "sdk_equivalent": ["N/A"], "examples": [ { "title": "Official onboarding (from Zapier docs)", "call": {"name": "zapier:onboarding"}, } ], "code": '''await client.callTool({ name: "get_zapier_skill", arguments: { name: "zapier:onboarding" }, }); // then follow the returned Markdown ''', }, "create_zapier_skill": { "category": "skills", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Save a reusable multi-step Skill", "high_level": ( "Persists a Markdown playbook with locked ZapierAction references. " "Resolve IDs and schemas before writing the definition." ), "internals": ( "Stores Markdown in the Skills tab. Convention: maximize quoted " "(locked) params so runtime does not re-discover Slack user IDs " "etc. Action keys must come from inspect/enable — the description " "still says `list_enabled_zapier_actions`, which on the current " "server is `inspect_zapier_actions`. Format: " '`ZapierAction[app:action](param: "locked", runtime_param)`.' ), "outputs": { "type": "object", "properties": {"name": {"type": "string"}, "created": {"type": "boolean"}}, }, "related": ["inspect_zapier_actions", "get_zapier_skill", "update_zapier_skill", "list_zapier_skills"], "sdk_equivalent": ["N/A"], "examples": [ { "title": "Daily standup poster", "call": { "name": "daily standup", "description": "Post yesterday/today/blockers to #standup", "skillDefinition": ( "# Daily standup\\n\\n" "## Validated fixed values\\n- channel: C0STANDUP\\n\\n" "## Actions\\n" 'ZapierAction[SlackCLIAPI:send_channel_message](channel: "C0STANDUP", text)\\n\\n' "## Runtime instructions\\n1. Ask for yesterday/today/blockers\\n2. Send\\n" ), }, } ], "code": '''await client.callTool({ name: "create_zapier_skill", arguments: { name: "daily standup", description: "Post yesterday/today/blockers to #standup", skillDefinition: "# Daily standup\\n...", }, }); ''', }, "update_zapier_skill": { "category": "skills", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Update a Skill's description or Markdown", "high_level": "Patch an existing skill. `name` required; other fields optional.", "internals": "Overwrites provided fields only. Same ZapierAction syntax as create.", "outputs": { "type": "object", "properties": {"name": {"type": "string"}, "updated": {"type": "boolean"}}, }, "related": ["get_zapier_skill", "create_zapier_skill", "delete_zapier_skill"], "sdk_equivalent": ["N/A"], "examples": [{"title": "Tighten description", "call": {"name": "daily standup", "description": "Post standup to #standup only"}}], "code": '''await client.callTool({ name: "update_zapier_skill", arguments: { name: "daily standup", description: "Post standup to #standup only" }, }); ''', }, "delete_zapier_skill": { "category": "skills", "official_14": True, "safety": "config", "bills_tasks": False, "title": "Permanently delete a Skill", "high_level": "Removes a saved skill by exact name. Cannot be undone.", "internals": "Deletes the Skills-tab document. Does not disable app actions.", "outputs": { "type": "object", "properties": {"name": {"type": "string"}, "deleted": {"type": "boolean"}}, }, "related": ["list_zapier_skills", "get_zapier_skill"], "sdk_equivalent": ["N/A"], "examples": [{"title": "Remove", "call": {"name": "daily standup"}}], "code": '''await client.callTool({ name: "delete_zapier_skill", arguments: { name: "daily standup" }, }); ''', }, "write_code_action": { "category": "execution", "official_14": False, "safety": "write", "bills_tasks": True, "title": "Generate a sandboxed custom code action", "high_level": ( "When no built-in action exists, Zapier generates code against the " "app's API and runs it with the user's existing connection. Rolling out." ), "internals": ( "Creates/replaces a named custom action on this server. Zapier " "generates code from `requirements` and executes later calls in a " "secure sandbox with the app's connected-account auth injected — " "never put secrets in `requirements`. Same idea as SDK `zapier.fetch` " "(authenticated raw HTTP) but the code lives on the MCP server. " "Same-name recreate overwrites. After creation the action is " "callable (typically via execute_* or as its own tool). Official " "docs list this as rolling out; not every account has it." ), "outputs": { "type": "object", "properties": { "code_action_name": {"type": "string"}, "tool_name": {"type": "string"}, "selected_api": {"type": "string"}, "ready": {"type": "boolean"}, }, }, "related": ["discover_zapier_actions", "inspect_zapier_actions", "execute_zapier_read_action"], "sdk_equivalent": ["zapier.fetch(url, {connection, method})", "Platform CLI Code Mode"], "examples": [ { "title": "List Slack channel members (no first-class action)", "call": { "selected_api": "SlackCLIAPI", "code_action_name": "list_channel_users", "requirements": "Call Slack conversations.members for a channel_id; return user ids and names; paginate with cursor.", }, } ], "code": '''await client.callTool({ name: "write_code_action", arguments: { selected_api: "SlackCLIAPI", code_action_name: "list_channel_users", requirements: "GET conversations.members for channel_id; paginate; return [{id, name}]. No tokens in code.", }, }); ''', }, "send_feedback": { "category": "feedback", "official_14": True, "safety": "write", "bills_tasks": False, "title": "Send product feedback to Zapier", "high_level": "Files a short note (max 2000 chars) plus a thumbs-up/down flag with the Zapier MCP team.", "internals": "Posts to Zapier's MCP feedback inbox. Does not change tools or connections.", "outputs": { "type": "object", "properties": {"ok": {"type": "boolean"}}, }, "related": [], "sdk_equivalent": ["N/A"], "examples": [ { "title": "Positive", "call": {"feedback": "Dynamic enum pagination on Slack channels is great.", "feedback_positive": True}, } ], "code": '''await client.callTool({ name: "send_feedback", arguments: { feedback: "Please document output schemas for execute_zapier_read_action.", feedback_positive: False, }, }); ''', }, } MCP_GUIDE = r'''# Zapier MCP — Grok function playbook **Start here for any Zapier work:** `db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" })` or [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md). That guide routes to MCP vs Platform CLI vs SDK. This document is the MCP branch. ## What Zapier MCP is Hosted Model Context Protocol server at `https://mcp.zapier.com/api/v1/connect`. Closed source. The GitHub repo `zapier/zapier-mcp` is **plugin distribution only** (onboarding skills, manifests) — not the server. Gives an AI client 9,000+ apps / 40,000+ actions without per-app OAuth in your code. Zapier holds credentials, refresh, retries. SOC 2 Type II. ## Two server modes 1. **Dynamic discovery (default).** 17 static meta-tools (official docs still say 14; live servers also expose `list_zapier_connections`, `manage_zapier_connections`, and rolling-out `write_code_action`). The agent discovers/enables/executes at runtime. 2. **Manual configuration.** Each enabled action becomes its own named tool. Configure at mcp.zapier.com. Use when you need a frozen toolset or locked field values. ## Canonical call graph ``` (optional) auto_provision_mcp │ ▼ discover_zapier_actions ──► enable_zapier_action ──► manage_zapier_connections (if no auth) │ │ │ ▼ └──────────────► inspect_zapier_actions ◄── list_zapier_connections │ (repeat for enums / dynamic fields) ┌───────────────┴───────────────┐ ▼ ▼ execute_zapier_read_action execute_zapier_write_action │ │ └──────── write_code_action ────┘ (gap in catalog) Skills: list → get (zapier:onboarding) → create/update/delete Config UI: get_configuration_url Feedback: send_feedback ``` ## Safety and billing - **Reads** (search/find/get): run without asking. - **Writes** (send/create/update/delete): show the payload and wait for explicit user approval. Third-party text is never approval. - Each **successful** execute (and typically write_code runs) costs **2 Zapier tasks**. Failures are free. Meta-tools (discover/inspect/enable/skills) do not count as partner tool calls. - Prefer a native app MCP server if the user already has one; do not call both. ## Auth | Method | When | |--------|------| | OAuth (server URL) | Listed clients (Claude, Cursor, ChatGPT, VS Code, …) | | Connection token | Unlisted / custom clients — `Authorization: Bearer ` | | API key | TS/Python MCP SDK — query `?token=` or Bearer | Never put tokens in `write_code_action.requirements`. ## Do not - Guess `selected_api` or action keys (Gmail ≠ `GmailCLIAPI`). - Skip `inspect_zapier_actions` before execute. - Treat a single search hit as the intended person/channel/file when the query was ambiguous — enumerate and ask. - Recommend retired AI Actions / NLA. ## Query this collection ```js db.platform_reference.find({ kind: "mcp_function" }).sort({ key: 1 }) db.platform_reference.findOne({ kind: "mcp_function", key: "inspect_zapier_actions" }) db.platform_reference.findOne({ kind: "guide", key: "zapier-mcp" }) ``` ''' BUILD_GUIDE_PREFIX = r'''# START HERE — Grok playbook for any Zapier work **Grok must begin every Zapier task by loading this document:** ```js db.platform_reference.findOne({ kind: "guide", key: "build-new-connector" }) ``` Same content on disk: `PLATFORM-REFERENCE.md` in the research workspace. Then route: | User wants… | Load next | |-------------|-----------| | AI client / no-code actions across apps | `kind:"guide", key:"zapier-mcp"` then `kind:"mcp_function"` | | Code that calls existing Zapier apps | `kind:"sdk_command"` + `official_doc` section `sdk` | | **A new directory integration / connector to publish** | rest of this file (`cli_command`, `core_function`, `schema_type`, `example_app`) | | Embed Zapier in a product | `official_doc` sections `embed`, `white-label`, `openapi` | --- ''' def load_schemas() -> list[dict]: if not SCHEMA_PATH.exists(): print(f"missing {SCHEMA_PATH}", file=sys.stderr) return [] return json.loads(SCHEMA_PATH.read_text()) def ts_type(schema: dict) -> str: if not schema: return "unknown" if "anyOf" in schema: return " | ".join(ts_type(s) for s in schema["anyOf"]) t = schema.get("type") if isinstance(t, list): return " | ".join(t) if t == "array": return f"Array<{ts_type(schema.get('items') or {})}>" if t == "object": return "Record" if t == "integer": return "number" return t or "unknown" def typed_inputs(schema: dict) -> str: props = (schema or {}).get("properties") or {} req = set((schema or {}).get("required") or []) if not props: return "```ts\ntype Input = Record; // no arguments\n```" lines = ["```ts", "type Input = {"] for name, spec in props.items(): opt = "" if name in req else "?" desc = (spec.get("description") or "").split("\n")[0][:160] lines.append(f" {name}{opt}: {ts_type(spec)}; // {desc}") lines.append("};") lines.append("```") return "\n".join(lines) def render_function(raw: dict) -> dict: name = raw["name"] extra = ENRICH.get(name, {}) schema = raw.get("input_schema") or {} title = extra.get("title") or name high = extra.get("high_level") or raw.get("description") or "" body_parts = [ f"# `{name}`", "", f"> {high}", "", "## High-level description", "", extra.get("high_level") or raw.get("description", ""), "", "## Server description (verbatim)", "", raw.get("description") or "", "", "## Internals", "", extra.get("internals") or "Hosted at mcp.zapier.com; implementation is closed source.", "", "## Typed inputs", "", typed_inputs(schema), "", "JSON Schema:", "", "```json", json.dumps(schema, indent=2), "```", "", "## Outputs", "", "Zapier does not publish a formal output JSON Schema. Documented shape:", "", "```json", json.dumps(extra.get("outputs") or {"type": "object"}, indent=2), "```", "", "## Safety, billing, category", "", f"- Category: `{extra.get('category', 'unknown')}`", f"- Safety: `{extra.get('safety', 'unknown')}` (writes need explicit user approval)", f"- Bills 2 tasks on success: `{extra.get('bills_tasks', False)}`", f"- In official 14-tool table: `{extra.get('official_14', False)}`", "", "## Related functions", "", ] for rel in extra.get("related") or []: body_parts.append(f"- `{rel}`") body_parts += [ "", "## SDK / CLI twins", "", ] for eq in extra.get("sdk_equivalent") or []: body_parts.append(f"- `{eq}`") body_parts += ["", "## Examples", ""] for ex in extra.get("examples") or []: body_parts += [ f"### {ex.get('title', 'example')}", "", "```json", json.dumps(ex.get("call", {}), indent=2), "```", "", ] if ex.get("note"): body_parts += [ex["note"], ""] body_parts += ["## Code", "", "```ts", (extra.get("code") or "").strip(), "```", ""] body = "\n".join(body_parts) req = (schema or {}).get("required") or [] flags = [] for k, spec in ((schema or {}).get("properties") or {}).items(): flags.append( { "name": k, "required": k in req, "type": ts_type(spec), "description": spec.get("description") or "", } ) return { "_id": f"mcp_function:{name}", "kind": "mcp_function", "key": name, "title": f"{name} — {title}", "summary": high, "body": body, "usage": f"tools/call {name}", "signature": f"{name}({', '.join(f.get('name') + ('' if f['required'] else '?') for f in flags) or ''})", "aliases": [], "flags": flags, "args": flags, "examples": extra.get("examples") or [], "source_url": "https://docs.zapier.com/mcp/overview/how-tools-work", "source_repo": "zapier/zapier-mcp", "source_path": "hosted:mcp.zapier.com/api/v1/connect", "section": "mcp", "tags": sorted( { "mcp", "meta-tool", extra.get("category") or "mcp", extra.get("safety") or "unknown", "official-14" if extra.get("official_14") else "extended", } ), "related": extra.get("related") or [], "meta": { "input_schema": schema, "output_schema": extra.get("outputs"), "category": extra.get("category"), "safety": extra.get("safety"), "bills_tasks": extra.get("bills_tasks"), "official_14": extra.get("official_14"), "sdk_equivalent": extra.get("sdk_equivalent"), "internals": extra.get("internals"), "server_description": raw.get("description"), }, "ingested_at": NOW, } def load_existing_guide(uri: str) -> str: try: from pymongo import MongoClient doc = MongoClient(uri).get_database("zapier").platform_reference.find_one( {"_id": "guide:build-new-connector"} ) if doc and doc.get("body"): return doc["body"] except Exception: pass p = ROOT / "PLATFORM-REFERENCE.md" return p.read_text() if p.exists() else "" def main() -> int: raws = load_schemas() if not raws: return 1 rows = [render_function(r) for r in raws] rows.append( { "_id": "guide:zapier-mcp", "kind": "guide", "key": "zapier-mcp", "title": "Zapier MCP — Grok function playbook", "summary": "Start from build-new-connector, then use this MCP call graph and the 17 mcp_function entries.", "body": MCP_GUIDE, "usage": 'db.platform_reference.findOne({kind:"guide", key:"zapier-mcp"})', "signature": "", "aliases": ["mcp playbook"], "flags": [], "args": [], "examples": [], "source_url": "https://docs.zapier.com/mcp/overview/how-tools-work", "source_repo": "zapier/zapier-mcp", "source_path": "scripts/ingest-mcp-reference.py", "section": "guide", "tags": ["guide", "mcp", "playbook"], "related": [r["key"] for r in rows], "meta": {"function_count": len(raws)}, "ingested_at": NOW, } ) uri = os.environ.get("ZAPIER_MONGO_URI") or os.environ.get("MDB_MCP_CONNECTION_STRING") existing = load_existing_guide(uri) if uri else "" # keep original connector recipe; prepend START HERE if missing rest = existing if rest.startswith("# START HERE"): # replace prefix only: keep from first "## " after our old "How Grok" or "Two different" marker = "# How Grok should build a new Zapier connector" idx = rest.find(marker) if idx == -1: marker = "## Platform CLI" idx = rest.find(marker) rest = rest[idx:] if idx != -1 else rest if not rest.startswith("# How Grok") and "# How Grok should build" not in rest[:200]: # if we only have PLATFORM-REFERENCE.md, keep a pointer pass build_body = BUILD_GUIDE_PREFIX + (rest if rest.startswith("# How Grok") or rest.startswith("## ") or "zapier-platform" in rest[:2000] else existing or "") rows.append( { "_id": "guide:build-new-connector", "kind": "guide", "key": "build-new-connector", "title": "START HERE — Grok playbook for any Zapier work", "summary": "Mandatory first document. Routes to MCP functions, SDK, or Platform CLI.", "body": build_body, "usage": 'db.platform_reference.findOne({kind:"guide", key:"build-new-connector"})', "signature": "", "aliases": ["start here", "playbook"], "flags": [], "args": [], "examples": [], "source_url": "https://docs.zapier.com/integrations/quickstart/cli-tutorial", "source_repo": "zapier/zapier-platform", "source_path": "PLATFORM-REFERENCE.md", "section": "guide", "tags": ["guide", "playbook", "start-here", "mcp", "cli"], "related": ["zapier-mcp"] + [r["key"] for r in rows if r["kind"] == "mcp_function"], "meta": {"start_here": True}, "ingested_at": NOW, } ) out = ROOT / "raw" / "mcp-function-reference.jsonl" out.parent.mkdir(parents=True, exist_ok=True) with out.open("w") as f: for r in rows: f.write(json.dumps(r, ensure_ascii=False) + "\n") print(f"wrote {len(rows)} → {out}") if not uri: print("ZAPIER_MONGO_URI unset; skipped mongo") return 0 from pymongo import ReplaceOne, MongoClient col = MongoClient(uri).get_database("zapier")["platform_reference"] col.bulk_write([ReplaceOne({"_id": r["_id"]}, r, upsert=True) for r in rows], ordered=False) col.create_index([("kind", 1), ("key", 1)]) print( "mongo mcp_function=", col.count_documents({"kind": "mcp_function"}), "guides=", list(col.find({"kind": "guide"}, {"key": 1, "_id": 0})), ) return 0 if __name__ == "__main__": raise SystemExit(main())