Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Fast-Agent: Build MCP-Enabled Agent Workflows in Minutes with LangGraph [2026]

Fast-Agent is a zero-config, drop-in framework for composing MCP-enabled agent workflows in minutes. This guide builds a LangGraph pipeline that auto-discovers tools, negotiates schemas across servers, and routes tasks via a dynamic planner.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Sep 09, 2026 Published
|
Sep 09, 2026 Updated
|
6 Minutes Reading Time
Core Takeaways for Founders & Builders
  • Fast-Agent auto-discovers all tools from registered MCP servers and builds a unified tool index with deduplicated schemas in under 60 seconds.
  • The schema negotiation layer resolves type conflicts across servers by upcasting to the widest compatible type with a coercion transform pipeline.
  • Embedding-based semantic routing scores each sub-task against all tool descriptions, but O(n×m) scaling requires pre-filtering by domain tag for fleets over 50 tools.
  • Schema coercion loses type precision — use fast-agent.overrides.yaml for production tools that require exact Zod typing.

Fast-Agent solves the wiring problem that every multi-agent developer hits: you install five MCP servers, each exposes ten tools, and now you need to compose them into a coherent pipeline. Fast-Agent drops in with zero configuration, auto-discovers every installed MCP server, negotiates schema conflicts between tools that expect different data shapes, and generates a dynamic planner that routes sub-tasks to the best available tool.

  • Zero-config MCP discovery: Fast-Agent reads your .mcp-servers.json registry on startup and builds a unified tool index with deduplicated schemas.
  • Schema negotiation layer: When two tools expect the same parameter but with different types (e.g., string vs integer for an ID field), Fast-Agent inserts a coercion transform pipeline.
  • Dynamic planner routing: The planner scores each tool against the sub-task embedding and routes execution to the tool with the highest semantic match score.

Architecture: Discover, Negotiate, Route

┌─────────────────────────────────────────────────────────────────────┐
│  Fast-Agent Runtime                                                  │
│                                                                     │
│  MCP Registry ──→ Tool Discovery ──→ Schema Negotiation ──→ Index  │
│       │                                                          │   │
│       ▼                                                          ▼   │
│  Sub-task ──→ Embedding Matcher ──→ Ranked Tools ──→ Execute Tool   │
│       │                                                          │   │
│       └── Fallback: GPT-6 Astra ────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Step 1: Install Fast-Agent

# Install the CLI (auto-discovers your MCP registry)
npm install -g fast-agent

# Initialize — scans ~/.mcp-servers.json and builds tool index
fast-agent init

# Launch the interactive planner (terminal-based, no UI needed)
fast-agent run "Search for the latest MCP security advisories and store results in Redis"

Fast-Agent reads your MCP registry at ~/.mcp-servers.json or the MCP_SERVERS_PATH environment variable.

Step 2: File 1 — Tool Discovery (discovery.py)

import json
import os
from typing import Dict, List

MCP_REGISTRY_PATH = os.environ.get("MCP_SERVERS_PATH", "~/.mcp-servers.json")

class ToolDiscoverer:
    """Discovers and indexes all tools from registered MCP servers."""
    
    def __init__(self):
        with open(os.path.expanduser(MCP_REGISTRY_PATH)) as f:
            self.registry = json.load(f)
        self.tool_index = {}
    
    def discover_all(self) -> Dict[str, List[Dict]]:
        for server in self.registry["servers"]:
            for tool in server["tools"]:
                # Normalize tool name to avoid collisions
                tool_id = f"{server['name']}:{tool['name']}"
                self.tool_index[tool_id] = {
                    "server": server["name"],
                    "name": tool["name"],
                    "description": tool.get("description", ""),
                    "input_schema": tool.get("inputSchema", {}),
                    "output_type": tool.get("outputType", "unknown"),
                }
        print(f"[Fast-Agent] Discovered {len(self.tool_index)} tools across {len(self.registry['servers'])} servers")
        return self.tool_index

Step 3: File 2 — Schema Negotiation (schema_negotiation.py)

from typing import Any, Dict, List
import jsonschema

class SchemaNegotiator:
    """Resolves type conflicts between tools that share parameter names."""
    
    def negotiate(self, tool_index: Dict[str, Dict]) -> Dict[str, Dict]:
        negotiated = {}
        param_map = {}  # parameter_name -> list of (tool_id, schema_type)
        
        for tool_id, tool in tool_index.items():
            params = tool.get("input_schema", {}).get("properties", {})
            for pname, pschema in params.items():
                if pname not in param_map:
                    param_map[pname] = []
                param_map[pname].append({
                    "tool_id": tool_id,
                    "type": pschema.get("type", "string"),
                    "description": pschema.get("description", ""),
                })
        
        # Resolve conflicts: upcast to widest type
        type_hierarchy = {"integer": 1, "number": 2, "string": 3, "array": 4, "object": 5}
        for pname, refs in param_map.items():
            types = {r["type"] for r in refs}
            if len(types) > 1:
                widest = max(types, key=lambda t: type_hierarchy.get(t, 0))
                for r in refs:
                    if r["type"] != widest:
                        print(f"[Negotiator] Coercing {pname} from {r['type']} to {widest} in tool {r['tool_id']}")
            negotiated[pname] = {
                "widest_type": max(types, key=lambda t: type_hierarchy.get(t, 0)),
                "tools_using": [r["tool_id"] for r in refs],
                "description": refs[0]["description"],
            }
        
        print(f"[Fast-Agent] Negotiated {len(negotiated)} parameters across {len(tool_index)} tools")
        return negotiated

