Adding Real-Time Weather Search to my City Explorer with MCP



This content originally appeared on DEV Community and was authored by Veliswa_Boya 🇿🇦

Building on my previous Strands agent – now with Model Context Protocol integration

In my previous post, I talked about how I built a city explorer app using the Strands Agents SDK. I loved how the agent was great at providing general information about cities, but I wanted to take it further. What if my city explorer could access real-time data such as current weather conditions?

That’s where Model Context Protocol (MCP) comes in – an open standard that lets AI agents connect to external tools and data sources. Today, I’ll show you how I enhanced my city explorer with MCP to add live weather search capabilities.

What is MCP?

Model Context Protocol is an open protocol that standardizes how applications provide context to Large Language Models (LLMs). Think of it as a universal adapter that lets your AI agent talk to databases, APIs, file systems, and other external services through a common interface.

Instead of hardcoding API calls into your agent, MCP lets you:

  • Connect to multiple data sources through standardized “servers”
  • Add new capabilities without changing your core application
  • Share tools across different AI applications
  • Keep your agent code clean and modular

The Challenge: From Static Facts to Dynamic Data

My original city explorer was limited to the knowledge in the LLM’s training data. When someone asked “What’s the weather like in Cape Town?”, it could only provide general climate information, not current conditions.

I wanted to bridge this gap by giving my agent access to real-time weather data while maintaining the clean architecture of the Strands framework. To do this, I installed a Brave Search MCP Server to integrate the Brave Search API, for both web and local search capabilities.

Building the MCP Integration

Step 1: MCP Configuration

First, I created an MCP configuration file:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"],
      "env": {}
    },
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": {
        "BRAVE_API_KEY": "${BRAVE_API_KEY}"
      }
    }
  }
}

To avoid hardcoding my secrets, in this case my brave-api-key, I opted for the configuration that uses ${BRAVE_API_KEY} as a placeholder that gets resolved at runtime from environment variables.

Step 2: Environment Variable Management

Here’s my setup script to handle environment configuration securely:

#!/bin/bash
# setup_env.sh - Environment setup for MCP integration

echo "Setting up MCP City Explorer Environment"

# Create .env template if it doesn't exist
if [ ! -f ".env" ]; then
    cat > .env << 'EOF'
# MCP City Explorer Environment Variables
BRAVE_API_KEY=brave-api-key-here
EOF
    echo "Created .env file template"
    echo "Please edit .env file and add your actual API keys"
fi

# Validate environment variables
check_env_var() {
    local var_name=$1
    local var_value=${!var_name}

    if [ -z "$var_value" ]; then
        echo "$var_name: Not set"
        return 1
    else
        echo "$var_name: Set (${var_value:0:10}...)"
        return 0
    fi
}

source .env
check_env_var "BRAVE_API_KEY"

Step 3: Enhanced MCP Integration Class

The core MCP integration class now handles environment variable resolution and validation:

import json
import os
import re
from typing import Dict, Any

class MCPIntegration:
    """MCP integration with secure environment variable handling"""

    def __init__(self, config_path: str = "mcp_config.json"):
        self.config_path = config_path
        self.servers = {}
        self.load_config()

    def load_config(self):
        """Load MCP server configuration and resolve environment variables"""
        try:
            with open(self.config_path, 'r') as f:
                config = json.load(f)
                # Resolve environment variables in the config
                resolved_config = self._resolve_env_variables(config)
                self.servers = resolved_config.get('mcpServers', {})

                # Validate that required environment variables are set
                self._validate_env_variables()

        except FileNotFoundError:
            print(f"MCP config file {self.config_path} not found. MCP features disabled.")
            self.servers = {}
        except ValueError as e:
            print(f"Configuration error: {e}")
            self.servers = {}

    def _resolve_env_variables(self, config):
        """Replace ${VAR_NAME} placeholders with actual environment variables"""
        config_str = json.dumps(config)

        def replace_env_var(match):
            var_name = match.group(1)
            env_value = os.getenv(var_name)
            if env_value is None:
                raise ValueError(f"Environment variable {var_name} is not set")
            return env_value

        # Replace ${VAR_NAME} with actual environment variable values
        resolved_str = re.sub(r'\$\{([^}]+)\}', replace_env_var, config_str)
        return json.loads(resolved_str)

    def _validate_env_variables(self):
        """Validate that required environment variables are set"""
        if "brave-search" in self.servers:
            brave_key = os.getenv('BRAVE_API_KEY')
            if not brave_key:
                print("Warning: BRAVE_API_KEY environment variable not set. Web search will be disabled.")
                # Remove brave-search server if API key is not available
                del self.servers["brave-search"]
            elif brave_key.startswith("${") or brave_key == "brave-api-key-here":
                print("Warning: BRAVE_API_KEY appears to be a placeholder. Insert your actual brave-api-key or web search may not work.")

