Skip to main content
Glama
Particle-Academy

fancy-flow-mcp-js

@particle-academy/fancy-flow-mcp-js

MCP server that lets an agent author fancy-flow workflows headlessly on a TypeScript host — the Node twin of fancy-flow-mcp (Laravel).

Same 15 tools, same names, same arguments. An agent that learned to build graphs against the PHP server drives this one without relearning anything — and that claim is asserted by a test that reads the PHP source, not maintained by hand.

npx @particle-academy/fancy-flow-mcp-js

The split it is built around

We own whether a graph is well-formed; the host owns whether it is allowed to run.

Every issue crosses the wire tagged with which of those refused it:

{ "source": "schema", "level": "error",   "message": "message: Message is required", "nodeId": "log-1" }
{ "source": "host",   "level": "error",   "message": "This host cannot resume a paused run.", "nodeId": "ask-1" }

Those have different remedies — one is fixed by editing the graph, the other by granting a capability or picking another kind. Collapsing them costs an author real time chasing the wrong fix, so they are never merged.

Two entry points

Import

What it costs

Use it when

@particle-academy/fancy-flow-mcp-js/authoring

nothing beyond fancy-flow

You have your own transport, or none

@particle-academy/fancy-flow-mcp-js

+ @modelcontextprotocol/sdk

You want an MCP server

The core was written first and deliberately depends on nothing, so a host that only wants to build and validate graphs never pays for a transport it will not use.

Use it from a host

import { createFlowServer, MemoryDraftStore } from "@particle-academy/fancy-flow-mcp-js";
import { registerBuiltinKinds } from "@particle-academy/fancy-flow/registry";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

registerBuiltinKinds(); // the registry does not populate itself in a bare process

const server = createFlowServer({
  store: new MemoryDraftStore(),

  // Your answer to "may I run this kind here?". Return a string to REFUSE —
  // and name the missing capability, because a refusal an author can act on is
  // worth more than a correct one they cannot.
  admits: (kind) =>
    kind.name.includes("terminal") ? "This host has no terminal sessions." : null,
});

await server.connect(new StdioServerTransport());

MemoryDraftStore is a convenience. Implement DraftStore (list / get / save / remove, sync or async) against your own storage and drafts survive a restart — this package never persists anything itself.

The tools

create_workflow list_workflows get_workflow delete_workflow

Drafts

add_node remove_node configure_node

Nodes

connect_nodes remove_edge

Edges

list_node_kinds describe_node_kind

The vocabulary

validate_workflow export_workflow import_workflow

Checking and portability

run_workflow

A smoke test of wiring — see below

list_node_kinds reads the live registry, so kinds a host registered itself and kinds vendored from the marketplace are included. It is not a fixed list.

configure_node writes a config that fails validation and reports warnings rather than refusing. A half-configured node is a normal intermediate state when an agent builds a graph one step at a time; validate_workflow is the place that says "not finished".

run_workflow — read this before relying on it

It is a smoke test of wiring and routing, not a production run.

It takes no executor argument, and the executor registry is bound as a literal at the call site — the same shape the PHP twin uses. An agent-reachable run is structurally incapable of being pointed at your real infrastructure. Unrepresentable beats forbidden: a policy can be relaxed by a later edit; an absent parameter cannot be passed.

Two honest limits:

  1. 9 of 31 builtin kinds have a TypeScript executor. The PHP twin's Builtin::executors() covers every kind; the TS runtime has no equivalent yet. The other 22 return ok: false with "No executor registered" — a true answer about this runtime, delivered as a result rather than an error. A test pins the count so the day it improves, this paragraph is forced to change.

  2. Kind-level executors still apply. A host process that has called registerTerminalHost or registerLlmClient makes those reachable from here. That is your doing, not this package's, and it is said plainly rather than papered over.

Protocol revision

Speaks 2025-11-25, negotiating back to 2024-11-05 — the same family laravel/mcp speaks, which is what the PHP twin is built on, and what Claude Code and Codex speak.

It does not speak 2026-07-28, the revision that removed initialize and made the protocol stateless. Neither does the PHP twin. If you need to be reached by a 2026-07-28-only client, that gap is open and belongs to both twins together — a Node server that jumped ahead alone would stop being a twin.

Licence

MIT

Latest Blog Posts

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/Particle-Academy/fancy-flow-mcp-js'

If you have feedback or need assistance with the MCP directory API, please join our Discord server