Everything you use, connected in one place. Then build whatever you need on top.
Executor is your agents' cloud. All of your connectors, skills, automations, personal software, browserComing soon, and computerComing soon, all in one place.
Rather keep it on your machine? Desktop, CLI, or self-hosted ↓
Everything you use.
Gmail, Calendar, GitHub, Linear, Stripe. Add an MCP server you already have, or ask your agent to write the tool. You connect each account once through a secure link, so credentials never end up in chat or in code.
Gmail
Calendar
GitHub
Linear
Slack
Notion
Stripe
Drive
PostHog
Sentry
X
- Missing something?Ask your agent to add it
Connected in one place.
Add Executor to your agent once. Every account you connect is then available to every agent you use. Your agent does the thinking; Executor is where it reaches your tools, and where the things it builds keep running.
ExecutorThen build whatever you need on top.
Start with the tools a service already has. When something's missing, ask your agent to add it. Here's how one Gmail connection grows:
“Connect my Gmail.”
Your agent can search, read, label, and send mail.
Some other things to ask for
“Every three hours, copy new Stripe customers into Notion.”
A cron job moves the data. No agent has to be running.
ScheduleTool“Every Friday, post what shipped on GitHub to Slack.”
Runs at 5pm every Friday, the same way every time.
ScheduleTool“When a Sentry issue spikes, open a Linear ticket.”
Sentry calls the app, and the app files the ticket.
WebhookTool“Show me signups, errors, and open issues on one page.”
A one-off dashboard stops being a project.
UITool
As small or as large as you need.
An app can be a single tool. It can also combine tools, storage, a page, schedules, webhooks, and workflows. Add each piece when you need it.
Tools
Call anything, it's just JavaScript.
Skills
Give your agent instructions it can use again.
Apps
A page for people, beside the tools for agents.
Storage
Keep data and state between runs.
Triggers
Run on a schedule or respond to webhooks.
Workflows
Durable work across multiple steps.
What it is, and isn't
- Is it an agent?
- No. Executor runs no AI. It extends the agent you already use, through one MCP server.
- Is it like Composio?
- Both connect your accounts. With Executor you can also bring your own integrations, change how any tool works, and build apps on top of them.
- Is it like n8n or Zapier?
- There's no flow builder. Your agent writes the automation as code, and Executor runs it on a schedule or when a webhook arrives.
- Is it still an MCP gateway?
- Yes. Connecting your tools and routing your agent to them is where it starts. Everything else is optional.
It's just code
And you don't have to write it
import { defineApp, router } from "apps";
import { github } from "./providers";
import { listBriefs, saveBrief, refreshBrief } from "./tools";
import { refresh } from "./workflows";
export const requirements = {
accounts: { github },
};
export default defineApp(requirements, {
tools: router({ listBriefs, saveBrief, refreshBrief }),
workflows: { refresh },
});The entry point
Brings the accounts, data, tools, and workflows together in one app.
import { query, mutation, object, string, number, array } from "apps";
import type { QueryContext, MutationContext } from "apps";
import type { requirements } from "./index";
type Read = QueryContext<typeof requirements>;
type Write = MutationContext<typeof requirements>;
export const Repository = object({ owner: string(), name: string() });
export const BriefInput = object({ repository: string(), openIssues: number() });
export const Brief = object({ id: string(), repository: string(), openIssues: number() });
const columns = "id, repository, open_issues AS openIssues";
export const listBriefs = query(
{ input: object({}), output: array(Brief) },
async ({ sql }: Read) =>
sql.exec(`SELECT ${columns} FROM briefs ORDER BY created_at DESC LIMIT 10`).toArray(),
);
export const saveBrief = mutation(
{ input: BriefInput, output: Brief },
async ({ sql }: Write, input) =>
sql.transaction((tx) =>
tx.exec(
`INSERT INTO briefs (id, repository, open_issues, created_at)
VALUES (?, ?, ?, ?) RETURNING ${columns}`,
crypto.randomUUID(),
input.repository,
input.openIssues,
Date.now(),
).one(),
),
);
export const refreshBrief = mutation(
{ input: Repository },
async (ctx: Write, input) =>
ctx.workflows.start({ workflow: "refresh", input }),
);Tools for your agent
Ordinary functions to read, save, and refresh a brief. Your agent can call them as tools.
import { defineProvider, oauth2 } from "apps";
export const github = defineProvider({
name: "GitHub",
auth: {
oauth: oauth2({
authorizationUrl: "https://github.com/login/oauth/authorize",
tokenUrl: "https://github.com/login/oauth/access_token",
scopes: ["repo"],
}),
},
});Connected accounts
Declares the service this app uses. Executor handles sign-in and keeps credentials out of these files.
CREATE TABLE briefs (
id TEXT PRIMARY KEY NOT NULL,
repository TEXT NOT NULL,
open_issues INTEGER NOT NULL,
created_at INTEGER NOT NULL
);Data that stays
Creates the table your app saves briefs in. Executor applies new migrations when you deploy.
import { workflow, object, number } from "apps";
import type { WorkflowContext } from "apps";
import type { requirements } from "./index";
import { Repository, saveBrief } from "./tools";
type Context = WorkflowContext<typeof requirements>;
const Stats = object({ open_issues_count: number() });
export const refresh = workflow(
{ input: Repository },
async (ctx: Context, { owner, name }) => {
const repository = owner + "/" + name;
const openIssues = await ctx.step.do("Read GitHub", async (step) => {
const path = encodeURIComponent(owner) + "/" + encodeURIComponent(name);
const response = await step.fetch("https://api.github.com/repos/" + path, {
headers: {
Authorization: "Bearer " + step.accounts.github.fields.access_token,
"User-Agent": "Daily brief",
},
});
if (!response.ok) throw new Error("Could not read the repository");
return Stats.parse(await response.json()).open_issues_count;
});
return ctx.step.runMutation("Save the brief", saveBrief, {
repository,
openIssues,
});
},
);Work in the background
Reads from GitHub, then saves the result. Executor tracks each step so work can resume after an interruption.
---
name: brief
description: Review recent GitHub activity with Daily brief.
---
# Give me a project brief
1. Ask which repository to review if it is not clear.
2. Use refreshBrief with the repository owner and name.
3. Wait for the workflow to finish, then call listBriefs.
4. Summarize the saved open issue count in plain language.
Mention which repository the brief covers.
If the refresh fails, say so instead of presenting old data as new.
Follow the user's instructions and Executor's approval requests.Instructions your agent can follow
A Markdown file teaches your agent when to use the app and how to work with its tools.
import { createRoot } from "react-dom/client";
import { array } from "apps";
import { createAppClient, queryReference } from "apps/client";
import { useAppQuery } from "apps/react";
import { Brief, type listBriefs } from "../tools";
const client = createAppClient();
const briefs = client.queryAtom(
queryReference<typeof listBriefs>("listBriefs"),
{},
array(Brief),
);
function DailyBrief() {
const { data, pending, error } = useAppQuery(briefs);
if (pending) return <p>Loading your briefs…</p>;
if (error) return <p>Could not load your briefs.</p>;
return (
<main>
<h1>Your project briefs</h1>
{data?.map((brief) => (
<article key={brief.id}>
<h2>{brief.repository}</h2>
<p>{brief.openIssues} open issues</p>
</article>
))}
</main>
);
}
const root = document.getElementById("root");
if (!root) throw new Error("Missing app root");
createRoot(root).render(<DailyBrief />);An interface for you
A React page that reads the same data as your agent. Executor gives it its own URL.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Daily brief</title>
<script type="module" src="./main.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>The page shell
A normal HTML document loads your app's interface.
{
"name": "daily-brief",
"private": true,
"type": "module",
"dependencies": {
"react": "^19.2.5",
"react-dom": "^19.2.5"
}
}Use the libraries you know
Add packages from npm when you need them. This app uses React for its interface.
Run it where you want
Use Executor Cloud, or run Executor on your own machine or server.
npm i -g executor@betaWhat people say
From people using Executor to connect their agents to their tools.
Pricing
Cloud is free for up to three people. Team is $15 per member per month. Running it yourself is free.
Writing
About
Deploying an app for your agent should be as simple as deploying a website. Executor is where your personal software lives.