Skip to main content
Glama

FlowMCP

Most MCP servers wrap an entire platform: every endpoint becomes a tool, the model gets a 40-tool surface, and orchestration is outsourced to sampling — then everyone blames the model. FlowMCP inverts that: workflows are the tools. Each MCP tool is one known, named workflow; a deterministic engine executes the steps; the model's only job is picking the flow and filling 2–3 parameters. Small models (7–30B) can drive this reliably, because there is almost nothing to get wrong.

Quickstart (60 seconds)

npm install -g @petergreenappliedai/flowmcp
flowmcp serve                    # serves the demo flows over stdio
flowmcp serve --flows ~/my-flows # serves yours

Or from a clone (for development):

git clone https://github.com/PeterGreenAppliedAI/FlowMCP.git && cd FlowMCP
npm install
npm test          # hermetic — no network needed
npm start         # serves MCP over stdio

Point any MCP client at it. Claude Desktop / Claude Code / anything MCP:

{
  "mcpServers": {
    "flowmcp": {
      "command": "npx",
      "args": ["-y", "@petergreenappliedai/flowmcp", "serve", "--flows", "/absolute/path/to/your/flows"]
    }
  }
}

Your client will list two tools — morning_brief and hn_top — not forty. Both run against keyless public APIs (Open-Meteo, Hacker News), so they work on a fresh clone with zero configuration.

> morning_brief city="Lisbon"

# Morning brief — Lisbon, Portugal

## Weather today
High 29.4°C / low 19.3°C, 0% chance of rain.

## Top of Hacker News
- **…** — 330 points https://…

Related MCP server: agentloop

Flow file format

Flows are data, not code. The server loads every flows/*.flow.json5 at startup and exposes each as one MCP tool. An invalid flow is a loud startup error naming the file and field.

{
  name: 'morning_brief',          // becomes the MCP tool name (snake_case)
  description: 'WHEN TO USE: …',  // ≤300 chars — this is the model's entire manual
  input: {                        // 0–3 parameters, no more
    city: { type: 'string', description: 'City for the weather', required: false, default: 'New York' },
  },
  env: ['WEATHER_API_KEY'],       // ONLY these env vars are visible to {{env.X}} — least privilege
  steps: [ /* run in order; each result is available as steps.<id> */ ],
  output: '{{steps.render}}',     // the tool's text result
}

Check a directory without serving: npm start -- --flows ./my-flows --validate exits 0 if every flow is valid, 1 with the file and field otherwise.

Step kinds

kind

fields

what it does

http_request

method (GET/POST), url, headers?, body?, timeoutMs? (default 15000)

Fetch a URL; JSON responses are parsed. One automatic retry on network error — GET only: a timed-out POST may have landed, so it is never retried.

transform

expr

Reshape prior results with a sandboxed expression — paths, object/array literals, comparisons. No code execution.

template

template

Mustache-style string build: {{steps.x.y[0]}}. Arrays join line-per-item; missing terminal values render as ''.

map

over, as? (default item), step

Run one leaf step per array element, sequentially, max 10 items — slice with steps.ids[0:5].

branch

if, then, else?

Evaluate a condition, run one of two step lists. No nested branches.

mcp_call

server, tool, args?, timeoutMs? (default 30000), maxResultChars? (default 8000)

Call one tool on a downstream MCP server from servers.json5. See Composition below.

Everything downstream of a step sees input.*, env.* (for {{env.API_KEY}} — never put secrets in flow files), and steps.<id>. A failed step aborts the flow and returns a structured isError result naming the step. Whole-flow timeout: 60s.

Writing your own flow

Drop a file in a flows directory, restart the server — that's the whole workflow. The server reads flows/ in the repo by default; point it anywhere with --flows (or the FLOWMCP_FLOWS_DIR env var), which is how you keep private flows out of a public checkout:

