Skip to main content
Glama
jui-hung-yuan

Smart Home MCP Server

Smart Home MCP Lab

A personal learning exercise for exploring MCP (Model Context Protocol) and agent development. Uses smart home control (TAPO L530E light bulbs) as a hands-on example for building and testing MCP servers and local agents.

Control Paths

Path

Status

Description

Local MCP

✅ Done

FastMCP server as Claude Desktop subprocess, LAN control

Remote MCP

✅ Done

AgentCore Gateway + Cognito + Lambda + IoT Core

Local Agent

✅ Done

OpenClaw-inspired agent loop with Markdown memory, hybrid search, and device skills

Related MCP server: IntelliGlow

Features

MCP Paths

  • Turn lights on/off, set brightness, get bulb status (on/off, brightness, color temp)

  • Real bulb control via tapo library over local network

  • Mock fallback — automatically uses a mock when credentials are missing or the bulb is unreachable

  • Persistent state — bulb state survives server restarts

  • DynamoDB state logging — fire-and-forget, never blocks MCP tools

  • AWS IoT Core integration — MQTT bridge with Device Shadow for state sync

  • AgentCore Gateway — remote MCP server with Cognito OAuth for Claude web app

  • Multi-device support — single bridge manages multiple devices via DeviceRegistry

  • Device-agnostic architectureBaseDevice interface makes adding new device types straightforward

Local Agent

  • Interactive CLI and Slack bot — two front-ends sharing the same agent loop, memory, and skills

  • Slack Socket Mode — per-thread sessions, @mention in channels, direct messages; idle sessions auto-evict after 30 min with memory flush

  • Multi-provider LLM support — swap between Anthropic (Claude) and Google Gemini by passing --model; provider auto-detected from the model name prefix

  • Persistent memory, no cloud dependency — Markdown files + SQLite index, runs entirely on-device

  • Hybrid memory search — BM25 (FTS5) + vector embeddings (ollama) merged via Reciprocal Rank Fusion

  • Pluggable skills — drop a SKILL.md into skills/<name>/ — no Python scripts, no boilerplate, zero changes to the loop

  • Progressive skill disclosure — only a skill index (name + description) is in the system prompt at startup; full docs load on first use via describe_skill, then stay injected for the session

  • Direct command executionrun_command tool lets the model run any shell command and parse JSON output; skills are pure documentation that describe which commands to run

  • Color temperature controlset_color_temp (2500–6500 K) via the agent skill

  • Heartbeat schedulerHeartbeatScheduler fires time-based automations from SCHEDULE.md; tasks store a shell command (cmd) and run as subprocesses; Claude manages tasks via schedule_task (add/remove/list), changes persist across restarts

Project Structure

src/smarthome/
├── devices/              # Shared device layer (all paths use this)
│   ├── base.py           # BaseDevice ABC: execute(), apply_desired_state(), get_shadow_state()
│   ├── device_registry.py # Manages multiple devices by ID
│   ├── tapo_bulb.py      # TapoBulb (real hardware) + MockTapoBulb (testing/fallback)
│   └── bulb_cli.py       # CLI entry point for agent skills (outputs JSON, reads TAPO_MOCK)
├── aws_mcp/              # AWS path: Local MCP server + Lambda + IoT bridge
│   ├── bridge/           # IoT Core MQTT bridge (config, iot_bridge, shadow_manager)
│   ├── cloud/            # Lambda-side IoT helpers (iot_commands)
│   ├── logging/          # DynamoDB state logger
│   ├── mcp_servers/      # FastMCP server (light_server.py)
│   └── lambda_handler.py # AgentCore Gateway Lambda entry point
└── agent/                # Local-first agent loop (CLI + Slack)
    ├── __main__.py       # Entry: `python -m smarthome.agent [--mock|--slack]`
    ├── config.py         # AgentConfig: paths, model, mock flag
    ├── loop.py           # AgentLoop: provider-agnostic tool-use loop + 6 built-in tools
    ├── scheduler.py      # HeartbeatScheduler: fires SCHEDULE.md cmd tasks as subprocesses
    ├── skill_loader.py   # Discovers skills/*/SKILL.md, builds system prompt index
    ├── slack_adapter.py  # Slack channel adapter (Socket Mode, per-thread sessions)
    ├── providers/        # LLM provider abstraction
    │   ├── types.py      # NeutralTool, ToolCall, ToolResult, ProviderResponse
    │   ├── base.py       # Abstract LLMProvider
    │   ├── anthropic.py  # AnthropicProvider
    │   └── google.py     # GoogleProvider (Gemini)
    ├── memory/
    │   ├── manager.py    # MemoryManager: search, write, sync, session context
    │   ├── schema.py     # SQLite schema: files, chunks, FTS5, vec, device_events
    │   ├── chunker.py    # Markdown → overlapping chunks (~400 tokens)
    │   └── embedder.py   # OllamaEmbedder: async HTTP → ollama /api/embed
    └── skills/
        ├── light-control/
        │   └── SKILL.md  # Skill docs: commands to run via bulb_cli.py
        └── trading-journal/
            └── SKILL.md  # Skill docs: analytics pipeline commands

