Circuit
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@CircuitSet up a workflow to triage my inbox and draft replies for my approval."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Circuit
A visual workflow builder that lives inside a Claude conversation — and runs on the connectors you already have.
You describe an automation in chat. Claude lays the board while you watch, you edit it by hand, and then Claude runs it one step at a time using your Gmail, your Slack, your Airtable. Circuit never asks for a single credential.
The idea
Every workflow builder makes you learn its canvas: drag a node, open a panel, map a field, repeat. Circuit inverts that. The conversation is the editor. You say what you want; the board appears; you drag a chip only when you disagree with where Claude put it.
Three things make it work.
It streams. While Claude is still emitting the circuit_design call, the host forwards ui/notifications/tool-input-partial. Each time a step object closes in the partial JSON, the board places that chip. The build animation is the model's output arriving — not a canned sequence.
It owns nothing. Circuit has no Gmail integration, no OAuth flow, no API keys, no secrets at rest. A step just names a tool from your tool list — Gmail:search_threads, Slack:send_message, Airtable:create_records_for_table — and Claude makes the call. Anything you can connect to Claude, Circuit can orchestrate on day one. Claude tells Circuit once what it can reach, and from then on a wrong tool name is caught while the board is still being drawn.
It drives. At run time Circuit is a state machine, not an executor. circuit_run hands Claude exactly one directive; Claude does that one thing and reports back with circuit_step; the board updates and the next directive comes out. Branching, filtering and looping never leave the server, so a nine-step workflow with a loop is nine deterministic hops instead of one long improvisation.
you ──"triage my inbox and draft the easy replies"──▶ Claude
│
circuit_design (streams) │
▼
┌───────────────────────────────── the board ─────────────────────────────────┐
│ watch ─▶ each ─▶ classify ─┬─▶ write ─▶ approve ─▶ send │
│ ├─▶ find slots │
│ └─▶ label │
└─────────────────────────────────────────────────────────────────────────────┘
│
circuit_run ─▶ one directive at a time │
▼
Claude calls YOUR connectors ──▶ Gmail · Slack · Calendar · Airtable · …Related MCP server: Agentled MCP Server
What a session looks like
You: Watch my inbox, work out what each unread message wants, draft replies to the sales ones, and don't send anything without showing me first.
Claude reads circuit_catalog, then makes one circuit_design call. The board draws itself chip by chip as the arguments stream in. Then:
You: Run it.
// circuit_run →
{ "act": "call_tool", "stepId": "watch",
"tool": "Gmail:search_threads",
"arguments": { "q": "in:inbox is:unread -from:me", "max_results": 5 },
"expect": "Call that tool, then send the whole result back with circuit_step." }Claude calls its own Gmail tool, reports the threads, and Circuit hands back the next directive — a classify, then a draft, then:
The run stops. You edit the draft in the box and press Approve & continue — the board calls back into the server, the edited text replaces the draft in the run data, and the next directive is the send, with your words in it. Then the loop turns over and does the next thread.
Connectors are checked, not assumed
Naming a tool as a plain string has one sharp edge: get the string wrong and nothing complains until a run is halfway through and a directive points at something Claude cannot call.
circuit_bind closes it. Claude reports the tools it actually has, once; Circuit remembers them and checks every step against that list — the same treatment an unknown step type already gets, extended to connectors.
The step is saved either way — a wrong chip is much easier to see than to describe — but it is marked on the board, listed in the console, and circuit_run refuses to start until it's fixed:
One step names a connector tool you do not have:
post: 'Slack:post_msg' is not in your tool list — did you mean 'Slack:send_message'?
Fix the tool names, or call circuit_bind again if the user has connected something since.Suggestions come from bigram similarity weighted toward the connector: a near-miss inside Gmail: scores far above a closer spelling in some other service, because that is almost always what happened. If nothing scores well enough, you get no suggestion rather than a confident wrong one.
Binding is optional. Without it Circuit says plainly that it could not check, and gets out of the way.
Every workflow has a second path
A step fails. The connector 403s, the record isn't there, the API is having a day. Circuit makes that a designed outcome rather than a dead end — each step carries its own policy:
| What happens |
| The run halts here and keeps everything it had. Nothing after it ran. |
| This path ends; the rest of the run — the next loop item, say — carries on. |
| Circuit hands Claude the same directive again, up to |
| The run leaves by the error port, so the board can show a fallback. |
A stopped run is not a lost one. It keeps its data, its queue, and its place, so
circuit_resume hands out the same directive again once you've fixed the cause —
or steps over it with skip: true. That distinction matters: everything before
the failure already happened, and re-running the whole workflow would repeat
every side effect it had.
The corresponding instruction to Claude is blunt, because this only works if it's honest: report the failure, never substitute a plausible result. A made-up success is the one thing that defeats the entire mechanism.
Quick start
git clone https://github.com/brandononchain/circuit-mcp.git
cd circuit-mcp
npm install
npm run dev # → http://localhost:8787/mcpThen in Claude: Settings → Connectors → Add custom connector, paste http://localhost:8787/mcp, and ask for an automation. That is the whole setup — there is nothing to authorize, because Circuit reaches nothing on its own.
For a public URL without a deploy, npx localtunnel --port 8787 or ngrok http 8787 is enough to try it from claude.ai.
The step kit
Twelve types. Four are settled by Circuit itself; the rest become directives.
On the chip | Type | Who does it | What it is |
when |
| you | Runs when you ask. The default. |
every |
| you | Stores a cron. Pair it with one of your scheduled tasks calling |
watch |
| Claude | Calls a connector tool to look for new items, and starts the run on what it finds. |
do |
| Claude | The workhorse. Names a tool from your connectors and the arguments to call it with. |
decide |
| Claude | Reads the input and picks one label. The label becomes the output port. |
write |
| Claude | Drafts text. Sends nothing — a later do step does that. |
read |
| Claude | Pulls named fields out of the input as structured data. |
only if |
| Circuit | Stops the path unless the conditions hold. |
route |
| Circuit | Routes on a value it already has. |
for each |
| Circuit | Runs everything downstream once per item, with a hard limit. |
ask you |
| you | Parks the run and shows you an editable preview on the board. |
report |
| Claude | Reports back in the conversation. |
The left column is what you actually see. A chip says what it does — model.classify
is a type name, decide is a thing a person recognizes — and the type only appears
when you select the chip.
Nothing here is Gmail-shaped, or Slack-shaped. tool.call is the integration layer, and its surface is whatever you have connected.
Writing a workflow
Steps are plain objects. Wires are named by the port they leave from, which is how a classify fans out:
{
"id": "intent",
"type": "model.classify",
"title": "Read what they want",
"config": {
"labels": ["sales", "scheduling", "other"],
"input": "item",
"instructions": "Pricing questions are sales."
},
"next": [
{ "port": "sales", "to": "draft" },
{ "port": "scheduling", "to": "slots" },
{ "port": "other", "to": "label" }
]
}Anywhere a string appears in a step's config, {{…}} reaches into the run's live data and Circuit substitutes it before the directive ever reaches Claude:
Template | Resolves to |
| the payload the run started with |
| what an earlier step returned |
| the current item inside a |
{ "id": "send", "type": "tool.call", "title": "Send the reply",
"config": { "tool": "Gmail:reply",
"arguments": { "thread_id": "{{item.id}}", "body": "{{steps.draft.text}}" } } }A whole-string template keeps its real type — "{{steps.search.threads}}" hands over the array, not its JSON.
The tool surface
Thirteen tools, one capability each. Three are invisible to the model.
Tool | Visible to | What it does |
| model | Step types and their config keys. Read before designing. |
| model | Reports the connector tools Claude can actually call. |
| model | What Circuit believes you can reach, and when it was told. |
| model + app | Draws a whole workflow. Streams. |
| model + app | Edits an existing board without discarding your layout. |
| model | Reads. |
| model + app | Starts a run, returns the first directive. |
| model + app | Reports a result — or an |
| app + model | Picks a failed run back up, retrying or skipping the step that broke. |
| model + app | History. |
| model + app | Marks a workflow live, and tells Claude how to schedule it. |
| app + model | Approve or reject a held gate, with edits. |
| app only | Persists a drag. Invisible to the model, so a nudge never costs a turn. |
| app + model | Small edits from either side. |
circuit_move is the reason the board feels like a canvas rather than a picture: visibility: ["app"] keeps it out of the model's tool list entirely, so dragging a chip writes to the server without waking Claude.
How it fits together
Claude ──tools/call──────────────▶ Circuit MCP ──▶ storage (Supabase or memory)
▲ │
│ directives, one at a time │ ui://circuit/board.html
│ ▼
└──── calls YOUR connectors ◀── the board ──tools/call (app-only)──▶ Circuit MCPsrc/
graph.ts workflow + run types, and the board layout
registry.ts the twelve step types: schema, summary, and who performs them
engine/run.ts the walker — ports, filters, loops, gates, resume
server.ts the MCP tool surface and the ui:// resource
http.ts one handler: /mcp, /health
board.ts what the canvas gets, and what the model reads back
store/ Store interface · in-memory · Supabase over PostgREST
app/board.src.html the board's markup and visual system
app/app.ts the board's logic
app/bridge.ts an 11 kB MCP Apps view client (the SDK's is 300 kB)
scripts/
build-app.mjs inlines the app into a single self-contained ui:// resource
build-output.mjs Vercel Build Output API v3
harness.ts a local host, running the OFFICIAL AppBridge
smoke.mjs drives a full run over the wireReading a chip
╭─────────────────────────────╮
│ ○ decide sales ─ … │ verb · what happened on the last run
├─────────────────────────────┤
│ Read what they want │ the title Claude wrote
│ sorts into sales, │ what it does, in English, from the config
│ scheduling or other │
│ [sales] [scheduling] [other]│ output ports — wires leave from these
╰─────────────────────────────╯The dot in the corner is a pin-1 mark and it carries information: filled means
the step is yours, an open ring means Claude performs it, a square means
Circuit settles it server-side without asking anyone. The status pill on the right
is blank until a run touches the step — a board full of IDLE badges tells you
nothing.
Type is Instrument Sans throughout, with Spline Sans Mono reserved for things that really are identifiers: ports, step ids, and the run console. Config summaries are set in the sans, not the mono, so a board reads like a description rather than a stack trace.
Layout
Columns come from a longest-path relaxation, so a chip always sits right of everything that reaches it. A logic.each loop is the one special case: whatever hangs off its done port belongs after the entire loop body, not one column after the loop. Lanes read like the spec — the first wire out of a step carries on in the same lane, later wires fan downward in the order they were written.
Traces route like copper: 45° mitred corners, never a curve. A wire spanning more than two columns drops to a bus below the board and runs there, instead of cutting through the chips in its way.
The view client
src/app/bridge.ts is a ~200-line implementation of the MCP Apps view side — handshake, notifications, tools/call, display mode, auto-resize. It exists because the official @modelcontextprotocol/ext-apps App class carries the whole MCP SDK with it, and that is 300 kB inlined into every resources/read of the board. This is 11 kB.
Hand-rolling a protocol is only defensible if you verify it against the real thing, so scripts/harness.ts runs the official AppBridge host implementation against the app in an iframe. If the handshake, the partial-input stream and the tool callbacks work there, they work in Claude.
Development
npm run dev # server on :8787, rebuilds the app on change
npm run harness # builds the app + a local host with real fixtures
open scripts/harness.html # ?scene=build | run | held | broken | failed &theme=light
npm run typecheckWith the server running:
node scripts/smoke.mjsThat connects a real MCP client, lists the tools and their _meta.ui bindings, reads the ui:// resource, designs a nine-step workflow with a loop and a gate, then plays the part of Claude — taking each directive, returning a plausible result, and checking that templates resolved, the loop turned over, the gate held, and the out-of-order guard fires.
Adding a step type
One entry in src/registry.ts. kind picks the chip's silkscreen label, actor decides whether Circuit settles it or hands it to Claude, config is the zod schema Claude reads out of circuit_catalog, and summary writes the small line under the chip title. If it is a claude step, add its directive shape in engine/run.ts. Nothing else changes — layout, catalog, board and engine all read from that one object.
Deploy
npm run build # → .vercel/output (Build Output API v3)
vercel deploy --prebuilt --prodAny Node 20 host works; src/vercel.ts is a nine-line adapter over the same handle(req, res) that npm run dev uses.
Variable | What happens without it |
| Falls back to in-memory storage — fine locally, lossy on serverless. Run |
|
|
There is deliberately no third row. Circuit stores workflows and run history; it never stores anything belonging to a connector.
Security notes
No credentials at rest. Circuit holds workflow definitions and run traces. Every side effect happens in Claude's own authorized connector calls, under the user's existing consent.
Directives are data, not instructions. A directive names a tool and arguments Circuit resolved from a stored workflow. Claude should refuse a directive naming a tool the user has not connected, and the server refuses out-of-order reports rather than replaying a step.
App-only tools cannot be reached by the model.
visibility: ["app"]is enforced by the host.The board is sandboxed. The
ui://resource declares an emptyconnectDomains, so the page can reach nothing but its own origin — Google Fonts is the only external resource, and it degrades to system faces.
Roadmap
Connector binding, so a mistyped tool is a design-time error
Failure policies, an
errorport, and a resumable runA test mode that actually withholds writes rather than just labelling the run
Wire editing on the canvas — drag from a port to a chip
Run replay: scrub a past run and watch the payload move
logic.parallel, for fan-out that does not need orderingWorkflow inputs, and an export format so boards are shareable
The reasoning behind the order, and what Circuit deliberately will not do, is in ROADMAP.md.
Contributing
Issues and PRs welcome — see CONTRIBUTING.md. The short version: npm run typecheck && node scripts/smoke.mjs should pass, new step types come with a line in the smoke test, and UI changes come with a harness screenshot.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI-powered building, optimization, debugging, and management of n8n workflows directly from Claude. Features workflow analysis, execution monitoring, security audits, drift detection, and intelligent error debugging with best practices guidance.1MIT
- AlicenseAqualityCmaintenanceAI workflow orchestration platform with 100+ integrations, knowledge graph, and unified credits. Build, manage, and execute workflows from Claude Code, Cursor, or any MCP client.404503MIT
- AlicenseNot gradedqualityDmaintenanceA universal AI API orchestrator that connects to 17+ services (Stripe, Slack, GitHub, etc.) and enables natural language task execution, multi-step automation, and complex orchestration across them without coding.2911MIT
- AlicenseCqualityDmaintenanceEnables connecting MCP hosts like Claude to various productivity tools (Notion, Monday, Jira, etc.) through a secure no-code integration hub.7164MIT
Related MCP Connectors
Build and run visual creative-production workflows from your AI agent.
Connect your team's living knowledge base — docs, data, issues, CRM — to Claude and ChatGPT.
SaaS intelligence for AI agents. 5 unified tools cover 1,000+ services with 91-96% token savings.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/brandononchain/circuit-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server