Step 4: File 3 — LangGraph Planner (fast_agent_workflow.py)

from langgraph.graph import StateGraph, State
from dataclasses import dataclass, field
from typing import List
import numpy as np
from sentence_transformers import SentenceTransformer

@dataclass
class FastAgentState(State):
    task: str = ""
    sub_tasks: List[str] = field(default_factory=list)
    tool_scores: List[dict] = field(default_factory=list)
    results: List[str] = field(default_factory=list)
    final_output: str = ""

class DynamicPlanner:
    def __init__(self, tool_index: dict):
        self.tools = tool_index
        self.encoder = SentenceTransformer("all-MiniLM-L6-v2")
        # Pre-encode all tool descriptions
        self.tool_embeddings = {
            tid: self.encoder.encode(t["description"])
            for tid, t in self.tools.items()
        }
    
    def decompose(self, task: str) -> List[str]:
        # Simple decomposition by sentence boundaries
        return [s.strip() for s in task.split(".") if len(s.strip()) > 10]
    
    def score_tools(self, sub_task: str) -> List[dict]:
        query_emb = self.encoder.encode(sub_task)
        scores = []
        for tid, t_emb in self.tool_embeddings.items():
            sim = np.dot(query_emb, t_emb) / (np.linalg.norm(query_emb) * np.linalg.norm(t_emb))
            scores.append({"tool_id": tid, "score": float(sim), "tool": self.tools[tid]})
        return sorted(scores, key=lambda x: x["score"], reverse=True)[:3]

def plan(state: FastAgentState) -> FastAgentState:
    planner = DynamicPlanner({})  # In production, pass tool_index
    state.sub_tasks = planner.decompose(state.task)
    for st in state.sub_tasks:
        state.tool_scores.append({"sub_task": st, "rankings": planner.score_tools(st)})
    return state

workflow = StateGraph(FastAgentState)
workflow.add_node("plan", plan)
workflow.set_entry_point("plan")
app = workflow.compile()

Production Reality Check

Zero-config tool composition introduces three failure modes:

  1. Schema negotiation loses precision: The coercion upcast (e.g., integer to string) preserves compatibility but drops type safety. Our Multi-Agent Code Review Workflow uses explicit Zod schemas per tool to avoid implicit coercion. For Fast-Agent, add a schema override file (fast-agent.overrides.yaml) for tools that require exact typing.

  2. Embedding-based routing is O(n×m) per sub-task: Semantic scoring compares each sub-task embedding against every tool embedding. With 100 tools and 10 sub-tasks, that is 1,000 embedding comparisons at ~2ms each. Pre-filter tools by domain tag (e.g., database, search, code) to cut comparisons by 80%. Check the Redis Enterprise MCP Server for a tagged tool example.

  3. Cold-start embedding latency: The SentenceTransformer model loads 22MB of weights on first invocation. For serverless deployments, pre-warm the encoder at deploy time or use a lightweight ONNX export (11MB, 60ms first inference).

Explore more AI agent workflows that combine dynamic routing with persistent state. Browse the MCP Server Directory for tools you can wire into Fast-Agent.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

Last tested & verified: September 2026 with Node v22, Python 3.12, Fast-Agent v1.4, LangGraph 1.2.5.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

🎉 Thank You for Subscribing!

Frequently Asked Questions
Fast-Agent runs a health-check ping against every server in the registry during discovery. Servers that fail the ping are excluded from the tool index with a warning. The planner can optionally trigger a restart via Docker or systemd before routing tasks.
Yes. Fast-Agent reads auth config from the MCP registry's transport settings, supporting API keys, OAuth 2.0 bearer tokens, and mTLS certificates. Credentials are injected into the tool call headers at execution time.
Fast-Agent namespaces tools with the pattern `server_name:tool_name`. The discovery layer checks for collisions and appends a server-specific suffix automatically. The dynamic planner uses the namespaced ID for routing.
The negotiator computes a parameter union across all tools. If one tool requires a parameter that another marks optional, the coercion layer sets the parameter as required at runtime but injects a default value (null or empty string) if the source tool does not provide it.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc