<?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: Mrutyunjay</title>
    <description>The latest articles on DEV Community by Mrutyunjay (@muswain).</description>
    <link>https://dev.to/muswain</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F696374%2Fbd437465-f15c-43e4-8ae2-2e104c3d8db2.jpeg</url>
      <title>DEV Community: Mrutyunjay</title>
      <link>https://dev.to/muswain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muswain"/>
    <language>en</language>
    <item>
      <title>Building a Local-First Agent Platform with Strands, Ollama, and an MCP Gateway</title>
      <dc:creator>Mrutyunjay</dc:creator>
      <pubDate>Mon, 14 Sep 2026 08:22:36 +0000</pubDate>
      <link>https://dev.to/muswain/building-a-local-first-agent-platform-with-strands-ollama-and-an-mcp-gateway-2gpo</link>
      <guid>https://dev.to/muswain/building-a-local-first-agent-platform-with-strands-ollama-and-an-mcp-gateway-2gpo</guid>
      <description>&lt;p&gt;Most agent demos start with one model, one prompt, and a handful of functions in the same process. That is a useful place to start, but it becomes difficult to evolve once the application needs multiple domains, multiple agent behaviors, and tools that should be owned independently.&lt;/p&gt;

&lt;p&gt;I wanted to build something closer to a small platform than a single chatbot: a local-first application where an agent can use travel, finance, and entertainment capabilities without knowing how those capabilities are implemented or where they run.&lt;/p&gt;

&lt;p&gt;The result is a Python application built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strands Agents&lt;/strong&gt; for agent orchestration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; for a local language model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; for the tool contract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastMCP&lt;/strong&gt; for the gateway and domain servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; for the backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamlit&lt;/strong&gt; for a lightweight chat interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mise&lt;/strong&gt; and &lt;strong&gt;uv&lt;/strong&gt; for reproducible local setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important design choice is the MCP gateway. The agent talks to one MCP endpoint, while the gateway composes several focused domain servers behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A first version of an agent application often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User -&amp;gt; Chat UI -&amp;gt; Agent -&amp;gt; Every tool in the application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That shape has a few problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The agent client becomes coupled to every tool server.&lt;/strong&gt; Adding a new domain means changing the client configuration or orchestration code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool ownership becomes unclear.&lt;/strong&gt; Travel, finance, and entertainment concerns end up mixed together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent deployment is harder.&lt;/strong&gt; A failure or restart in one domain can affect the whole tool surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The system is difficult to inspect.&lt;/strong&gt; There is no single place to ask which downstream services are healthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent behavior and domain capabilities get tangled.&lt;/strong&gt; Changing an agent's tone or workflow should not require changing the tool implementation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The underlying problem was not simply "how do I call an LLM?" It was how to create clean boundaries around an agent that needs to use capabilities from several domains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Goal
&lt;/h2&gt;

&lt;p&gt;I set three constraints for the design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model should run locally during development.&lt;/li&gt;
&lt;li&gt;The agent should have one stable tool endpoint.&lt;/li&gt;
&lt;li&gt;Each domain should expose a small, independently owned MCP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led to this architecture:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    UI[Streamlit UI] --&amp;gt; API[FastAPI backend]
    API --&amp;gt; A[Strands agent]
    A --&amp;gt; G[MCP gateway]
    G --&amp;gt; T[Travel MCP server]
    G --&amp;gt; F[Finance MCP server]
    G --&amp;gt; E[Entertainment MCP server]
    T --&amp;gt; TT[Travel tools]
    F --&amp;gt; FT[Finance tools]
    E --&amp;gt; ET[Entertainment tools]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The UI and backend have a straightforward responsibility: accept a conversation and return an answer. The agent decides whether tools are needed. The gateway hides the topology of the domain servers from the agent runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A registry of agent behaviors
&lt;/h3&gt;