Step 4: Secure Strands Agent Integration

I then integrated this with my Strands agent, ensuring secure API key handling:

from strands import Agent
from strands.models import BedrockModel

# Initialize MCP integration with environment variable support
mcp = MCPIntegration()

# Enhanced system prompt that includes MCP capabilities
available_tools = list(mcp.get_available_tools().keys())
enhanced_system_prompt = f"""You are a knowledgeable city facts assistant with access to additional tools through MCP. 

You can:
- Provide concise, interesting facts about cities
- Access local files and directories when needed
- Search the web for up-to-date information (if configured)

Available MCP tools: {available_tools}

Keep responses brief and engaging. If you need current information, mention that you're using external tools."""

# Create the enhanced agent
city_explorer_agent = Agent(
    model=bedrock_model,
    system_prompt=enhanced_system_prompt
)

# Check environment setup on startup
brave_key = os.getenv('BRAVE_API_KEY')
if not brave_key:
    print("\n Note: Set BRAVE_API_KEY environment variable to enable web search functionality")
    print("   Example: export BRAVE_API_KEY="brave-api-key-here")

Step 5: Secure Tool Execution

The tool execution now includes proper API key validation:

def _handle_search_tool(self, tool_name: str, parameters: Dict[str, Any]) -> str:
    """Handle web search operations with secure API key handling"""
    query = parameters.get('query', '')
    brave_key = os.getenv('BRAVE_API_KEY')

    if not brave_key or brave_key.startswith("${"):
        return f"Web search for '{query}' requires BRAVE_API_KEY environment variable to be set with a valid API key"

    # In a production implementation, this would make an actual API call to Brave Search
    return f"Web search for '{query}' would be performed here using Brave API (API key configured)"

def process_user_input_with_mcp(user_input: str) -> str:
    """Process user input and handle any MCP tool requests"""
    response = city_explorer_agent(user_input)

    additional_info = ""

    # Detect weather queries and trigger search
    if "search for" in user_input.lower() and "brave-search" in mcp.servers:
        search_query = user_input.lower().replace("search for", "").strip()
        search_result = mcp.execute_tool("brave-search___search", {"query": search_query})
        additional_info += f"\n\n[MCP Search Result]: {search_result}"

    return response + additional_info

Testing the Enhanced City Explorer

With secure MCP integration complete, my city explorer could now handle queries such as the one seen in my test below:

Setup:

# Set environment variable securely
export BRAVE_API_KEY="brave-api-key-here"

# Run the application
python3 testing_for_kbs_mcp_env.py

User: “search for Cape Town weather”

Agent Response:

Cape Town is a beautiful coastal city in South Africa, known for Table Mountain, its wine regions, and stunning beaches.

[MCP Search Result]: 
Current weather in Cape Town: 18°C, partly cloudy
Today: High 22°C, Low 15°C
Tomorrow: High 20°C, Low 14°C, chance of rain
Cape Town has a Mediterranean climate with mild, wet winters and warm, dry summers.

The agent seamlessly combined its built-in knowledge with real-time data from the web, all while keeping API keys secure!

Challenges and Lessons Learned

The Good

  • Clean Architecture: MCP kept my agent code focused on conversation logic
  • Security: Environment variables eliminated hardcoded secrets
  • Easy Extension: Adding new tools was just a matter of configuration
  • Debugging: Clear separation made it easy to test components independently

The Tricky Parts

  • Environment Management: Ensuring all environments have the right variables set
  • Tool Detection: Deciding when to use which MCP tool required careful prompt engineering
  • Error Handling: Network calls to external services need robust error handling
  • Performance: Each MCP call adds latency – batching and caching become important

What’s Next?

In the future, I could add these possible features using this secure MCP integration:

  • Database Integration: Connect to city databases for population, economic data
  • Image Analysis: Add computer vision for landmark recognition
  • Translation Services: Multi-language city information
  • Social Media: Real-time sentiment about cities from social platforms

..and more 🙂

Conclusion

Adding MCP to my Strands city explorer transformed it from a static knowledge base into a dynamic, real-time information system. By implementing proper security practices with environment variables, the solution is now production-ready and follows AWS best practices.

The combination of MCP’s modular architecture and secure configuration management means I can continue adding new capabilities without compromising security or rebuilding the core agent.

MCP represents the future of AI agent development – where agents aren’t limited to their training data but can reach out to the world for fresh, relevant information. Combined with the power of Strands for agent orchestration and proper security practices, it’s a compelling foundation for building truly useful AI applications.

What external tools would you connect to your AI agent? And how do you handle API key security in your projects? Let me know in the comments!

Want to learn more about building secure AI agents? Check out my previous posts on Strands. Learn more about the Brave Search API. Follow me for more AI development content.


This content originally appeared on DEV Community and was authored by Veliswa_Boya 🇿🇦