MCP Server
The mcp module is a Model Context Protocol server over stdio — it exposes generic workflow CRUD + execute tools to an agent (Claude Desktop, Claude Code, etc.), with no template- or node-type-specific shortcuts. Every tool operates on an arbitrary WorkflowDefinition by id or raw JSON.
It embeds the engine directly (createGraphynServerRuntime() + FileWorkflowStore) — no running :server process required.
Quick start
./gradlew :mcp:installDist
This produces a runnable binary at mcp/build/install/mcp/bin/mcp. Add it to your MCP client config — for a project-level .mcp.json:
{
"mcpServers": {
"graphyn-workflows": {
"command": "./mcp/build/install/mcp/bin/mcp",
"args": []
}
}
}
For Claude Desktop, add the equivalent entry to its own config with an absolute path to the binary.
Tools
| Tool | Description |
|---|---|
workflow_list |
List all stored workflows (id, name, timestamps, version count) |
workflow_get |
Fetch a workflow's full definition by id |
workflow_publish |
Save/update a workflow from raw WorkflowDefinition JSON — validates before saving, id must start with mcp- |
workflow_delete |
Delete a workflow and its version history — id must start with mcp- |
workflow_execute |
Run a stored workflow by id, with optional overrides (per-node config) and async |
workflow_execution_status |
Poll buffered progress/result frames for an async run |
workflow_list_node_types |
List every registered node type — for authoring workflow_publish payloads |
workflow_publish and workflow_execute/workflow_delete are annotated destructiveHint/openWorldHint in their ToolSchema — the protocol's own mechanism for flagging that they run against the real engine (see Unsandboxed execution below), not a custom permission layer on top.
Discriminator note
workflow_publish's workflow JSON uses a "kind" discriminator for WorkflowValue fields (it goes through the same DefaultWorkflowJsonCodec the :server HTTP routes use):
{"kind": "string", "value": "hello"}
workflow_execute's overrides argument uses a different, plain Json instance with the default "type" discriminator instead:
{"type": "string", "value": "hello"}
This split mirrors a real difference already in :server itself (GraphynWorkflowJson vs. the plugin's own default Json {}) — not something introduced by the MCP layer.
Configuring which plugins load
By default :mcp installs Shorts, MediaCore, MediaAi, and StableDiffusion on top of the base runtime plugins (Control, ListOps, Types, Text, Io, Json, Preview). Trim or reorder with GRAPHYN_MCP_PLUGINS — comma-separated plugin names, or the literal all:
{
"mcpServers": {
"graphyn-workflows": {
"command": "./mcp/build/install/mcp/bin/mcp",
"args": [],
"env": { "GRAPHYN_MCP_PLUGINS": "shorts,media-core" }
}
}
}
An unknown plugin name fails fast at startup with the available list rather than silently being ignored. StableDiffusionPlugin()'s default backend shells out to a local sd-cli binary (SD_CLI_PATH env var) — swap in com.ronjunevaldoz.graphyn.plugins.stablesd.http.HttpStableDiffusionBackend in Main.kt if your SD generation runs on a remote server instead.
Where workflows are stored
FileWorkflowStore defaults to <project-root>/.graphyn/workflows, using the process's working directory as the root — reliable here since .mcp.json launches the binary via a relative command path, so the client has already cd'd to the project root. This keeps different projects' MCP-published workflows from colliding in one shared folder, unlike the desktop editor's own default of ~/.graphyn/workflows (global, one folder for every project).
Override with GRAPHYN_MCP_WORKFLOWS_DIR — including pointing it back at the global ~/.graphyn/workflows if you want MCP and the desktop editor to share state for a specific project:
"env": { "GRAPHYN_MCP_WORKFLOWS_DIR": "/Users/you/.graphyn/workflows" }
Add .graphyn/ to the project's .gitignore — it's local, ephemeral run state, not something to commit.
Namespace scoping
workflow_publish and workflow_delete share the same store as the desktop editor (~/.graphyn/workflows). Without a boundary, an agent could silently overwrite or delete one of your real hand-built workflows just by reusing its id — so both tools refuse any id that doesn't start with mcp-:
workflow_publish({"workflow": "...\"id\":\"comparison-short\"..."})
→ "Refusing to publish 'comparison-short': id must start with 'mcp-'."
This is enforced by construction (an id-prefix check before every write), not a per-call confirmation flag — there's nothing to forget to pass. workflow_get, workflow_list, and workflow_execute are unscoped and can read/run any stored workflow, including your own; only writes (publish/delete) are restricted.
Unsandboxed execution
workflow_publish and workflow_execute run against the same production node executors as :server and the editor — including script.eval (arbitrary Kotlin) and io.file_write/io.http_request (filesystem/network access). There is no sandbox and no per-call confirmation gate for which node types a workflow may contain; this is the same trust boundary as running the :mcp process itself. Namespace scoping (above) protects your existing workflows from being touched — it does not restrict what a new mcp--prefixed workflow is allowed to do. A node-type allow-list would need to be built on top if you need that.
Node authoring
Since there's no node-catalog UI available over MCP, workflow_list_node_types is the way an agent discovers valid node types and ports before hand-authoring a workflow_publish payload from scratch — or start from workflow_get on an existing workflow and edit that JSON instead.