<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amol Kavitkar</title>
    <description>The latest articles on DEV Community by Amol Kavitkar (@amol_kavitkar).</description>
    <link>https://dev.to/amol_kavitkar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3138759%2Fc0609e85-2bc2-4fb9-97b2-36f1254cfe5f.png</url>
      <title>DEV Community: Amol Kavitkar</title>
      <link>https://dev.to/amol_kavitkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amol_kavitkar"/>
    <language>en</language>
    <item>
      <title>Building an Advanced AI Agent: A Step-by-Step Guide to Integrating MCP Servers with LangGraph</title>
      <dc:creator>Amol Kavitkar</dc:creator>
      <pubDate>Tue, 19 Aug 2025 12:57:03 +0000</pubDate>
      <link>https://dev.to/amol_kavitkar/building-an-advanced-ai-agent-a-step-by-step-guide-to-integrating-mcp-servers-with-langgraph-4ocf</link>
      <guid>https://dev.to/amol_kavitkar/building-an-advanced-ai-agent-a-step-by-step-guide-to-integrating-mcp-servers-with-langgraph-4ocf</guid>
      <description>&lt;p&gt;In this tutorial, we’ll walk through the process of building a sophisticated, tool-using AI agent. We’ll leverage the power of &lt;code&gt;LangGraph&lt;/code&gt; to orchestrate the agent’s logic and integrate external services via the MCP. This approach allows you to create agents that can interact with a wide range of external systems, from simple APIs to complex, stateful services.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This is an project core backbone, code snippets given are ref. working models which needs to be little polished/enhanced to build as per requirement but using this as core structure and putting it in copilot/windsurf/cursor can quickly build your project&lt;br&gt;
    Also in last section I have provided considerations for taking this to production scale&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Core Concepts
&lt;/h3&gt;

&lt;p&gt;Before we dive in, let’s clarify the key technologies we’ll be using:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;: A library for building stateful, multi-actor applications with LLMs. It allows us to define our agent’s behavior as a graph, making it easy to manage complex control flows.&lt;br&gt;
&lt;strong&gt;MCP&lt;/strong&gt; : A standardized protocol for communication between an AI agent and external tools or services. MCP servers expose their functionality as a set of tools that the agent can discover and use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Project Structure
&lt;/h2&gt;

&lt;p&gt;A well-organized project structure is key to a maintainable application. Here’s a recommended layout:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/your-project
| — streamlit_app.py # Main application entry point (UI)
| — chatbot.py # Core LangGraph agent logic
| — mcp_tools.py # Logic for loading tools from MCP servers
| — custom_tools.py # Definitions for simple, local tools
| — tools_config.json # Configuration for all tools and MCP servers
| — .env # Environment variables (API keys, etc.)
| — … (other files)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 2: Configuring Tools with &lt;code&gt;tools_config.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This file is the central hub for defining your agent’s capabilities. It specifies which tools are available, how to connect to MCP servers, and any necessary configurations.&lt;/p&gt;

&lt;p&gt;Here’s an example &lt;code&gt;tools_config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "tools": [
   {
     "name": "tavily_search",
     "description": "Search the web using Tavily API",
     "type": "tavily",
     "enabled": true
   },
   {
     "name": "calculator",
     "description": "Performs basic mathematical operations",
     "type": "custom",
     "function": "calculator",
     "enabled": true
   }
 ],
 "mcp_servers": {
     "grafana-mcp": {
        "command": "docker",
        "args": [
           "run",
           "-i",
           " - rm",
           "-e", "GRAFANA_URL=https://grafana.xxxxxx.com/",
           "-e", "GRAFANA_API_KEY=&amp;lt;auth_key&amp;gt;",
           "mcp/grafana",
           " - transport=stdio"
         ],
         "type": "stdio"
      },
      "some-remote-service": {
         "url": "https://example.com/mcp-sse",
         "type": "sse"
      }
   }
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We define a &lt;code&gt;tavily_search&lt;/code&gt; tool for web searches and a &lt;code&gt;calculator&lt;/code&gt; tool, which will be a custom Python function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We specify two MCP servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;grafana&lt;/code&gt; is run to get metrics information via &lt;code&gt;stdio&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;some-remote-service&lt;/code&gt; is a remote service accessed via Server-Sent Events (&lt;code&gt;SSE&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;In real we have two more MCP servers developed internally for product usecase, we can add as many MCP as needed but keep it few so that model doesn’t load up with too many tools and context can be limited.&lt;br&gt;
    As its all about giving right context to the model in prompts as well as in MCP tools definitions&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Loading MCP Tools
&lt;/h2&gt;

&lt;p&gt;Now, let’s write the Python code to load the tools from our MCP servers. We’ll use the &lt;code&gt;langchain_mcp_adapters&lt;/code&gt; library for this. In &lt;code&gt;mcp_tools.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mcp_tools.py
import json
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain_mcp_adapters.sessions import StdioConnection, SSEConnection

async def load_mcp_servers_from_config(config_path: str):
    with open(config_path, 'r') as f:
        config = json.load(f)
    all_tools = []
    mcp_servers = config.get("mcp_servers", {})
    for name, server_config in mcp_servers.items():
        server_type = server_config.get("type", "stdio")
        connection = None
        if server_type == "stdio":
            connection = StdioConnection(
            command=server_config.get("command"),
            args=server_config.get("args", []),
            )
        elif server_type == "sse":
            connection = SSEConnection(url=server_config.get("url"))
        if connection:
             tools = await load_mcp_tools(session=None, connection=connection)
             all_tools.extend(tools)
    return all_tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code reads the &lt;code&gt;mcp_servers&lt;/code&gt; section of our config, creates the appropriate connection (&lt;code&gt;StdioConnection&lt;/code&gt; or &lt;code&gt;SSEConnection&lt;/code&gt;), and then uses &lt;code&gt;load_mcp_tools&lt;/code&gt; to fetch the tools from each server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Sync and Async MCP Tools
&lt;/h3&gt;

&lt;p&gt;Tools loaded from MCP servers might be asynchronous, but &lt;code&gt;LangGraph&lt;/code&gt; may need to invoke them in a synchronous context. To handle this, we wrap each MCP tool in a &lt;code&gt;StructuredTool&lt;/code&gt; that provides both synchronous (&lt;code&gt;func&lt;/code&gt;) and asynchronous (&lt;code&gt;coroutine&lt;/code&gt;) entry points.&lt;/p&gt;

&lt;p&gt;First, we need a synchronous function to load the tools, as the chatbot’s initialization is synchronous. We create a simple wrapper using &lt;code&gt;asyncio.run()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mcp_tools.py
import asyncio
# … (async load_mcp_servers_from_config function)
def load_mcp_servers_sync(config_path: str):
   """Synchronous wrapper for loading MCP servers."""
   return asyncio.run(load_mcp_servers_from_config(config_path))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Building the LangGraph Agent
&lt;/h2&gt;

&lt;p&gt;This is where we define the core logic of our agent. In &lt;code&gt;chatbot.py&lt;/code&gt;, we’ll create a &lt;code&gt;LangGraphChatbot&lt;/code&gt; class that builds and runs the graph.&lt;/p&gt;

&lt;p&gt;First, define the state of our graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py
from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create the &lt;code&gt;LangGraphChatbot&lt;/code&gt; class. The &lt;code&gt;__init__&lt;/code&gt; method will set up the LLM and load all the tools (both custom and MCP).  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;we are showcasing with ollama as local llm but this can be extended with any chat-model, we have multiplexed with bedrock further down, which can be converted to OpenAI as well easily&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py
from langchain_ollama import ChatOllama
from langgraph.prebuilt import ToolNode
from custom_tools import CUSTOM_FUNCTIONS # Assuming you have this map
from mcp_tools import load_mcp_servers_from_config

class LangGraphChatbot:
    def __init__(self, tools_config_path: str):
        self.llm = ChatOllama(model="llama3") # Or any other LLM

        # load tools will load tools from standard tools defined such as tavily search 
        # also it will load all mcp tools from mcp servers
        self.tools = self._load_all_tools(tools_config_path)

        self.llm_with_tools = self.llm.bind_tools(self.tools)
        self.graph = self._build_graph()

    def _load_all_tools(self, config_path: str):
        # … (logic to load custom tools and call load_mcp_servers_from_config)
        pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, in &lt;code&gt;chatbot.py&lt;/code&gt;, we load the tools and wrap them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py
from langchain_core.tools import StructuredTool
class LangGraphChatbot:
 # …
    def _load_mcp_tools(self, config_path: str):
       mcp_tools = load_mcp_servers_sync(config_path)
       return [self._create_wrapped_tool(tool) for tool in mcp_tools]

    def _create_wrapped_tool(self, tool):
       """Create a tool that works in both sync and async contexts."""
       return StructuredTool(
           name=tool.name,
           description=tool.description,
           args_schema=tool.args_schema,
           func=self._make_sync_wrapper(tool), # For sync calls
           coroutine=self._make_async_wrapper(tool) # For async calls
       )

    def _make_sync_wrapper(self, tool):
       def sync_wrapper(**kwargs):
           # If the tool is async-only, run its async method in a new event loop
           if not hasattr(tool, "invoke"):
               return asyncio.run(tool.ainvoke(kwargs))
           return tool.invoke(kwargs)
       return sync_wrapper

    def _make_async_wrapper(self, tool):
       async def async_wrapper(**kwargs):
           # Directly call the async method
           return await tool.ainvoke(kwargs)
       return async_wrapper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup ensures that no matter how &lt;code&gt;LangGraph&lt;/code&gt; decides to call a tool, our wrapper will execute it correctly, either by calling its native method or by bridging the sync/async gap.&lt;/p&gt;

&lt;p&gt;Now, let’s build the graph itself. We’ll define a more advanced node for calling the LLM, which injects a system prompt to guide the model’s behavior. We will also integrate conversation memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  The LLM-Calling Node with a System Prompt
&lt;/h3&gt;

&lt;p&gt;Instead of a simple LLM call, we’ll create a dedicated function that dynamically adds a &lt;strong&gt;system message&lt;/strong&gt; to the conversation. This message instructs the LLM on how to behave, especially regarding tool usage, ensuring it uses your specialized MCP tools when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py

class LangGraphChatbot:
    # ... (previous code)

    def _tool_calling_llm(self, state: State):
        """Node function for calling LLM with a system prompt for tools."""
        messages = state["messages"]

        # Create a list of available tool names for the system message
        tool_names = [tool.name for tool in self.tools]

        # Add system message to explicitly instruct tool usage
        system_msg = (
            f"""You are an AI assistant with access to specialized tools.\n"
            f"When asked about specific resources, you MUST use the appropriate tool.\n"
            f"Available tools: {', '.join(tool_names)}"""
        )

        # Combine system message with conversation history
        messages_with_prompt = [("system", system_msg)] + messages

        # Invoke the LLM with the modified messages
        response = self.llm_with_tools.invoke(messages_with_prompt)
        return {"messages": [response]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the Graph with Memory
&lt;/h3&gt;

&lt;p&gt;With our new LLM-calling node, we can build the graph. The structure remains similar, but we’ll use our new &lt;code&gt;_tool_calling_llm&lt;/code&gt; function for the &lt;code&gt;llm&lt;/code&gt; node. Crucially, we’ll also add a &lt;code&gt;checkpointer&lt;/code&gt; during compilation to enable conversation memory.&lt;/p&gt;

&lt;p&gt;Now, let’s build the graph itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py

from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver

class LangGraphChatbot:
    # ... (previous code)

    def _build_graph(self):
        graph_builder = StateGraph(State)

        # Add our custom LLM node and the standard tool node
        graph_builder.add_node("llm", self._tool_calling_llm)
        graph_builder.add_node("tools", ToolNode(self.tools))

        graph_builder.add_edge(START, "llm")
        graph_builder.add_conditional_edges(
            "llm",
            tools_condition,
            {
                "tools": "tools",
                "__end__": END
            }
        )
        graph_builder.add_edge("tools", "llm")

        # Compile the graph with a memory checkpointer
        self.memory = MemorySaver()
        return graph_builder.compile(checkpointer=self.memory)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This updated graph now intelligently guides the LLM with a system prompt and preserves conversation history across turns, making the agent much more robust and stateful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Integrating Multiple LLM Providers (AWS Bedrock and Ollama)
&lt;/h2&gt;

&lt;p&gt;A powerful feature of this architecture is the ability to easily switch between different LLM providers. This allows you to use powerful cloud-based models from services like AWS Bedrock for production scenarios, while leveraging local models via Ollama for development, testing, and offline use. This flexibility is achieved by reading an environment variable to decide which LLM to initialize.&lt;/p&gt;

&lt;p&gt;Let’s add this logic to our &lt;code&gt;LangGraphChatbot&lt;/code&gt;’s &lt;code&gt;__init__&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chatbot.py
import os
from dotenv import load_dotenv
from langchain_aws import ChatBedrockConverse
from langchain_ollama import ChatOllama

class LangGraphChatbot:
    def __init__(self, tools_config_path: str):
        load_dotenv()
        # Determine which LLM to use from environment variables
        llm_provider = os.getenv("LLM_PROVIDER", "ollama").lower()
        if llm_provider == "bedrock":
             # Initialize AWS Bedrock LLM
             model_id = os.getenv("BEDROCK_MODEL_ID", "anthropic.claude-3-sonnet-v1:0")
             self.llm = ChatBedrockConverse(
                 model_id=model_id,
                 region_name=os.getenv("AWS_REGION"),
                 credentials_profile_name=os.getenv("AWS_CREDENTIALS_PROFILE")
             )
             print(f"🤖 Initialized AWS Bedrock with model: {model_id}")
        elif llm_provider == "ollama":
             # Initialize Ollama LLM for local models
             ollama_model = os.getenv("OLLAMA_MODEL", "llama3")
             self.llm = ChatOllama(
                 model=ollama_model,
                 base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
             )
             print(f"🤖 Initialized Ollama with model: {ollama_model}")
        else:
             raise ValueError(f"Unsupported LLM provider: {llm_provider}")
        self.tools = self._load_all_tools(tools_config_path)
        self.llm_with_tools = self.llm.bind_tools(self.tools)
        self.graph = self._build_graph()

     # … rest of the class
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, you can switch from a local &lt;code&gt;llama3&lt;/code&gt; model to a powerful Claude 3 Sonnet model on Bedrock by simply changing the &lt;code&gt;LLM_PROVIDER&lt;/code&gt; environment variable from &lt;code&gt;”ollama”&lt;/code&gt; to &lt;code&gt;”bedrock”&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Creating the UI with Streamlit
&lt;/h2&gt;

&lt;p&gt;Finally, let’s create a simple UI in &lt;code&gt;streamlit_app.py&lt;/code&gt; to interact with our agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# streamlit_app.py

import streamlit as st
from chatbot import LangGraphChatbot

st.title("My AI Agent")

# Initialize the chatbot
chatbot = LangGraphChatbot(tools_config_path="tools_config.json")

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("What would you like to do?"):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        response = chatbot.graph.invoke({"messages": [("user", prompt)]})
        assistant_response = response["messages"][-1].content
        st.markdown(assistant_response)

    st.session_state.messages.append({"role": "assistant", "content": assistant_response})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates a basic chat interface. When the user enters a prompt, it’s sent to our &lt;code&gt;LangGraph&lt;/code&gt; agent, and the response is displayed on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Deploying with Docker and a Start Script
&lt;/h2&gt;

&lt;p&gt;To streamline deployment, we’ll use Docker Compose to manage our application’s services, including the Streamlit UI, the LLM server, and multiple MCP servers. A powerful &lt;code&gt;start.sh&lt;/code&gt; script will serve as the main entry point for managing the entire stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Comprehensive &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;docker-compose.yml&lt;/code&gt; defines the Streamlit app and multiple MCP servers, each configured to communicate via &lt;code&gt;stdio&lt;/code&gt;. We use Docker Compose &lt;code&gt;profiles&lt;/code&gt; to selectively run only the services we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'

services:
  # --- Application UI and Local LLM --- 
  streamlit_app:
    build: .
    container_name: ai_agent_ui
    ports: ["8501:8501"]
    environment:
      - LLM_PROVIDER=ollama
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on: [ollama]
    volumes: [.:/app]
    networks: [mcp-network]
    profiles: ["app"]

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports: ["11434:11434"]
    volumes: [./ollama_data:/root/.ollama]
    networks: [mcp-network]
    profiles: ["app"]

  # --- Example MCP Tool Server (stdio communication) ---
  grafana-mcp:
    image: mcp/grafana:latest
    container_name: grafana-mcp
    environment:
      - GRAFANA_URL=https://grafana.example.com/
      - GRAFANA_API_KEY=${GRAFANA_API_KEY} # Loaded from .env file
    stdin_open: true  # Required for stdio MCP
    tty: true         # Required for stdio MCP
    profiles: ["mcp"]
    networks: [mcp-network]

networks:
  mcp-network:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The &lt;code&gt;start.sh&lt;/code&gt; Management Script
&lt;/h3&gt;

&lt;p&gt;Instead of running &lt;code&gt;docker-compose&lt;/code&gt; commands manually, we use a &lt;code&gt;start.sh&lt;/code&gt; script that provides a simple, high-level interface for managing the application. It handles environment checks, configuration validation, and service management.&lt;/p&gt;

&lt;p&gt;Make the script executable with &lt;code&gt;chmod +x start.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Commands:&lt;/strong&gt;&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;code&gt;./start.sh web&lt;/code&gt;: The primary command. It checks your environment, validates configs, and starts the Streamlit web UI.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./start.sh chat&lt;/code&gt;: Starts the interactive terminal-based chat client.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh mcp-start&lt;/code&gt;: Starts all MCP server containers defined in the &lt;code&gt;docker-compose.yml&lt;/code&gt; under the &lt;code&gt;mcp&lt;/code&gt; profile.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh mcp-stop&lt;/code&gt;: Stops all running MCP server containers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh mcp-status&lt;/code&gt;: Checks the status and health of the MCP servers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh mcp-test&lt;/code&gt;: Runs communication tests against the MCP servers to ensure they are responsive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh check&lt;/code&gt;: Checks system requirements, such as the availability of your configured LLM provider (Ollama or AWS Bedrock).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./start.sh validate&lt;/code&gt;: Runs a script to validate your &lt;code&gt;tools_config.json&lt;/code&gt; and other configuration files.
&lt;/li&gt;
&lt;/ul&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;


Scale Considerations
&lt;/h2&gt;


&lt;p&gt;Taking a LangGraph agent from a prototype to a production system requires careful consideration of scalability, reliability, and performance. Here are key areas to focus on:   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory and State Management&lt;/strong&gt;: For production, the default in-memory &lt;code&gt;MemorySaver&lt;/code&gt; is insufficient. You should switch to a persistent, scalable check-pointer backend like &lt;code&gt;langgraph.checkpoint.sqlite.SqliteSaver&lt;/code&gt; or a custom implementation using Redis or a PostgreSQL database. This ensures that conversation state is not lost between application restarts and can be shared across multiple instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Window Optimization&lt;/strong&gt;: LLMs have finite context windows. As conversations grow, you must implement strategies to prune the context sent to the model. This can be done by summarizing older messages, using a sliding window approach, or implementing more advanced techniques like vector-based retrieval of relevant past messages to keep the context concise and effective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency and Asynchronous Execution&lt;/strong&gt;: To handle multiple users simultaneously, your application must be asynchronous. LangGraph is designed for this, but you need to deploy it on an ASGI server like Uvicorn, potentially managed by Gunicorn with multiple worker processes. Ensure all your custom tools and I/O operations are non-blocking to maximize throughput. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Model&lt;/strong&gt;: While Streamlit is excellent for demos, production applications should typically run the LangGraph agent as a standalone web service (e.g., using FastAPI). This service exposes an API that any frontend client can interact with, allowing you to scale the agent logic independently of the user interface and place it behind a load balancer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability and Tracing&lt;/strong&gt;: Production systems require robust monitoring. Integrating a tool like LangSmith is highly recommended for tracing the execution of your graphs, debugging issues, and monitoring performance metrics like latency, cost, and token usage. Structured logging is also essential for tracking the agent’s behavior and diagnosing errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust Tool Handling&lt;/strong&gt;: Production-grade tools need to be resilient. Implement proper error handling, timeouts, and retry mechanisms (e.g., with exponential backoff) for any external API calls. This prevents a single failing tool from causing the entire agent to fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By following the core structure, you can build a powerful and extensible AI agent. The combination of &lt;code&gt;LangGraph&lt;/code&gt; for orchestration and MCP for tool integration provides a robust framework for creating agents that can interact with a wide variety of external systems. From here, you can add more tools, integrate different LLMs, and build more complex agent behaviors.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>langgraph</category>
      <category>ai</category>
    </item>
    <item>
      <title>Life of a Developer in the World of AI</title>
      <dc:creator>Amol Kavitkar</dc:creator>
      <pubDate>Sat, 14 Jun 2025 20:20:40 +0000</pubDate>
      <link>https://dev.to/amol_kavitkar/life-of-a-developer-in-the-world-of-ai-3i38</link>
      <guid>https://dev.to/amol_kavitkar/life-of-a-developer-in-the-world-of-ai-3i38</guid>
      <description>&lt;p&gt;The world of technology is evolving at an unprecedented pace, driven largely by advancements in Artificial Intelligence (AI). As this transformation continues, the role of developers is undergoing a fundamental shift. It's no longer just about writing code—it's about designing solutions, thinking holistically, and piecing together complex systems in a world where execution has become faster than ever. Let's explore how developers navigate this new reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design First, Execute Faster
&lt;/h2&gt;

&lt;p&gt;In the AI-driven world, execution has become exponentially faster due to automation, prebuilt libraries, and powerful tools. However, the real challenge lies in having a larger vision—one that considers scalability, long-term impact, and the big picture. The ability to design first and execute faster is the key to success, but it demands a nuanced approach.&lt;br&gt;
While smaller components of a product can be designed and executed quickly using a &lt;strong&gt;fail-fast approach&lt;/strong&gt;, this method works effectively only when it is part of a broader, well-defined strategy. Developers must focus on designing products with a broader vision and scale, breaking them down into smaller, actionable steps. Each step can then be executed rapidly and iteratively, using fail-fast principles to refine and improve.&lt;br&gt;
This mindset ensures that while developers move quickly on smaller pieces, they don't lose sight of the larger goal. A well-designed roadmap allows for agility in execution without compromising the overall vision. It's not just about moving fast—it's about moving smart and ensuring that every small win contributes to the bigger picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing Solutions: The Developer's Primary Skill
&lt;/h2&gt;

&lt;p&gt;In the AI era, solution design has emerged as the most critical skill for developers. It's not just about writing code anymore; it's about understanding the bigger picture. Developers must focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What to Design:&lt;/strong&gt; Identifying the core problem and defining clear, scalable solutions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to Design:&lt;/strong&gt; Choosing the right models, frameworks, and architectural patterns that align with the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connecting the Dots:&lt;/strong&gt; Piecing together disparate components—such as AI models, cloud services, and APIs—to create a cohesive and functional system.
This shift to solution-oriented thinking requires developers to possess not only technical expertise but also the ability to collaborate with stakeholders, understand business needs, and anticipate future challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Ever-Relevant Foundations: Data Structures and Algorithms
&lt;/h2&gt;

&lt;p&gt;While the tools and technologies around AI are constantly evolving, some fundamentals never go out of style. Data structures and algorithms remain as relevant as ever. They form the bedrock of problem-solving and efficiency, enabling developers to optimize systems and handle large-scale data processing—an essential skill in the AI-driven world.&lt;br&gt;
Even as AI automates certain tasks, understanding these core concepts allows developers to build better, faster, and more innovative solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of Coding in the AI Era
&lt;/h2&gt;

&lt;p&gt;Surprisingly, coding itself is no longer the centerpiece of a developer's skillset. With advancements in low-code and no-code platforms, pre-trained AI models, and comprehensive libraries, the need to write extensive code has diminished. However, knowing programming languages is still crucial. Why? Because understanding code enables developers to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debug and fine-tune automated systems.&lt;/li&gt;
&lt;li&gt;Customize AI models and frameworks to fit specific requirements.&lt;/li&gt;
&lt;li&gt;Communicate effectively with tools and systems.
Thus, while coding is not the primary focus, being fluent in programming languages remains an essential skill for developers in the AI world.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Myth of "Everything is MCP"
&lt;/h2&gt;

&lt;p&gt;The rise of AI and automation has brought a new model into focus—MCP (Model, Control, Protocol). While MCPs are powerful tools for abstraction and standardization, there's a growing misconception that everything can (or should) be converted into MCP. This is not the solution.&lt;br&gt;
Blindly migrating from everything microservices to everything MCP is not a sustainable approach. While MCPs work well for certain use cases, they are not a one-size-fits-all solution. &lt;strong&gt;Developers must adopt a hybrid understanding, recognizing the need for balance between microservices, APIs, and MCPs.&lt;/strong&gt;&lt;br&gt;
Instead of running away from one model to fully embrace another, the focus should be on understanding the specific requirements of a system and tailoring the architecture accordingly. A hybrid approach ensures flexibility, scalability, and efficiency, without forcing a rigid structure onto every problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolving as a Developer
&lt;/h2&gt;

&lt;p&gt;The life of a developer in the AI-driven world is about adaptability and evolution. It's about stepping back, thinking critically, and designing solutions that leverage the full potential of AI and other technologies.&lt;br&gt;
Execution is no longer the bottleneck; instead, having a larger vision, breaking it into actionable steps, and executing those steps with speed and agility is where the real value lies. At the same time, developers must avoid the trap of oversimplifying systems by forcing everything into a single model like MCP. Balance and understanding are key.&lt;br&gt;
In this fast-paced world, developers who master solution design, embrace the ever-relevant fundamentals, and approach architecture with flexibility and foresight will thrive. AI isn't replacing developers—it's empowering them to think bigger, innovate smarter, and build the future.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>developers</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Build and Run K3s on macOS with Multipass and k3d</title>
      <dc:creator>Amol Kavitkar</dc:creator>
      <pubDate>Thu, 08 May 2025 18:43:04 +0000</pubDate>
      <link>https://dev.to/amol_kavitkar/how-to-build-and-run-k3s-on-macos-with-multipass-and-k3d-334c</link>
      <guid>https://dev.to/amol_kavitkar/how-to-build-and-run-k3s-on-macos-with-multipass-and-k3d-334c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Step by step guide for Developer’s using Multipass and k3d&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;K3s is a lightweight Kubernetes distribution designed for resource-constrained environments, making it ideal for local development or edge computing. However, building K3s from source on macOS isn’t straightforward, since K3s requires a Linux environment to compile. In this guide, we’ll walk through how to build K3s from source using Canonical’s Multipass, transfer the resulting image to your Mac, and then run it using &lt;code&gt;k3d&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prerequisites:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before we begin, ensure that Homebrew is installed on your macOS system. You can install all required dependencies using Homebrew.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Install Multipass
&lt;/h2&gt;

&lt;p&gt;Multipass is a fast and easy way to spin up lightweight Linux virtual machines (VMs) on macOS. We’ll use it to create a Linux environment where K3s can be built. Install it via Homebrew:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install multipass&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify Installation:&lt;/strong&gt; After the installation is complete, you can verify it by checking the version&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multipass version&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn More :&lt;/strong&gt; For additional details about Multipass and alternative installation methods, visit the Multipass Installation Guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Install K3d
&lt;/h2&gt;

&lt;p&gt;Install k3d, a lightweight wrapper for running K3s clusters in Docker, using Homebrew:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install k3d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify Installation:&lt;/strong&gt; After the installation is complete, you can verify it by checking the version&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~ k3d version&lt;br&gt;
k3d version v5.8.3&lt;br&gt;
k3s version v1.31.5-k3s1 (default)&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🚀 Launch Multipass and Set Up the Build Environment
&lt;/h2&gt;

&lt;p&gt;Now, let’s create a Linux VM using Multipass to serve as the build environment for K3s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Launch a Multipass Instance:&lt;/strong&gt; Run the following command to create a Multipass VM named k3sServer with 2 CPUs, 3GB of memory, and 20GB of disk space.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multipass launch --name k3sServer --cpus 2 --memory 3G --disk 20G&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Login to the Multipass Instance:&lt;/strong&gt; Once the VM is up, log into it using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multipass shell k3sServer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’ll now be inside the Multipass shell, and You’ll be greeted with the prompt:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ubuntu@k3sServer:~$&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Required Tools Inside VM:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build K3s, you’ll need Docker and make installed inside the Multipass VM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Docker:&lt;/strong&gt; Run the following commands to install Docker:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt update&lt;br&gt;
sudo apt install docker.io&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Configuration (Optional):&lt;/strong&gt; If you encounter issues with docker build, ensure the Docker daemon is properly configured. You may need to add a DNS configuration as below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~ cat /etc/docker/daemon.json&lt;br&gt;
{&lt;br&gt;
  "dns": ["172.17.0.1", "8.8.8.8"]&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install make:&lt;/strong&gt; Install the make utility:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo apt install make&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Clone and Build K3s
&lt;/h2&gt;

&lt;p&gt;Now that the environment is ready, let’s proceed with building K3s.&lt;/p&gt;

&lt;p&gt;Clone the K3s Repository: Clone the official K3s GitHub repository&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone --depth 1 https://github.com/k3s-io/k3s.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigate to the Repository:&lt;/strong&gt; Go into the K3s directory&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd k3s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prepare the Build Environment:&lt;/strong&gt; Run the following commands to download dependencies and generate required files. Run these commands one by one if you want to check what each step is doing&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo mkdir -p build/data &amp;amp;&amp;amp; make download &amp;amp;&amp;amp; make generate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build K3s:&lt;/strong&gt; Finally, build K3s with the following command (The SKIP_VALIDATE=true flag skips some validation steps, making the build process faster.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo SKIP_VALIDATE=true make&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build Documentation:&lt;/strong&gt; Build instructions are available at the official repo:&lt;br&gt;
👉 &lt;a href="https://github.com/k3s-io/k3s/blob/master/BUILDING.md" rel="noopener noreferrer"&gt;https://github.com/k3s-io/k3s/blob/master/BUILDING.md&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  📦 Verify Docker Images
&lt;/h2&gt;

&lt;p&gt;Once the build is complete, you can verify the generated Docker images.&lt;br&gt;
&lt;strong&gt;List Docker Images:&lt;/strong&gt; Take note of the rancher/k3s image you just built&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo docker images
REPOSITORY                         TAG                          IMAGE ID       SIZE
rancher/k3s                        v1.33.0-k3s-c2efae3e-arm64   9ae40bc58195   227MB
k3s                                master                       622b36d40a23   1.25GB
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identify the K3s image (e.g., rancher/k3s:v1.33.0-k3s-c2efae3e-arm64) for the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔁 Transfer Image to macOS Host
&lt;/h2&gt;

&lt;p&gt;To use the built K3s image on your macOS host, transfer the Docker image from the Multipass instance to Mac&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save the Docker Image:&lt;/strong&gt; Inside the Multipass shell, save the K3s Docker image as a compressed .tar.gz file&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo docker save rancher/k3s:v1.33.0-k3s-c2efae3e-arm64 | gzip &amp;gt; rk3s.tar.gz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exit the Multipass Shell:&lt;/strong&gt; Exit the Multipass instance or user can use another terminal for next step&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transfer the Image to macOS:&lt;/strong&gt; Use the multipass transfer command to copy the tar file to your macOS host&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multipass transfer k3sServer:rk3s.tar.gz ~/Downloads&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load the Image into Docker:&lt;/strong&gt; On your macOS host, load the image into Docker&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker load -i ~/Downloads/rk3s.tar.gz&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 Create a K3s Cluster with k3d
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create Cluster:&lt;/strong&gt; Now create a Kubernetes cluster using the built image you just loaded&lt;/p&gt;

&lt;p&gt;&lt;code&gt;k3d cluster create --image rancher/k3s:v1.33.0-k3s-c2efae3e-arm64&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you run into any issues, use the --verbose or --trace flags to get more details during cluster creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify Cluster:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~ k3d cluster list
NAME          SERVERS   AGENTS   LOADBALANCER
k3s-default   1/1       0/0      true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check Pods:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
~ kubectl get pods -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS   AGE
kube-system   coredns-697968c856-gbvc8                  1/1     Running     0          25h
kube-system   helm-install-traefik-crd-vlrvr            0/1     Completed   0          25h
kube-system   helm-install-traefik-j5tm8                0/1     Completed   1          25h
kube-system   local-path-provisioner-774c6665dc-pt44v   1/1     Running     0          25h
kube-system   metrics-server-6f4c6675d5-6j47v           1/1     Running     0          25h
kube-system   svclb-traefik-a74de106-kzddc              2/2     Running     0          25h
kube-system   traefik-c98fdf6fb-gc2f5                   1/1     Running     0          25h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✅ Conclusion
&lt;/h2&gt;

&lt;p&gt;You’ve just built K3s from source in a Linux VM, transferred the custom Docker image to your macOS host, and used it to spin up a Kubernetes cluster via k3d. This setup gives you full control over the version and build of K3s you’re using — great for testing new features, debugging, or contributing upstream.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;With K3s now running in a Kubernetes cluster on your macOS host, you are ready to:
• Experiment with lightweight Kubernetes for development or testing environments.
• Deploy containerized workloads and explore Kubernetes features.
• Build further expertise with Kubernetes, multi-cloud setups, or edge computing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you encounter any issues, refer to the official K3s Build Guide or the k3d Documentation.&lt;/p&gt;

</description>
      <category>k3s</category>
      <category>kubernetes</category>
      <category>multipass</category>
      <category>k3d</category>
    </item>
  </channel>
</rss>