npm start -- --flows ~/my-flows
// flows/cat_fact.flow.json5
{
  name: 'cat_fact',
  description: 'WHEN TO USE: the user wants a random cat fact.',
  input: {},
  steps: [
    { id: 'fact', kind: 'http_request', url: 'https://catfact.ninja/fact' },
    { id: 'render', kind: 'template', template: 'Cat fact: {{steps.fact.fact}}' },
  ],
  output: '{{steps.render}}',
}

Composition: wrapping other MCP servers

Flows can call tools on other MCP servers — and this is where the thesis becomes an operation instead of an opinion. Register downstream servers in a servers.json5 next to your flow files:

Downstream servers speak either transport: stdio (command) or remote Streamable HTTP (url + headers, e.g. a hosted Shopify/Business Central MCP — tokens interpolated from env):

{
  erp: {
    url: 'https://your-tenant.example.com/mcp',
    headers: { Authorization: 'Bearer {{env.ERP_TOKEN}}' },
    attestReadOnly: ['list_customers', 'get_customer'],  // operator-attested reads (server annotates nothing)
    allow: ['post_invoice'],                       // write-capable, two-phase gated
  },
  github: {
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-github'],
    env: { GITHUB_TOKEN: '{{env.GITHUB_TOKEN}}' },  // interpolated — never inline secrets
    allow: [],                                       // non-read-only tools need explicit listing
    shell: true,                                     // Windows: npx is a .cmd shim — raw spawn can't exec it
  },
}

(shell defaults to false. On Windows, .cmd shims like npx need shell: true — or point command directly at a Node entry point. servers.json5 is operator-trusted config, so the shell opt-in is a portability knob, not an injection surface. Relative paths in command/args resolve against the directory containing servers.json5 — the config works no matter where the flowmcp process was started from.)

Then use an mcp_call step like any other:

{ id: 'issue', kind: 'mcp_call', server: 'github', tool: 'get_issue',
  args: { owner: 'x', repo: 'y', issue_number: '{{input.n}}' } }

The key property: the wrapped server's 40 tools never appear in FlowMCP's tools/list. 40 tools in, 3 workflows out — the model's surface never grows, no matter how many servers sit behind it.

Rules of engagement:

  • Read-only by default, fail-closed. A downstream tool is callable only if it declares annotations.readOnlyHint: true, is operator-attested as a read in that server's attestReadOnly list (a security assertion — production servers often annotate nothing), or is explicitly named in its allow list. Naming a write tool is a consent moment, on purpose — and it changes what FlowMCP advertises: annotations are computed per flow from its steps, so a flow containing a POST or an allowlisted write tool is published with readOnlyHint: false, destructiveHint: true. FlowMCP never tells a client a write-capable flow is read-only.

  • Children get a minimal environment. Downstream servers receive a baseline (PATH, HOME, …) plus the vars you configure in their env block — never the whole parent environment, unless you set inheritEnv: true for that server.

  • One session per child, not per flow. Downstream servers spawn lazily on first use, stay alive across calls, respawn on crash (3 attempts, then a 5s backoff), and shut down after 5 minutes idle. The client handshakes at the newest supported protocol revision and validates what comes back.

  • The step timeout covers spawn + handshake + call as one unit, bounded by the flow's 60s deadline — a slow cold-start can't invisibly eat the budget.

  • Results are capped at maxResultChars (default 8K) — downstream verbosity is not your flow's problem to inherit. structuredContent is preferred when the downstream tool provides it; otherwise JSON text results are parsed so later steps can path into them.

The benchmark

The thesis is an empirical claim, so we tested it: six conditions, ten local models (4B–35B plus DeepSeek v4-flash), identical fixture data, outcome-based scoring. Headline: 79% task success through the two-flow façade vs 10% on the same 35 tools raw — paired McNemar 33 discordant pairs, every one favoring the façade (exact p ≈ 2.3×10⁻¹⁰) — at a tenth of the tokens per attempt. A 7B through the façade outscored a 35B driving the raw surface. Full report with charts, per-model tables, and everything the data does not prove: petergreenappliedai.github.io/FlowMCP · method, harness, raw results, and transcripts in bench/.

The CLI

