MCP CLI provides comprehensive terminal management utilities for handling terminal state, cleanup, and cross-platform operations. The TerminalManager class offers a unified interface for terminal operations that work across Windows, macOS, and Linux.
Clear the terminal screen using platform-appropriate commands:
from mcp_cli.ui.terminal import clear_screen
# Clear the terminal
clear_screen()
# Or using the manager directly
from mcp_cli.ui.terminal import TerminalManager
TerminalManager.clear()- Windows: Uses
clscommand - Unix/Linux/macOS: Uses
clearcommand
Reset terminal to sane defaults (Unix-like systems only):
from mcp_cli.ui.terminal import reset_terminal
# Reset terminal settings
reset_terminal()
# Useful after programs leave terminal in bad state- Runs
stty saneon Unix-like systems - No-op on Windows
- Handles errors gracefully with debug logging
Get current terminal dimensions:
from mcp_cli.ui.terminal import get_terminal_size
# Get size as (columns, rows)
cols, rows = get_terminal_size()
print(f"Terminal size: {cols}×{rows}")
# Check terminal size categories
if cols < 80:
print("Narrow terminal - content may wrap")
elif cols < 120:
print("Standard terminal width")
else:
print("Wide terminal - good for side-by-side views")- Uses
shutil.get_terminal_size()internally - Returns default (80, 24) if detection fails
Set the terminal window title:
from mcp_cli.ui.terminal import set_terminal_title
# Set a custom title
set_terminal_title("MCP CLI - Processing...")
# Update title with status
for i in range(100):
set_terminal_title(f"Progress: {i+1}%")
# ... do work ...
set_terminal_title("MCP CLI - Complete")- Windows: Uses
titlecommand - Unix/Linux/macOS: Uses ANSI escape sequence
\033]0;title\007
Check terminal color capabilities:
from mcp_cli.ui.terminal import TerminalManager
# Basic color support
if TerminalManager.supports_color():
print("\033[32mGreen text\033[0m") # Use ANSI colors
else:
print("Plain text") # No colors
# Advanced color detection
if TerminalManager.supports_truecolor():
print("24-bit RGB colors supported")
elif TerminalManager.supports_256_colors():
print("256 colors supported")
else:
print("Basic 16 colors")
# Get color level
color_level = TerminalManager.get_color_level()
# Returns: 'truecolor', '256', '16', or 'mono'- supports_color(): Returns
Trueif stdout is a TTY - supports_truecolor(): Checks for 24-bit RGB support via COLORTERM
- supports_256_colors(): Checks for 256 color support via TERM
- get_color_level(): Returns the highest supported color level
Control cursor visibility and position (Unix-like systems):
from mcp_cli.ui.terminal import (
hide_cursor, show_cursor,
TerminalManager
)
# Hide/show cursor
hide_cursor()
# ... do work without visible cursor ...
show_cursor()
# Save and restore position
TerminalManager.save_cursor_position()
print("Line 1")
print("Line 2")
TerminalManager.restore_cursor_position() # Back to saved position
# Move cursor
TerminalManager.move_cursor_up(2) # Move up 2 lines
TerminalManager.move_cursor_down(1) # Move down 1 line
TerminalManager.clear_line() # Clear current line- Cursor control uses ANSI escape sequences
- No-op on Windows
- Useful for creating dynamic terminal UIs
Create clickable hyperlinks in supported terminals:
from mcp_cli.ui.terminal import hyperlink, TerminalManager
# Create hyperlink with TerminalManager
link = TerminalManager.hyperlink("https://example.com")
print(f"Visit: {link}")
# Custom link text
link = TerminalManager.hyperlink("https://docs.example.com", "Documentation")
print(f"Read the {link}")
# Using Rich for hyperlinks (recommended with Rich output)
from mcp_cli.ui.output import get_output
ui = get_output()
ui.print("Visit [link=https://example.com]our website[/link]")Supported terminals include:
- iTerm2 (macOS)
- Kitty
- WezTerm
- Hyper
- Most modern terminal emulators with OSC 8 support
For unsupported terminals, falls back to "text (url)" format.
Sound the terminal bell/beep:
from mcp_cli.ui.terminal import bell
# Sound the bell
bell()
# Multiple bells with delay
import time
for i in range(3):
bell()
time.sleep(1)- Uses ASCII bell character (
\a) - May be disabled in terminal settings
- Some terminals flash instead of beeping
Use alternate screen like vim, less, or htop:
from mcp_cli.ui.terminal import alternate_screen
# Using context manager (recommended)
with alternate_screen():
# Now in alternate screen
print("This is in alternate screen")
print("Original screen content is preserved")
input("Press Enter to return...")
# Automatically returns to main screen
# Manual control
from mcp_cli.ui.terminal import TerminalManager
TerminalManager.enter_alternate_screen()
# ... work in alternate screen ...
TerminalManager.exit_alternate_screen()The alternate screen:
- Preserves main screen content
- Automatically hides cursor in context manager
- Used by full-screen applications
- Clears when exiting
Get comprehensive terminal information:
from mcp_cli.ui.terminal import get_terminal_info, TerminalManager
# Get all terminal info
info = get_terminal_info()
print(f"Terminal: {info['program']}")
print(f"Type: {info['type']}")
print(f"Size: {info['size']['columns']}×{info['size']['rows']}")
print(f"Encoding: {info['encoding']}")
# Check specific environments
if TerminalManager.is_tmux():
print("Running in tmux")
if TerminalManager.is_ssh():
print("Connected via SSH")
if TerminalManager.is_screen():
print("Running in GNU screen")
# Get terminal program
program = TerminalManager.get_terminal_program()
# Returns: 'iTerm.app', 'Terminal.app', 'unknown', etc.Show progress in the terminal title bar:
from mcp_cli.ui.terminal import TerminalManager
# Basic progress
for i in range(101):
TerminalManager.set_title_progress(i)
# ... do work ...
time.sleep(0.1)
# Custom prefix
for i in range(101):
TerminalManager.set_title_progress(i, "Downloading")
# Shows: "Downloading: [████░░░░░░] 40%"Creates a visual progress bar in the title with percentage.
Restore terminal and clean up all resources:
from mcp_cli.ui.terminal import restore_terminal
try:
# Your application code
run_app()
finally:
# Always restore on exit
restore_terminal()The restore_terminal() function performs:
- Terminal reset (
stty saneon Unix) - Asyncio cleanup (cancels tasks, closes event loops)
- Garbage collection
The terminal manager provides comprehensive asyncio cleanup to prevent resource leaks:
from mcp_cli.ui.terminal import TerminalManager
# Cleanup asyncio resources
TerminalManager.cleanup_asyncio()This function:
- Detects running vs non-running event loops
- Cancels pending tasks gracefully
- Shuts down async generators
- Closes event loops properly
- Handles all errors without raising exceptions
- Loop Detection: Checks for running or existing event loops
- Task Cancellation: Only cancels tasks that aren't done
- Graceful Shutdown: Gives tasks time to cancel cleanly
- Generator Cleanup: Shuts down async generators
- Loop Closure: Closes the event loop if not running
from mcp_cli.ui.terminal import (
clear_screen,
get_terminal_size,
set_terminal_title,
TerminalManager
)
# Clear and setup
clear_screen()
set_terminal_title("My Application")
# Get and display size
cols, rows = get_terminal_size()
print(f"Working with {cols}×{rows} terminal")
# Check capabilities
if TerminalManager.supports_color():
print("✅ Color support detected")
else:
print("No color support")import asyncio
from mcp_cli.ui.terminal import restore_terminal, set_terminal_title
async def main():
try:
set_terminal_title("App Running")
# Create background tasks
tasks = [
asyncio.create_task(background_work()),
asyncio.create_task(monitor_input())
]
# Run application
await run_application()
finally:
# Always restore terminal on exit
restore_terminal()
print("Terminal restored successfully")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
restore_terminal()import asyncio
from mcp_cli.ui.terminal import get_terminal_size
async def monitor_terminal_size():
"""Monitor and react to terminal size changes."""
last_size = (0, 0)
while True:
current_size = get_terminal_size()
if current_size != last_size:
cols, rows = current_size
print(f"Terminal resized to {cols}×{rows}")
# Adjust UI layout based on new size
if cols < 80:
use_compact_layout()
else:
use_full_layout()
last_size = current_size
await asyncio.sleep(0.5)| Feature | Windows | macOS | Linux | Notes |
|---|---|---|---|---|
| Clear Screen | ✅ cls |
✅ clear |
✅ clear |
Platform-specific commands |
| Terminal Reset | ❌ | ✅ stty sane |
✅ stty sane |
Unix-only feature |
| Size Detection | ✅ | ✅ | ✅ | Falls back to 80×24 |
| Set Title | ✅ title |
✅ ANSI | ✅ ANSI | May not work in all terminals |
| Progress in Title | ✅ | ✅ | ✅ | Uses set title functionality |
| Color Support | ✅ | ✅ | ✅ | Based on TTY detection |
| 256/True Color Detection | ✅ | ✅ | ✅ | Via environment variables |
| Cursor Control | ❌ | ✅ ANSI | ✅ ANSI | Unix-only, uses escape sequences |
| Hyperlinks (OSC 8) | ✅ | ✅ | Terminal-dependent support | |
| Terminal Bell | ✅ | ✅ | ✅ | May be disabled in settings |
| Alternate Screen | ❌ | ✅ ANSI | ✅ ANSI | Unix-only feature |
| Terminal Info | ✅ | ✅ | ✅ | Environment detection |
| Asyncio Cleanup | ✅ | ✅ | ✅ | Cross-platform |
try:
run_application()
finally:
restore_terminal()import sys
if not sys.stdout.isatty():
print("Warning: Not running in a terminal")
# Disable color output, interactive featuresdef setup_ui():
if TerminalManager.supports_color():
enable_rich_output()
else:
use_plain_text_mode()
cols, rows = get_terminal_size()
if cols < 80:
use_narrow_layout()try:
set_terminal_title("My App")
except Exception:
# Some terminals don't support title changes
passThe UI system automatically handles theme differences. Never check themes in application code:
# ✅ GOOD - Theme-agnostic
from rich.table import Table
from mcp_cli.ui.output import get_output
ui = get_output()
# Create table normally
table = Table(show_header=True, title="Status")
table.add_column("Property")
table.add_column("Value")
table.add_row("Size", f"{cols}×{rows}")
table.add_row("Color", "Supported" if has_color else "Not supported")
# Let the UI system handle theme-appropriate rendering
ui.print_table(table) # Automatically converts to text for minimal/terminal themes
# ❌ BAD - Theme-aware (don't do this!)
if theme.name == "minimal":
print(f"Size: {cols}×{rows}")
else:
# Create rich table
...The output system automatically:
- Renders Rich tables with full formatting for default/dark/light themes
- Converts tables to aligned plain text for minimal/terminal themes
- Handles all theme-specific formatting internally
A comprehensive terminal demo script is available:
# Run the terminal management demo
uv run examples/ui_terminal_demo.pyThe demo includes:
- Terminal Information - Display comprehensive terminal details
- Clear Screen - Platform-specific screen clearing
- Terminal Title - Dynamic title updates
- Terminal Reset - Reset to sane defaults (Unix)
- Size Detection - Monitor terminal resizing
- Color Support - Check color capabilities (16/256/truecolor)
- Cursor Control - Hide/show, move, save/restore cursor position
- Hyperlinks - Clickable links in supported terminals
- Terminal Bell - System beep/flash demonstration
- Alternate Screen - Full-screen buffer like vim/less
- Asyncio Tasks - Task management and cleanup
- Theme Integration - Terminal features with themes
- Stress Test - Rapid terminal operations
All terminal operations handle errors gracefully:
- Missing TTY: Operations degrade gracefully
- Permission Errors: Logged but don't crash
- Platform Limitations: Fallback behaviors provided
- Asyncio Errors: Caught and logged during cleanup
- UI Themes - Theme system that works with terminal features
- Output System - Rich terminal output
- Prompts - Interactive terminal prompts
- Testing - Terminal management tests (99% coverage)