scripts/aws/              # AWS provisioning and operation scripts
docs/                     # Setup guides and architecture notes
tests/                    # Unit tests (200 tests, all passing)

How It Works

Local MCP (Claude Desktop)

Claude Desktop runs the FastMCP server as a local subprocess:

Claude Desktop → FastMCP server (subprocess) → TapoBulb / MockTapoBulb

Tools exposed: turn_on, turn_off, set_brightness(level), get_status

On first tool call the server tries to connect to a real bulb using credentials from ~/.smarthome/.env. Falls back to mock automatically if credentials are missing or the bulb is unreachable.

Remote MCP (Claude Web App)

Claude Web App
  → AgentCore Gateway (Cognito JWT auth)
  → Lambda (smarthome-gateway-handler)
  → IoT Core MQTT
  → IoT Bridge (local network)
  → TapoBulb

See docs/claude-web-oauth.md for the OAuth flow details and docs/mcp-setup.md for full provisioning steps.

Local Agent

An OpenClaw-inspired agent loop that runs entirely locally — no cloud dependency. Two front-ends share the same AgentLoop, memory, and skills:

CLI input   →  AgentLoop  →  LLMProvider (Anthropic or Google Gemini)
Slack input ↗     ├── memory/  ~/.smarthome/memory/ — MEMORY.md, USER.md, SOUL.md, daily logs
                  └── skills/  light-control, trading-journal — SKILL.md only, no scripts

Front-ends:

  • CLI — interactive REPL, one session per process

  • Slack (--slack) — Socket Mode bot; one session per (channel, thread), responds to @mention in channels and all messages in DMs; idle sessions auto-evict after 30 min with memory flush

6 built-in tools the model can call:

  1. run_command(cmd) — run any shell command; parses JSON stdout; lifts Slack blocks

  2. describe_skill(skill_name) — loads full skill docs once; injected into system prompt for the session

  3. memory_search(query) — hybrid BM25 + vector search (Reciprocal Rank Fusion)

  4. memory_write(path, content, mode) — persists to Markdown files

  5. schedule_task(action, name, hour, minute, cmd) — add/remove/list scheduled automations in SCHEDULE.md

Provider abstraction: tools and conversation history are stored in a neutral format. AnthropicProvider and GoogleProvider each serialize to their own wire format on every call. Provider-specific data that must survive conversation round-trips (e.g. Gemini's thought signatures when thinking is enabled) is carried opaquely in ProviderResponse.assistant_replay.

Memory is stored in ~/.smarthome/memory/ as Markdown files (MEMORY.md, USER.md, SOUL.md, daily logs), indexed in SQLite with FTS5 and optional sqlite-vec embeddings. Embeddings use ollama; BM25-only fallback if unavailable.

Adding a skill: drop a folder under skills/, write SKILL.md with frontmatter (name, description) and shell command examples. No Python scripts, no boilerplate. Zero changes to loop.py.

Device Layer

All devices implement BaseDevice:

  • execute(action, parameters) — dispatch any action (turn_on, set_brightness, …)

  • apply_desired_state(desired) — apply state from IoT Shadow delta

  • get_shadow_state() — report current state to shadow

TapoBulb connects to real hardware. MockTapoBulb simulates a bulb in memory, optionally persisting state to ~/.smarthome/tapo_bulb_state.json.

Setup

See docs/mcp-setup.md for full step-by-step instructions covering both paths.

Quick start — Local MCP

  1. Create ~/.smarthome/.env with bulb credentials (or skip — mock mode works without it):

    TAPO_USERNAME=your_tapo_email
    TAPO_PASSWORD=your_tapo_password
    TAPO_IP_ADDRESS=192.168.x.x
  2. Add to Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):

    {
      "mcpServers": {
        "smarthome": {
          "command": "uv",
          "args": ["run", "--directory", "/path/to/smarthome",
                   "fastmcp", "run",
                   "src/smarthome/aws_mcp/mcp_servers/light_server.py"]
        }
      }
    }
  3. Restart Claude Desktop.

