Skip to content
Blog

Agentic Workflows with the Model Context Protocol (MCP)

A single prompt is not a workflow. Learn how to build multi-step agentic workflows on top of the Model Context Protocol: planning loops, tool chaining, human-in-the-loop gates, and orchestration patterns that survive production.

Published on August 10, 2026

AI Assistant

A chat completion that calls one tool is not an agentic workflow. It’s a function call with extra steps. The moment your agent must decide what to do next — based on the result of what it just did — you have a workflow, and the Model Context Protocol (MCP) is the layer that keeps that workflow from turning into a pile of hand-rolled glue code.

In this post, you’ll learn how to build real agentic workflows on MCP: the planning loop, how to compose multiple MCP servers into a single pipeline, where to insert human approval gates, and the orchestration patterns that actually hold up in production.

From single shot to agentic loop

A non-agentic call looks like this: prompt in, answer out. If your tool fails, the model doesn’t know — the conversation just ends. An agentic workflow adds a loop:

plan -> call tool -> observe result -> re-plan -> call next tool -> ... -> done

The loop is where the model, not your code, decides the sequence of steps. MCP gives that loop a stable vocabulary:

  • Tools — actions the agent can take (search_issues, create_pr, query_db).
  • Resources — read-only data the agent can load into context (a schema, a file, a doc).
  • Prompts — reusable templates the agent can pull in for structured sub-tasks.

Your orchestration code stays tiny because all of the actual work lives behind the protocol. Swap one MCP server for another and the loop doesn’t change.

The core primitive: the planner-executor loop

Every agentic workflow is, underneath, a variant of the same loop. Here’s a minimal one in Python using the official MCP SDK, running an agent that searches a codebase and then drafts a PR description:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from google import genai

client = genai.Client()

async def run_workflow(user_task: str):
    # 1. Connect to the tools the workflow needs
    server = StdioServerParameters(command="npx", args=["-y", "@modelcontextprotocol/server-github"])
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            tool_specs = [
                {"name": t.name, "description": t.description,
                 "parameters": t.inputSchema}
                for t in tools.tools
            ]

            # 2. Run the plan -> act -> observe loop
            messages = [
                {"role": "user", "content": (
                    f"You are a code review agent. You have these tools: {tool_specs}\n\n"
                    f"Task: {user_task}. Inspect the relevant code, then write a PR description.")}
            ]
            for step in range(10):  # hard budget; never loop forever
                resp = client.models.generate_content(
                    model="gemini-2.5-pro", contents=messages, config={
                        "tools": tool_specs, "temperature": 0,
                    })
                call = next((c for c in resp.function_calls or []), None)
                if call is None:
                    return resp.text  # agent decided it's done
                # 3. Execute the chosen tool via MCP and feed the result back
                result = await session.call_tool(call.name, call.args)
                messages.append({"role": "model", "content": str(call.args)})
                messages.append({"role": "user", "content": f"Tool result: {result}"})
            raise RuntimeError("Workflow exceeded step budget")

asyncio.run(run_workflow("Find all usages of `requests` in the repo and summarize migration risk"))

Three details make this safe enough to ship:

  1. Step budget. Cap the loop (for step in range(10)) — a runaway agent is a bill, not a bug.
  2. Deterministic planning. Low temperature on the planner keeps the path stable.
  3. Full transcript. Keep the whole tool-call history; you’ll need it to debug and to audit.

Chaining multiple MCP servers

Real workflows touch more than one system: GitHub for code, a database for data, Slack for notification. MCP shines here because each capability is a separate server and the workflow just connects to all of them.

async def run_pipeline(task: str):
    # Each server is self-contained: github, postgres, slack
    servers = {
        "github": StdioServerParameters(command="npx", args=["-y", "@modelcontextprotocol/server-github"]),
        "postgres": StdioServerParameters(command="npx", args=["-y", "@modelcontextprotocol/server-postgres", "--connection-string", os.environ["DATABASE_URL"]]),
        "slack": StdioServerParameters(command="npx", args=["-y", "@modelcontextprotocol/server-slack", os.environ["SLACK_TOKEN"]]),
    }
    # ... connect each, merge tool_specs, run the same loop ...

Composition strategy: expose a flat tool namespace to the planner and let the model pick. Name tools with their domain prefix (github_search_issues, db_run_query, slack_post_message) so the planner can route correctly without extra scaffolding.

Two rules for multi-server workflows:

  • Keep side effects out of planning. The planner should only propose; execute mutations with a guard (see human-in-the-loop below).
  • Order matters. Design tools so the pipeline flows naturally: read the data first, transform it, then write or notify. Document the intended order in the tool descriptions.

Human-in-the-loop gates

The single most valuable pattern for production agentic workflows is the approval gate. The agent can draft anything, but destructive or expensive actions pause until a human confirms.

The cleanest way is to model the gate as an MCP tool that returns a pending status, then polls:

GATE_TOOLS = {
    "approve_action": {
        "name": "approve_action",
        "description": "Request human approval before executing a mutation. Returns 'approved' or 'denied'.",
        "parameters": {"type": "object", "properties": {
            "action": {"type": "string"},
            "summary": {"type": "string"},
        }, "required": ["action", "summary"]},
    }
}

async def guarded_call(session, name, args, risky=False):
    if not risky:
        return await session.call_tool(name, args)
    status = await session.call_tool("approve_action", {"action": name, "summary": str(args)})
    if "denied" in str(status):
        return {"error": "Human denied this action", "safe": True}
    return await session.call_tool(name, args)

Even better: implement the approval as a separate MCP server (an “approval server”) that wraps all other tools. Then every host that connects gets the gate, not just your workflow.

Orchestration patterns

Three patterns cover most production workflows:

1. Supervisor pattern. One agent plans and delegates to specialist agents. The supervisor keeps the goal, the specialists each handle one domain (each backed by their own MCP servers).

2. Pipeline pattern. Fixed order, no branching — analyze → decide → act → report. Use this when the steps are known in advance and correctness depends on sequence, not on search.

3. Bounded autonomy. The agent loops freely but only within a sandboxed set of tools, with a step budget and gates on any side effects. This is the sweet spot for internal tooling: maximum flexibility, minimum blast radius.

Production hardening

An agentic workflow is a distributed system. Harden it like one:

  • Retries with backoff. MCP servers are subprocesses or remote services; they 429 and 5xx like anything else. Wrap session.call_tool with retries + jitter.
  • Idempotency keys. If a tool call times out and you retry, you must not double-post. Pass an idempotency key for mutating tools.
  • Observability. Log every tool call: name, arguments hash, latency, result status, and step index. Reconstructing “what did the agent do and why” is the #1 debugging task.
  • Secrets stay on the server. The client never sees credentials — MCP servers own their auth, exactly like a microservice. Never pass tokens through prompts.
  • Test the loop, not the prompt. A unit test that asserts “tool A then tool B” against a mock MCP server catches orchestration regressions better than eyeballing outputs.

Conclusion & Next Steps

Agentic workflows are the difference between a chatbot with tools and an automated operator. MCP gives you the standard interface to build that loop on — planner loops, multi-server chains, approval gates, and bounded autonomy. Start small: pick one two-step workflow, put it behind the loop, add a gate, and measure the step budget before you scale it up.

Next: wire the loop into an orchestration framework like LangGraph or Google ADK when you outgrow the hand-rolled loop, add tracing so every tool call is replayable, and explore A2A (Agent-to-Agent Protocol) for workflows that need agents talking to agents rather than just to tools.

References / Sources