hud-python 0.4.2__py3-none-any.whl → 0.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hud-python might be problematic. Click here for more details.

hud/agents/langchain.py CHANGED
@@ -9,13 +9,18 @@ import mcp.types as types
9
9
  from langchain.agents import AgentExecutor, create_tool_calling_agent
10
10
  from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
11
11
  from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
12
- from mcp_use.adapters.langchain_adapter import LangChainAdapter
13
12
 
14
13
  import hud
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from langchain.schema.language_model import BaseLanguageModel
18
17
  from langchain_core.tools import BaseTool
18
+ from mcp_use.adapters.langchain_adapter import LangChainAdapter
19
+
20
+ try:
21
+ from mcp_use.adapters.langchain_adapter import LangChainAdapter
22
+ except ImportError:
23
+ LangChainAdapter = None # type: ignore[misc, assignment]
19
24
 
20
25
  from hud.types import AgentResponse, MCPToolCall, MCPToolResult
21
26
 
@@ -51,6 +56,12 @@ class LangChainAgent(MCPAgent):
51
56
  """
52
57
  super().__init__(**kwargs)
53
58
 
59
+ if LangChainAdapter is None:
60
+ raise ImportError(
61
+ "LangChainAdapter is not available. "
62
+ "Please install the optional agent dependencies: pip install 'hud-python[agent]'"
63
+ )
64
+
54
65
  self.llm = llm
55
66
  self.adapter = LangChainAdapter(disallowed_tools=self.disallowed_tools)
56
67
  self._langchain_tools: list[BaseTool] | None = None
@@ -5,6 +5,8 @@ from typing import Literal
5
5
 
6
6
  from openai import AsyncOpenAI
7
7
 
8
+ from hud.settings import settings
9
+
8
10
  ResponseType = Literal["STOP", "CONTINUE"]
9
11
 
10
12
 
@@ -15,7 +17,7 @@ class ResponseAgent:
15
17
  """
16
18
 
17
19
  def __init__(self, api_key: str | None = None) -> None:
18
- self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
20
+ self.api_key = api_key or settings.openai_api_key or os.environ.get("OPENAI_API_KEY")
19
21
  if not self.api_key:
