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, 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.
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,needsedges 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-nodestatus/outputand the output node'sfinaltext.
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).
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-climust be onPATHand authenticated (envKIRO_API_KEYor an existing login underHOME).- One child process per agent run; per-run timeout via
AGENT_TIMEOUT_MS(default 300000).
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/globonly. Shell is OFF unless the definition listsallowed_commandspatterns, which become the profile's scopedallowedCommands.--trust-all-toolsis never passed. - Shell patterns are linted at load time. A pattern like
git status.*matchesgit 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: trueand the tool caller names awrite_dir; writes are confined to that directory. - No network tools. No builtin network tool is ever granted, and
allowed_commandsnaming network clients (curl, wget, nc, ssh, …) are rejected at load time unless the definition setsnetwork: true. - Minimal child environment. Children receive
PATH,HOME,TMPDIR, andKIRO_API_KEY— nothing else. In particularGITHUB_TOKEN/GH_TOKENare 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_DIRoverride), never from the caller's cwd.includeMcpJsonis 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.
Agent (definitions/agents/<name>.md):
---
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):
{
"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.
The package ships a self-contained node bundle (node >= 18, no bun required)
plus the default definitions/ (agents and workflows):
{
"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.
Check out this repository at a pinned SHA in a prior step, then:
mcp_servers: |
{
"agent_workflows": {
"command": "bun",
"args": ["run", "${{ github.workspace }}/.tools/agent-workflows-mcp/src/server.ts"]
}
}/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 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:
{
"mcpServers": {
"agent_workflows": {
"command": "bun",
"args": ["run", "/path/to/agent-workflows-mcp/src/server.ts"]
}
}
}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).
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 — the
closest neighbor: markdown-defined sub-agents behind a
run_agenttool, 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 and fast-agent — 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.
- 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.