+44
-56
update-lights
+44
-56
update-lights
···
1
1
#!/usr/bin/env -S uv run --script --quiet
2
2
# /// script
3
3
# requires-python = ">=3.12"
4
-
# dependencies = ["pydantic-ai", "pydantic-settings", "rich"]
4
+
# dependencies = ["marvin@git+https://github.com/prefecthq/marvin.git"]
5
5
# ///
6
6
"""
7
7
Make some change to my phillips hue network of lights via agent + MCP server.
···
13
13
```
14
14
15
15
Details:
16
-
- uses a [`pydantic-ai`](https://github.com/pydantic/pydantic-ai) agent
16
+
- uses a [`marvin`](https://github.com/prefecthq/marvin) (built on [`pydantic-ai`](https://github.com/pydantic/pydantic-ai)) agent
17
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
18
- set `HUE_BRIDGE_IP` and `HUE_BRIDGE_USERNAME` in `.env` or otherwise in environment
19
-
- uses `ANTHROPIC_API_KEY` and defaults to claude-sonnet-4-5, but you can set `AI_MODEL` in `.env` to use a different model
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
20
"""
21
21
22
+
import marvin
22
23
import argparse
23
-
import asyncio
24
24
from pydantic_settings import BaseSettings, SettingsConfigDict
25
25
from pydantic import Field
26
-
from pydantic_ai import Agent
27
26
from pydantic_ai.mcp import MCPServerStdio
28
27
from pydantic_ai.models import KnownModelName
29
28
from rich.console import Console
···
36
35
37
36
hue_bridge_ip: str = Field(default=...)
38
37
hue_bridge_username: str = Field(default=...)
39
-
anthropic_api_key: str | None = Field(default=None)
40
38
41
-
ai_model: KnownModelName = Field(default="anthropic:claude-sonnet-4-5")
39
+
ai_model: KnownModelName = Field(default="gpt-4.1-mini")
42
40
43
41
44
42
settings = Settings()
45
43
console = Console()
46
44
47
-
async def run_agent():
48
-
hub_mcp = MCPServerStdio(
49
-
command="uvx",
50
-
args=[
51
-
"smart-home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home"
52
-
],
53
-
env={
54
-
"HUE_BRIDGE_IP": settings.hue_bridge_ip,
55
-
"HUE_BRIDGE_USERNAME": settings.hue_bridge_username,
56
-
},
45
+
hub_mcp = MCPServerStdio(
46
+
command="uvx",
47
+
args=[
48
+
"smart-home@git+https://github.com/jlowin/fastmcp.git#subdirectory=examples/smart_home"
49
+
],
50
+
env={
51
+
"HUE_BRIDGE_IP": settings.hue_bridge_ip,
52
+
"HUE_BRIDGE_USERNAME": settings.hue_bridge_username,
53
+
},
54
+
)
55
+
56
+
57
+
if __name__ == "__main__":
58
+
parser = argparse.ArgumentParser(description="Send a command to the Marvin agent.")
59
+
parser.add_argument(
60
+
"--message",
61
+
"-m",
62
+
type=str,
63
+
default="soft and dim - Jessica Pratt energy, all areas",
64
+
help="The message to send to the agent (defaults to 'soft and dim - Jessica Pratt energy, all areas').",
57
65
)
66
+
args = parser.parse_args()
58
67
59
-
agent = Agent(
60
-
settings.ai_model,
68
+
agent = marvin.Agent(
69
+
model=settings.ai_model,
61
70
mcp_servers=[hub_mcp],
62
71
)
63
72
···
69
78
)
70
79
)
71
80
72
-
parser = argparse.ArgumentParser(description="Send a command to the lights agent.")
73
-
parser.add_argument(
74
-
"--message",
75
-
"-m",
76
-
type=str,
77
-
default="soft and dim - Jessica Pratt energy, all areas",
78
-
help="The message to send to the agent (defaults to 'soft and dim - Jessica Pratt energy, all areas').",
79
-
)
80
-
parser.add_argument(
81
-
"--once",
82
-
action="store_true",
83
-
help="Run once and exit instead of entering interactive mode.",
84
-
)
85
-
args = parser.parse_args()
86
-
87
-
console.print(f"\n[bold yellow]→[/bold yellow] {args.message}")
88
-
result = await agent.run(args.message)
89
-
console.print(f"[dim green]{result.output}[/dim green]")
90
-
91
-
if not args.once:
81
+
with marvin.Thread():
82
+
first = True
92
83
while True:
93
-
try:
94
-
user_input = Prompt.ask("\n[bold green]enter a message[/bold green]")
95
-
console.print(f"[bold yellow]→[/bold yellow] {user_input}")
96
-
result = await agent.run(user_input)
97
-
console.print(f"[dim green]{result.output}[/dim green]")
98
-
except KeyboardInterrupt:
99
-
console.print("\n[dim red]exiting...[/dim red]")
100
-
break
101
-
102
-
103
-
if __name__ == "__main__":
104
-
import os
105
-
106
-
if settings.anthropic_api_key:
107
-
os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key
108
-
109
-
asyncio.run(run_agent())
84
+
if first:
85
+
console.print(f"\n[bold yellow]→[/bold yellow] {args.message}")
86
+
agent.run(str(args.message))
87
+
first = False
88
+
else:
89
+
try:
90
+
user_input = Prompt.ask(
91
+
"\n[bold green]enter a message[/bold green]"
92
+
)
93
+
console.print(f"[bold yellow]→[/bold yellow] {user_input}")
94
+
agent.run(str(user_input))
95
+
except KeyboardInterrupt:
96
+
console.print("\n[dim red]exiting...[/dim red]")
97
+
break