Every multi-step tool workflow burns an LLM round-trip per step. The agent calls tool A, waits, sends the full context back to the model, gets a decision to call tool B, calls it, sends everything back again. Each round-trip re-transmits 2K–10K tokens on what is essentially plumbing. For a session with 20 two-step workflows, that's 20 wasted model calls, ~100K wasted tokens, and 20–40 seconds of a
Add this skill
npx mdskills install mk-in/mcp-chainEliminates LLM round-trips by chaining 2-3 tool calls into pipelines with excellent JSONPath reference support.
1# mcp-chain23> Unix pipes for MCP. Compose 2–3 tool calls into deterministic pipelines — no LLM round-trips between steps.45[](https://www.npmjs.com/package/mcp-chain)6[](./LICENSE)7[](#)89---1011## The Problem1213Every multi-step tool workflow burns an LLM round-trip per step. The agent calls tool A, waits, sends the full context back to the model, gets a decision to call tool B, calls it, sends everything back again. Each round-trip re-transmits 2K–10K tokens on what is essentially plumbing.1415For a session with 20 two-step workflows, that's 20 wasted model calls, ~100K wasted tokens, and 20–40 seconds of added latency.1617## The Solution1819`mcp-chain` is an MCP server that connects to your other MCP servers and lets agents compose tool calls into short pipelines. One agent decision triggers a deterministic sequence. No LLM in the loop between steps.2021```22# Before: 2 LLM round-trips23agent → LLM → web_search → LLM → web_fetch → result2425# After: 1 LLM decision26agent → chain([web_search, web_fetch]) → result27```2829**Hard limit: 3 steps.** This is a feature, not a limitation — it keeps error handling trivial and chains readable at a glance.3031---3233## Quick Start3435Add to your `mcp.json` / `claude_desktop_config.json`:3637```json38{39 "mcpServers": {40 "chain": {41 "command": "npx",42 "args": ["-y", "mcp-chain", "--config", "./mcp.json"]43 }44 }45}46```4748The chain server reads the same config file it's listed in, connecting to all your other MCP servers automatically.4950---5152## Usage5354### Ad-hoc chain5556```57chain([58 { tool: "web_search", params: { query: "MCP protocol spec" } },59 { tool: "web_fetch", params: { url: "$1.results[0].url", maxChars: 5000 } }60])61```6263`$1` refers to the output of step 1. Supports full JSONPath: `$1.results[0].url`, `$1.items[*].id`, `$input.query`.6465### Saved chain6667```68run_chain("research", { query: "MCP protocol spec" })69```7071Chains are JSON files in a `chains/` directory. Three example chains ship with the package.7273### Parallel fan-out7475```76chain([77 { tool: "web_search", params: { query: "$input.query", count: 3 } },78 { tool: "web_fetch", parallel: true, foreach: "$1.results[:3]", params: { url: "$item.url" } }79])80```8182Fetches the top 3 results simultaneously. Up to 10 concurrent invocations per fan-out step.8384### Dry run8586```87chain([...], { dry_run: true })88```8990Validates the chain and returns the execution plan without calling any tools.9192---9394## Reference Syntax9596| Expression | Resolves to |97|---|---|98| `$input` | Input object passed to the chain |99| `$input.query` | Nested property of input |100| `$1` | Full output of step 1 |101| `$1.results[0].url` | Nested array index + property |102| `$1.results[:3]` | Array slice (first 3 items) |103| `$1.results[*].url` | Map — extract `url` from every item |104| `$item` | Current item in a `foreach` fan-out |105| `$item.url` | Property of current foreach item |106107---108109## Example Chains110111### `research.json` — Search + fetch top result112```json113{114 "name": "research",115 "description": "Search the web and fetch the top result",116 "steps": [117 { "id": "search", "tool": "web_search", "params": { "query": "$input.query" } },118 { "id": "fetch", "tool": "web_fetch", "params": { "url": "$1.results[0].url", "maxChars": 8000 } }119 ]120}121```122123### `deep-research.json` — Search + fetch top 3 in parallel124```json125{126 "name": "deep-research",127 "description": "Search and fetch top 3 results in parallel",128 "steps": [129 { "id": "search", "tool": "web_search", "params": { "query": "$input.query", "count": 3 } },130 { "id": "fetch_all", "tool": "web_fetch", "parallel": true, "foreach": "$1.results[:3]",131 "params": { "url": "$item.url", "maxChars": 5000 } }132 ]133}134```135136### `email-to-calendar.json` — Gmail → read → create calendar event137```json138{139 "name": "email-to-calendar",140 "description": "Find email about a meeting and create calendar event",141 "steps": [142 { "id": "find", "tool": "gmail_search", "server": "gog", "params": { "query": "$input.query", "limit": 1 } },143 { "id": "read", "tool": "gmail_read", "server": "gog", "params": { "message_id": "$1.messages[0].id" } },144 { "id": "create", "tool": "calendar_create", "server": "gog", "params": { "summary": "$2.subject", "description": "$2.body" } }145 ]146}147```148149---150151## Performance152153| Scenario | Without chain | With chain | Token savings |154|---|---|---|---|155| 2-step sequential | 2 LLM calls × 4K ctx | 1 LLM call × 4K ctx | **50%** |156| 3-step sequential | 3 × 4K | 1 × 4K | **67%** |157| Search + 3× parallel fetch | 4 × 4K | 1 × 4K | **75%** |158| 20 research queries/session | 40 × 4K = 160K tokens | 20 × 4K = 80K tokens | **50%** |159160Chain overhead (no-op 2-step): **< 50ms**. Reference resolution: **< 5ms** per step.161162---163164## CLI Options165166```167mcp-chain [options]168169 --config <path> MCP config file (default: auto-detect)170 --chains <dir> Saved chain definitions dir (default: ./chains)171 --sse Use SSE transport instead of stdio172 --port <number> SSE port (default: 8399)173 --log-level <level> debug | info | warn | error (default: info)174 --timeout <seconds> Default per-step timeout (default: 30)175 --max-fanout <n> Max fan-out concurrency (default: 10)176```177178### Environment variables179180```181MCP_CHAIN_CONFIG=./mcp.json182MCP_CHAIN_CHAINS_DIR=./chains183MCP_CHAIN_LOG_LEVEL=info184MCP_CHAIN_DEFAULT_TIMEOUT=30185MCP_CHAIN_MAX_FANOUT=10186```187188---189190## Chain Definition Format191192```json193{194 "$schema": "https://mcp-chain.dev/schema/chain-v1.json",195 "name": "my-chain",196 "description": "What this chain does",197 "version": "1.0.0",198 "input_schema": {199 "type": "object",200 "properties": { "query": { "type": "string" } },201 "required": ["query"]202 },203 "steps": [204 {205 "id": "step1",206 "tool": "tool_name",207 "server": "server_name",208 "params": { "key": "$input.query" },209 "timeout": 30210 },211 {212 "id": "step2",213 "tool": "another_tool",214 "params": { "url": "$1.result.url" }215 }216 ]217}218```219220Save chain files to the `chains/` directory. Use `run_chain("list")` to see available chains.221222---223224## Error Handling225226Any step failure terminates the chain immediately and returns:227228```json229{230 "error": true,231 "error_type": "step_error",232 "message": "Step 2 (web_fetch) failed: HTTP 404",233 "failed_step": 2,234 "partial_results": { "1": { ... } },235 "_chain_meta": { "steps_executed": 1, "total_duration_ms": 423 }236}237```238239Fan-out partial failures return per-item status — the chain continues if at least one item succeeded.240241---242243## Architecture244245```246Host Client (Claude Desktop / Cursor / OpenClaw)247 │ MCP Protocol248 ▼249MCP Chain Server ← you are here250 │ MCP Protocol (as client)251 ├──► MCP Server A (brave-search)252 ├──► MCP Server B (gmail/calendar)253 └──► MCP Server C (filesystem)254```255256`mcp-chain` acts as both an MCP server (to your AI client) and an MCP client (to your other MCP servers). It reads the same `mcp.json` config file, discovers all connected tools automatically, and resolves tool name ambiguity when the same tool exists on multiple servers.257258**Zero AI/LLM dependencies.** Pure TypeScript plumbing.259260---261262## Requirements263264- Node.js 20+265- Any MCP client (Claude Desktop, Cursor, OpenClaw, etc.)266267---268269## Contributing270271Issues and PRs welcome. The 3-step limit is intentional — please don't open issues requesting 5+ steps.272273## License274275MIT276
Full transparency — inspect the skill content before installing.