MCP Chaining Pipeline
A research-to-implement pipeline that chains 5 MCP tools for end-to-end workflows.
When to Use Building multi-tool MCP pipelines Understanding how to chain MCP calls with graceful degradation Debugging MCP environment variable issues Learning the tool naming conventions for different MCP servers What We Built
A pipeline that chains these tools:
Step Server Tool ID Purpose 1 nia nia__search Search library documentation 2 ast-grep ast-grep__find_code Find AST code patterns 3 morph morph__warpgrep_codebase_search Fast codebase search 4 qlty qlty__qlty_check Code quality validation 5 git git__git_status Git operations Key Files scripts/research_implement_pipeline.py - Main pipeline implementation scripts/test_research_pipeline.py - Test harness with isolated sandbox workspace/pipeline-test/sample_code.py - Test sample code Usage Examples
Dry-run pipeline (preview plan without changes)
uv run python -m runtime.harness scripts/research_implement_pipeline.py \ --topic "async error handling python" \ --target-dir "./workspace/pipeline-test" \ --dry-run --verbose
Run tests
uv run python -m runtime.harness scripts/test_research_pipeline.py --test all
View the pipeline script
cat scripts/research_implement_pipeline.py
Critical Fix: Environment Variables
The MCP SDK's get_default_environment() only includes basic vars (PATH, HOME, etc.), NOT os.environ. We fixed src/runtime/mcp_client.py to pass full environment:
In _connect_stdio method:
full_env = {os.environ, (resolved_env or {})}
This ensures API keys from ~/.claude/.env reach subprocesses.
Graceful Degradation Pattern
Each tool is optional. If unavailable (disabled, no API key, etc.), the pipeline continues:
async def check_tool_available(tool_id: str) -> bool: """Check if an MCP tool is available.""" server_name = tool_id.split("__")[0] server_config = manager._config.get_server(server_name) if not server_config or server_config.disabled: return False return True
In step function:
if not await check_tool_available("nia__search"): return StepResult(status=StepStatus.SKIPPED, message="Nia not available")
Tool Name Reference nia (Documentation Search) nia__search - Universal documentation search nia__nia_research - Research with sources nia__nia_grep - Grep-style doc search nia__nia_explore - Explore package structure
ast-grep (Structural Code Search) ast-grep__find_code - Find code by AST pattern ast-grep__find_code_by_rule - Find by YAML rule ast-grep__scan_code - Scan with multiple patterns
morph (Fast Text Search + Edit) morph__warpgrep_codebase_search - 20x faster grep morph__edit_file - Smart file editing
qlty (Code Quality) qlty__qlty_check - Run quality checks qlty__qlty_fmt - Auto-format code qlty__qlty_metrics - Get code metrics qlty__smells - Detect code smells
git (Version Control) git__git_status - Get repo status git__git_diff - Show differences git__git_log - View commit history git__git_add - Stage files
Pipeline Architecture +----------------+ | CLI Args | | (topic, dir) | +-------+--------+ | +-------v--------+ | PipelineContext| | (shared state) | +-------+--------+ | +-------+-------+-------+-------+-------+ | | | | | | +---v---+---v---+---v---+---v---+---v---+ | nia |ast-grp| morph | qlty | git | |search |pattern|search |check |status | +---+---+---+---+---+---+---+---+---+---+ | | | | | +-------v-------v-------v-------+ | +-------v--------+ | StepResult[] | | (aggregated) | +----------------+
Error Handling
The pipeline captures errors without failing the entire run:
try: result = await call_mcp_tool("nia__search", {"query": topic}) return StepResult(status=StepStatus.SUCCESS, data=result) except Exception as e: ctx.errors.append(f"nia: {e}") return StepResult(status=StepStatus.FAILED, error=str(e))
Creating Your Own Pipeline Copy the pattern from scripts/research_implement_pipeline.py Define your steps as async functions Use check_tool_available() for graceful degradation Chain results through PipelineContext Aggregate with print_summary()