&lt;p&gt;Agent definitions live as flat modules under &lt;code&gt;src/agents/&lt;/code&gt;. Each definition describes an agent identity, system prompt, and skills. The registry is the single source of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_AGENT_REGISTRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentDefinition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;DEFAULT_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RESEARCH_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RESEARCH_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SUPPORT_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SUPPORT_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TRAVEL_ITINERARY_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TRAVEL_ITINERARY_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FINANCE_TRACKER_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FINANCE_TRACKER_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ENTERTAINMENT_SCOUT_AGENT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ENTERTAINMENT_SCOUT_AGENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separates &lt;strong&gt;how the agent should behave&lt;/strong&gt; from &lt;strong&gt;which tools exist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The backend exposes the available profiles through &lt;code&gt;GET /api/agents&lt;/code&gt;, and the chat endpoint accepts an &lt;code&gt;agent_id&lt;/code&gt;. That makes it possible to add a research-focused agent or a travel-planning agent without duplicating the MCP connection logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. One MCP client surface for the agent
&lt;/h3&gt;

&lt;p&gt;The orchestrator connects to the configured MCP endpoint, discovers its tools, and gives the discovered tool list to Strands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;create_mcp_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator does not need to know whether a tool belongs to travel or finance. It only needs the MCP contract.&lt;/p&gt;

&lt;p&gt;The MCP URL is normalized before connecting so streamable HTTP endpoints do not introduce avoidable redirect failures. This is a small detail, but transport details like this matter when local services are composed together.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A config-driven MCP gateway
&lt;/h3&gt;

&lt;p&gt;The gateway loads its downstream routes from &lt;code&gt;src/mcp/gateway/routes.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"travel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8001/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envVar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRAVEL_MCP_SERVER_URL"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At startup, each route is mounted as a proxy namespace. A new downstream server can therefore be introduced by adding route configuration and implementing its own MCP server, without rewriting the agent client.&lt;/p&gt;

&lt;p&gt;The route loader also supports environment-variable overrides and a complete configuration-path override through &lt;code&gt;MCP_GATEWAY_ROUTES_CONFIG&lt;/code&gt;. That keeps local defaults convenient while leaving room for another environment to provide different service locations.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Focused domain servers
&lt;/h3&gt;

&lt;p&gt;Each domain server owns only its own tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Travel:&lt;/strong&gt; geocoding, weather, place search, route distance, and Wikipedia summaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance:&lt;/strong&gt; foreign exchange rates, historical rates, stock quotes, and crypto markets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entertainment:&lt;/strong&gt; local events and movie search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gateway composes these servers, but it does not absorb their domain logic. This keeps the boundaries useful: domain changes stay in the domain server, while gateway changes stay focused on composition and operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Health visibility through the gateway
&lt;/h3&gt;

&lt;p&gt;The gateway exposes a &lt;code&gt;gateway_health&lt;/code&gt; tool and the backend exposes &lt;code&gt;GET /api/mcp-health&lt;/code&gt;. The health response includes the overall gateway status and the status of each downstream server.&lt;/p&gt;

&lt;p&gt;That gives the application an operational view like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gateway"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MCP Gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overall_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downstream"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"travel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"finance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"entertainment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tool gateway should not be treated as a black box. If the agent cannot use a tool, the first question should be whether the downstream server is available and advertising the expected tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Request Through the System
&lt;/h2&gt;

&lt;p&gt;A chat request travels through the application like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant U as User
    participant UI as Streamlit UI
    participant API as FastAPI
    participant A as Agent orchestrator
    participant G as MCP gateway
    participant D as Domain MCP server

    U-&amp;gt;&amp;gt;UI: Ask a question
    UI-&amp;gt;&amp;gt;API: POST /api/chat
    API-&amp;gt;&amp;gt;A: Build conversation prompt
    A-&amp;gt;&amp;gt;G: Discover and call tools
    G-&amp;gt;&amp;gt;D: Proxy the domain tool call
    D--&amp;gt;&amp;gt;G: Return structured result
    G--&amp;gt;&amp;gt;A: Return tool result
    A--&amp;gt;&amp;gt;API: Generate final answer
    API--&amp;gt;&amp;gt;UI: Return response JSON&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For example, a question such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the weather in Sydney, and how far is it from the airport?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can be handled by the travel tool group without the UI knowing which server provides geocoding, weather, or route calculations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Keep the Model Local?
&lt;/h2&gt;

&lt;p&gt;The project uses Ollama during development, with the model and host configured through environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OLLAMA_MODEL=qwen3.5:4b
OLLAMA_HOST=http://127.0.0.1:11434
MCP_SERVER_URL=http://127.0.0.1:8000/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local model makes experimentation cheaper and keeps the basic development loop independent of a hosted inference provider. It also makes the architecture easier to test: the agent, gateway, and domain servers can be run on one machine with explicit local boundaries.&lt;/p&gt;

