google-task-ifttt-webhook-mcp
by doitian
README.md
# google-task-ifttt-webhook-mcp
MCP server running on Cloudflare Workers that exposes a `create_google_task` tool.
Sends tasks to Google Tasks via the [IFTTT Maker webhook](https://ifttt.com/maker_webhooks).
Authentication is handled by **Cloudflare Access** (Managed OAuth). The worker validates the `Cf-Access-Jwt-Assertion` JWT against Access JWKS.
## How it works
```
MCP Client Cloudflare Access Cloudflare Worker IFTTT
| | | |
|-- OAuth login ------------>| | |
|<-- access granted ---------| | |
| | | |
|-- POST /mcp -------------->|-- Cf-Access-Jwt-Assertion->| |
| | |-- POST JSON ------->|
| | |<-- "Congratulations"|
|<-- result -----------------|<-------------------------| |
```
## Prerequisites
- [Node.js](https://nodejs.org/) 22.18+ (also runs the TypeScript source in the test suite)
- [Cloudflare account](https://dash.cloudflare.com/) with Workers enabled
- [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/applications/) (requires Cloudflare Zero Trust)
- IFTTT account with the [Maker Webhooks](https://ifttt.com/maker_webhooks) service connected
- A Google Tasks applet on IFTTT triggered by a JSON web request event named `task`
## Setup
### 1. Install dependencies
```pwsh
npm install
```
### 2. Get your IFTTT Maker key
1. Go to [IFTTT Maker Webhooks](https://ifttt.com/maker_webhooks)
2. Click **Documentation**
3. Your key is the last path segment of the URL shown:
`https://maker.ifttt.com/use/{YOUR_KEY}`
4. Set it as a Worker secret:
```pwsh
npx wrangler secret put IFTTT_MAKER_TASK_KEY
```
### 3. Set up the IFTTT applet
Create an applet on IFTTT:
- **If**: Maker Webhooks → Receive a web request with a JSON payload, event name `task`
- **Then**: Google Tasks → Create a task
- Title: `{{JsonPayload.title}}`
- Notes: `{{JsonPayload.notes}}`
- Due date: `{{JsonPayload.due}}`
The worker posts to:
`https://maker.ifttt.com/trigger/task/json/with/key/{KEY}`
with a JSON body like `{ "title": "...", "notes": "...", "due": "..." }`.
### 4. Configure Cloudflare Access
1. Go to [Cloudflare Zero Trust dashboard](https://one.dash.cloudflare.com/)
2. Navigate to **Access** → **Applications** → **Add an application** → **Self-hosted**
3. Configure:
- **Application name**: `google-task-mcp`
- **Application domain**: your worker hostname (custom domain or `*.workers.dev`)
- **Identity providers**: Select your preferred IdP (Google, GitHub, email OTP, etc.)
- **Policy**: Create an allow policy for the users who should access the MCP server
- **Managed OAuth (Beta)**: Must be enabled
4. Note your **team domain** (e.g. `myteam.cloudflareaccess.com`) and the **Application Audience (AUD)** tag
5. Set them as Worker secrets:
```pwsh
npx wrangler secret put CF_ACCESS_TEAM_DOMAIN
npx wrangler secret put CF_ACCESS_AUD
```
`CF_ACCESS_AUD` is optional but recommended (enables audience validation on the JWT).
### 5. Deploy
```pwsh
npm run deploy
```
### 6. Test locally (optional)
```pwsh
npm run dev
```
The worker is available at `http://localhost:8787`. When `CF_ACCESS_TEAM_DOMAIN` is unset, any present `Cf-Access-Jwt-Assertion` header is accepted (use a dummy value for local calls).
Run the local checks with:
```sh
npm run typecheck
npm test
```
The tests use signed fixture JWTs and mocked JWKS responses to check authentication,
routing, and capability discovery. They do not contact Cloudflare or IFTTT.
### 7. Configure the MCP client
Point the client at the worker URL (prefer `/mcp`; `/` and `/mcp/` are also accepted without redirects). Auth is Cloudflare Access Managed OAuth — the client completes Access login; Access injects `Cf-Access-Jwt-Assertion` on requests to the worker.
The worker uses JSON responses over Streamable HTTP with protocol version
`2025-06-18`. Initialization returns that supported version even if a client requests
another version; the client must support it to continue. No session ID is required.
There is no legacy SSE endpoint. An authenticated `GET` returns HTTP 405, which is
allowed for servers without a standalone stream by the
[Streamable HTTP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports).
#### Cloudflare MCP Portal
Set the upstream **HTTP URL** to:
`https://google-task-ifttt-webhook-mcp.doitian.workers.dev/mcp`
After saving, select **Sync capabilities**. Cloudflare documents that URLs ending
in `/mcp` use Streamable HTTP directly; other URL patterns can trigger SSE fallback.
See [Cloudflare's transport and troubleshooting documentation](https://developers.cloudflare.com/cloudflare-one/access-controls/ai-controls/mcp-portals/).
#### opencode
```json
{
"mcp": {
"google-tasks": {
"type": "remote",
"url": "https://google-task-mcp.yourdomain.com/mcp",
"oauth": {}
}
}
}
```
#### Claude Desktop / other streamable HTTP clients
Use your client's remote MCP + OAuth settings with:
`https://google-task-mcp.yourdomain.com/mcp`
## Tool: `create_google_task`
| Parameter | Required | Description |
|-----------|----------|-------------|
| `title` | yes | Single-line task title |
| `notes` | no | Optional detailed notes. `\n` for line breaks |
| `due` | no | Optional due date in ISO 8601 UTC (e.g. `2026-07-25T23:59:59Z`) |
### Example MCP call
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_google_task",
"arguments": {
"title": "Buy groceries",
"notes": "Milk, eggs, bread",
"due": "2026-07-25T23:59:59Z"
}
}
}
```
## Secrets / env
| Name | Required | Description |
|------|----------|-------------|
| `IFTTT_MAKER_TASK_KEY` | yes | IFTTT Maker Webhooks key |
| `CF_ACCESS_TEAM_DOMAIN` | prod | Cloudflare Zero Trust team domain (e.g. `myteam.cloudflareaccess.com`). Omit locally to skip JWKS verification |
| `CF_ACCESS_AUD` | no | Access application audience tag (enables audience validation) |
| `ICON_URL` | no | Override the default server icon URL returned on `initialize` |
Set secrets via:
```pwsh
npx wrangler secret put <NAME>
```
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/`, `/mcp`, or `/mcp/` | MCP JSON-RPC (requires valid `Cf-Access-Jwt-Assertion`) |
| `GET`, `DELETE` | `/`, `/mcp`, or `/mcp/` | HTTP 405 after authentication; no SSE stream or session deletion |
| `OPTIONS` | `*` | CORS preflight |
Supported MCP methods: `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`.
Notifications receive an empty HTTP 202 response. Unsupported RPC methods return
JSON-RPC `-32601` over HTTP 200; HTTP 404 is reserved for unknown paths.
## Troubleshooting
**Portal reports `Not found: /mcp/sse`**
Earlier versions rejected `/mcp/` before JWT validation. Use the exact `/mcp` URL
above and sync capabilities, or deploy this version to also accept `/mcp/`.
The `/mcp/sse` error is consistent with the portal exhausting its transport
fallbacks, but does not reveal which earlier request failed. This worker does not
implement legacy SSE, so `/mcp/sse` should still return 404.
If syncing still fails, inspect the server status hover or API `error_details`
(`status_code`, `mcp_code`, and `cause`) and correlate with Worker request logs.
An unauthenticated HTTP 401 from Cloudflare Access is expected and does not test
the authenticated Worker handshake. A successful local test also does not verify
the deployed server or the portal's OAuth credentials.
**"Unauthorized: Cloudflare Access authentication required"**
Missing or invalid `Cf-Access-Jwt-Assertion`. Ensure Access protects the worker hostname, Managed OAuth is enabled, and the client completed Access login. Locally, send any non-empty `Cf-Access-Jwt-Assertion` header when `CF_ACCESS_TEAM_DOMAIN` is unset.
**"IFTTT webhook failed (HTTP 404)"**
The Maker key is invalid or no applet listens for event `task`. Verify the key and applet event name.
**"IFTTT_MAKER_TASK_KEY is not configured on the server."**
```pwsh
npx wrangler secret put IFTTT_MAKER_TASK_KEY
```
Then redeploy.
**JWT verification failures in production**
Confirm `CF_ACCESS_TEAM_DOMAIN` matches your team domain exactly (hostname only, or full `https://…` URL). If set, `CF_ACCESS_AUD` must match the Access application AUD tag.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues