Skip to main content
Glama
ndmxjp

agent-workflows-mcp

by ndmxjp
README.md
# agent-workflows-mcp

[![CI](https://github.com/ndmxjp/agent-workflows-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ndmxjp/agent-workflows-mcp/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/agent-workflows-mcp)](https://www.npmjs.com/package/agent-workflows-mcp)
[![npm downloads](https://img.shields.io/npm/dm/agent-workflows-mcp)](https://www.npmjs.com/package/agent-workflows-mcp)
[![node](https://img.shields.io/node/v/agent-workflows-mcp)](https://www.npmjs.com/package/agent-workflows-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

An MCP server that exposes **agents and DAG workflows as plain MCP tools**, so a
client with no native subagent or workflow feature (or one that deliberately does
not enable it) can still delegate multi-step work. Agent Plugins 1.0.0
standardizes skills and MCP servers — agents and workflows are outside the spec —
so shipping them as MCP tools is the client-portable route: the same repository
works from [Kiro-action](https://github.com/ndmxjp/Kiro-action), Claude Code,
Kiro, or any other MCP client.

Definitions live in this repository (`definitions/agents/*.md`,
`definitions/workflows/*.json`); the server reads them from its **own** directory — never from the caller's working
directory, which may be an untrusted checkout. Every tool returns text. The
server never posts to GitHub and needs no GitHub token; the calling agent owns
its own reporting channel.

## Tools

- **`list_agents`** — names, descriptions, and the permission surface (read /
  shell patterns / write / network) of every agent definition.
- **`list_workflows`** — names, declared inputs, node graphs, and review loops
  of every workflow definition.
- **`run_agent`** `{ name, task, context?, write_dir? }` — runs one agent
  definition against a task and returns its text output.
- **`run_workflow`** `{ name, inputs, write_dir? }` — runs a DAG of agents:
  nodes are agent runs, `needs` edges are dependencies, independent nodes run
  in parallel, and an optional review loop re-runs the target node until a
  reviewer agent accepts (capped at 10 iterations). Returns JSON with per-node
  `status`/`output` and the output node's `final` text.

Failures are loud: an unparsable definition, a cyclic workflow, or an agent
that cannot start returns `isError: true` with a message, and an invalid
definitions directory makes the server **exit non-zero at startup** (so a
client using `--require-mcp-startup` sees exit 3, not a silent absence).

## Runner backend

Agent definitions are executed by shelling out to
`kiro-cli chat --no-interactive --agent <generated-profile>`. This was chosen
first because the primary consumer (Kiro-action's runner) already has the CLI
and `KIRO_API_KEY` — no extra model API key is required. The runner is a small
pluggable interface (`src/runner/`); other hosts can add a backend and select
it with `AGENT_WORKFLOWS_RUNNER`.

Constraints of the kiro-cli backend:

- `kiro-cli` must be on `PATH` and authenticated (env `KIRO_API_KEY` or an
  existing login under `HOME`).
- One child process per agent run; per-run timeout via `AGENT_TIMEOUT_MS`
  (default 300000).

## Security model

The generated per-run profile is the security boundary; children are never
less restricted than their definition earns:

- **Read-only by default.** An agent gets `read`/`grep`/`glob` only. Shell is
  OFF unless the definition lists `allowed_commands` patterns, which become the
  profile's scoped `allowedCommands`. `--trust-all-tools` is never passed.
- **Shell patterns are linted at load time.** A pattern like `git status.*`
  matches `git status; curl x | sh`, so it is rejected. Patterns must be
  anchored and provably unable to match shell metacharacters — safe literals,
  `(a|b)` groups, and `[^…]` classes excluding `` ;&|<>$` `` and newline, e.g.
  ``^git (status|log)[^;&|<>$`\n]*$``. A definition with an unsafe pattern
  fails the server at startup.
- **Read access is NOT path-scoped (known limitation).** kiro-cli 2.21 ignores
  read-path restrictions in `toolsSettings` (verified empirically), so a child
  can read any file the job's user can — including credentials — and return
  them in its output. Redaction (below) catches common token shapes and PEM
  private-key blocks, but treat child output as able to contain anything the
  user account can read. Run the server under a job user whose HOME holds
  nothing secret beyond what the job needs.
- **Writes need two keys.** A write tool appears only when the definition sets
  `write: true` **and** the tool caller names a `write_dir`; writes are
  confined to that directory.
- **No network tools.** No builtin network tool is ever granted, and
  `allowed_commands` naming network clients (curl, wget, nc, ssh, …) are
  rejected at load time unless the definition sets `network: true`.
- **Minimal child environment.** Children receive `PATH`, `HOME`, `TMPDIR`,
  and `KIRO_API_KEY` — nothing else. In particular `GITHUB_TOKEN`/`GH_TOKEN`
  are never forwarded. The forwarded set is logged to stderr per spawn.
- **Redaction.** Secret-shaped strings (`ksk_`, `ghp_`/`gho_`/`github_pat_`,
  AWS access keys and labeled secrets, JWTs, PEM private-key blocks) are
  redacted from every tool result.
- **Definitions are trusted code-adjacent data.** They load from the server's
  own directory (or an explicit `--definitions` / `AGENT_WORKFLOWS_DIR`
  override), never from the caller's cwd. `includeMcpJson` is false in every
  generated profile, so children cannot pick up ambient MCP servers.

**This server is code you run with the job's credentials.** Trust it the way
you trust a `uses:` line in a workflow, and pin it by SHA. The host's shell and
write scoping do not extend into what an MCP server does internally — that is
exactly why this server rebuilds those guarantees for its children.

## Definition formats

Agent (`definitions/agents/<name>.md`):

```markdown
---
name: repo-analyst
description: Inspects the current repository read-only.
allowed_commands: # optional; omit for no shell at all. Anchored + metacharacter-excluding (linted at load)
  - "^git (status|log)[^;&|<>$`\\n]*$"
write: false # optional; true still requires the caller's write_dir
network: false # optional; gates network clients in allowed_commands
model: null # optional model override
---

System prompt for the agent goes here.
```

Workflow (`definitions/workflows/<name>.json`):

```json
{
  "name": "analyze-and-summarize",
  "description": "Analyze the repo and merge with notes.",
  "inputs": ["question", { "name": "notes", "required": false }],
  "nodes": [
    { "id": "analyze", "agent": "repo-analyst", "task": "Answer: {{inputs.question}}" },
    { "id": "digest", "agent": "summarizer", "task": "Summarize: {{inputs.notes}}" },
    {
      "id": "merge",
      "agent": "summarizer",
      "task": "Merge {{analyze}} and {{digest}}",
      "needs": ["analyze", "digest"]
    }
  ],
  "output": "merge",
  "review": {
    "target": "merge",
    "reviewer": "critic",
    "trigger": "NEEDS_REVISION",
    "max_iterations": 2
  }
}
```

Task templates may reference `{{inputs.<name>}}` and `{{<node-id>}}` — only for
node ids listed in `needs`, so the data flow and the scheduling graph always
agree. Cycles, self-loops, unknown agents, and out-of-range `max_iterations`
(1–10) are rejected at load time.

## Install

### npm / npx

The package ships a self-contained node bundle (`node >= 18`, no bun required)
plus the default `definitions/` (agents and workflows):

```json
{
  "mcpServers": {
    "agent_workflows": {
      "command": "npx",
      "args": ["-y", "agent-workflows-mcp"]
    }
  }
}
```

To serve your own definitions instead of the bundled ones, point
`AGENT_WORKFLOWS_DIR` (or `--definitions <dir>`) at a directory containing
`agents/` and `workflows/` subdirectories. Note the security caveat: only do this with a
directory you control, never a checked-out PR.

### Kiro-action (`mcp_servers` input)

Check out this repository at a pinned SHA in a prior step, then:

```yaml
mcp_servers: |
  {
    "agent_workflows": {
      "command": "bun",
      "args": ["run", "${{ github.workspace }}/.tools/agent-workflows-mcp/src/server.ts"]
    }
  }
```

### Claude Code (plugin)

```
/plugin marketplace add ndmxjp/agent-workflows-mcp
/plugin install agent-workflows-mcp@agent-workflows-mcp
```

The plugin's MCP server is declared once in the root `mcp.json` (Agent Plugins
1.0.0 format, `npx -y agent-workflows-mcp`); `.claude-plugin/plugin.json`
points at that same file, so Claude Code and any Agent Plugins client share one
server definition. Agent and workflow definitions live under `definitions/`
deliberately: a top-level `agents/` would be picked up by Claude Code as native
subagents with inherited tools, bypassing this server's sandbox.

### Kiro (Power)

Kiro Powers accept Agent Plugins 1.0.0 packages: Powers panel → Add Custom Power
→ Import power from GitHub → `https://github.com/ndmxjp/agent-workflows-mcp`.
The root `plugin.json` + `mcp.json` are what Kiro reads; the MCP server is
managed inside the Power.

Or as a plain project MCP server in `.mcp.json`:

```json
{
  "mcpServers": {
    "agent_workflows": {
      "command": "bun",
      "args": ["run", "/path/to/agent-workflows-mcp/src/server.ts"]
    }
  }
}
```

## Development

```
bun install
bun test          # unit + real stdio handshake tests
bun run format
bun run build     # bundle dist/server.js (node target) for npm publishing
```

TypeScript runs from source under bun; the build step exists only to produce
the node-compatible bundle that npm/npx installs (`prepublishOnly` runs it).

## Prior art and how this differs

Exposing agents as MCP tools is not a new idea; this server exists for the
combination the existing projects don't cover:

- **[shinpr/sub-agents-mcp](https://github.com/shinpr/sub-agents-mcp)** — the
  closest neighbor: markdown-defined sub-agents behind a `run_agent` tool, with
  many CLI backends (cursor-agent, claude, gemini, codex, …). It has no
  workflows (single-agent runs only), no kiro-cli backend, and no child
  sandboxing — children run with whatever the backend CLI allows. Its
  multi-backend abstraction is a good reference for future runners here.
- **[lastmile-ai/mcp-agent](https://github.com/lastmile-ai/mcp-agent)** and
  **[fast-agent](https://fast-agent.ai/agents/workflows)** — Python frameworks
  where agents and workflows (parallel, evaluator-optimizer, orchestrator) are
  defined in code and can be served over MCP. Powerful, but code-defined and
  heavyweight where this server wants declarative data files a CI runner can
  load at a pinned SHA.
- **MCP workflow engines** (e.g. the MCP Mediator pattern) — run DAGs of MCP
  _tool calls_, not agent runs; a different layer.

What this server adds that none of the above combine: declarative definitions
(markdown agents + JSON DAG workflows) loaded only from the server's own
directory, a kiro-cli backend for hosts that already carry `KIRO_API_KEY`, and
a per-run least-privilege child sandbox (generated profile, minimal env, no
GitHub token, output redaction) as a design requirement rather than an option.

## Future (not in v1)

- Additional runner backends (Claude Code / claude CLI, direct model APIs).
- HTTP/SSE transport (stdio only for now, matching the consumers).
- Streaming per-node progress notifications.