&lt;p&gt;This does not mean a local model is always the right production choice. The orchestration layer is intentionally separate from the model implementation, so the model can change without changing the MCP architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It Locally
&lt;/h2&gt;

&lt;p&gt;The project uses &lt;code&gt;mise&lt;/code&gt; to manage the toolchain and &lt;code&gt;uv&lt;/code&gt; for dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mise &lt;span class="nb"&gt;install
&lt;/span&gt;mise run &lt;span class="nb"&gt;sync
cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the domain servers first, then the integrated API and UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mise run mcp-travel
mise run mcp-finance
mise run mcp-entertainment
mise run api
mise run ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main endpoints are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streamlit UI: the local Streamlit URL printed by &lt;code&gt;mise run ui&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chat API: &lt;code&gt;POST /api/chat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Agent catalog: &lt;code&gt;GET /api/agents&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;API health: &lt;code&gt;GET /api/health&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP health: &lt;code&gt;GET /api/mcp-health&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP gateway: &lt;code&gt;/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository also includes tests for conversation prompt construction, agent skill composition, and invalid agent selection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Tradeoffs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why a gateway instead of connecting to every server directly?
&lt;/h3&gt;

&lt;p&gt;A direct multi-client design can be simpler at very small scale. The gateway becomes valuable when the number of domain servers grows or when the agent runtime should have one stable connection point.&lt;/p&gt;

&lt;p&gt;The tradeoff is an additional network hop and another component to operate. In this project, the gateway earns its place by providing composition, namespaces, route configuration, and downstream health checks in one location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why flat agent modules instead of a large class hierarchy?
&lt;/h3&gt;

&lt;p&gt;The agent profiles are mostly configuration and prompt behavior. Flat modules make each profile easy to locate and review. The registry provides explicit discovery without adding inheritance or lifecycle complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Streamlit for the UI?
&lt;/h3&gt;

&lt;p&gt;The goal was to validate the architecture and interaction loop, not to build a full product frontend. Streamlit provides a usable chat surface quickly while keeping the backend contract visible and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The most useful lesson was that agent architecture is largely about boundaries.&lt;/p&gt;

&lt;p&gt;The model is only one part of the system. The more durable decisions were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;giving agents a stable capability surface&lt;/li&gt;
&lt;li&gt;separating agent behavior from domain tools&lt;/li&gt;
&lt;li&gt;keeping each MCP server focused&lt;/li&gt;
&lt;li&gt;making service locations configurable&lt;/li&gt;
&lt;li&gt;exposing health information where composition happens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP is useful here not because it makes every tool intelligent, but because it gives tools a common interface that the agent runtime can discover and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Build Next
&lt;/h2&gt;

&lt;p&gt;The current implementation is a foundation. The next improvements would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;streaming agent responses through the API and UI&lt;/li&gt;
&lt;li&gt;richer tool metadata and request tracing&lt;/li&gt;
&lt;li&gt;authentication and authorization at the gateway&lt;/li&gt;
&lt;li&gt;contract tests for every downstream MCP server&lt;/li&gt;
&lt;li&gt;retries and timeouts per route&lt;/li&gt;
&lt;li&gt;persistent conversation storage&lt;/li&gt;
&lt;li&gt;production model adapters alongside the local Ollama adapter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core shape would stay the same: specialized agents, focused domain servers, and one observable MCP gateway between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The project started as a question about building a useful local agent. It became a question about how to let an agent grow without turning the entire application into one tightly coupled tool registry.&lt;/p&gt;

&lt;p&gt;The answer was to put the boundary in the right place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User experience -&amp;gt; API -&amp;gt; Agent -&amp;gt; MCP gateway -&amp;gt; Domain servers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure keeps the system understandable today and gives it a path to evolve tomorrow.&lt;/p&gt;

&lt;p&gt;The complete project is available on &lt;a href="https://github.com/muswain/agentic-ai" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, including the setup commands, route configuration, agent registry, MCP servers, and tests.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>strands</category>
      <category>python</category>
    </item>
  </channel>
</rss>
