Smart Home Server
FreeNot checkedControls TAPO L530E smart light bulbs via MCP, supporting on/off, brightness, and color temperature, with local and remote AWS deployment.
About
Controls TAPO L530E smart light bulbs via MCP, supporting on/off, brightness, and color temperature, with local and remote AWS deployment.
README
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 |
Features
MCP Paths
- Turn lights on/off, set brightness, get bulb status (on/off, brightness, color temp)
- Real bulb control via
tapolibrary 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 architecture —
BaseDeviceinterface 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,
@mentionin 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.mdintoskills/<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 execution —
run_commandtool lets the model run any shell command and parse JSON output; skills are pure documentation that describe which commands to run - Color temperature control —
set_color_temp(2500–6500 K) via the agent skill - Heartbeat scheduler —
HeartbeatSchedulerfires time-based automations fromSCHEDULE.md; tasks store a shell command (cmd) and run as subprocesses; Claude manages tasks viaschedule_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@mentionin channels and all messages in DMs; idle sessions auto-evict after 30 min with memory flush
6 built-in tools the model can call:
run_command(cmd)— run any shell command; parses JSON stdout; lifts Slack blocksdescribe_skill(skill_name)— loads full skill docs once; injected into system prompt for the sessionmemory_search(query)— hybrid BM25 + vector search (Reciprocal Rank Fusion)memory_write(path, content, mode)— persists to Markdown filesschedule_task(action, name, hour, minute, cmd)— add/remove/list scheduled automations inSCHEDULE.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 deltaget_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
Create
~/.smarthome/.envwith 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.xAdd 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"] } } }Restart Claude Desktop.
Quick start — Local Agent
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/.envSeed memory files (optional but recommended):
mkdir -p ~/.smarthome/memory echo "# Memory" > ~/.smarthome/memory/MEMORY.md echo "# User Preferences" > ~/.smarthome/memory/USER.mdRun 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 GeminiRun with real bulb — add
TAPO_USERNAME,TAPO_PASSWORD,TAPO_IP_ADDRESSto~/.smarthome/.env, then:uv run python -m smarthome.agent uv run python -m smarthome.agent --model gemini-3.1-flash-liteRun as a Slack bot — add
SLACK_BOT_TOKEN,SLACK_APP_TOKEN,SLACK_SIGNING_SECRETto~/.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.mdandschedule_tasktool - 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
Installing Smart Home Server
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/jui-hung-yuan/smarthome-mcp-labFAQ
Is Smart Home Server MCP free?
Yes, Smart Home Server MCP is free — one-click install via Unyly at no cost.
Does Smart Home Server need an API key?
No, Smart Home Server runs without API keys or environment variables.
Is Smart Home Server hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Smart Home Server in Claude Desktop, Claude Code or Cursor?
Open Smart Home Server on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Smart Home Server with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
