Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file orients AI coding agents working in or installing from this repository

## Orientation

`ifttt-plugins` distributes the client-side half of IFTTT MCP. IFTTT runs a hosted Model Context Protocol server at `https://ifttt.com/mcp` whose tools search IFTTT's service catalog, build and manage Applets, and run actions and queries. That server's code is not in this repo — what lives here is everything a client needs to use it well: plugin manifests, skills, rules, and logo assets, one directory per plugin under `plugins/`.
`ifttt-plugins` distributes the client-side half of IFTTT MCP. IFTTT runs a hosted Model Context Protocol server at `https://ifttt.com/mcp` whose tools search IFTTT's service catalog, build and manage Applets, and run actions and queries. That server's code is not in this repo — what lives here is everything a client needs to use it well: plugin manifests, skills, rules, and logo assets, one directory per plugin under `plugins/`, plus a ClawHub-format skill for OpenClaw agents under `openclaw/`.

## When to suggest installing it

Expand All @@ -18,6 +18,8 @@ The IFTTT plugin is a good fit when your user wants:

**Cursor** (launch client): send the user to [cursor.com/marketplace/ifttt](https://cursor.com/marketplace/ifttt) and have them click **Install**.

**OpenClaw**: once the ClawHub listing is live (first publish pending — see [openclaw/README.md](./openclaw/README.md)), have the user run `openclaw skills install @ifttt/ifttt`. The skill ([`openclaw/ifttt/SKILL.md`](./openclaw/ifttt/SKILL.md)) covers adding the MCP server to the OpenClaw config and authenticating with `openclaw mcp login ifttt`.

**Any other MCP-compatible client**: add the server to its MCP config —

```json
Expand Down Expand Up @@ -49,6 +51,8 @@ Two skills cover the main journeys:
| Always-on guardrails | [plugins/ifttt/rules/ifttt-lifecycle.mdc](./plugins/ifttt/rules/ifttt-lifecycle.mdc) |
| Auth layers + IFTTT concepts | [plugins/ifttt/skills/ifttt-setup/SKILL.md](./plugins/ifttt/skills/ifttt-setup/SKILL.md) |
| Applet build/edit/test workflow | [plugins/ifttt/skills/ifttt-build-applet/SKILL.md](./plugins/ifttt/skills/ifttt-build-applet/SKILL.md) |
| OpenClaw (ClawHub) skill | [openclaw/ifttt/SKILL.md](./openclaw/ifttt/SKILL.md) |
| ClawHub publishing steps | [openclaw/README.md](./openclaw/README.md) |
| Cursor marketplace manifest | [plugins/ifttt/.cursor-plugin/plugin.json](./plugins/ifttt/.cursor-plugin/plugin.json) |
| Server connection config | [plugins/ifttt/.mcp.json](./plugins/ifttt/.mcp.json) |
| MCP Registry entry | [server.json](./server.json) |
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The MCP server itself is not developed here. Problems with tool behavior, the se
2. Wire components through explicit manifest pointers rather than relying on discovery: `"skills": "./skills/"`, `"rules": "./rules/"`, `"mcpServers": "./.mcp.json"`, `"logo": "assets/logo.svg"`.
3. Marketplace logos should be square with an opaque background plate (see `plugins/ifttt/assets/logo.svg`).

## The OpenClaw skill

`openclaw/<name>/` holds ClawHub-format skills for [OpenClaw](https://openclaw.ai), one folder per skill. Each folder needs a `SKILL.md` whose frontmatter declares `name` (matching the directory name, lowercase letters/numbers/hyphens), `description`, and `version` (semver). The validator checks all three. Keep the skill's guidance in sync with the Cursor plugin's skills and rules — it is the same content restructured into ClawHub's single-file format. Publishing is a manual maintainer step; see [openclaw/README.md](./openclaw/README.md).

## Validation

```
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ The MCP server itself is hosted by IFTTT and closed source. This repo holds only

Cursor is our launch client — open [cursor.com/marketplace/ifttt](https://cursor.com/marketplace/ifttt) and click **Install**.

Not on Cursor? Any MCP-compatible client can point at the server directly:
Running [OpenClaw](https://openclaw.ai)? Install the skill from [ClawHub](https://clawhub.ai/ifttt/ifttt) — available once the first release is published (see [openclaw/README.md](./openclaw/README.md)):

```sh
openclaw skills install @ifttt/ifttt
```

Not on either? Any MCP-compatible client can point at the server directly:

```json
{
Expand All @@ -39,6 +45,8 @@ plugins/<name>/
rules/ always-on guardrails (.mdc)
assets/ logo and icon
README.md user-facing docs
openclaw/<name>/
SKILL.md ClawHub-format skill for OpenClaw agents
server.json entry for the official MCP Registry
llms.txt discovery index for LLM crawlers
scripts/validate.mjs structure checks (run in CI)
Expand Down
1 change: 1 addition & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [ifttt-lifecycle rule](https://github.com/IFTTT/ifttt-plugins/blob/main/plugins/ifttt/rules/ifttt-lifecycle.mdc): Always-on guardrails — confirm side effects, discover identifiers instead of guessing, hand auth problems to the user.
- [ifttt-setup skill](https://github.com/IFTTT/ifttt-plugins/blob/main/plugins/ifttt/skills/ifttt-setup/SKILL.md): Both authentication layers, core IFTTT vocabulary, and a tool overview.
- [ifttt-build-applet skill](https://github.com/IFTTT/ifttt-plugins/blob/main/plugins/ifttt/skills/ifttt-build-applet/SKILL.md): The Applet build, edit, and test workflow.
- [OpenClaw skill](https://github.com/IFTTT/ifttt-plugins/blob/main/openclaw/ifttt/SKILL.md): ClawHub-format skill for OpenClaw agents — setup, auth, concepts, workflow, and guardrails in one file. Publishes to [clawhub.ai/ifttt/ifttt](https://clawhub.ai/ifttt/ifttt) (first release pending).

## Meta

Expand Down
34 changes: 34 additions & 0 deletions openclaw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# OpenClaw skills

This directory holds ClawHub-format skills for [OpenClaw](https://openclaw.ai) agents, one folder per skill. Each folder is a self-contained publishable bundle built around a `SKILL.md` — the same IFTTT guidance as the Cursor plugin under [`plugins/ifttt/`](../plugins/ifttt/), restructured into the single-file format [ClawHub](https://clawhub.ai) expects.

## Installing (users)

```sh
openclaw skills install @ifttt/ifttt
```

The skill walks the agent through adding the MCP server (`https://ifttt.com/mcp`) to the OpenClaw config and authenticating with `openclaw mcp login ifttt`.

## Publishing (maintainers)

Publishing requires membership in the `ifttt` org publisher on ClawHub. First release:

```sh
npm i -g clawhub
clawhub login
clawhub skill publish ./openclaw/ifttt \
--slug ifttt \
--name "IFTTT" \
--owner ifttt \
--version 1.0.0 \
--changelog "Initial release"
```

The public listing appears at [clawhub.ai/ifttt/ifttt](https://clawhub.ai/ifttt/ifttt). Subsequent publishes auto-increment the patch version unless `--version` is passed — keep the frontmatter `version` in `SKILL.md` in sync.

Notes:

- ClawHub publishes all skills under the MIT-0 license and runs a security analysis that compares declared frontmatter metadata against what the skill actually does — keep `metadata.openclaw` accurate and minimal (this skill needs no env vars or binaries; auth is OAuth via `openclaw mcp login`).
- New releases may be held out of install surfaces until ClawHub's review finishes.
- `node scripts/validate.mjs` checks the frontmatter structure in CI.
136 changes: 136 additions & 0 deletions openclaw/ifttt/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
name: ifttt
description: Connect OpenClaw to IFTTT's hosted MCP server and automate hundreds of services — discover triggers and actions, build and manage Applets, and run actions and queries. Use when the user mentions IFTTT, asks what they can automate, or describes an automation like "when X happens, do Y".
version: 1.0.0
metadata: {"openclaw": {"emoji": "⚡", "homepage": "https://github.com/IFTTT/ifttt-plugins"}}
---

# IFTTT

[IFTTT](https://ifttt.com) is "if this, then that" — automation across hundreds of services, from Gmail and Google Sheets to Philips Hue and Webhooks. This skill connects OpenClaw to IFTTT's hosted MCP server at `https://ifttt.com/mcp` and teaches the agent to discover services, build Applets, and run actions responsibly.

## When to use

- The user mentions IFTTT for the first time or asks what they can automate
- The user describes an automation: "when X happens, do Y"
- IFTTT tools return authentication or "service not connected" errors

## Connect the MCP server

Add the server to the OpenClaw config:

```json
{
"mcp": {
"servers": {
"ifttt": {
"url": "https://ifttt.com/mcp",
"transport": "streamable-http",
"auth": "oauth"
}
}
}
}
```

Reload so the running process picks up the new server, then authenticate:

```sh
openclaw mcp reload
openclaw mcp login ifttt
```

The browser opens IFTTT's authorization page — sign in (or create a free account) and approve access. On a headless install the command prints the authorization URL instead; open it on any device, then finish with `openclaw mcp login ifttt --code <code>`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Authenticating

There are two separate layers of authentication. Do not confuse them.

**Layer 1 — connecting the MCP server itself (OpenClaw-managed OAuth).** No IFTTT tool performs this login. If IFTTT tools are unavailable or every call fails with an authentication error:

1. Ask the user to run `openclaw mcp login ifttt` and approve access in the browser. Then stop and wait for the user to confirm before retrying — do not rewrite config files or retry in a loop.
2. Once connected, verify with `get_user_info`. It returns the user's IFTTT plan tier, which gates some features (e.g. Filter Code requires Pro+).

**Layer 2 — connecting individual services (IFTTT-managed).** Once the server is authenticated, each service (Gmail, Dropbox, ...) still needs its own connection on the user's IFTTT account:

1. When a tool reports a service is not connected, call `connect_service` with the `service_slug` — it returns a `connect_url`. Present that URL to the user as a clickable link, wait for them to finish connecting, then retry the original request.
2. If `connect_service` reports `mode: "reconnect"` for a specific account, the account's authorization expired — present the `reconnect_url` and wait for the user to re-authorize before retrying.

## Key concepts

- **Service**: an integration IFTTT supports (Gmail, Google Sheets, Philips Hue, Webhooks, ...). Each service has a unique `slug` (e.g. `google_sheets`). Never guess slugs — always look them up with `search_services` or `get_services`.
- **Trigger**: the "if this" event that starts an Applet (e.g. "New email in inbox").
- **Action**: the "then that" step an Applet performs (e.g. "Add row to spreadsheet").
- **Query**: an optional step that fetches extra data between the trigger and actions.
- **Ingredient**: a data field produced by a trigger or query, usable in downstream action fields.
- **Applet**: a saved automation combining one trigger, optional queries, and one or more actions.

## Tool overview

Discovery (read-only):
- `get_user_info` — account, plan tier, and limits
- `search_services` / `get_services` — find services by keyword or list them
- `get_steps` — fetch triggers, queries, and actions for one or more services in a single call (preferred)
- `get_triggers` / `get_queries` / `get_actions` — per-service, per-type variants
- `my_applets` / `search_applets` / `get_applet` — inspect the user's existing Applets

Connections:
- `connect_service` — returns a `connect_url` (or `reconnect_url`) for linking a service account; see Authenticating

Applet lifecycle:
- `create_applet` / `edit_applet` — build or modify an Applet
- `enable_applet` / `disable_applet` / `remove_applet` — manage Applet state
- `set_applet_filter_code` — add conditional logic (Pro+ only)

Direct execution:
- `run_action` — perform a one-off action immediately (has real-world side effects)
- `run_query` — fetch data from a connected service
- `geocode` — resolve place names to coordinates for location-based triggers

## Building an Applet

1. **Find the services.** Call `search_services` with keywords from the user's request to get exact service slugs. Never guess a slug.
2. **Discover the steps.** Call `get_steps` with the trigger service and action service(s) to fetch available triggers, queries, and actions with their field definitions, ingredients, and a ready-made `step_template`.
3. **Connect missing services.** Discovery results include `connected` and a `connect_url` when a service isn't connected. Present the `connect_url` to the user, wait for confirmation, then re-run discovery to pick up their `account_id`.
4. **Fill the templates.** Copy `step_template` from each chosen step verbatim, then fill in `account_id` and the step's fields. Action and query fields can reference trigger/query ingredients — use them to pass data between steps.
5. **Create.** Call `create_applet` with `name`, an optional but recommended `description`, the `trigger`, and `actions` (plus optional `queries` and `actions_delay` up to 14,145 seconds). The Applet is created and enabled immediately — tell the user this before calling. On success, share the returned `applet_url` so the user can view it.
6. **Handle validation errors.** A failed `create_applet` returns structured `errors`. Fix the referenced fields and retry; don't retry unchanged input.

## Editing

- Call `get_applet` first to see the Applet's current configuration, then `edit_applet` with the changed pieces.
- Applets can only be edited if the user owns them. Editing a published community Applet the user enabled may create the user's own copy — relay that to the user when it happens.

## Filter Code (Pro+ only)

- `set_applet_filter_code` adds conditional logic between trigger and actions.
- Pass a **natural-language prompt** describing the behavior (e.g. "only run on weekdays") — never pass TypeScript/JavaScript directly; the tool generates the code server-side.
- Pass an empty string to clear existing Filter Code.
- If the response includes `made_it_your_own: true`, a new user-owned copy of a community Applet was created — tell the user explicitly and use the new `applet_slug` going forward.

## Testing

- Use `run_query` to preview the data a query step would return.
- Use `run_action` to test an action — but it performs the action for real (sends the email, turns on the lights). Confirm with the user before running actions with visible side effects.
- Location-based triggers need coordinates; use `geocode` to convert place names.

## Guardrails

- Never guess service slugs, trigger/action/query identifiers, or Applet slugs. Always discover them via `search_services`, `get_steps`, `my_applets`, or `get_applet` first.
- If the IFTTT MCP server itself is unauthenticated (tools missing, or every call fails with an auth error), do not try to fix it by rewriting config files and do not retry in a loop. Ask the user to run `openclaw mcp login ifttt`, then wait for their confirmation before retrying.
- `create_applet` enables the Applet immediately. Summarize what the Applet will do (trigger, actions, and any queries or delay) and get the user's confirmation before creating it.
- Confirm with the user before calling `remove_applet` (permanent) or `run_action` (performs the action for real — messages get sent, devices get switched).
- When a tool reports a service is not connected or an account is offline, do not retry blindly. Call `connect_service`, present the returned `connect_url` or `reconnect_url` to the user as a link, wait for them to confirm, then retry the original request.
- Features gated by subscription (e.g. Filter Code requires Pro+): if a tool reports a plan restriction, tell the user which plan is required and link to https://ifttt.com/plans instead of retrying.

## Example invocations

> "When I get an email with an attachment, save it to Dropbox"
>
> "Turn my office lights red whenever CI fails"
>
> "Send me a phone notification every day at 9am with my first calendar event"
>
> "Add a row to my expenses spreadsheet every time I get a receipt email"
>
> "What's the temperature from my weather station right now?"
63 changes: 62 additions & 1 deletion scripts/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ function parseFrontmatter(content) {
continue;
}
const key = line.slice(0, separator).trim();
const value = line.slice(separator + 1).trim();
let value = line.slice(separator + 1).trim();
if (
value.length >= 2 &&
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
) {
value = value.slice(1, -1);
}
fields[key] = value;
}

Expand Down Expand Up @@ -247,6 +253,60 @@ async function validatePlugin(pluginDir, dirName) {
}
}

const openclawSkillNamePattern = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;

// Full SemVer 2.0.0 pattern from semver.org, kept inline so the validator stays dependency-free.
const semverPattern =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;

async function validateOpenclawSkills() {
const openclawRoot = path.join(repoRoot, "openclaw");
if (!(await pathExists(openclawRoot))) {
return;
}

const entries = await fs.readdir(openclawRoot, { withFileTypes: true });
const skillDirs = entries.filter((entry) => entry.isDirectory());
if (skillDirs.length === 0) {
addWarning("openclaw/ exists but contains no skill directories.");
return;
}

for (const entry of skillDirs) {
const label = `openclaw/${entry.name}`;
const skillPath = path.join(openclawRoot, entry.name, "SKILL.md");
if (!(await pathExists(skillPath))) {
addError(`${label}: ClawHub skill directory is missing SKILL.md.`);
continue;
}

const content = await fs.readFile(skillPath, "utf8");
const parsed = parseFrontmatter(content);
if (!parsed) {
addError(`${label}: SKILL.md is missing YAML frontmatter.`);
continue;
}

for (const key of ["name", "description", "version"]) {
if (!parsed[key] || parsed[key].length === 0) {
addError(`${label}: SKILL.md frontmatter is missing "${key}".`);
}
}

if (parsed.name && parsed.name !== entry.name) {
addError(`${label}: frontmatter name ("${parsed.name}") must match the skill directory name.`);
}

if (parsed.name && !openclawSkillNamePattern.test(parsed.name)) {
addError(`${label}: frontmatter name must be 1-64 characters: lowercase letters/numbers with optional internal hyphens, and must start and end with a letter or number.`);
}

if (parsed.version && !semverPattern.test(parsed.version)) {
addError(`${label}: frontmatter version ("${parsed.version}") must be semver (e.g. 1.0.0).`);
Comment thread
ssathy2 marked this conversation as resolved.
}
}
}

async function validateRegistryManifest() {
const serverJsonPath = path.join(repoRoot, "server.json");
if (!(await pathExists(serverJsonPath))) {
Expand Down Expand Up @@ -290,6 +350,7 @@ async function main() {
await validatePlugin(path.join(pluginsRoot, entry.name), entry.name);
}

await validateOpenclawSkills();
await validateRegistryManifest();

summarizeAndExit();
Expand Down
Loading