master-zapier-plan-draft/research/zapier/scripts/ingest-mcp-reference.py
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

1256 lines
49 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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.<app>.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 <app> search <key>",
"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 <app> write <key>",
"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` 1100, 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 <token>` |
| 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<string, unknown>"
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<string, never>; // 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())