20
22
  raise ValueError(
21
23
  "OpenAI API key must be provided or set as OPENAI_API_KEY environment variable"
hud/cli/build.py CHANGED
@@ -12,13 +12,54 @@ from typing import Any
12
12
 
13
13
  import typer
14
14
  import yaml
15
- from rich.console import Console
16
15
 
17
16
  from hud.clients import MCPClient
18
17
  from hud.utils.design import HUDDesign
19
18
  from hud.version import __version__ as hud_version
20
19
 
21
- console = Console()
20
+
21
+ def parse_version(version_str: str) -> tuple[int, int, int]:
22
+ """Parse version string like '1.0.0' or '1.0' into tuple of integers."""
23
+ # Remove 'v' prefix if present
24
+ version_str = version_str.lstrip('v')
25
+
26
+ # Split by dots and pad with zeros if needed
27
+ parts = version_str.split('.')
28
+ parts.extend(['0'] * (3 - len(parts))) # Ensure we have at least 3 parts
29
+
30
+ try:
31
+ return (int(parts[0]), int(parts[1]), int(parts[2]))
32
+ except (ValueError, IndexError):
33
+ # Default to 0.0.0 if parsing fails
34
+ return (0, 0, 0)
35
+
36
+
37
+ def increment_version(version_str: str, increment_type: str = "patch") -> str:
38
+ """Increment version string. increment_type can be 'major', 'minor', or 'patch'."""
39
+ major, minor, patch = parse_version(version_str)
40
+
41
+ if increment_type == "major":
42
+ return f"{major + 1}.0.0"
43
+ elif increment_type == "minor":
44
+ return f"{major}.{minor + 1}.0"
45
+ else: # patch
46
+ return f"{major}.{minor}.{patch + 1}"
47
+
48
+
49
+ def get_existing_version(lock_path: Path) -> str | None:
50
+ """Get the internal version from existing lock file if it exists."""
51
+ if not lock_path.exists():
52
+ return None
53
+
54
+ try:
55
+ with open(lock_path) as f:
56
+ lock_data = yaml.safe_load(f)
57
+
58
+ # Look for internal version in build metadata
59
+ build_data = lock_data.get("build", {})
60
+ return build_data.get("version", None)
61
+ except Exception:
62
+ return None
22
63
 
23
64
 
24
65
  def get_docker_image_digest(image: str) -> str | None:
@@ -39,7 +80,8 @@ def get_docker_image_digest(image: str) -> str | None:
39
80
  # Return full image reference with digest
40
81
  return digest_list[0]
41
82
  except Exception:
42
- console.print("Failed to get Docker image digest")
83
+ # Don't print error here, let calling code handle it
84
+ pass
43
85
  return None
44
86
 
45
87
 
@@ -272,17 +314,17 @@ def build_environment(
272
314
 
273
315
  # Provide helpful debugging tips
274
316
  design.section_title("Debugging Tips")
275
- console.print("1. Test your server directly:")
276
- console.print(f" [cyan]docker run --rm -it {temp_tag}[/cyan]")
277
- console.print(" (Should see MCP initialization output)")
278
- console.print("")
279
- console.print("2. Check for common issues:")
280
- console.print(" - Server crashes on startup")
281
- console.print(" - Missing dependencies")
282
- console.print(" - Syntax errors in server.py")
283
- console.print("")
284
- console.print("3. Run with verbose mode:")
285
- console.print(" [cyan]hud build . --verbose[/cyan]")
317
+ design.info("1. Test your server directly:")
318
+ design.command_example(f"docker run --rm -it {temp_tag}")
319
+ design.dim_info(" Expected output", "MCP initialization messages")
320
+ design.info("")
321
+ design.info("2. Check for common issues:")
322
+ design.info(" - Server crashes on startup")
323
+ design.info(" - Missing dependencies")
324
+ design.info(" - Syntax errors in server.py")
325
+ design.info("")
326
+ design.info("3. Run with verbose mode:")
327
+ design.command_example("hud build . --verbose")
286
328
 
287
329
  raise typer.Exit(1)
288
330
 
@@ -292,6 +334,19 @@ def build_environment(
292
334
  dockerfile_path = env_dir / "Dockerfile"
293
335
  required_env, optional_env = extract_env_vars_from_dockerfile(dockerfile_path)
294
336
 
337
+ # Check for existing version and increment
338
+ lock_path = env_dir / "hud.lock.yaml"
339
+ existing_version = get_existing_version(lock_path)
340
+
341
+ if existing_version:
342
+ # Increment existing version
343
+ new_version = increment_version(existing_version)
344
+ design.info(f"Incrementing version: {existing_version} → {new_version}")
345
+ else:
346
+ # Start with 0.1.0 for new environments
347
+ new_version = "0.1.0"
348
+ design.info(f"Setting initial version: {new_version}")
349
+
295
350
  # Create lock file content - minimal and useful
296
351
  lock_content = {
297
352
  "version": "1.0", # Lock file format version
@@ -300,6 +355,7 @@ def build_environment(
300
355
  "generatedAt": datetime.utcnow().isoformat() + "Z",
301
356
  "hudVersion": hud_version,
302
357
  "directory": str(env_dir.name),
358
+ "version": new_version, # Internal environment version
303
359
  },
304
360
  "environment": {
305
361
  "initializeMs": analysis["initializeMs"],
@@ -338,13 +394,21 @@ def build_environment(
338
394
  design.progress_message("Rebuilding with lock file metadata...")
339
395
 
340
396
  # Build final image with label (uses cache from first build)
397
+ # Also tag with version
398
+ base_name = tag.split(":")[0] if tag and ":" in tag else tag
399
+ version_tag = f"{base_name}:{new_version}"
400
+
341
401
  label_cmd = [
342
402
  "docker",
343
403
  "build",
344
404
  "--label",
345
405
  f"org.hud.manifest.head={lock_hash}:{lock_size}",
406
+ "--label",
407
+ f"org.hud.version={new_version}",
346
408
  "-t",
347
409
  tag,
410
+ "-t",
411
+ version_tag,
348
412
  str(env_dir),
349
413
  ]
350
414
 
@@ -398,21 +462,28 @@ def build_environment(
398
462
  # Print summary
399
463
  design.section_title("Build Complete")
400
464
 
401
- # Show the actual image reference from the lock file
402
- final_image_ref = lock_content.get("image", tag)
403
- console.print(f"[green]✓[/green] Local image : {final_image_ref}")
404
- console.print("[green]✓[/green] Lock file : hud.lock.yaml")
405
- console.print(f"[green]✓[/green] Tools found : {analysis['toolCount']}")
465
+ # Show the version tag as primary since that's what will be pushed
466
+ design.status_item("Built image", version_tag, primary=True)
467
+ if tag:
468
+ design.status_item("Also tagged", tag)
469
+ design.status_item("Version", new_version)
470
+ design.status_item("Lock file", "hud.lock.yaml")
471
+ design.status_item("Tools found", str(analysis['toolCount']))
472
+
473
+ # Show the digest info separately if we have it
474
+ if image_id:
475
+ design.dim_info("\nImage digest", image_id)
406
476
 
407
477
  design.section_title("Next Steps")
408
- console.print("Test locally:")
409
- console.print(" [cyan]hud dev[/cyan] # Hot-reload development")
410
- console.print(f" [cyan]hud run {tag}[/cyan] # Run the built image")
411
- console.print("")
412
- console.print("Publish to registry:")
413
- console.print(f" [cyan]hud push --image <registry>/{tag}[/cyan]")
414
- console.print("")
415
- console.print("The lock file can be used to reproduce this exact environment.")
478
+ design.info("Test locally:")
479
+ design.command_example("hud dev", "Hot-reload development")
480
+ design.command_example(f"hud run {tag}", "Run the built image")
481
+ design.info("")
482
+ design.info("Publish to registry:")
483
+ design.command_example("hud push", f"Push as {version_tag}")
484
+ design.command_example("hud push --tag latest", "Push with custom tag")
485
+ design.info("")
486
+ design.info("The lock file can be used to reproduce this exact environment.")
416
487
 
417
488
 
418
489
  def build_command(
hud/cli/init.py CHANGED
@@ -5,11 +5,10 @@ from __future__ import annotations
5
5
  from pathlib import Path
6
6
 
7
7
  import typer
8
- from rich.console import Console
9
8
  from rich.panel import Panel
10
9
  from rich.syntax import Syntax
11
10
 
12
- console = Console()
11
+ from hud.utils.design import HUDDesign
13
12
 
14
13
  # Embedded templates
15
14
  DOCKERFILE_TEMPLATE = """FROM python:3.11-slim
@@ -249,26 +248,26 @@ def create_environment(name: str | None, directory: str, force: bool) -> None:
249
248
 
250
249
  design.section_title("Files created")
251
250
  for file in files_created:
252
- console.print(f" ✓ {file}")
251
+ design.status_item(file, "created")
253
252
 
254
253
  design.section_title("Next steps")
255
254
 
256
255
  # Show commands based on where we created the environment
257
256
  if target_dir == Path.cwd():
258
- console.print("1. Start development server (with MCP inspector):")
259
- console.print(" [cyan]hud dev --inspector[/cyan]")
257
+ design.info("1. Start development server (with MCP inspector):")
258
+ design.command_example("hud dev --inspector")
260
259
  else:
261
- console.print("1. Enter the directory:")
262
- console.print(f" [cyan]cd {target_dir}[/cyan]")
263
- console.print("\n2. Start development server (with MCP inspector):")
264
- console.print(" [cyan]hud dev --inspector[/cyan]")
260
+ design.info("1. Enter the directory:")
261
+ design.command_example(f"cd {target_dir}")
262
+ design.info("\n2. Start development server (with MCP inspector):")
263
+ design.command_example("hud dev --inspector")
265
264
 
266
- console.print("\n3. Connect from Cursor or test via the MCP inspector:")
267
- console.print(" Follow the instructions shown by [cyan]hud dev --inspector[/cyan]")
265
+ design.info("\n3. Connect from Cursor or test via the MCP inspector:")
266
+ design.info(" Follow the instructions shown by hud dev --inspector")
268
267
 
269
- console.print("\n4. Customize your environment:")
270
- console.print(" - Add tools to [cyan]src/hud_controller/server.py[/cyan]")
271
- console.print(" - Add state to [cyan]src/hud_controller/context.py[/cyan]")
268
+ design.info("\n4. Customize your environment:")
269
+ design.info(" - Add tools to src/hud_controller/server.py")
270
+ design.info(" - Add state to src/hud_controller/context.py")
272
271
 
273
272
  # Show a sample of the server code
274
273
  design.section_title("Your MCP server")
@@ -278,4 +277,4 @@ async def act() -> str:
278
277
  return f"Action #{ctx.act()}"'''
279
278
 
280
279
  syntax = Syntax(sample_code, "python", theme="monokai", line_numbers=False)
281
- console.print(Panel(syntax, border_style="dim"))
280
+ design.console.print(Panel(syntax, border_style="dim"))
hud/cli/mcp_server.py CHANGED
@@ -12,8 +12,12 @@ import click
12
12
  import toml
13
13
  from fastmcp import FastMCP
14
14
 
15
+ from hud.utils.design import HUDDesign
15
16
  from .docker_utils import get_docker_cmd, image_exists, inject_supervisor
16
17
 
18
+ # Global design instance
19
+ design = HUDDesign()
20
+
17
21
 
18
22
  def get_image_name(directory: str | Path, image_override: str | None = None) -> tuple[str, str]:
19
23
  """
@@ -34,7 +38,7 @@ def get_image_name(directory: str | Path, image_override: str | None = None) ->
34
38
  if config.get("tool", {}).get("hud", {}).get("image"):
35
39
  return config["tool"]["hud"]["image"], "cache"
36
40
  except Exception:
37
- click.echo("Failed to load pyproject.toml", err=True)
41
+ pass # Silent failure, will use auto-generated name
38
42
 
39
43
  # Auto-generate with :dev tag
40
44
  dir_path = Path(directory).resolve() # Get absolute path first
@@ -68,17 +72,14 @@ def update_pyproject_toml(directory: str | Path, image_name: str, silent: bool =
68
72
  toml.dump(config, f)
69
73
 
70
74
  if not silent:
71
- click.echo(f"Updated pyproject.toml with image: {image_name}")
75
+ design.success(f"Updated pyproject.toml with image: {image_name}")
72
76
  except Exception as e:
73
77
  if not silent:
74
- click.echo(f"⚠️ Could not update pyproject.toml: {e}")
78
+ design.warning(f"Could not update pyproject.toml: {e}")
75
79
 
76
80
 
77
81
  def build_and_update(directory: str | Path, image_name: str, no_cache: bool = False) -> None:
78
82
  """Build Docker image and update pyproject.toml."""
79
- from hud.utils.design import HUDDesign
80
-
81
- design = HUDDesign()
82
83
 
83
84
  build_cmd = ["docker", "build", "-t", image_name]
84
85
  if no_cache:
@@ -115,7 +116,7 @@ def create_proxy_server(
115
116
  # Get the original CMD from the image
116
117
  original_cmd = get_docker_cmd(image_name)
117
118
  if not original_cmd:
118
- click.echo(f"⚠️ Could not extract CMD from {image_name}, using default")
119
+ design.warning(f"Could not extract CMD from {image_name}, using default")
119
120
  original_cmd = ["python", "-m", "hud_controller.server"]
120
121
 
121
122
  # Generate container name from image
@@ -167,18 +168,15 @@ def create_proxy_server(
167
168
  # Debug output - only if verbose
168
169
  if verbose:
169
170
  if not no_reload:
170
- click.echo("📁 Watching: /app/src for changes", err=True)
171
+ design.info("Watching: /app/src for changes")
171
172
  else:
172
- click.echo("🔧 Container will run without hot-reload", err=True)
173
- click.echo(f"📊 docker logs -f {container_name}", err=True)
173
+ design.info("Container will run without hot-reload")
174
+ design.command_example(f"docker logs -f {container_name}", "View container logs")
174
175
 
175
176
  # Create the HTTP proxy server using config
176
177
  try:
177
178
  proxy = FastMCP.as_proxy(config, name=f"HUD Dev Proxy - {image_name}")
178
179
  except Exception as e:
179
- from hud.utils.design import HUDDesign
180
-
181
- design = HUDDesign(stderr=True)
182
180
  design.error(f"Failed to create proxy server: {e}")
183
181
  design.info("")
184
182
  design.info("💡 Tip: Run the following command to debug the container:")
@@ -277,7 +275,7 @@ async def start_mcp_proxy(
277
275
  # Now check for src directory
278
276
  src_path = Path(directory) / "src"
279
277
  if not src_path.exists():
280
- click.echo(f"Source directory not found: {src_path}", err=(transport == "stdio"))
278
+ design.error(f"Source directory not found: {src_path}")
281
279
  raise click.Abort
282
280
 
283
281
  # Extract container name from the proxy configuration
@@ -293,20 +291,20 @@ async def start_mcp_proxy(
293
291
  check=False, # Don't raise error if container doesn't exist
294
292
  )
295
293
  except Exception:
296
- click.echo(f"Failed to remove existing container {container_name}", err=True)
294
+ pass # Silent failure, container might not exist
297
295
 
298
296
  if transport == "stdio":
299
297
  if verbose:
300
- click.echo("🔌 Starting stdio proxy (each connection gets its own container)", err=True)
298
+ design.info("Starting stdio proxy (each connection gets its own container)")
301
299
  else:
302
300
  # Find available port for HTTP
303
301
  actual_port = find_free_port(port)
304
302
  if actual_port is None:
305
- click.echo(f"No available ports found starting from {port}")
303
+ design.error(f"No available ports found starting from {port}")
306
304
  raise click.Abort
307
305
 
308
306
  if actual_port != port and verbose:
309
- click.echo(f"⚠️ Port {port} in use, using port {actual_port} instead")
307
+ design.warning(f"Port {port} in use, using port {actual_port} instead")
310
308
 
311
309
  # Launch MCP Inspector if requested
312
310
  if inspector:
@@ -329,11 +327,8 @@ async def start_mcp_proxy(
329
327
  )
330
328
 
331
329
  # Print inspector info cleanly
332
- from hud.utils.design import HUDDesign
333
-
334
- inspector_design = HUDDesign(stderr=(transport == "stdio"))
335
- inspector_design.section_title("MCP Inspector")
336
- inspector_design.link(inspector_url)
330
+ design.section_title("MCP Inspector")
331
+ design.link(inspector_url)
337
332
 
338
333
  # Set environment to disable auth (for development only)
339
334
  env = os.environ.copy()
@@ -359,7 +354,7 @@ async def start_mcp_proxy(
359
354
 
360
355
  except (FileNotFoundError, Exception):
361
356
  # Silently fail - inspector is optional
362
- click.echo("Failed to launch inspector", err=True)
357
+ design.error("Failed to launch inspector")
363
358
 
364
359
  # Launch inspector asynchronously so it doesn't block
365
360
  asyncio.create_task(launch_inspector())
@@ -369,8 +364,7 @@ async def start_mcp_proxy(
369
364
  if transport != "http":
370
365
  from hud.utils.design import HUDDesign
371
366
 
372
- interactive_design = HUDDesign(stderr=True)
373
- interactive_design.warning("Interactive mode only works with HTTP transport")
367
+ design.warning("Interactive mode only works with HTTP transport")
374
368
  else:
375
369
  server_url = f"http://localhost:{actual_port}/mcp"
376
370
 
@@ -383,12 +377,9 @@ async def start_mcp_proxy(
383
377
  time.sleep(3)
384
378
 
385
379
  try:
386
- from hud.utils.design import HUDDesign
387
-
388
- interactive_design = HUDDesign(stderr=(transport == "stdio"))
389
- interactive_design.section_title("Interactive Mode")
390
- interactive_design.info("Starting interactive testing mode...")
391
- interactive_design.info("Press Ctrl+C in the interactive session to exit")
380
+ design.section_title("Interactive Mode")
381
+ design.info("Starting interactive testing mode...")
382
+ design.info("Press Ctrl+C in the interactive session to exit")
392
383
 
393
384
  # Import and run interactive mode in a new event loop
394
385
  from .interactive import run_interactive_mode
@@ -404,7 +395,7 @@ async def start_mcp_proxy(
404
395
  except Exception as e:
405
396
  # Log error but don't crash the server
406
397
  if verbose:
407
- click.echo(f"Interactive mode error: {e}", err=True)
398
+ design.error(f"Interactive mode error: {e}")
408
399
 
409
400
  # Launch interactive mode in a separate thread
410
401
  import threading
@@ -415,10 +406,7 @@ async def start_mcp_proxy(
415
406
  # Function to stream Docker logs
416
407
  async def stream_docker_logs() -> None:
417
408
  """Stream Docker container logs asynchronously."""
418
- # Import design system for consistent output
419
- from hud.utils.design import HUDDesign
420
-
421
- log_design = HUDDesign(stderr=(transport == "stdio"))
409
+ log_design = design
422
410
 
423
411
  # Always show waiting message
424
412
  log_design.info("") # Empty line for spacing
@@ -567,39 +555,33 @@ async def start_mcp_proxy(
567
555
  show_banner=False,
568
556
  )
569
557
  except (ConnectionError, OSError) as e:
570
- from hud.utils.design import HUDDesign
571
-
572
- error_design = HUDDesign(stderr=True)
573
- error_design.error(f"Failed to connect to Docker container: {e}")
574
- error_design.info("")
575
- error_design.info("💡 Tip: Run the following command to debug the container:")
576
- error_design.info(f" hud debug {image_name}")
577
- error_design.info("")
578
- error_design.info("Common issues:")
579
- error_design.info(" • Container failed to start or crashed immediately")
580
- error_design.info(" • Server initialization failed")
581
- error_design.info(" • Port binding conflicts")
558
+ design.error(f"Failed to connect to Docker container: {e}")
559
+ design.info("")
560
+ design.info("💡 Tip: Run the following command to debug the container:")
561
+ design.info(f" hud debug {image_name}")
562
+ design.info("")
563
+ design.info("Common issues:")
564
+ design.info(" Container failed to start or crashed immediately")
565
+ design.info(" • Server initialization failed")
566
+ design.info(" Port binding conflicts")
582
567
  raise
583
568
  except KeyboardInterrupt:
584
- from hud.utils.design import HUDDesign
585
-
586
- shutdown_design = HUDDesign(stderr=(transport == "stdio"))
587
- shutdown_design.info("\n👋 Shutting down...")
569
+ design.info("\n👋 Shutting down...")
588
570
 
589
571
  # Show next steps tutorial
590
572
  if not interactive: # Only show if not in interactive mode
591
- shutdown_design.section_title("Next Steps")
592
- shutdown_design.info("🏗️ Ready to test with real agents? Run:")
593
- shutdown_design.info(f" [cyan]hud build {directory}[/cyan]")
594
- shutdown_design.info("")
595
- shutdown_design.info("This will:")
596
- shutdown_design.info(" 1. Build your environment image")
597
- shutdown_design.info(" 2. Generate a hud.lock.yaml file")
598
- shutdown_design.info(" 3. Prepare it for testing with agents")
599
- shutdown_design.info("")
600
- shutdown_design.info("Then you can:")
601
- shutdown_design.info(" • Test locally: [cyan]hud run <image>[/cyan]")
602
- shutdown_design.info(
573
+ design.section_title("Next Steps")
574
+ design.info("🏗️ Ready to test with real agents? Run:")
575
+ design.info(f" [cyan]hud build {directory}[/cyan]")
576
+ design.info("")
577
+ design.info("This will:")
578
+ design.info(" 1. Build your environment image")
579
+ design.info(" 2. Generate a hud.lock.yaml file")
580
+ design.info(" 3. Prepare it for testing with agents")
581
+ design.info("")
582
+ design.info("Then you can:")
583
+ design.info(" • Test locally: [cyan]hud run <image>[/cyan]")
584
+ design.info(
603
585
  " • Push to registry: [cyan]hud push --image <registry/name>[/cyan]"
604
586
  )
605
587
  except Exception as e:
@@ -613,10 +595,7 @@ async def start_mcp_proxy(
613
595
  "Application shutdown complete",
614
596
  ]
615
597
  ):
616
- from hud.utils.design import HUDDesign
617
-
618
- shutdown_design = HUDDesign(stderr=(transport == "stdio"))
619
- shutdown_design.error(f"Unexpected error: {e}")
598
+ design.error(f"Unexpected error: {e}")
620
599
  finally:
621
600
  # Cancel log streaming task if it exists
622
601
  if log_task and not log_task.done():
@@ -624,7 +603,7 @@ async def start_mcp_proxy(
624
603
  try:
625
604
  await log_task
626
605
  except asyncio.CancelledError:
627
- click.echo("Log streaming task cancelled", err=True)
606
+ pass # Log streaming cancelled, normal shutdown
628
607
 
629
608
 
630
609
  def run_mcp_dev_server(
@@ -654,10 +633,6 @@ def run_mcp_dev_server(
654
633
  hud dev . --image custom:tag # Use specific image
655
634
  hud dev . --no-cache # Force clean rebuild
656
635
  """
657
- from hud.utils.design import HUDDesign
658
-
659
- design = HUDDesign(stderr=(transport == "stdio"))
660
-
661
636
  # Ensure directory exists
662
637
  if not Path(directory).exists():
663
638
  design.error(f"Directory not found: {directory}")
@@ -780,10 +755,10 @@ def run_mcp_dev_server(
780
755
  )
781
756
  )
782
757
  except Exception as e:
783
- design.error(f"Failed to start MCP server: {e}")
784
- design.info("")
785
- design.info("💡 Tip: Run the following command to debug the container:")
786
- design.info(f" hud debug {resolved_image}")
787
- design.info("")
788
- design.info("This will help identify connection issues or initialization failures.")
758
+ d.error(f"Failed to start MCP server: {e}")
759
+ d.info("")
760
+ d.info("💡 Tip: Run the following command to debug the container:")
761
+ d.info(f" hud debug {resolved_image}")
762
+ d.info("")
763
+ d.info("This will help identify connection issues or initialization failures.")
789
764
  raise