One entry point for the whole loop (from a clone: npx tsx src/cli.ts <cmd>, or npm run build once and use node dist/cli.js; installed as a package it's the flowmcp bin):

command

what it does

flowmcp serve [--flows <dir>]

serve flows as MCP tools over stdio (default)

flowmcp validate [--flows <dir>]

check flows + servers.json5 + registry.json5, exit 0/1

flowmcp status [--flows <dir>]

registry health and advisory nominations

flowmcp explain [--flows <dir>]

print a routing preamble for LLM hosts

flowmcp author --servers-dir <dir> --name <flow> --model <id> [--gateway <url>] "<intent>"

author a flow with a model, under observation

flowmcp compile <run.v0.json> [outDir] [flowName] [serverName]

compile a recorded trace into a candidate flow

flowmcp detect <executions.jsonl>

nominate recurring procedures from execution logs

flowmcp shadow <flow> --agent '<cmd>' [--judge '<cmd>']

shadow-verify a flow against a host-supplied agent

flowmcp compile-graphql <query-log.jsonl> --server <name> --tool <tool>

compile recurring GraphQL operations into candidate flows

author needs an OpenAI-compatible endpoint via --gateway or the GATEWAY env var — there is no default endpoint or model; any local or hosted model works.

The authoring loop (experimental)

"Workflows as tools" has an obvious objection: someone has to author the workflows. The answer, shipped as flowmcp author / compile / detect: a model helps author the flow once, under observation and validation — it does not improvise the workflow at runtime. A model writes a program against the tool surface; an instrumented runner records its execution (cassette record/replay for live, nondeterministic APIs); the compiler derives a candidate flow from the observed trace — dataflow classified by variant differencing, constants separated from inputs, redundant calls removed — and emits it with provenance, warnings, and fail-closed refusals for anything it cannot prove. Replay against mutated data catches hardcoding before a human ever reviews it.

The full loop exists and has run end-to-end: detect.ts nominates flow candidates from execution logs (frequency × cost × success, inputs discovered from cross-run argument variance); flowmcp author takes an intent, introspects the configured servers' read-only tools, has a model write and repair a script in a disposable sandboxed process, records it against the real servers, and compiles the trace. Dogfooded on a real recurring news-gathering workflow: the compiled flow replaced a multi-minute agentic search sweep with one 4.5-second deterministic call at zero model tokens. Nothing in the loop is provider-specific — the dogfood happens to use a self-hosted SearXNG wrapper, but any MCP server exposing a search (or any other read-only) tool slots into servers.json5 the same way.

This is not "automatic workflow generation": a generated flow carries provenance for every inference, warns where the DSL cannot express the source, and requires review before serving — and always before writes. The precise claim: existing MCP workflow engines execute workflows; FlowMCP is designed to compile observed tool use into small, reviewable workflow tools, and then execute them deterministically. Intelligence at build time, determinism at runtime.

The registry: promotion and rot detection (v0.6)

A compiled flow is deterministic code, and deterministic code can silently rot. The registry is the maintenance layer: drop a registry.json5 beside your flows and the directory becomes governed — every flow must be listed with a state (candidate → reviewed → active → retired), only active flows are served, and an unlisted flow file is a loud startup error. Entries carry provenance (source trace, authoring model) and review records. No registry file → nothing changes.

With a registry present, every flow execution is logged to registry-log.jsonl — an open append-only contract that external systems write to as well: a consuming agent's editorial layer can append signal records ("this lens of the output was thin, I patched it"), and a shadow-replay harness can append shadow records (flow output vs the specialist path). npm start -- --flows <dir> --status computes per-flow health and prints advisory nominations, in cost order:

  • Loud failure counting (free): 3+ consecutive failed runs → needs review.

  • Consumer signals (free): the same lens patched in each of the last 3 gap-check signals → recompile candidate — this catches stale-but-well-formed output, the rot no structural check can see, using judgment the consumer was already paying for.

  • Shadow replay (paid, scheduled): flowmcp shadow <flow> --agent '<cmd>' --judge '<cmd>' re-derives the task through a host-supplied agent, has a host-supplied judge compare, and records the verdict. FlowMCP never calls a model — the agent and judge are injected commands; without a judge nothing is recorded. Write flows are refused (shadowing one would write twice).

Nominations are advisory: --status never mutates the registry. Promotion and retirement stay human decisions — the registry's job is to make them informed and cheap. Full spec in FORMAT.md.

Trust model

Flow files are trusted programs — treat them like code, review them like code. The expression language can't execute code, but a flow can still send data to any URL it names; what bounds the blast radius is what the flow can see: only the env vars it declares in env: [...] (never all of process.env), only the 0–3 inputs it declares, and only downstream MCP tools that are read-only or explicitly allowlisted. servers.json5 is operator configuration, same trust level as the server's own command line. Don't load flow files you haven't read.

Design constraints (on purpose)

  • Hand-rolled server protocol, ~150 lines: initialize, tools/list, tools/call, ping over newline-delimited JSON-RPC on stdio — small enough to audit in one sitting. The line we hold: we implement the MCP surface we govern; we use the reference client for downstream interoperability.

  • Dependencies: zod, json5, and the official MCP SDK — used ONLY as the reference client transport for consuming remote (Streamable HTTP) downstream servers, pinned to its v1 line. FlowMCP's governed server runtime and workflow engine are hand-built and intentionally small; commodity protocol churn is delegated to the reference client.

  • Small surfaces everywhere: few tools, ≤300-char descriptions, ≤3 params. Every token in tools/list is budget spent by every client on every turn.

  • Writes are gated by construction. A flow containing a write step (a POST, or an mcp_call to an allowlisted tool) automatically gets a two-phase confirmation protocol — there is no opt-out flag. The first call runs the read steps, pauses before the first write, and returns a proposal plus a single-use confirmation token (5-minute expiry) bound to the frozen pre-write state; confirming executes exactly what was proposed, never a recomputation. A proposal template on the flow customizes the prompt. Write flows advertise readOnlyHint: false and a confirm parameter — all computed from the steps, never declared. With an elicitation-capable client (v0.5), approval is host-mediated instead: the server elicits {approve} through the host and the model never holds a token; missing required parameters are elicited the same way. With plain clients, the token protocol applies — a checkpoint, not a guaranteed human gate.

  • stdout is the protocol channel; all logging goes to stderr.

Roadmap

Done and shipped: the six-condition benchmark (report, frozen at tag bench-2026-07-31), the trace→flow compiler and authoring loop (now first-class CLI: flowmcp author / compile / detect; the benchmark corpus stays in bench/), remote Streamable HTTP downstreams with operator attestation + schema drift pinning (v0.4), host-mediated write approval via elicitation (v0.5), the flow registry with promotion states, run logging, and staleness nominations (v0.6), the unified flowmcp CLI (v0.7, on npm), and the shadow-verification harness with host-injected agent and judge (v0.8).

Ahead:

  • A broader benchmark task suite: more decline and partial-match shapes, tasks with no flow coverage, more trials per cell

  • MCP conformance matrix (Inspector-based CI against current protocol revisions)

  • Destination allowlists and HTTPS policy for http_request

  • HTTP transport for the server itself

  • Flow hot-reload

Development

npm test            # vitest: spawns the real server, speaks JSON-RPC, mocks only outbound HTTP
npm run typecheck   # strict TS, no emit
npm run build       # emits dist/ — the `flowmcp` bin entry points there

CI runs typecheck + tests on Node 20 and 22 for every push. Engineering log — what worked, what didn't, what the fix was — lives in DECISIONS.md. The flow file format is specified as a portable contract in FORMAT.md; benchmark method and results live in bench/.

MIT license.

Available Tools

2 tools
hn_topA
Read-only

WHEN TO USE: the user asks what is on Hacker News right now. Returns the top 5 stories as a markdown list. Takes no parameters.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds the output format ('markdown list') and the fixed count ('top 5 stories'), which is mildly useful, but it does not disclose any additional behavioral traits such as rate limits, authentication, or network dependencies.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise, using three short clauses to convey when to use, what it returns, and that it takes no parameters. Every sentence earns its place and the key trigger is front-loaded with WHEN TO USE.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple, parameterless, read-only tool with strong annotations, the description fully covers the trigger condition, the output content, and the output format. No additional context is necessary for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the schema already fully describes the input contract. The description explicitly states 'Takes no parameters,' reinforcing the schema and providing a baseline score of 4 as per rubric guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns the top 5 Hacker News stories as a markdown list, with a specific verb ('Returns') and resource. It also includes a concrete trigger condition ('the user asks what is on Hacker News right now'), which distinguishes it from the sibling tool morning_brief.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly provides a WHEN TO USE condition, making the intended usage clear. However, it does not mention when not to use it or mention alternatives such as morning_brief, so it misses the highest bar for usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

morning_briefA
Read-only

WHEN TO USE: the user asks for a morning briefing or daily summary. Returns one markdown brief with today's weather for a city plus the top 5 Hacker News stories. No API keys needed.

ParametersJSON Schema
NameRequiredDescriptionDefault
cityNoCity for the weather section

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds valuable behavioral context beyond the annotations: it specifies the output is a single markdown brief, includes today's weather and exactly 5 HN stories, and notes 'No API keys needed.' This is useful, and it does not contradict the readOnly/openWorld hints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, with the WHEN TO USE trigger front-loaded. Every sentence conveys essential information: when to use, what it returns, and a notable constraint (no API keys). No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one optional parameter and no output schema, the description is largely complete. It covers the trigger, the content of the brief, and a key operational detail. It could mention the exact format of the markdown (e.g., headings) or pagination, but for this scope it is sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides 100% coverage for the one parameter (city) with a clear description. The tool description only repeats this by saying 'today's weather for a city,' adding no new semantic information. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns a markdown brief with today's weather for a city plus the top 5 Hacker News stories. This specific verb+resource combination distinguishes it from the sibling hn_top, which likely focuses solely on HN stories.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description opens with an explicit WHEN TO USE condition: 'the user asks for a morning briefing or daily summary.' It provides clear context, though it does not explicitly mention when NOT to use it (e.g., if only HN stories are requested, hn_top might be more appropriate).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 2 tool updatesv0.3.0
    • First observedhn_top
    • First observedmorning_brief

TDQS

A4.1/5.0
Disambiguation3/5

The tools overlap: morning_brief includes the top 5 HN stories, so both tools can satisfy a request for HN top stories. However, the descriptions clearly distinguish when to use each: hn_top for immediate HN queries and morning_brief for a broader daily summary. This overlap creates some ambiguity, but the usage cues help.

Naming Consistency4/5

Both tool names follow a consistent pattern of lowercase noun phrases with underscores (hn_top, morning_brief). While they don't use verb_noun naming, the style is predictable and coherent across the set, with no mixed conventions.

Tool Count3/5

With only 2 tools, the set is on the thin side, but it matches the server's narrow purpose of providing a morning briefing and HN top stories. The count is borderline but not unreasonable for such a small scope.

Completeness4/5

For the apparent domain of news and weather summaries, the core workflows are covered. A minor gap is the lack of a standalone weather-only tool, but the combined briefing handles the main use case. The surface is largely complete for its stated purpose.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP server that lets AI agents execute structured business processes by exposing process steps as tools with a sequenced event bus to prevent skipping steps.
    1
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that enables AI agents to run a deterministic orchestration loop with decomposition, subagent execution, and review feedback across multiple LLM backends.
    55
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that helps AI coordinate sequential tool calls and maintain a comprehensive journal of execution workflows, decisions, and actions.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    This MCP server provides a stateful, resettable, verifiable API runtime that gates every tool call, enabling agents to run long workflows against provider-shaped environments without live provider write access. It records decisions, side effects, and outcome evidence for replayable, verifiable benchmark runs.
    Apache 2.0

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/PeterGreenAppliedAI/FlowMCP'

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