starts a marvin-based while loop with thread history and a phillips hue MCP for updating your lights
update_lights.py
106 lines 3.4 kB view raw
1#!/usr/bin/env -S uv run --script --quiet 2# /// script 3# requires-python = ">=3.12" 4# dependencies = ["marvin@git+https://github.com/prefecthq/marvin.git"] 5# /// 6""" 7Make some change to my phillips hue network of lights via agent + MCP server. 8 9Usage: 10 11```bash 12./update-lights -m "turn on sahara in the living room and nightlight in the kitchen" 13``` 14 15Details: 16- uses a [`marvin`](https://github.com/prefecthq/marvin) (built on [`pydantic-ai`](https://github.com/pydantic/pydantic-ai)) agent 17- the agent spins up a [`fastmcp`](https://github.com/jlowin/fastmcp) MCP server that talks to my [`phue`](https://github.com/studioimaginaire/phue) bridge 18- set `HUE_BRIDGE_IP` and `HUE_BRIDGE_USERNAME` in `.env` or otherwise in environment 19- uses `OPENAI_API_KEY` by default, but you can set `AI_MODEL` in `.env` or otherwise in environment to use a different model 20""" 21 22import marvin 23import argparse 24from pydantic_settings import BaseSettings, SettingsConfigDict 25from pydantic import Field 26from pydantic_ai.mcp import MCPServerStdio 27from pydantic_ai.models import KnownModelName 28from rich.console import Console 29from rich.panel import Panel 30from rich.prompt import Prompt 31 32 33class Settings(BaseSettings): 34 model_config = SettingsConfigDict(env_file=".env", extra="ignore") 35 36 hue_bridge_ip: str = Field(default=...) 37 hue_bridge_username: str = Field(default=...) 38 anthropic_api_key: str | None = Field(default=None) 39 40 ai_model: KnownModelName = Field(default="anthropic:claude-opus-4-5") 41 42 43settings = Settings() 44console = Console() 45 46hub_mcp = MCPServerStdio( 47 command="uvx", 48 args=[ 49 "smart-home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home" 50 ], 51 env={ 52 "HUE_BRIDGE_IP": settings.hue_bridge_ip, 53 "HUE_BRIDGE_USERNAME": settings.hue_bridge_username, 54 }, 55) 56 57 58if __name__ == "__main__": 59 import os 60 61 if settings.anthropic_api_key: 62 os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key 63 64 parser = argparse.ArgumentParser(description="Send a command to the Marvin agent.") 65 parser.add_argument( 66 "--message", 67 "-m", 68 type=str, 69 default="soft and dim - Jessica Pratt energy, all areas", 70 help="The message to send to the agent (defaults to 'soft and dim - Jessica Pratt energy, all areas').", 71 ) 72 parser.add_argument( 73 "--once", 74 action="store_true", 75 help="Run once and exit instead of entering interactive mode.", 76 ) 77 args = parser.parse_args() 78 79 agent = marvin.Agent( 80 model=settings.ai_model, 81 mcp_servers=[hub_mcp], 82 ) 83 84 console.print( 85 Panel.fit( 86 f"[bold cyan]🏠 lights agent[/bold cyan]\n" 87 f"[dim]model: {settings.ai_model}[/dim]", 88 border_style="blue", 89 ) 90 ) 91 92 with marvin.Thread(): 93 console.print(f"\n[bold yellow]→[/bold yellow] {args.message}") 94 agent.run(str(args.message)) 95 96 if not args.once: 97 while True: 98 try: 99 user_input = Prompt.ask( 100 "\n[bold green]enter a message[/bold green]" 101 ) 102 console.print(f"[bold yellow]→[/bold yellow] {user_input}") 103 agent.run(str(user_input)) 104 except KeyboardInterrupt: 105 console.print("\n[dim red]exiting...[/dim red]") 106 break