Workflow Node Reference

September 5, 2026 · View on GitHub

This document provides detailed specifications for all workflow node types.

Node Types Overview

CategoryNodesDescription
Variablesvariable, setDeclare and update variables
Controlif, while, sleepConditional branching, loops, pausing
LLMcommandExecute prompts via Gemini API
Datahttp, json, scriptHTTP requests, JSON parsing, JavaScript execution
Drivedrive-file, drive-read, drive-search, drive-list, drive-folder-list, drive-save, drive-deleteGoogle Drive file operations
Promptsprompt-value, prompt-file, prompt-selection, dialog, drive-file-pickerUser input dialogs
CompositionworkflowExecute another workflow as a sub-workflow
ExternalmcpCall remote MCP servers
RAGrag-syncSync files to RAG stores
Commandsgemihub-commandExecute GemiHub file operations

Node Reference

variable

Declare and initialize a variable.

- id: init
  type: variable
  name: counter
  value: "0"
PropertyRequiredTemplateDescription
nameYesNoVariable name
valueNoYesInitial value (default: empty string)

Numeric values are auto-detected: if the value parses as a number, it's stored as a number.

value is optional on variable nodes. Omitting it gives two useful behaviors:

  • Input declaration — If the variable has already been set by the caller (parent workflow, skill invocation, or hotkey trigger), the existing value is preserved. This lets a workflow declare the inputs it expects without overwriting them.
  • Empty accumulator — If no caller set the variable, it is initialized to "". This is safe for accumulators that will be appended to later.
# Input declaration — uses the caller's value, or "" if not provided
- id: declare-input
  type: variable
  name: inputText

# Accumulator — starts as "" and is appended to downstream
- id: init-output
  type: variable
  name: outputMarkdown

# Explicit initial value — always resets to 0 regardless of caller state
- id: init-counter
  type: variable
  name: counter
  value: 0

set

Update a variable with an expression.

- id: increment
  type: set
  name: counter
  value: "{{counter}} + 1"
PropertyRequiredTemplateDescription
nameYesNoVariable name to update
valueYesYesExpression to evaluate

Supports arithmetic operators: +, -, *, /, %. Variables are resolved first, then the result is evaluated as arithmetic if it matches the pattern number operator number.

Note: Only a single binary operation is supported (e.g., {{counter}} + 1). Compound expressions like 5 + 3 * 2 are treated as a plain string because they don't match the number operator number pattern.


if

Conditional branching.

- id: branch
  type: if
  condition: "{{count}} > 10"
  trueNext: handleMany
  falseNext: handleFew
PropertyRequiredTemplateDescription
conditionYesYesExpression with comparison operator

Supported operators: ==, !=, <, >, <=, >=, contains

Edge routing: trueNext / falseNext (defined in YAML, not as properties)

The contains operator works with both strings and JSON arrays:

  • String: {{text}} contains error
  • Array: {{dialogResult.selected}} contains Option A

while

Loop with condition.

- id: loop
  type: while
  condition: "{{counter}} < {{total}}"
  trueNext: processItem
  falseNext: done
PropertyRequiredTemplateDescription
conditionYesYesLoop condition (same format as if)

Edge routing: trueNext (loop body) / falseNext (exit)

Maximum iterations per while node: 1000 (global limit).


sleep

Pause workflow execution.

- id: wait
  type: sleep
  duration: "2000"
PropertyRequiredTemplateDescription
durationYesYesSleep duration in milliseconds

command

Execute an LLM prompt via Gemini API.

- id: ask
  type: command
  prompt: "Summarize: {{content}}"
  model: gemini-3.8-flash
  ragSetting: __websearch__
  driveToolMode: all
  mcpServers: "mcp_server_id_1,mcp_server_id_2"
  attachments: "imageVar"
  saveTo: summary
  saveImageTo: generatedImage
  systemPrompt: "You are a helpful assistant."
