# activepieces/activepieces

> Self-hosted workflow automation platform with about 760 TypeScript "pieces", run by a worker engine and exposed to agents over MCP.

- Category: [API layer & connectors](https://llms-technical-reviews.com/connectors/)
- Repository: https://github.com/activepieces/activepieces (reviewed at commit `e3b8c02cb4def72b908902c7bf515b09d5110e63`, 2026-10-06)
- Stars: 24924 · Language: TypeScript · License: n/a
- Canonical page: https://llms-technical-reviews.com/p/activepieces/

## Overview

Activepieces is a self-hosted workflow automation platform in the Zapier/n8n mould, with an MCP server added on top. Its integrations are called **pieces**: npm packages written in TypeScript against a small framework (`createPiece`, `createAction`, `createTrigger`). At the pinned commit the repo ships 736 community pieces and 27 core pieces (HTTP, webhook, schedule, tables, code helpers and so on). A piece declares props, auth and a `run` function. The platform supplies everything else: the flow builder UI, encrypted connection storage, OAuth2 refresh, trigger scheduling, a job queue, and a separate worker process that loads the piece and runs it.

For agents, the product's "~400 MCP servers" label overstates what the code does. The MCP endpoint does not mount each piece as its own MCP server. It exposes one server per project with about 45 `ap_*` tools for building, testing and publishing flows. There is also a generic `ap_run_action` that runs any single piece action once, and every enabled flow with an MCP trigger becomes its own tool. An agent therefore reaches the pieces through a small, fixed tool surface plus search. It never sees a list of hundreds of per-API tools.

The core is MIT-licensed. Code under `packages/ee/` and `packages/server/api/src/app/ee` falls under a separate commercial license ([LICENSE](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/LICENSE#L1-L25)).

## Architecture

```mermaid
flowchart LR
  A["MCP client / agent"] --> M["POST /mcp (Streamable HTTP)"]
  UI["React builder"] --> API["Fastify API server"]
  M --> API
  API --> T["ap_* tools + flow tools"]
  T --> Q["Job queue (BullMQ on Redis)"]
  WH["Webhook / polling triggers"] --> Q
  Q --> W["Worker (Socket.IO poll loop)"]
  W --> S["Sandbox runtime"]
  S --> E["Engine process"]
  E --> P["Piece package: action.run()"]
  P --> X["Third-party API"]
  API --> DB["Postgres + pgvector"]
```

| Component | Path | Role |
|---|---|---|
| Pieces framework | `packages/pieces/framework/` | `createPiece`, `createAction`, `createTrigger`, `PieceAuth`, `Property`, metadata schemas |
| Pieces | `packages/pieces/community/`, `packages/pieces/core/` | 736 + 27 integration packages |
| API server | `packages/server/api/src/app/` | Fastify app: connections, flows, triggers, webhooks, MCP, tool search |
| MCP server | `packages/server/api/src/app/mcp/` | Per-request `McpServer`, `ap_*` tools, flow tools, permissions, billing, OAuth |
| Tool search | `packages/server/api/src/app/tool-search/` | pgvector cosine search over actions/triggers, keyword fallback (off by default) |
| Worker | `packages/server/worker/` | Connects to the API over Socket.IO, polls jobs, dispatches to job handlers |
| Sandbox | `packages/server/sandbox/` | Provisions piece/code caches, starts the engine in `isolate` or a plain child process |
| Engine | `packages/server/engine/` | Loads the piece module, resolves props and auth, calls `run`, executes flows |
| Enterprise | `packages/ee/`, `packages/server/api/src/app/ee/` | SSO, audit logs, platform features (commercial license) |

## How a request flows

Take an agent that calls `ap_run_action` to send one Slack message:

1. **MCP ingress.** `POST /mcp` requires a Bearer token, resolves the MCP record and user, builds a fresh server, and serves the call through a stateless `StreamableHTTPServerTransport` (`sessionIdGenerator: undefined`), so every request builds its own server ([mcp-oauth.controller.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/oauth/mcp-oauth.controller.ts#L51-L93)).
2. **Tool registration.** `buildMcpServer` resolves call billing and a permission checker, then registers flow tools and static tools for the project ([mcp-server-builder.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L41-L95)). Each static tool is wrapped with the permission check, credit billing and activity recording ([L292-L307](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L292-L307)).
3. **Validate.** `ap_run_action` takes `pieceName`, `actionName`, `input` and a connection `externalId` ([ap-run-action.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/tools/ap-run-action.ts#L8-L46)). `executePieceActionRun` looks up the piece, turns the connection into a `{{connections['…']}}` reference, and diagnoses unknown, missing or invalid props before anything runs ([flow-run-utils.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/tools/flow-run-utils.ts#L120-L190)).
4. **Enqueue and wait.** It builds a one-step `PieceAction` and calls `actionRunService.run`, which submits an `EXECUTE_ACTION` job and waits synchronously, with the flow timeout as deadline ([action-run.service.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/action-run/action-run.service.ts#L11-L36)).
5. **Worker.** The worker holds a Socket.IO connection to the API and polls for jobs over RPC ([worker.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/worker.ts#L60-L127)). `executeActionJob` resolves the piece archive and calls `ctx.runtime.execute` with an `EXECUTE_ACTION` operation ([execute-action.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-action.ts#L9-L53)).
6. **Sandbox.** The runtime acquires a box, provisions the piece and code caches, then starts the engine and sends it the operation ([sandbox.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/sandbox/src/lib/sandbox.ts#L29-L90)).
7. **Engine.** `executeAction` loads the piece module with `require`, resolves and validates props, and builds an `ActionContext` with `auth`, `store`, `files`, `connections` and `run` hooks for the piece's `run` function ([piece-executor.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/engine/src/lib/handler/piece-executor.ts#L34-L140), [piece-loader.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/engine/src/lib/helper/piece-loader.ts#L9-L37)).
8. **Return.** The tool turns the outcome into MCP text. A timeout before the job started says "safe to retry". A timeout after it started warns that the action may have partly completed ([flow-run-utils.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/tools/flow-run-utils.ts#L236-L259)).

## Key components

### Piece framework

`createPiece` wraps display name, auth, actions and triggers into a `Piece`, rejects duplicate auth types, and clamps `minimumSupportedRelease` ([piece.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/piece.ts#L15-L103)). `createAction` defaults `requireAuth` to true and both `retryOnFailure` and `continueOnFailure` to false ([action.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/action/action.ts#L71-L100)). Actions can carry agent-facing metadata: `audience` (`human`/`ai`/`both`), `aiMetadata`, and a `classification` of `READ`, `SEARCH`, `WRITE` or `DESTRUCTIVE` ([piece-metadata.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/piece-metadata.ts#L52-L108)). Triggers add a `type` strategy, `onEnable`/`onDisable`/`run` hooks and optional webhook handshake and renewal ([trigger.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/trigger/trigger.ts#L41-L74)). All pieces are hand-written. Nothing is generated from OpenAPI.

### MCP surface

Besides the per-project server, a platform-scoped server makes the agent call `ap_set_project_context` first and then routes each tool into the selected project ([mcp-server-builder.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L97-L162)). The server's `instructions` string teaches the flow DSL: step references such as `{{step_1['output'].id}}`, full piece names, and "never delete and recreate a step" ([L22-L39](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L22-L39)). Flow tools run the flow through the webhook service. They block for the result only when the MCP trigger is set to return a response ([L202-L290](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L202-L290)).

### Tool search

`ap_search_actions`/`ap_search_triggers` embed the query and run a pgvector cosine scan over `tool_search_index`. The scan filters by tenant, piece and audience, a τ gate rejects weak top matches, and each hit is flagged `connected` when the project has a connection for that piece. Without an embedder, or when embedding fails, the tools fall back to Fuse keyword search ([tool-search.service.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/tool-search/tool-search.service.ts#L31-L180)). The feature ships disabled (`AP_TOOL_SEARCH_ENABLED`, [tool-search-flag.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/tool-search/tool-search-flag.ts#L1-L12)).

### Connections and OAuth2

Connection values are encrypted with AES-256-CBC under `AP_ENCRYPTION_KEY`. A key is generated automatically only in the in-memory-Redis dev mode ([encryption.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/helper/encryption.ts#L26-L81)). On read, `decryptAndRefreshConnection` checks expiry and refreshes under a distributed lock keyed on platform and connection. It also strips the refresh token and client secret before the value leaves the service ([app-connection-service.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts#L496-L511), [app-connection.handler.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/app-connection.handler.ts#L155-L185)). A token counts as expired 15 minutes before its real expiry ([oauth2-util.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/oauth2/oauth2-util.ts#L31-L48)).

### Triggers

Enabling a polling trigger adds a repeating job keyed by flow version. The interval comes from the piece's schedule or `AP_TRIGGER_DEFAULT_POLL_INTERVAL` ([flow-trigger-side-effect.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts#L183-L208)). Each poll runs the trigger's `RUN` hook in the sandbox and submits any returned payloads as new runs ([execute-polling.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-polling.ts#L9-L74)). Webhook flows get `/:flowId` (async) and `/:flowId/sync` (blocks for the flow's response) on every HTTP method ([webhook-controller.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/webhooks/webhook-controller.ts#L15-L81)). Server-side dedupe is shallow: a payload's `_dedupe_key` is counted in Redis for 30 seconds ([dedupe-service.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/dedupe-service.ts#L6-L49)). Real "new item" detection lives in each piece's polling helper and its `store`.

### Sandbox modes

`isolate` (the IOI contest sandbox) is used only in `SANDBOX_PROCESS` and `SANDBOX_CODE_AND_PROCESS` modes. `UNSANDBOXED` and `SANDBOX_CODE_ONLY` start the engine as a plain child process ([create-sandbox-for-job.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/sandbox/src/lib/create-sandbox-for-job.ts#L20-L58)). The isolate path validates mounts and env, then runs `--cleanup`/`--init` per box ([isolate.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/sandbox/src/lib/sandbox/isolate.ts#L18-L90)). The server default is `UNSANDBOXED` ([system.ts](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/helper/system/system.ts#L25-L40)).

## Extending it

- **New piece.** Scaffold a package under `packages/pieces/community/`, export `createPiece({...})` with actions and triggers ([webhook piece](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/core/webhook/src/index.ts#L1-L17) is the smallest example). Use `PieceAuth.OAuth2`, `SecretText` or `CustomAuth` for credentials.
- **Make it agent-friendly.** Set `audience`, `aiMetadata` and `classification` on actions. Search, tool annotations and read-only filtering use them.
- **Custom tools.** Build a flow with an MCP trigger. It shows up as a tool named after `toolName` plus four characters of the flow id, with an input schema taken from the trigger settings.
- **Test before publishing.** `ap_validate_step_config` returns field-level errors without touching the flow. `ap_test_step` runs the flow up to a given step, using sample or supplied trigger data.

## Running it

- **Docker Compose.** `app` and `worker` run the same image with `AP_CONTAINER_TYPE` set to `APP` or `WORKER` (5 worker replicas by default), plus `pgvector/pgvector:0.8.0-pg14` and `redis:7.0.7` ([docker-compose.yml](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/docker-compose.yml#L1-L57)).
- **Required.** Postgres (pgvector for tool search) and Redis. An `AP_ENCRYPTION_KEY` is needed in any non-dev setup.
- **Sandboxing.** Set `AP_EXECUTION_MODE` to a `SANDBOX_*` mode on a host that can run `isolate` if pieces or code steps are not fully trusted.
- **Optional.** `AP_TOOL_SEARCH_ENABLED` plus an OpenAI key for semantic search.

## Strengths and caveats

- **Strength: a full platform, not just a library.** Credential storage, OAuth refresh with locking, polling, webhooks, retries, run history and a UI all come in the box.
- **Strength: agent tooling is deliberate.** Prop diagnosis before execution, started/not-started timeout messages, permission checks, per-call billing and classification metadata show real care for LLM callers.
- **Strength: pieces are plain TypeScript.** A new integration is a small npm package, and the catalog is large.
- **Caveat: not 400 MCP servers.** The MCP surface is one server with flow-building tools and a generic `ap_run_action`. An agent must search, read props and call through that indirection.
- **Caveat: no isolation by default.** The default execution mode is `UNSANDBOXED`, so third-party piece code runs in an ordinary Node process next to the worker.
- **Caveat: heavy for "just tools".** Running one action means API, job queue, worker, sandbox and engine. Latency and moving parts are much higher than for an in-process SDK.
- **Caveat: split license.** SSO, audit logs and several platform features sit under the commercial `ee` license.

*Sources: code at e3b8c02, verified Q&A.*

## How activepieces/activepieces answers the API layer & connectors questions

### How is third-party authentication implemented? (answered)

Third-party authentication is implemented through a layered system of piece-level auth schemas and server-side OAuth2 handlers. 

**Piece auth definitions** live in each piece's source — Slack's `slackAuth` field shows the pattern: `PieceAuth.OAuth2({ authUrl, tokenUrl, scope, required })` for OAuth2, `PieceAuth.SecretText()` for API keys, and `PieceAuth.CustomAuth({ props, validate })` for multi-field credentials (Slack: botToken + userToken). Each auth type can carry a `validate` callback that is invoked at connection time (packages/pieces/community/slack/src/lib/auth.ts:11-47, 82-118).

**Server-side OAuth2 flow** is orchestrated by `appConnectionService`. On upsert, `validateConnectionValue` dispatches by connection type (packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:691-788). For `OAUTH2`, the `credentialsOauth2Service.claim()` POSTs the auth code to the piece's `tokenUrl` using `safeHttp.retryingAxios`, supporting both `authorization_code` and `client_credentials` grants, BODY or HEADER authorization methods, and PKCE (packages/server/api/src/app/app-connection/app-connection-service/oauth2/services/credentials-oauth2-service.ts:16-106). After claiming, `engineValidateAuth` sends the credential to the engine sandbox for the piece to self-validate (packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:790-841).

**Token refresh** occurs automatically on each connection read (via `decryptAndRefreshConnection`): `isExpired()` checks if the token is within 15 minutes of expiry (packages/server/api/src/app/app-connection/app-connection-service/oauth2/oauth2-util.ts:31-48), then `credentialsOauth2Service.refresh()` POSTs the refresh_token to the token URL with timing-safe client credential resolution through the secrets manager (packages/server/api/src/app/app-connection/app-connection-service/oauth2/services/credentials-oauth2-service.ts:108-181). A distributed lock prevents concurrent refreshes.

**Credential storage** encrypts the entire `value` object via AES-256-CBC (`encryptUtils.encryptObject`) using the `AP_ENCRYPTION_KEY` env var or an auto-generated secret (packages/server/api/src/app/helper/encryption.ts:45-47, 72-81). Three OAuth2 "flavors" exist: `OAUTH2` (user-provided client), `CLOUD_OAUTH2` (cloud-managed), and `PLATFORM_OAUTH2` (platform-admin-managed central credentials).

**Multi-tenant isolation** is enforced through `projectIds` array columns, `platformId`, and `scope` (PROJECT/PLATFORM) — every query filters by these (packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:158-163, 194-198).


Citations: [packages/pieces/community/slack/src/lib/auth.ts:11-80](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/community/slack/src/lib/auth.ts#L11-L80) · [packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:691-788](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts#L691-L788) · [packages/server/api/src/app/app-connection/app-connection-service/oauth2/services/credentials-oauth2-service.ts:16-106](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/oauth2/services/credentials-oauth2-service.ts#L16-L106) · [packages/server/api/src/app/app-connection/app-connection-service/oauth2/oauth2-util.ts:31-48](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/oauth2/oauth2-util.ts#L31-L48) · [packages/server/api/src/app/helper/encryption.ts:26-81](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/helper/encryption.ts#L26-L81) · [packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:790-841](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts#L790-L841)

### How is an integration / connector defined? (answered)

An integration/connector is called a **piece** and is defined as a TypeScript class in the `@activepieces/pieces-framework` SDK. 764 pieces ship: 736 under `packages/pieces/community/` and 28 core pieces under `packages/pieces/core/`.

**Piece creation** uses the `createPiece()` factory with a declarative params object: `{ displayName, description, auth, categories, actions: [...], triggers: [...] }` (packages/pieces/framework/src/lib/piece.ts:78-80). Each piece exports its `index.ts` as the entry point — for example, the webhook piece is a single `createPiece({ auth: PieceAuth.None(), actions: [returnResponse], triggers: [catchWebhook] })` call (packages/pieces/core/webhook/src/index.ts:7-17).

**Actions** are defined via `createAction({ name, displayName, description, props, run })` (packages/pieces/framework/src/lib/action/action.ts:71-100). The `props` field accepts a declarative `PiecePropertyMap` of input fields: `Property.Text`, `Property.Number`, `Property.File`, `Property.Dropdown`, `Property.StaticDropdown`, `Property.Array`, `Property.Object`, `Property.Json`, `Property.Markdown`, and property-grouping via `propertyGroups` for UI layout (packages/pieces/framework/src/lib/piece-metadata.ts:82-108). Actions carry optional `audience` (`human`/`ai`/`both`), `aiMetadata`, and `classification` (`READ`/`SEARCH`/`WRITE`/`DESTRUCTIVE`).

**Triggers** follow the same pattern with `createTrigger(...)`, adding `type` (one of `TriggerStrategy`: `WEBHOOK`, `APP_WEBHOOK`, `POLLING`, `MANUAL`), `sampleData`, optional `handshakeConfiguration`/`renewConfiguration`, and lifecycle hooks `onEnable`/`onDisable`/`run` (packages/pieces/framework/src/lib/trigger/trigger.ts:41-60).

**Metadata schema** is defined as `PieceMetadata`: extends `PieceBase` (name, displayName, logoUrl, description, authors, version, auth, categories) with `actions: Record<string, ActionBase>` and `triggers: Record<string, TriggerBase>` (packages/pieces/framework/src/lib/piece-metadata.ts:134-145). Versioning is via semver in `package.json`, with `minimumSupportedRelease`/`maximumSupportedRelease` constraints in the piece body.

**No OpenAPI codegen** is used — all pieces are hand-written TypeScript against the SDK. The `packages/pieces/framework` JIT-installs each piece package into the sandbox by resolving its npm package (via archive bundle from the server or npm registry).


Citations: [packages/pieces/framework/src/lib/piece.ts:15-80](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/piece.ts#L15-L80) · [packages/pieces/core/webhook/src/index.ts:1-17](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/core/webhook/src/index.ts#L1-L17) · [packages/pieces/framework/src/lib/action/action.ts:71-100](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/action/action.ts#L71-L100) · [packages/pieces/framework/src/lib/piece-metadata.ts:82-145](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/piece-metadata.ts#L82-L145) · [packages/pieces/framework/src/lib/trigger/trigger.ts:41-60](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/trigger/trigger.ts#L41-L60) · [packages/pieces/community/slack/src/index.ts:1-80](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/community/slack/src/index.ts#L1-L80)

### How are integrations exposed to LLM agents? (answered)

Integrations are exposed to LLM agents through two channels: **MCP (Model Context Protocol)** and **semantic tool search**.

**MCP server** is the primary agent interface. `buildMcpServer()` creates an `McpServer` from the `@modelcontextprotocol/sdk` with instructions describing the workflow DSL (step references, piece naming, auth patterns) (packages/server/api/src/app/mcp/mcp-server-builder.ts:22-39, 41-73). Two classes of MCP tools are registered: **static tools** (50+ Activepieces-managed operations like `ap_create_flow`, `ap_add_step`, `ap_search_actions`, `ap_get_run`, `ap_test_flow`) and **flow tools** (each MCP-triggered flow in a project becomes a tool calling its `toolName`, description, and input schema from the flow's trigger `McpTrigger` settings) (packages/server/api/src/app/mcp/mcp-server-builder.ts:202-233, 292-307). The MCP server supports OAuth2 client authentication, per-tool permission checking (`McpPermissionChecker`), per-call billing/usage tracking, and activity recording (packages/server/api/src/app/mcp/mcp-server-builder.ts:77-83).

**Semantic tool search** is powered by pgvector. `toolSearchService.searchActions()` and `searchTriggers()` embed the user query via an AI model into a vector, then run a cosine-similarity scan over the `tool_search_index` table: `1 - (embedding <=> $1::vector) AS cosine` with tenant isolation, platform piece filtering, and audience exclusion (packages/server/api/src/app/tool-search/tool-search.service.ts:31-46, 70-141). A τ-gate (`applyNoMatchGate`) rejects semantically distant results. When no embedder is available or embedding fails, it degrades gracefully to a keyword search via the `pieceMetadataService.list` + piece `suggestedActions`/`suggestedTriggers` catalog (Fuse) (packages/server/api/src/app/tool-search/tool-search.service.ts:150-180). Results include `connected` status (whether the project has an active AppConnection for the piece). Each action also carries `audience` (`human`/`ai`/`both`) and `classification` (`READ`/`SEARCH`/`WRITE`/`DESTRUCTIVE`) for agent-appropriate filtering (packages/pieces/framework/src/lib/piece-metadata.ts:52-67).

> **Editor's note.** Correction: semantic tool search is disabled by default (`AP_TOOL_SEARCH_ENABLED=false`), and with it off the `ap_search_actions`/`ap_search_triggers` tools are not registered. The generic `ap_run_action` tool, which runs any single piece action without a flow, is the main way an agent calls a piece directly.

Citations: [packages/server/api/src/app/mcp/mcp-server-builder.ts:41-73](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L41-L73) · [packages/server/api/src/app/mcp/mcp-server-builder.ts:202-306](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L202-L306) · [packages/server/api/src/app/tool-search/tool-search.service.ts:31-141](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/tool-search/tool-search.service.ts#L31-L141) · [packages/server/api/src/app/tool-search/tool-search.service.ts:150-180](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/tool-search/tool-search.service.ts#L150-L180) · [packages/pieces/framework/src/lib/piece-metadata.ts:52-67](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/pieces/framework/src/lib/piece-metadata.ts#L52-L67) · [packages/server/api/src/app/mcp/mcp-server-builder.ts:22-39](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/mcp/mcp-server-builder.ts#L22-L39)

### How is a tool call executed? (answered)

Tool call execution follows a **Worker → Sandbox → Engine** pipeline with sandboxed isolation.

**Job dispatch**: All execution starts in a BullMQ job queue. The API server enqueues jobs (e.g. `WorkerJobType.EXECUTE_ACTION`), and the worker picks them up via a polling loop. `executeActionJob.ts` handles individual action/tool calls: it resolves code steps (compiling user code into Deno/bundled JS artifacts via `actionRunCache`), resolves piece packages (fetching piece npm archives), provisions the sandbox with resolved pieces and code, then calls `ctx.runtime.execute()` (packages/server/worker/src/lib/execute/jobs/execute-action.ts:11-53).

**Flow execution** (`executeFlowJob.ts`) follows the same pattern: resolve the flow version → build an `ExecuteFlowOperation` (BEGIN or RESUME) → execute via sandbox runtime → report status to the API server (packages/server/worker/src/lib/execute/jobs/execute-flow.ts:12-125). The engine handles branch evaluation, step sequencing, AI model calls, and loop/approval gates internally.

**Sandbox isolation** uses the Linux `isolate` binary (from the IOI contest sandbox project), which creates a chroot-like environment with restricted filesystem mounts, process isolation, and resource limits (CPU time, memory, disk writes) (packages/server/sandbox/src/lib/sandbox/isolate.ts:1-80). The sandbox manager acquires a box, provisions caches (pieces + code), starts the engine node process, sends the operation via WebSocket (the `AP_SANDBOX_WS_PORT`/`AP_SANDBOX_WS_TOKEN` env vars), and returns the result. Timeout, memory overflow, and log-size-exceeded errors are detected and translated into structured error codes (`TIMEOUT`, `MEMORY_LIMIT_EXCEEDED`, `LOG_SIZE_EXCEEDED`) (packages/server/sandbox/src/lib/sandbox.ts:29-57, 66-80).

**Retries and rate limiting**: Actions can declare `errorHandlingOptions.retryOnFailure` (default false). BullMQ automatically retries `INTERNAL_ERROR`-terminated jobs (only if it's not the last attempt, `!ctx.lastAttempt`). MCP calls have per-call billing via `mcpUsageTracker.resolveCallBilling()`, which checks credit balance. The `safeHttp.retryingAxios` wrapper provides automatic retry with exponential backoff for HTTP requests (packages/server/worker/src/lib/execute/jobs/execute-flow.ts:91-97).

**Error mapping**: Engine statuses are mapped to run statuses — sandbox timeout → `FlowRunStatus.TIMEOUT`, memory issue → `MEMORY_LIMIT_EXCEEDED`, log overflow → `LOG_SIZE_EXCEEDED`, engine internal error → `INTERNAL_ERROR`, piece bundle not available → `FAILED` with a descriptive `failedStep` (packages/server/worker/src/lib/execute/jobs/execute-flow.ts:91-124).

> **Editor's note.** Correction: the `isolate` sandbox is only used when `AP_EXECUTION_MODE` is `SANDBOX_PROCESS` or `SANDBOX_CODE_AND_PROCESS`; the default is `UNSANDBOXED`, where the engine runs as a plain Node child process (packages/server/sandbox/src/lib/create-sandbox-for-job.ts L48-L58). Workers also do not read BullMQ directly; they poll the API for jobs over a Socket.IO RPC channel.

Citations: [packages/server/worker/src/lib/execute/jobs/execute-action.ts:9-53](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-action.ts#L9-L53) · [packages/server/worker/src/lib/execute/jobs/execute-flow.ts:12-125](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-flow.ts#L12-L125) · [packages/server/sandbox/src/lib/sandbox/isolate.ts:1-80](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/sandbox/src/lib/sandbox/isolate.ts#L1-L80) · [packages/server/sandbox/src/lib/sandbox.ts:29-80](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/sandbox/src/lib/sandbox.ts#L29-L80) · [packages/server/worker/src/lib/execute/jobs/execute-flow.ts:91-124](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-flow.ts#L91-L124)

### How are data sync, webhooks and triggers implemented? (answered)

Triggers implement four strategies via `TriggerStrategy` enum, each handling data ingestion differently.

**POLLING** (periodic sync): When a flow with a polling trigger is enabled, `flowTriggerSideEffect.handlePollingTrigger()` creates a BullMQ `REPEATING` job configured with the piece's `scheduleOptions` (or a default interval from `TRIGGER_DEFAULT_POLL_INTERVAL`) (packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts:183-208). The worker's `executePollingJob` resolves the flow, runs the trigger's `RUN` hook inside the sandbox, and if the hook returns output, submits the payloads via `apiClient.submitPayloads()` which triggers new flow runs (packages/server/worker/src/lib/execute/jobs/execute-polling.ts:9-74).

**WEBHOOK** (ingress per flow): Each webhook-triggered flow receives a unique URL path. The `webhookController` at `/:flowId` and `/:flowId/sync` handles ALL HTTP methods, supporting both async (immediate 200, queue execution) and sync (block until flow completion) modes (packages/server/api/src/app/webhooks/webhook-controller.ts:17-81). `webhookService.handleWebhook()` resolves the flow, optionally processes a handshake challenge, checks payload size limits, and either queues the job (async) or executes synchronously via `handleSync()` (packages/server/api/src/app/webhooks/webhook.service.ts:45-206).

**APP_WEBHOOK** (event routing): Used by pieces that send events to a central Activepieces endpoint (e.g. Slack, GitHub). When enabled, `handleAppWebhookTrigger()` registers listeners via `appEventRoutingService.createListeners()`, keyed by `appName`, `event`, and `identifierValue` (packages/server/api/src/app/trigger/app-event-routing/app-event-routing.service.ts:18-41). Incoming events are matched to flows by these keys.

**Trigger lifecycle**: `triggerSourceService.enable()` soft-deletes any existing trigger source for the flow, saves a new `TriggerSource` record, calls `flowTriggerSideEffect.enable()` which sends the piece's `ON_ENABLE` hook to the engine sandbox, and then the side-effect method creates the appropriate schedule/queue/webhook listener (packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts:17-97). Disabling reverses: calls `ON_DISABLE`, removes repeating jobs, and soft-deletes the source. `TriggerEventService` stores incoming events as files in the `file` service and manages pagination for UI display (packages/server/api/src/app/trigger/trigger-events/trigger-event.service.ts:16-139).

**Deduplication** for incoming webhook events is handled by `dedupe-service.ts` which implements a sliding-window dedup using Redis.


Citations: [packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts:18-208](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts#L18-L208) · [packages/server/worker/src/lib/execute/jobs/execute-polling.ts:9-74](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/worker/src/lib/execute/jobs/execute-polling.ts#L9-L74) · [packages/server/api/src/app/webhooks/webhook-controller.ts:17-81](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/webhooks/webhook-controller.ts#L17-L81) · [packages/server/api/src/app/webhooks/webhook.service.ts:45-206](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/webhooks/webhook.service.ts#L45-L206) · [packages/server/api/src/app/trigger/app-event-routing/app-event-routing.service.ts:1-51](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/app-event-routing/app-event-routing.service.ts#L1-L51) · [packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts:17-97](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts#L17-L97)

### How is it self-hosted and what is open vs proprietary? (answered)

Activepieces is self-hostable via Docker Compose. The `docker-compose.yml` defines four services: `app` (Fastify API server), `worker` (BullMQ job runners, 5 replicas), `postgres` (pgvector 0.8.0 on PostgreSQL 14), and `redis` (7.0.7) (docker-compose.yml:1-58). The app and worker use the same `ghcr.io/activepieces/activepieces` image with `AP_CONTAINER_TYPE` set to `APP` or `WORKER`. All configuration is via `.env` file. Required infrastructure: PostgreSQL with pgvector extension and Redis — no other external services are mandatory.

**License split**: The CE code (Community Edition) is available under the MIT Expat license. Everything outside `packages/ee/` and `packages/server/api/src/app/ee/` directories is MIT (LICENSE:1-25). The EE code is licensed under the Activepieces Enterprise License requiring a paid subscription for production use (packages/ee/LICENSE:1-20). CE and EE are kept separate through a `hooksFactory` pattern: CE exports a default implementation, EE hooks into it via `hooksFactory.set()` in `app.ts`, and CE code must never import from `src/app/ee/`.

**Included in the repo**: All 764 pieces (community + core), the entire engine, sandbox, worker, API server, and web frontend are present in the open repository. **Proprietary components** in `packages/ee/` and `packages/server/api/src/app/ee/` include: SSO/SAML/SCIM managed authentication, enterprise audit logs, API-keys with project scoping, OAuth apps management, managed authn (SAML/OIDC), global connections, platform webhooks, embedded project domain, alerts, secret managers, and advanced AI agent tools (cross-project tools, email, flow-gates, personalization). EE features are gated behind `platform.plan.*` feature flags enforced by `platformMustHaveFeatureEnabled()` middleware on the backend and `LockedFeatureGuard` on the frontend.

The **cloud edition** (`AP_EDITION=cloud`) adds Activepieces-hosted infrastructure — AppSumo integration, license key reporting, embed domain management — and is functionally similar to EE with usage-based billing. No component requires the hosted cloud to function; the open source runs fully standalone with PostgreSQL + Redis.


Citations: [LICENSE:1-25](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/LICENSE#L1-L25) · [packages/ee/LICENSE:1-20](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/ee/LICENSE#L1-L20) · [packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts:525-528](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts#L525-L528) · [packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts:16-17](https://github.com/activepieces/activepieces/blob/e3b8c02cb4def72b908902c7bf515b09d5110e63/packages/server/api/src/app/trigger/trigger-source/flow-trigger-side-effect.ts#L16-L17)
