# Zapier SDK MCP Server

A MCP (Model Context Protocol) server implementation for the Zapier SDK, allowing AI assistants to interact with Zapier's APIs through a standardized protocol.

## What is MCP?

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is a protocol developed by Anthropic that enables AI assistants like Claude and Cursor to interact with external tools and data sources through a standardized interface. This package creates an MCP server that exposes all Zapier SDK functions as MCP tools.

## Installation

This package is part of the Zapier SDK monorepo and is automatically installed when you install the CLI:

```bash
npm install -D @zapier/zapier-sdk-cli
```

## Usage

### Starting the MCP Server

You can start the MCP server using the CLI:

```bash
# Start the MCP server
npx zapier-sdk mcp

# Enable debug logging
npx zapier-sdk mcp --debug
```

### Using with Claude Code

To use this MCP server with Claude Code, add it to your MCP configuration:

```json
{
  "mcpServers": {
    "zapier-sdk": {
      "command": "npx",
      "args": ["zapier-sdk", "mcp"]
    }
  }
}
```

### Programmatic Usage

You can also use the server programmatically:

```typescript
import { createZapierMcpServer, startMcpServer } from "@zapier/zapier-sdk-mcp";

// Create and configure the server
const server = createZapierMcpServer({ debug: true });

// Or start it directly
await startMcpServer({ debug: true });
```

## How It Works

The MCP server:

1. **Dynamically discovers** all available Zapier SDK functions from the SDK's internal registry
2. **Converts function schemas** from Zod format (used internally by the SDK) to JSON Schema format (required by MCP)
3. **Exposes functions as MCP tools** with kebab-case naming (e.g., `listApps` becomes `list-apps`)
4. **Handles tool execution** by validating inputs, calling the appropriate SDK function, and returning results

## Available Tools

All Zapier SDK functions are automatically exposed as MCP tools:

- `list-apps` - List available Zapier apps
- `list-actions` - List actions for a specific app
- `get-action` - Get details about a specific action
- `run-action` - Execute a Zapier action
- `list-connections` - List your connections
- And many more...

## Error Handling

The server provides comprehensive error handling:

- **Validation errors** include the expected input schema
- **Unknown tools** list all available alternatives
- **Execution errors** provide context about which function failed
- **Debug mode** provides detailed error logging

## Architecture

```
AI Assistant (Claude)
      ↓ MCP Protocol
MCP Server (@zapier/zapier-sdk-mcp)
      ↓ Function Calls
Zapier SDK (@zapier/zapier-sdk)
      ↓ HTTP Requests
Zapier APIs
```

The MCP server acts as an adapter, translating between the MCP protocol and the Zapier SDK's function-based interface.

## Development

### Building

```bash
pnpm build
```

### Testing

```bash
pnpm test
```

## Table of Contents

