@zapier/mcp-integration
SDK for using remote Model Context Protocol (MCP) servers in/as Zapier integrations.

Table of Contents
Prerequisites
To use @zapier/mcp-integration
, a Zapier integration needs to:
- Use
zapier-platform-core@17.7.0
or later.
- Use TypeScript as language, and ESM modules.
- Have been opted in to use
z.cache()
.
The remote MCP server needs to:
- Support OAuth2 with Dynamic Client Registration.
- StreamableHTTP or SSE.
Create an MCP integration
To create an integration that primarily uses MCP, including to handle authentication, follow these steps.
-
Initialize the integration:
zapier init my-integration --language=typescript --module esm --template oauth2
cd my-integration
NOTE: We will provide a mcp
template to replace some of the following steps.
-
Remove files we we won't need:
rm -rf src/test src/authentication.ts src/middleware.ts
-
Install this package and all other dependencies:
npm install @zapier/mcp-integration
-
Create src/mcp.ts
:
import { MCPIntegration } from '@zapier/mcp-integration';
import packageJson from '../package.json' with { type: 'json' };
export default new MCPIntegration({
name: packageJson.name,
version: packageJson.version,
serverUrl: 'https://example.com/mcp',
transport: 'streamable',
auth: {
type: 'oauth2',
},
});
Replace https://example.com/mcp
with your remote MCP server URL, and change transport
when needed.
-
Create src/actions.generated.ts
:
export default {};
This is just for the next step to compile before we have actually generated actions.
-
Replace src/index.ts
:
import zapier from 'zapier-platform-core';
import packageJson from '../package.json' with { type: 'json' };
import mcp from './mcp.js';
import actions from './actions.generated.js';
export default mcp.defineApp({
version: packageJson.version,
platformVersion: zapier.version,
...actions,
});
-
Compile:
npm run build
NOTE: To not have to repeat this, add "dev": "npm run build -- --watch",
to your scripts
in package.json
, and keep a terminal window open running npm run dev
.
-
Create .env
:
CLIENT_ID='dynamic'
CLIENT_SECRET='dynamic'
This is because zapier invoke auth
does not support Dynamic Client Registration and assumes these to be needed.
-
Create a local connection:
zapier invoke auth start
NOTE: If you run into issues, make sure you at on 17.7.0 or later.
-
Generate the actions:
npx @zapier/mcp-integration generate
NOTE: If you get a "Missing or invalid access token" error, check .env
to make sure there is a linebreak before authData_type
. This is a zapier-platform-cli
bug (#1129).
-
Register the integration:
zapier register
-
Push the integration:
zapier push
-
Request to have your integration be opted in to to use z.cache()
.
NOTE: This feature isn't enabled by default yet. Staff can enable it using the zache
switch.
Update an MCP integration
You can run npx @zapier/mcp-integration generate
at any time to generate actions for new tools and update actions when tools have changed.
NOTE: We don't yet delete generated actions for tools that no longer exist, but they will be dropped from actions.generated.ts
.
After running the command again, run any linter and formatter tools before using git diff
to inspect the changes.
Customize generated actions
Once you have created or updated an MCP integration, run zapier validate
and perform manual tests (e.g. in a Zap) to identify where actions need to be customized using a transformer.
Setup
Create src/transformer.ts
:
import { defineTransformer, extendAction } from '@zapier/mcp-integration';
export default defineTransformer({
transformCreate: (action, tool) => {
console.log(`Create: ${action.key} / Tool: ${tool.name}`);
if (action.key === 'create_example') {
return extendAction(action).build();
} else {
return action;
}
},
transformSearch: (action, tool) => {
console.log(`Search: ${action.key} / Tool: ${tool.name}`);
return action;
},
transformTrigger: (action, tool) => {
console.log(`Trigger: ${action.key} / Tool: ${tool.name}`);
return action;
},
});
Hook the transformer up via src/mcp.ts
:
import transformer from './transformer.js';
export default new MCPIntegration({
transformer,
});
The above setup won't have any effect. Read on how to chain calls on extendAction
to customize them.
Field Manipulation
Add fields:
extendAction(action)
.prependFields({ key: 'token', type: 'string', required: true })
.appendFields({ key: 'debug', type: 'boolean', default: false })
.build();
Modify fields:
extendAction(action)
.modifyField('page_id', (field) => ({
...field,
dynamic: 'list_pages.id.name',
}))
.build();
Replace all fields:
extendAction(action)
.replaceInputFields(
{ key: 'url', type: 'string', required: true },
{ key: 'method', type: 'string', choices: ['GET', 'POST', 'PUT'] },
)
.build();
Input Field Groups
Organize fields in the UI with groups:
export const inputFieldGroups = [
{ key: 'content', label: 'Content', emphasize: true },
{ key: 'formatting', label: 'Formatting', emphasize: false },
];
export const inputFields = [
{ key: 'title', type: 'string', required: true, group: 'content' },
{ key: 'text', type: 'text', required: true, group: 'content' },
{
key: 'formatting',
type: 'string',
list: true,
choices: { bold: 'Bold', italic: 'Italic' },
group: 'formatting',
},
];
extendAction(action)
.replaceInputFields(withNamedImport('../utils/myAction.js', 'inputFields'))
.replaceInputFieldGroups(
withNamedImport('../utils/myAction.js', 'inputFieldGroups'),
)
.build();
Dynamic Dropdowns
Link fields with given keys to use (custom) triggers to populate dynamic dropdowns:
const addDropdowns = addCommonDynamicDropdowns({
page_id: 'list_pages.id.name',
user_id: 'list_users.id.name',
});
extendAction(action).transformAllFields(addDropdowns).build();
Drop actions
Return undefined
instead of the (build customized) action to not have it generated.
Control Action Types
transformTool: (tool) => {
if (tool.name === 'get-user') {
return {
...tool,
annotations: { ...tool.annotations, readOnlyHint: true },
};
}
return undefined;
},