PropertyRequiredTemplateDescription
promptYesYesPrompt text to send to the LLM
modelNoYesModel name (default: user's selected model)
ragSettingNoNoRAG setting name, __websearch__ for web search, or __none__ (default)
driveToolModeNoNonone (default), all, noSearch — enables Drive tool calling
mcpServersNoNoComma-separated MCP server IDs to enable
attachmentsNoYesComma-separated variable names containing FileExplorerData
saveToNoNoVariable to store text response
saveImageToNoNoVariable to store generated image (FileExplorerData JSON)
systemPromptNoYesSystem prompt for the LLM
enableThinkingNoNo"true" (default) to enable thinking/reasoning, "false" to disable

command node uses the same tool constraints as chat:

  • Gemma models force function tools (Drive/MCP) off
  • Web Search mode forces function tools (Drive/MCP) off

http

Make HTTP requests.

- id: fetch
  type: http
  url: "https://api.example.com/data"
  method: POST
  contentType: json
  headers: '{"Authorization": "Bearer {{token}}"}'
  body: '{"query": "{{searchTerm}}"}'
  saveTo: response
  saveStatus: statusCode
  throwOnError: "true"
PropertyRequiredTemplateDescription
urlYesYesRequest URL
methodNoYesGET (default), POST, PUT, PATCH, DELETE
contentTypeNoYesjson (default), form-data, text, binary
headersNoYesJSON object or Key: Value format (one per line)
bodyNoYesRequest body (for POST/PUT/PATCH)
responseTypeNoYesauto (default), text, binary — override Content-Type auto-detection
saveToNoNoVariable for response body
saveStatusNoNoVariable for HTTP status code
throwOnErrorNoYesThrow on 4xx/5xx responses. Default: "true" — HTTP errors abort the workflow. Set "false" only when the workflow explicitly handles errors downstream.

throwOnError — default is "true". A 4xx/5xx response aborts the workflow so the failure surfaces to the chat AI, the user, and the "Open workflow" recovery UI (for skill workflows). Only set "false" when the workflow genuinely reads saveStatus and takes a different downstream path on failure — not merely to prevent a crash.

# ✅ Default: HTTP errors abort the workflow
- id: fetch
  type: http
  url: "{{url}}"
  saveTo: body
  saveStatus: status

# ✅ Acceptable: explicit error-handling branch downstream
- id: fetch
  type: http
  url: "{{url}}"
  throwOnError: "false"
  saveTo: body
  saveStatus: status
- id: check-status
  type: if
  condition: "{{status}} >= 400"
  trueNext: handle-error    # real branch that does something useful

# ❌ Anti-pattern: swallows every HTTP failure with no handler
- id: fetch
  type: http
  url: "{{url}}"
  throwOnError: "false"   # workflow "succeeds" even on 503
  saveTo: body

Cross-origin requests (CORS): Requests run as browser fetch(). Same-origin and CORS-enabled cross-origin endpoints work directly. For other cross-origin URLs (news sites, OGP scraping, legacy APIs without CORS headers), both plans transparently route through a server proxy (/api/workflow/http-fetch) so the request succeeds. Free accounts are rate-limited to 2 req/min; Premium gets 60 req/min.

Proxy limits: The server proxy enforces:

  • Per-user rate limit — 60 req/min on Premium, 2 req/min on the free plan. 429 with Retry-After: 60 when exceeded.
  • 20 MB response size cap — over-limit bodies return 413 (via Content-Length check and read-time enforcement).
  • 30 s upstream timeout — stuck upstreams return 502.
  • SSRF guard — hostnames resolving to private / loopback / metadata IP ranges are rejected with 400. DNS resolve failures are reported as 502 (not SSRF) so retry logic behaves correctly.

Response type detection: By default (auto), binary vs text is auto-detected from the Content-Type header. Use responseType: text to force text processing (e.g., when a server returns application/octet-stream for JSON), or responseType: binary to force binary handling.

Binary responses are automatically detected and stored as FileExplorerData JSON (Base64 encoded).

binary contentType: Sends FileExplorerData as raw binary with its original mimeType. Use with drive-file-picker or image generation results.

form-data example:

- id: upload
  type: http
  url: "https://example.com/upload"
  method: POST
  contentType: form-data
  body: '{"file": "{{fileData}}"}'
  saveTo: response

For form-data:

  • FileExplorerData (from drive-file-picker / drive-save) is auto-detected and sent as binary
  • Use fieldName:filename syntax for text file fields (e.g., "file:report.html": "{{htmlContent}}")

json

Parse a JSON string into an object for property access.

- id: parseResponse
  type: json
  source: response
  saveTo: data
PropertyRequiredTemplateDescription
sourceYesNoBare variable name holding the JSON string — no {{...}}, no surrounding quotes
saveToYesNoVariable for parsed result

After parsing, access properties using dot notation: {{data.items[0].name}}

JSON in markdown code blocks: Automatically extracted from ```json ... ``` fences.

source is a bare variable name. Pass just the name — don't interpolate, don't wrap it, don't put it inside brackets:

# ✅ Correct
- id: parse-body
  type: json
  source: apiResponseBody
  saveTo: parsed

# ❌ Wrong
- id: parse-body
  type: json
  source: "{{apiResponseBody}}"          # no interpolation here
  # or: source: "[{{apiResponseBody}}]"  # wrapping corrupts valid JSON
  saveTo: parsed

script

Execute JavaScript code in a sandboxed iframe. The sandbox has no access to DOM, network, or storage — only pure computation. Useful for string manipulation, data transformation, calculations, encoding/decoding, and other operations that set node arithmetic cannot handle.

- id: transform
  type: script
  code: |
    var items = '{{rawList}}'.split(',').map(function(s){ return s.trim(); });
    items.sort();
    return items.join('\n');
  saveTo: sortedList
  timeout: "5000"
PropertyRequiredTemplateDescription
codeYesYesJavaScript code to execute. Use return to return a value.
saveToNoNoVariable to store the result
timeoutNoNoTimeout in milliseconds (default: 10000)

Return values:

  • undefined / null → empty string
  • Non-string values → JSON-serialized (e.g., arrays, objects, numbers)
  • Strings → stored as-is

Async support: Promises are automatically awaited.

Security: Runs in an iframe with sandbox="allow-scripts" (opaque origin). No access to parent DOM, cookies, localStorage, or network.

Example — Base64 encode:

- id: encode
  type: script
  code: "return btoa('{{plainText}}')"
  saveTo: encoded

Example — extract unique words:

- id: unique-words
  type: script
  code: |
    var words = '{{text:json}}'.split(/\s+/);
    var unique = [...new Set(words)];
    return unique.sort().join(', ');
  saveTo: uniqueWords

Note: The command node also has access to an execute_javascript tool via function calling, allowing the AI to write and run JavaScript code dynamically during a chat or workflow command execution.


drive-file

Write content to a Google Drive file.

- id: save
  type: drive-file
  path: "output/{{filename}}.md"
  content: "{{result}}"
  mode: overwrite
  confirm: "true"
  history: "true"
  open: "true"
PropertyRequiredTemplateDescription
pathYesYesFile path (.md extension auto-appended if missing)
contentNoYesContent to write (default: empty string)
modeNoNooverwrite (default), append, create (skip if exists)
confirmNoNo"true" (default) to show diff review dialog when updating existing files; "false" to write without confirmation
historyNoNo"true" to save edit history
openNoNo"true" to open the file in the editor after workflow completes

drive-read

Read content from a Google Drive file.

- id: read
  type: drive-read
  path: "notes/config.md"
  saveTo: content
  saveMetadataTo: metadata
PropertyRequiredTemplateDescription
pathYesYesFile path or Drive file ID
saveToYesNoVariable to store file content
saveMetadataToNoNoVariable to store unencrypted metadata as JSON. For encrypted files this contains description plus visible fields such as email

Smart path resolution:

  • If path looks like a Drive file ID (no extension, >20 chars): reads directly
  • Otherwise: searches by file name, tries with .md extension as fallback

Search for files on Google Drive.

- id: search
  type: drive-search
  query: "{{searchTerm}}"
  searchContent: "true"
  limit: "10"
  saveTo: results
PropertyRequiredTemplateDescription
queryYesYesSearch query string
searchContentNoNo"true" to search file contents (default: name only)
limitNoYesMaximum results (default: 10)
saveToYesNoVariable for results

Output format:

[
  {"id": "abc123", "name": "notes/todo.md", "modifiedTime": "2026-01-01T00:00:00Z"}
]

drive-list

List files with filtering.

- id: list
  type: drive-list
  folder: "Projects"
  limit: "20"
  sortBy: modified
  sortOrder: desc
  modifiedWithin: "7d"
  saveTo: fileList
PropertyRequiredTemplateDescription
folderNoYesVirtual folder prefix (e.g., "Projects")
limitNoYesMaximum results (default: 50)
sortByNoYesmodified (default), created, name
sortOrderNoYesdesc (default), asc
modifiedWithinNoYesTime filter (e.g., "7d", "24h", "30m")
createdWithinNoYesTime filter (e.g., "30d")
saveToYesNoVariable for results

Output format:

{
  "notes": [
    {"id": "abc123", "name": "Projects/todo.md", "modifiedTime": "...", "createdTime": "..."}
  ],
  "count": 5,
  "totalCount": 12,
  "hasMore": true
}

Uses sync metadata for fast listing (no per-file API calls). "Folders" are virtual — derived from path prefixes in file names.


drive-folder-list

List virtual folders.

- id: listFolders
  type: drive-folder-list
  folder: "Projects"
  saveTo: folderList
PropertyRequiredTemplateDescription
folderNoYesParent virtual folder path
saveToYesNoVariable for results

Output format:

{
  "folders": [{"name": "Active"}, {"name": "Archive"}],
  "count": 2
}

Returns only immediate subfolders (one level deep), sorted alphabetically.


drive-save

Save FileExplorerData as a file on Google Drive.

- id: saveImage
  type: drive-save
  source: imageData
  path: "images/output"
  savePathTo: savedPath
PropertyRequiredTemplateDescription
sourceYesYesVariable name or template containing FileExplorerData JSON
pathYesYesTarget file path (extension auto-added from source data)
savePathToNoNoVariable to store final file name

Note: drive-save always creates a new file, even if a file with the same path already exists. This differs from drive-file, which supports overwrite and append modes. Running the same workflow repeatedly may create duplicate files.


drive-delete

Soft-delete a file by moving it to the trash/ subfolder.

- id: cleanup
  type: drive-delete
  path: "notes/old-file.md"
PropertyRequiredTemplateDescription
pathYesYesFile path to delete (.md extension auto-appended if missing)

The file is moved to trash/ (not permanently deleted) and removed from sync metadata. Supports the same path resolution as drive-file (companion _fileId variables, exact name fallback).


prompt-value

Show a text input dialog.

- id: input
  type: prompt-value
  title: "Enter a value"
  default: "{{defaultText}}"
  multiline: "true"
  saveTo: userInput
PropertyRequiredTemplateDescription
titleNoYesPrompt label (default: "Input")
defaultNoYesDefault value
multilineNoNo"true" for multi-line textarea
saveToYesNoVariable to store user input

Throws error if user cancels.


prompt-file

Show a file picker and read the selected file's content.

- id: pickFile
  type: prompt-file
  title: "Select a file"
  saveTo: fileContent
  saveFileTo: fileInfo
PropertyRequiredTemplateDescription
titleNoYesPicker dialog title (default: "Select a file")
saveToNoNoVariable to store file content (text)
saveFileToNoNoVariable to store file info JSON ({path, basename, name, extension})

At least one of saveTo or saveFileTo is required. Unlike drive-file-picker, this node reads the file content automatically.

Throws error if user cancels.


prompt-selection

Show a multiline text input dialog.

- id: getText
  type: prompt-selection
  title: "Enter your text"
  saveTo: selection
PropertyRequiredTemplateDescription
titleNoYesPrompt label (default: "Enter text")
saveToYesNoVariable to store user input

Always shows a multiline textarea. Throws error if user cancels.


dialog

Display a dialog with options, buttons, and/or text input.

- id: ask
  type: dialog
  title: Select Options
  message: "Choose items to process"
  markdown: "true"
  options: "Option A, Option B, Option C"
  multiSelect: "true"
  inputTitle: "Additional notes"
  multiline: "true"
  defaults: '{"input": "default text", "selected": ["Option A"]}'
  button1: Confirm
  button2: Cancel
  saveTo: dialogResult
PropertyRequiredTemplateDescription
titleNoYesDialog title (default: "Dialog")
messageNoYesMessage content
markdownNoNo"true" renders message as Markdown
optionsNoYesComma-separated list of choices
multiSelectNoNo"true" for checkboxes, "false" for radio
inputTitleNoYesLabel for text input field (shows input when set)
multilineNoNo"true" for multi-line textarea
defaultsNoYesJSON with input and selected initial values
button1NoYesPrimary button label (default: "OK")
button2NoYesSecondary button label
saveToNoNoVariable for result

Result format (saveTo variable):

{
  "button": "Confirm",
  "selected": ["Option A", "Option B"],
  "input": "some text"
}

Important: When checking selected value in an if condition:

  • Single option: {{dialogResult.selected[0]}} == Option A
  • Array contains (multiSelect): {{dialogResult.selected}} contains Option A

drive-file-picker

Show a file picker dialog to select a Drive file. When saveTo is used, the file content is automatically loaded — binary files (PDF, images, etc.) are Base64-encoded, text files are read as-is.

- id: selectFile
  type: drive-file-picker
  title: "Select a file"
  mode: select
  extensions: "pdf,doc,md"
  saveTo: fileData
  savePathTo: filePath
PropertyRequiredTemplateDescription
titleNoYesPicker dialog title (default: "Select a file")
modeNoNoselect (default) to pick existing file, create to enter a new path
defaultNoYesDefault file path (used as initial value in create mode)
extensionsNoNoComma-separated allowed extensions
pathNoYesDirect file path (bypasses picker when set)
saveToNoNoVariable for FileExplorerData JSON (includes file content)
savePathToNoNoVariable for file name/path

At least one of saveTo or savePathTo is required.

FileExplorerData format:

{
  "id": "abc123",
  "path": "notes/report.pdf",
  "basename": "report.pdf",
  "name": "report",
  "extension": "pdf",
  "mimeType": "application/pdf",
  "contentType": "binary",
  "data": "JVBERi0xLjQg..."
}
  • contentType: "binary" for binary files (PDF, images, etc.), "text" for text files
  • data: Base64-encoded content for binary files, plain text for text files
  • In create mode, data is empty (file does not exist yet)

Example — image analysis:

- id: select-image
  type: drive-file-picker
  title: "Select an image to analyze"
  extensions: "png,jpg,jpeg,gif,webp"
  saveTo: imageData
- id: analyze
  type: command
  prompt: "Describe this image in detail"
  attachments: imageData
  saveTo: analysis

Example — load file without dialog:

- id: load-pdf
  type: drive-file-picker
  path: "{{pdfPath}}"
  saveTo: pdfData

workflow

Execute another workflow as a sub-workflow.

- id: runSub
  type: workflow
  path: "workflows/summarize.yaml"
  name: "Summarizer"
  input: '{"text": "{{content}}"}'
  output: '{"result": "summary"}'
  prefix: "sub_"
PropertyRequiredTemplateDescription
pathYesYesPath to workflow file
nameNoYesWorkflow name (for multi-workflow files)
inputNoYesJSON or key=value mapping for sub-workflow input
outputNoYesJSON or key=value mapping for output variables
prefixNoNoPrefix for all output variables (when output not specified)

Input mapping: '{"subVar": "{{parentValue}}"}' or subVar={{parentValue}},x=hello

Output mapping: '{"parentVar": "subResultVar"}' or parentVar=subResultVar

If neither output nor prefix is specified, all sub-workflow variables are copied directly.


mcp

Call a remote MCP (Model Context Protocol) server tool via HTTP.

- id: search
  type: mcp
  url: "https://mcp.example.com/v1"
  tool: "web_search"
  args: '{"query": "{{searchTerm:json}}"}'
  headers: '{"Authorization": "Bearer {{apiKey}}"}'
  saveTo: searchResults
  saveUiTo: uiData
PropertyRequiredTemplateDescription
urlYesYesMCP server endpoint URL
toolYesYesTool name to call
argsNoYesJSON object with tool arguments
headersNoYesJSON object with HTTP headers
saveToNoNoVariable for result
saveUiToNoNoVariable for UI resource data (when server returns _meta.ui.resourceUri)

Uses JSON-RPC 2.0 protocol (tools/call method). Text content parts from the response are joined with newlines.


rag-sync

Sync a Drive file to a Gemini RAG store (File Search).

- id: sync
  type: rag-sync
  path: "notes/knowledge-base.md"
  ragSetting: "myRagStore"
  saveTo: syncResult
PropertyRequiredTemplateDescription
pathYesYesFile path on Drive
ragSettingYesYesRAG setting name (from Settings > RAG)
saveToNoNoVariable for sync result

Uploads the specified Drive file to the RAG store. Creates the store if it doesn't already exist. The result contains {path, ragSetting, fileId, storeName, mode, syncedAt}.

Use this to prepare files for RAG-powered command nodes (set ragSetting on the command node to the same setting name).


gemihub-command

Execute GemiHub file operations (encrypt, publish, rename, etc.) as workflow nodes.

- id: pub
  type: gemihub-command
  command: publish
  path: "notes/readme.md"
  saveTo: url
PropertyRequiredTemplateDescription
commandYesYesCommand name (see table below)
pathYesYesFile path, Drive file ID, or {{variable}}
textNoYesAdditional text argument (usage depends on command)
metadataNoYesFor encrypt: JSON object of searchable, unencrypted string fields
saveToNoNoVariable to store the result

Available commands:

Commandtext usagesaveTo result
encryptSearchable description (optional)New file name (with .encrypted suffix)
publishPublic URL
unpublish"ok"
duplicateCustom name (optional; default: "name (copy).ext")New file name
convert-to-pdfPDF file name (saved to temporaries/)
convert-to-htmlHTML file name (saved to temporaries/)
renameNew name (required)New file name

Path resolution follows the same pattern as drive-read:

  • Direct Drive file ID (20+ alphanumeric chars)
  • Companion _fileId variable from drive-file-picker
  • Search by file name → findFileByExactName fallback

Examples:

# Encrypt a file
- id: enc
  type: gemihub-command
  command: encrypt
  path: "notes/secret.md"
  text: "Production deploy token"
  metadata: '{"email":"ops@example.com","environment":"production"}'
  saveTo: encryptedName

# Duplicate with custom name
- id: dup
  type: gemihub-command
  command: duplicate
  path: "templates/report.md"
  text: "reports/2026-report.md"
  saveTo: newFile

# Rename a file picked by user
- id: pick
  type: drive-file-picker
  title: "Select file to rename"
  savePathTo: filePath
- id: ren
  type: gemihub-command
  command: rename
  path: "{{filePath}}"
  text: "{{filePath}}-archived.md"
  saveTo: renamedName

# Convert markdown to PDF
- id: pdf
  type: gemihub-command
  command: convert-to-pdf
  path: "notes/report.md"
  saveTo: pdfName

# Convert markdown to HTML
- id: html
  type: gemihub-command
  command: convert-to-html
  path: "notes/report.md"
  saveTo: htmlName

Variable Expansion

Use {{variable}} syntax to reference variables:

# Basic
path: "{{folder}}/{{filename}}.md"

# Object/Array access
url: "https://api.example.com?id={{data.id}}"
content: "{{items[0].name}}"

# Dynamic index (for loops)
path: "{{parsed.notes[counter].path}}"

JSON Escape Modifier

Use {{variable:json}} to escape the value for embedding inside a string literal. It properly escapes newlines, quotes, and other special characters.

Important: :json only escapes the content — it does not add surrounding quotes. You must provide the quotes yourself when embedding inside a string.

# Without :json - breaks if content has newlines/quotes
args: '{"text": "{{content}}"}'       # ERROR if content has special chars

# With :json - safe for any content (the "..." around it is your string literal)
args: '{"text": "{{content:json}}"}'  # OK - properly escaped

In script nodes (JavaScript):

:json substitutes plain text before the code runs, so you must wrap it in quotes when the value should be a JS string:

# ✅ Correct — string literal with escaped content
code: |
  var text = "{{userInput:json}}";
  var data = JSON.parse("{{jsonStr:json}}");

# ❌ Wrong — missing outer quotes, produces invalid JS
code: |
  var text = {{userInput:json}};          # syntax error
  JSON.parse({{jsonStr:json}});           # needs a string argument

If the variable already holds a parsed object/array (e.g. from a previous json node), use {{var:json}} without quotes so it becomes a JS object/array literal:

code: |
  var arr = {{parsedArray:json}};         # becomes: var arr = [{"url":"..."}]

This is essential when passing file content or user input to mcp, http, or script nodes with JSON bodies.


Workflow Termination

Use next: end to explicitly terminate the workflow:

- id: save
  type: drive-file
  path: "output.md"
  content: "{{result}}"
  next: end    # Workflow ends here

- id: branch
  type: if
  condition: "{{cancel}}"
  trueNext: end      # End workflow on true branch
  falseNext: continue

Practical Examples

1. Drive File Summary

name: Summarize File
nodes:
  - id: select
    type: drive-file-picker
    title: "Select a file to summarize"
    extensions: "md,txt"
    savePathTo: filePath
  - id: read
    type: drive-read
    path: "{{filePath}}"
    saveTo: content
  - id: summarize
    type: command
    prompt: "Summarize this text:\n\n{{content}}"
    saveTo: summary
  - id: save
    type: drive-file
    path: "summaries/{{filePath}}"
    content: "# Summary\n\n{{summary}}"

2. API Integration

name: Weather Report
nodes:
  - id: city
    type: dialog
    title: City name
    inputTitle: City
    saveTo: cityInput
  - id: geocode
    type: http
    url: "https://geocoding-api.open-meteo.com/v1/search?name={{cityInput.input}}&count=1"
    method: GET
    saveTo: geoResponse
  - id: parseGeo
    type: json
    source: geoResponse
    saveTo: geo
  - id: weather
    type: http
    url: "https://api.open-meteo.com/v1/forecast?latitude={{geo.results[0].latitude}}&longitude={{geo.results[0].longitude}}&current=temperature_2m"
    method: GET
    saveTo: weatherData
  - id: report
    type: command
    prompt: "Create a weather report:\n{{weatherData}}"
    saveTo: summary
  - id: save
    type: drive-file
    path: "weather/{{cityInput.input}}.md"
    content: "# Weather: {{cityInput.input}}\n\n{{summary}}"

3. Batch Processing with Loop

name: Tag Analyzer
nodes:
  - id: init
    type: variable
    name: counter
    value: "0"
  - id: initReport
    type: variable
    name: report
    value: "# Tag Suggestions\n\n"
  - id: list
    type: drive-list
    folder: "Clippings"
    limit: "5"
    saveTo: notes
  - id: parse
    type: json
    source: notes
    saveTo: parsed
  - id: loop
    type: while
    condition: "{{counter}} < {{parsed.count}}"
    trueNext: read
    falseNext: finish
  - id: read
    type: drive-read
    path: "{{parsed.notes[counter].name}}"
    saveTo: content
  - id: analyze
    type: command
    prompt: "Suggest 3 tags for:\n\n{{content}}"
    saveTo: tags
  - id: append
    type: set
    name: report
    value: "{{report}}## {{parsed.notes[counter].name}}\n{{tags}}\n\n"
  - id: increment
    type: set
    name: counter
    value: "{{counter}} + 1"
    next: loop
  - id: finish
    type: drive-file
    path: "reports/tag-suggestions.md"
    content: "{{report}}"

4. Sub-Workflow Composition

File: workflows/translate.yaml

name: Translator
nodes:
  - id: translate
    type: command
    prompt: "Translate to {{targetLang}}:\n\n{{text}}"
    saveTo: translated

File: workflows/main.yaml

name: Multi-Language Export
nodes:
  - id: input
    type: dialog
    title: Enter text to translate
    inputTitle: Text
    multiline: "true"
    saveTo: userInput
  - id: toJapanese
    type: workflow
    path: "workflows/translate.yaml"
    name: "Translator"
    input: '{"text": "{{userInput.input}}", "targetLang": "Japanese"}'
    output: '{"japaneseText": "translated"}'
  - id: toSpanish
    type: workflow
    path: "workflows/translate.yaml"
    name: "Translator"
    input: '{"text": "{{userInput.input}}", "targetLang": "Spanish"}'
    output: '{"spanishText": "translated"}'
  - id: save
    type: drive-file
    path: "translations/output.md"
    content: |
      # Original
      {{userInput.input}}

      ## Japanese
      {{japaneseText}}

      ## Spanish
      {{spanishText}}

5. MCP with RAG Server

name: RAG Search
nodes:
  - id: query
    type: mcp
    url: "http://localhost:8080"
    tool: "query"
    args: '{"store_name": "mystore", "question": "How does auth work?", "show_citations": true}'
    headers: '{"X-API-Key": "mysecretkey"}'
    saveTo: result
  - id: show
    type: dialog
    title: "Search Result"
    message: "{{result}}"
    markdown: "true"
    button1: "OK"

MCP permissions and read-only tools

MCP tool calls require approval by default. The dialog shows the server name, tool name and execution arguments. Choose Allow once, Always allow this tool, or Deny; dismissing the dialog denies execution. Server settings provide an Always approve option (off by default) and a removable allowed-tool list. Workflow command and mcp nodes can use confirm: "false" to skip MCP approval for that node, including automatic runs. Later MCP App interactions follow the server settings again. Read-only mode allows built-in reading, listing and searching, and blocks built-in writes (including timeline append). External MCP and skill/workflow operations retain their own permissions. Connection tests disable editing while running and display errors for correction.