- [Installation](#installation)
- [Usage](#usage)
- [Available Tools](#available-tools)
- [Accounts](#accounts)
  - [`get-profile`](#get-profile)
- [Actions](#actions)
  - [`create-action-run`](#create-action-run)
  - [`get-action`](#get-action)
  - [`get-action-input-fields-schema`](#get-action-input-fields-schema)
  - [`get-action-run`](#get-action-run)
  - [`list-action-input-field-choices`](#list-action-input-field-choices)
  - [`list-action-input-fields`](#list-action-input-fields)
  - [`list-actions`](#list-actions)
  - [`run-action`](#run-action)
- [Apps](#apps)
  - [`get-app`](#get-app)
  - [`list-apps`](#list-apps)
- [Client Credentials](#client-credentials)
  - [`create-client-credentials`](#create-client-credentials)
  - [`delete-client-credentials`](#delete-client-credentials)
  - [`list-client-credentials`](#list-client-credentials)
- [Code Workflows (Experimental)](#code-workflows-experimental)
  - [`cancel-durable-run`](#cancel-durable-run--experimental)
  - [`create-workflow`](#create-workflow--experimental)
  - [`create-workflow-draft`](#create-workflow-draft--experimental)
  - [`delete-workflow`](#delete-workflow--experimental)
  - [`disable-workflow`](#disable-workflow--experimental)
  - [`discard-workflow-draft`](#discard-workflow-draft--experimental)
  - [`enable-workflow`](#enable-workflow--experimental)
  - [`get-durable-run`](#get-durable-run--experimental)
  - [`get-trigger-run`](#get-trigger-run--experimental)
  - [`get-workflow`](#get-workflow--experimental)
  - [`get-workflow-draft`](#get-workflow-draft--experimental)
  - [`get-workflow-run`](#get-workflow-run--experimental)
  - [`get-workflow-version`](#get-workflow-version--experimental)
  - [`import-workflow`](#import-workflow--experimental)
  - [`list-durable-runs`](#list-durable-runs--experimental)
  - [`list-workflow-drafts`](#list-workflow-drafts--experimental)
  - [`list-workflow-runs`](#list-workflow-runs--experimental)
  - [`list-workflow-versions`](#list-workflow-versions--experimental)
  - [`list-workflows`](#list-workflows--experimental)
  - [`publish-workflow-draft`](#publish-workflow-draft--experimental)
  - [`publish-workflow-version`](#publish-workflow-version--experimental)
  - [`run-durable`](#run-durable--experimental)
  - [`trigger-workflow`](#trigger-workflow--experimental)
  - [`update-workflow`](#update-workflow--experimental)
  - [`update-workflow-draft`](#update-workflow-draft--experimental)
  - [`validate-workflow`](#validate-workflow--experimental)
- [Connections](#connections)
  - [`create-connection`](#create-connection)
  - [`find-first-connection`](#find-first-connection)
  - [`find-unique-connection`](#find-unique-connection)
  - [`get-connection`](#get-connection)
  - [`get-connection-start-url`](#get-connection-start-url)
  - [`list-connections`](#list-connections)
  - [`wait-for-new-connection`](#wait-for-new-connection)
- [HTTP Requests](#http-requests)
  - [`fetch`](#fetch)
- [Human Inputs (Experimental)](#human-inputs-experimental)
  - [`create-form`](#create-form--experimental)
- [Tables](#tables)
  - [`create-table`](#create-table)
  - [`create-table-fields`](#create-table-fields)
  - [`create-table-records`](#create-table-records)
  - [`delete-table`](#delete-table)
  - [`delete-table-fields`](#delete-table-fields)
  - [`delete-table-records`](#delete-table-records)
  - [`get-table`](#get-table)
  - [`get-table-record`](#get-table-record)
  - [`list-table-fields`](#list-table-fields)
  - [`list-table-records`](#list-table-records)
  - [`list-tables`](#list-tables)
  - [`update-table-records`](#update-table-records)
- [Triggers](#triggers)
  - [`ack-trigger-inbox-messages`](#ack-trigger-inbox-messages)
  - [`create-trigger-inbox`](#create-trigger-inbox)
  - [`delete-trigger-inbox`](#delete-trigger-inbox)
  - [`ensure-trigger-inbox`](#ensure-trigger-inbox)
  - [`get-trigger-inbox`](#get-trigger-inbox)
  - [`get-trigger-input-fields-schema`](#get-trigger-input-fields-schema)
  - [`lease-trigger-inbox-messages`](#lease-trigger-inbox-messages)
  - [`list-trigger-inbox-messages`](#list-trigger-inbox-messages)
  - [`list-trigger-inboxes`](#list-trigger-inboxes)
  - [`list-trigger-input-field-choices`](#list-trigger-input-field-choices)
  - [`list-trigger-input-fields`](#list-trigger-input-fields)
  - [`list-triggers`](#list-triggers)
  - [`pause-trigger-inbox`](#pause-trigger-inbox)
  - [`release-trigger-inbox-messages`](#release-trigger-inbox-messages)
  - [`resume-trigger-inbox`](#resume-trigger-inbox)
  - [`update-trigger-inbox`](#update-trigger-inbox)

## Available Tools

All SDK functions are automatically exposed as MCP tools using kebab-case naming.

### Accounts

#### `get-profile`

Get current user's profile information

### Actions

#### `create-action-run`

Start an action run and return its ID without waiting for the result. Running an action is asynchronous: this hands back a run ID immediately, and `getActionRun` fetches the outcome. Reach for this pair when you want to start work and collect it later (fan out many runs, hand the ID to another process, survive a restart). `runAction` is the one-call form that starts a run and waits for its result.

#### `get-action`

Get detailed information about a specific action

#### `get-action-input-fields-schema`

Get the JSON Schema representation of input fields for an action. Returns a JSON Schema object describing the structure, types, and validation rules for the action's input parameters.

#### `get-action-run`

Fetch the current state of an action run started by `createActionRun`. This is a point-in-time read that returns immediately: a run Zapier has not finished executing comes back with status `waiting`, so call again to check for a result. `runAction` starts a run and waits for its result in one call. Results are stored for seven days after the run was created.

#### `list-action-input-field-choices`

Get the available choices for a dynamic dropdown input field

#### `list-action-input-fields`

Get the input fields required for a specific action

#### `list-actions`

List all actions for a specific app

#### `run-action`

Execute an action with the given inputs

### Apps

#### `get-app`

Get detailed information about a specific app

#### `list-apps`

List all available apps with optional filtering

### Client Credentials

#### `create-client-credentials`

Create new client credentials for the authenticated user

#### `delete-client-credentials`

Delete client credentials by client ID

#### `list-client-credentials`

List client credentials for the authenticated user

### Code Workflows (Experimental)

> ℹ️ **Experimental.** Start the MCP server via `zapier-sdk-experimental mcp`, pass `--experimental` to `zapier-sdk mcp`, or set `ZAPIER_EXPERIMENTAL=true` in the environment. Tools and behavior may change.

#### `cancel-durable-run` 🧪 _experimental_

Cancel a run-once durable run in initialized or started status. Returns 409 if the run is already terminal.

#### `create-workflow` 🧪 _experimental_

Create a durable workflow container. Starts disabled with no version; publish a version to add code.

#### `create-workflow-draft` 🧪 _experimental_

Fork a new draft from the workflow's current live version (or a blank stub before the first publish)

#### `delete-workflow` 🧪 _experimental_

Delete a durable workflow. Throws `ZapierNotFoundError` if the workflow doesn't exist; callers wanting idempotency should catch that themselves.

#### `disable-workflow` 🧪 _experimental_

Disable a durable workflow so it stops accepting triggers

#### `discard-workflow-draft` 🧪 _experimental_

Discard an open workflow draft (soft delete). The draft's unpublished edits stop resolving; the published version is untouched.

#### `enable-workflow` 🧪 _experimental_

Enable a durable workflow so it accepts triggers

#### `get-durable-run` 🧪 _experimental_

Get the full state of a run-once durable run, including its operations journal

#### `get-trigger-run` 🧪 _experimental_

Get the workflow run associated with a deployed workflow's trigger. Useful immediately after firing a trigger, when you have the trigger ID but not yet the run ID.

#### `get-workflow` 🧪 _experimental_

Get a durable workflow with its current version details and trigger claim status

#### `get-workflow-draft` 🧪 _experimental_

Get full details of a workflow draft including source files

#### `get-workflow-run` 🧪 _experimental_

Get the current state of a workflow run (a triggered execution of a deployed workflow)

#### `get-workflow-version` 🧪 _experimental_

Get full details of a workflow version including source files

#### `import-workflow` 🧪 _experimental_

Convert an existing Zap into a durable workflow. Transforms the Zap's latest published version (falling back to its draft only when the Zap has never been published) and puts the resulting source in an open draft. The workflow is created disabled with no published version, so publish the draft to make it live. The original Zap is untouched and keeps running.

#### `list-durable-runs` 🧪 _experimental_

List run-once durable runs for the authenticated account, newest first

#### `list-workflow-drafts` 🧪 _experimental_

List drafts for a workflow, most recently edited first (open drafts by default)

#### `list-workflow-runs` 🧪 _experimental_

List workflow runs (triggered executions) for a specific deployed workflow, newest first

#### `list-workflow-versions` 🧪 _experimental_

List published versions for a workflow, newest first

#### `list-workflows` 🧪 _experimental_

List all active durable workflows for the authenticated account

#### `publish-workflow-draft` 🧪 _experimental_

Publish an open draft as a new immutable workflow version. Advances the workflow's live pointer and discards the draft — publish consumes it, so an open draft always means unpublished work. Continue editing by creating a new draft, which forks from the just-published version.

#### `publish-workflow-version` 🧪 _experimental_

Publish a new version of a durable workflow. Enables the workflow by default.

#### `run-durable` 🧪 _experimental_

Run a workflow source file as a run-once durable run on code-substrate-runner (no deployed workflow required). Returns the run ID immediately; poll via getDurableRun for terminal status.

#### `trigger-workflow` 🧪 _experimental_

Look up a workflow's trigger URL and fire it manually, as the authenticated account.

#### `update-workflow` 🧪 _experimental_

Update a durable workflow's name and/or description

#### `update-workflow-draft` 🧪 _experimental_

Update (autosave) an open workflow draft

#### `validate-workflow` 🧪 _experimental_

Validate durable workflow source without publishing it. Returns source diagnostics; issue kinds are extensible and may be unfamiliar to the client.

### Connections

#### `create-connection`

Create a new app connection, end-to-end. Mints the start URL via `get-connection-start-url`, prints it to stderr, opportunistically opens it in a browser when it looks safe to do so (skipping CI / SSH / headless-Linux by default — pass `--browser always` to force, `--browser never` to suppress), then polls via `wait-for-new-connection` until the user completes OAuth and the new connection appears. Returns the connection.

This is the right command for most callers. Reach for the lower-level building blocks when you want either of: (a) hand off the URL and _not_ block on completion — call `get-connection-start-url` alone, no `wait-for-new-connection` needed, or (b) do something custom between minting the URL and waiting — call `get-connection-start-url`, do your work (email or DM the URL, render a QR code, etc.), then `wait-for-new-connection`.

#### `find-first-connection`

Find the first connection matching the criteria

#### `find-unique-connection`

Find a unique connection matching the criteria

#### `get-connection`

Get details for a specific connection

#### `get-connection-start-url`

Mint a short-lived URL that begins an SDK-initiated connection flow. The URL is signed by zapier.com and bound to the current user/account — opening it in a different browser session will fail the binding check. Returns the URL as data so the caller decides what to do with it.

Use this directly (rather than the higher-level `create-connection`) when you want either of: (a) hand off the URL and _not_ block waiting for completion — call this alone, skip `wait-for-new-connection` entirely, or (b) do something custom between minting the URL and waiting for the connection — call this, then email or DM the URL, render it as a QR code for mobile sign-in, etc., then call `wait-for-new-connection`. For the common case where you'd just print and poll back-to-back, `create-connection` is one call.

Pair with `wait-for-new-connection` to detect completion: pass the `startedAt` returned here straight through (it's the server's mint time, so polling isn't affected by client clock skew). Example (JS):

```ts
const {
  data: { url, app, startedAt },
} = await zapier.getConnectionStartUrl({ app: "slack" });
// hand `url` off — print it, DM it, email it, render a button, whatever
const { data: conn } = await zapier.waitForNewConnection({ app, startedAt });
```

#### `list-connections`

List available connections with optional filtering

#### `wait-for-new-connection`

Wait for a new connection to appear for the given app. Polls `/api/v0/connections` with server-side `ordering=-date` until the most recent matching row's `date` is at or after the started-at timestamp, then returns it. Pair with `get-connection-start-url` — that mints the URL the user opens, this waits for the resulting connection to land. Errors with a timeout after the configured timeout (default 5 min). Example (JS):

```ts
const {
  data: { url, app, startedAt },
} = await zapier.getConnectionStartUrl({ app: "slack" });
// show `url` to the user via the channel they're reading from
const { data: conn } = await zapier.waitForNewConnection({ app, startedAt });
```

### HTTP Requests

#### `fetch`

Make authenticated HTTP requests to any API through Zapier. Pass a connectionId to automatically inject the user's stored credentials (OAuth tokens, API keys, etc.) into the outgoing request. Mirrors the native fetch(url, init?) signature with additional Zapier-specific options.

### Human Inputs (Experimental)

> ℹ️ **Experimental.** Start the MCP server via `zapier-sdk-experimental mcp`, pass `--experimental` to `zapier-sdk mcp`, or set `ZAPIER_EXPERIMENTAL=true` in the environment. Tools and behavior may change.

#### `create-form` 🧪 _experimental_

Create a Zapier Form from a title and optional fields. Forms publish by default and return a shareable `hosted_url`; set `draft` to save without publishing.

### Tables

#### `create-table`

Create a new table

#### `create-table-fields`

Create one or more fields in a table

#### `create-table-records`

Create one or more records in a table

#### `delete-table`

Delete a table by its ID

#### `delete-table-fields`

Delete one or more fields from a table

#### `delete-table-records`

Delete one or more records from a table

#### `get-table`

Get detailed information about a specific table

#### `get-table-record`

Get a single record from a table by ID

#### `list-table-fields`

List fields for a table

#### `list-table-records`

List records in a table with optional filtering and sorting

#### `list-tables`

List tables available to the authenticated user

#### `update-table-records`

Update one or more records in a table

### Triggers

#### `ack-trigger-inbox-messages`

Acknowledge messages from a lease. Acked messages are removed from the inbox; unacked ones return to the available pool when the lease expires.

#### `create-trigger-inbox`

Create a new trigger inbox subscription. Always creates a new inbox; use ensureTriggerInbox for get-or-create on a stable key.

#### `delete-trigger-inbox`

Mark a trigger inbox for deletion

#### `ensure-trigger-inbox`

Get-or-create a trigger inbox by key. Idempotent on (user, account, key): returns the existing inbox if a matching subscription is registered, creates a new one otherwise. Throws ZapierConflictError if the key exists with a different subscription.

#### `get-trigger-inbox`

Get details of a trigger inbox by ID

#### `get-trigger-input-fields-schema`

Get the JSON Schema representation of input fields for a trigger. Returns a JSON Schema object describing the structure, types, and validation rules for the trigger's input parameters.

#### `lease-trigger-inbox-messages`

Lease up to N messages from a trigger inbox. Returns messages plus a lease ID; ack within the lease window to remove from the inbox.

#### `list-trigger-inbox-messages`

List messages in a trigger inbox (no payload, status-only)

#### `list-trigger-inboxes`

List all trigger inboxes for the authenticated user

#### `list-trigger-input-field-choices`

Get the available choices for a dynamic dropdown input field on a trigger

#### `list-trigger-input-fields`

Get the input fields required for a specific trigger

#### `list-triggers`

List all triggers for a specific app

#### `pause-trigger-inbox`

Pause a trigger inbox; events stop being collected

#### `release-trigger-inbox-messages`

Release messages from a lease back to the inbox without acknowledging them. Released messages become immediately available for re-leasing. The lease attempt still counts against the per-message lease limit; releasing does not refund the attempt.

#### `resume-trigger-inbox`

Resume a paused trigger inbox; events resume being collected

#### `update-trigger-inbox`

Update settings on an existing trigger inbox
