Chain apps: from an email to an issue to a message
Run a job across several apps: read a Gmail thread, file a Linear issue about it and post the link in Slack, with the agent carrying ids between steps and checking before it creates anything.
This recipe runs one job across three apps: read a customer's Gmail thread, file a Linear issue about it, then post the issue link in a Slack channel. It works the same for any apps you connect.
Status: Shipping
How chaining works
There is no workflow engine on the Flux side. Your agent's own model does the chaining:
- It makes one
apps_executecall per step. Each call runs exactly one action. - It reads each step's result and carries what the next step needs (a thread id, an issue link) into that step's arguments.
- Nothing is rolled back. If the Slack post fails, the Linear issue still exists. So the agent should check before it creates anything, and retry only the step that failed.
Connected apps are shared by every key on your Flux account. Any key that can call the tools server can run this job in these apps. See Who can see your connections.
Before you start
Know the Gmail thread you mean, the Linear team to file in and the Slack channel to post to. Then add the Flux tools server to your agent, if you have not already:
Built into Murage, which decides which abilities it uses. Nothing to set up here. See murage.ai.
Built into Wayland, which decides which abilities it uses. Nothing to set up here. See getwayland.com.
Add the Flux tools server from your terminal.
claude mcp add --transport http flux-tools https://api.fluxrouter.ai/mcp-tools/ \
--header "Authorization: Bearer sk-YOUR-FLUX-KEY"
Then ask Claude Code:
Call the Flux list_capabilities tool and tell me what it returns.
From Claude Code's own docs, checked 2026-09-28.
Add the Flux tools server to the same config. Codex reads the key from FLUX_API_KEY, which you exported above. File: ~/.codex/config.toml
[mcp_servers.flux-tools]
url = "https://api.fluxrouter.ai/mcp-tools/"
bearer_token_env_var = "FLUX_API_KEY"
Then ask Codex:
Call the Flux list_capabilities tool and tell me what it returns.
From Codex's own docs, checked 2026-09-28.
Add the Flux tools server to your global MCP config, then restart Cursor. File: ~/.cursor/mcp.json
{
"mcpServers": {
"flux-tools": {
"url": "https://api.fluxrouter.ai/mcp-tools/",
"headers": {
"Authorization": "Bearer sk-YOUR-FLUX-KEY"
}
}
}
}
Then ask Cursor:
Call the Flux list_capabilities tool and tell me what it returns.
From Cursor's own docs, checked 2026-09-28.
Add the Flux tools server to the same config, then restart Hermes. File: ~/.hermes/config.yaml
mcp_servers:
flux-tools:
url: "https://api.fluxrouter.ai/mcp-tools/"
headers:
Authorization: "Bearer sk-YOUR-FLUX-KEY"
Then ask Hermes:
Call the Flux list_capabilities tool and tell me what it returns.
From Hermes's own docs, checked 2026-09-28.
Not checked yet. We have not checked how OpenClaw adds an MCP server, so we do not show a recipe. See OpenClaw's own docs.
Open Cline's MCP servers panel, edit the settings file, and add the Flux tools server.
{
"mcpServers": {
"flux-tools": {
"type": "streamableHttp",
"url": "https://api.fluxrouter.ai/mcp-tools/",
"headers": {
"Authorization": "Bearer sk-YOUR-FLUX-KEY"
}
}
}
}
Then ask Cline:
Call the Flux list_capabilities tool and tell me what it returns.
From Cline's own docs, checked 2026-09-28.
Step 1: connect the three apps
Ask your agent:
Using the Flux apps tools, connect Gmail, Linear and Slack. Give me each sign-in link, then check
apps_listuntil all three show as connected.
The agent calls apps_connect three times. You open each link and sign in. When apps_list shows "connected": true for gmail, linear and slack, you are ready. An app you have connected before is skipped.
Step 2: run the job
Give the whole job in one message, including the check:
Using the Flux apps tools:
- Find the Gmail thread from Acme about the export bug and read it. Note the thread id.
- Search Linear for an open issue that mentions that thread id. If there is one, use it and do not create another.
- If there is none, create a Linear issue in the Support team. Title it from the thread's subject, summarise the problem in the description, and put
gmail-thread: <thread id>on the last line.- Post the issue's link in the #support Slack channel, with a one-line summary. Use
apps_find_actionsto find each action before you run it. If a step fails, stop and tell me which one; do not start again from the top.
What the agent does with that:
| Step | Tool call | What it carries forward |
|---|---|---|
| Find the actions | apps_find_actions for each task (not billed) | The action slugs and their argument names |
| Read the thread | apps_execute with the Gmail action | Thread id, subject, the customer's words |
| Check for an issue | apps_execute with the Linear search action | An existing issue link, or nothing |
| Create the issue | apps_execute with the Linear create action, only if nothing was found | The new issue's id and link |
| Post to Slack | apps_execute with the Slack message action | Nothing: the job is done |
Why the check matters
The thread id on the last line of the issue is a marker. It makes the job safe to run twice: on a second run, step 2 finds the issue and step 3 is skipped, so a retry or a repeated request never files a duplicate.
Use the same pattern whenever a chain creates something: put a stable id from the source into what you create, and search for that id before you create it again.
If a step fails
| What went wrong | What to do |
|---|---|
A step answers "ok": false | The app refused the action. Read its reason in result, fix the arguments, and run that step again with the values already carried. |
rate_limited | Wait for retry_after_seconds, then run the same step again. |
payment_required | The free runs are used up. Add credit in Billing, then carry on from the step that failed. |
invalid_request on an action | The slug or an argument name was wrong. Ask apps_find_actions again. |
| The agent created two issues | It skipped the check. Add the marker and the search step to your prompt, as above. |
More fixes are on App problems.
From your own code
The same job in code. The script calls the tools server directly, one step at a time, and stops at the first failure. Take each action slug and its argument names from apps_find_actions; the ones below are examples.
import json
import requests
TOOLS_URL = "https://api.fluxrouter.ai/mcp-tools/"
HEADERS = {
"Authorization": "Bearer sk-YOUR-FLUX-KEY",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
def call_tool(name, arguments=None):
"""Call one Flux tool and return its structured answer."""
body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": name, "arguments": arguments or {}}}
res = requests.post(TOOLS_URL, headers=HEADERS, json=body, timeout=320)
res.raise_for_status()
for line in res.text.splitlines():
if line.startswith("data:"):
result = json.loads(line[len("data:"):])["result"]["structuredContent"]
if "error" in result:
raise RuntimeError(f"{name}: {result['error']}: {result.get('message')}")
return result
raise RuntimeError(f"{name}: no answer")
def run(action, arguments):
"""One step: run one action, and stop the job if the app refused it."""
out = call_tool("apps_execute", {"action": action, "arguments": arguments})
if not out["ok"]:
raise RuntimeError(f"{action} failed: {out['result']}")
return out["result"]
# See what each app offers for these tasks (not billed).
for task, app in [("fetch an email thread", "gmail"),
("search issues", "linear"),
("create an issue", "linear"),
("send a message to a channel", "slack")]:
print(app, json.dumps(call_tool("apps_find_actions", {"task": task, "app": app})["actions"])[:500])
# 1. Read the thread.
thread_id = "18f2a9c4e1b7d305"
thread = run("GMAIL_FETCH_MESSAGE_BY_THREAD_ID", {"thread_id": thread_id})
marker = f"gmail-thread: {thread_id}"
# 2. Look before creating: is there already an issue with this marker?
found = run("LINEAR_SEARCH_ISSUES", {"query": marker})
issues = found.get("issues") or []
# 3. Create the issue only if none exists.
if issues:
issue_url = issues[0]["url"]
else:
created = run("LINEAR_CREATE_ISSUE", {
"team_id": "SUPPORT_TEAM_ID",
# Build the title and summary from what `thread` returned.
"title": "Export bug reported by Acme",
"description": f"Summary of the customer's report.\n\n{marker}",
})
issue_url = created["url"]
# 4. Tell the team.
run("SLACK_SEND_MESSAGE", {"channel": "#support", "text": f"New support issue: {issue_url}"})
Each app's result is its own shape, so read the fields your actions return (for example the issue's link) from the apps_find_actions answer and a first test run.
What it costs
apps_find_actions,apps_listandapps_connectare not billed. On an account without a paid plan or credit, eachapps_connectuses one free run.- Each
apps_executeis one action run. This job is four runs, or three when the issue already exists. - Your agent's own thinking between steps is billed as normal chat on the same key.
- App actions: priced per call.
Prices and limits are on Use apps.
Another chain: a weekly digest
The same pattern works for reading several things and writing one. For example:
Using the Flux apps tools, list the pull requests merged in our GitHub repo
acme/webin the last week, then create a Notion page in the Engineering space titled with this week's date, listing each one with its link and a one-line summary.
Here the agent reads first (one or more GitHub runs), then writes once (one Notion run). There is nothing to check for duplicates unless you run it twice in a week; if you might, ask the agent to search Notion for a page with that title first.