Quick start — Local Agent

  1. Add your API key(s) to ~/.smarthome/.env:

    mkdir -p ~/.smarthome
    
    # Anthropic (default model: claude-sonnet-4-6)
    echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.smarthome/.env
    
    # Google Gemini (optional — use with --model gemini-*)
    echo 'GEMINI_API_KEY=AIza...' >> ~/.smarthome/.env
  2. Seed memory files (optional but recommended):

    mkdir -p ~/.smarthome/memory
    echo "# Memory" > ~/.smarthome/memory/MEMORY.md
    echo "# User Preferences" > ~/.smarthome/memory/USER.md
  3. Run with mock bulb (no hardware needed):

    uv run python -m smarthome.agent --mock                               # Claude (default)
    uv run python -m smarthome.agent --mock --model gemini-3.1-flash-lite # Google Gemini
  4. Run with real bulb — add TAPO_USERNAME, TAPO_PASSWORD, TAPO_IP_ADDRESS to ~/.smarthome/.env, then:

    uv run python -m smarthome.agent
    uv run python -m smarthome.agent --model gemini-3.1-flash-lite
  5. Run as a Slack bot — add SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SLACK_SIGNING_SECRET to ~/.smarthome/.env, then:

    uv run python -m smarthome.agent --slack --mock   # mock bulb
    uv run python -m smarthome.agent --slack          # real bulb

Quick start — Remote MCP (AWS)

Run provisioning scripts in order (requires AWS profile self):

AWS_PROFILE=self uv run python scripts/aws/create_bridge_thing.py
AWS_PROFILE=self uv run python scripts/aws/create_cognito.py
uv run python scripts/aws/package_lambda.py
AWS_PROFILE=self uv run python scripts/aws/create_lambda.py
AWS_PROFILE=self uv run python scripts/aws/create_agentcore_gateway.py

# Start the local bridge (keep running on-premises)
uv run python scripts/aws/run_bridge.py

# Test end-to-end
AWS_PROFILE=self uv run python scripts/aws/test_gateway.py

Testing

# Unit tests
uv run pytest tests/ -v

# Interactive MCP dev UI (localhost:6274)
uv run fastmcp dev src/smarthome/aws_mcp/mcp_servers/light_server.py

Key Dependencies

Dependencies are split so the local agent can be installed without the AWS/MCP stack.

Core (local agent):

Package

Purpose

anthropic

Claude API SDK

google-genai

Google Gemini API SDK

tapo

TAPO device control over local network

slack-bolt

Slack Socket Mode bot

sqlite-vec

Vector search extension for SQLite

httpx

Async HTTP client (ollama embeddings)

aiohttp, pydantic, python-dotenv

HTTP, validation, env config

aws-mcp extra (MCP paths only):

Package

Purpose

fastmcp

MCP server framework

boto3

AWS SDK (DynamoDB, IoT Core, Lambda, Cognito)

awsiotsdk

AWS IoT Core MQTT client

Dev:

Package

Purpose

moto[dynamodb]

In-memory AWS mock for tests

pytest, pytest-asyncio

Test runner

Install for each environment:

uv sync --no-dev             # Raspberry Pi — local agent only
uv sync --extra aws-mcp      # Dev machine — everything

What's Next

  • Local MCP via Claude Desktop

  • Remote MCP via AgentCore Gateway + Cognito OAuth

  • Multi-device support via DeviceRegistry

  • Local agent loop with Markdown memory

  • Bulb control as an agent skill

  • Color temperature control

  • Heartbeat scheduler with SCHEDULE.md and schedule_task tool

  • Multi-provider LLM support (Anthropic + Google Gemini)

  • Interoperable skills — SKILL.md-only, executable via run_command (no Python scripts required)

  • Additional device types (smart plugs, sensors)

  • Device auto-discovery on local network

License

MIT

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jui-hung-yuan/smarthome-mcp-lab'

If you have feedback or need assistance with the MCP directory API, please join our Discord server