Add --copy flag for fastmcp version

This commit is contained in:
Jeremiah Lowin 2025-07-06 21:10:45 -04:00
commit e9fa9fcf0d
5 changed files with 64 additions and 14 deletions

View file

@ -13,6 +13,7 @@ dependencies = [
"cyclopts>=3.0.0",
"authlib>=1.5.2",
"pydantic[email]>=2.11.7",
"pyperclip>=1.9.0",
]
requires-python = ">=3.10"
readme = "README.md"

View file

@ -10,6 +10,7 @@ from pathlib import Path
from typing import Annotated, Literal
import cyclopts
import pyperclip
from pydantic import TypeAdapter
from rich.console import Console
from rich.table import Table
@ -85,7 +86,17 @@ def _build_uv_command(
@app.command
def version():
def version(
*,
copy: Annotated[
bool,
cyclopts.Parameter(
"--copy",
help="Copy version information to clipboard",
negative=False,
),
] = False,
):
"""Display version information and platform details."""
info = {
"FastMCP version": fastmcp.__version__,
@ -100,7 +111,15 @@ def version():
g.add_column(style="cyan", justify="right")
for k, v in info.items():
g.add_row(k + ":", str(v).replace("\n", " "))
console.print(g)
if copy:
# Use Rich's capture to get text representation
with console.capture() as capture:
console.print(g)
pyperclip.copy(capture.get())
console.print("[green]✓[/green] Version information copied to clipboard")
else:
console.print(g)
sys.exit(0)

View file

@ -6,6 +6,7 @@ from pathlib import Path
from typing import Annotated
import cyclopts
import pyperclip
from rich import print
from fastmcp.utilities.logging import get_logger
@ -79,18 +80,8 @@ def install_mcp_config(
# Handle output
if copy:
try:
import pyperclip
pyperclip.copy(json_output)
print(
f"[green]MCP configuration for '{name}' copied to clipboard[/green]"
)
except ImportError:
print(
"[red]The --copy flag requires pyperclip. Please install pyperclip and try again: pip install pyperclip[/red]"
)
return False
pyperclip.copy(json_output)
print(f"[green]MCP configuration for '{name}' copied to clipboard[/green]")
else:
# Print to stdout (for piping)
print(json_output)

View file

@ -108,6 +108,43 @@ class TestVersionCommand:
mock_print.assert_called_once()
mock_exit.assert_called_once_with(0)
def test_version_command_parsing(self):
"""Test that the version command parses arguments correctly."""
command, bound, _ = app.parse_args(["version"])
assert command.__name__ == "version"
# Default arguments aren't included in bound.arguments
assert bound.arguments == {}
def test_version_command_with_copy_flag(self):
"""Test that the version command parses --copy flag correctly."""
command, bound, _ = app.parse_args(["version", "--copy"])
assert command.__name__ == "version"
assert bound.arguments == {"copy": True}
@patch("fastmcp.cli.cli.sys.exit")
@patch("fastmcp.cli.cli.pyperclip.copy")
@patch("fastmcp.cli.cli.console")
def test_version_command_copy_functionality(
self, mock_console, mock_pyperclip_copy, mock_exit
):
"""Test that the version command copies to clipboard when --copy is used."""
# Mock console.capture
mock_capture = Mock()
mock_capture.get.return_value = "FastMCP version: 1.0.0\nMCP version: 1.10.0"
mock_console.capture.return_value.__enter__.return_value = mock_capture
mock_console.capture.return_value.__exit__.return_value = None
command, bound, _ = app.parse_args(["version", "--copy"])
command(**bound.arguments)
mock_pyperclip_copy.assert_called_once_with(
"FastMCP version: 1.0.0\nMCP version: 1.10.0"
)
mock_console.print.assert_called_with(
"[green]✓[/green] Version information copied to clipboard"
)
mock_exit.assert_called_once_with(0)
class TestDevCommand:
"""Test the dev command."""

2
uv.lock generated
View file

@ -502,6 +502,7 @@ dependencies = [
{ name = "mcp" },
{ name = "openapi-pydantic" },
{ name = "pydantic", extra = ["email"] },
{ name = "pyperclip" },
{ name = "python-dotenv" },
{ name = "rich" },
]
@ -544,6 +545,7 @@ requires-dist = [
{ name = "mcp", specifier = ">=1.10.0" },
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
{ name = "pyperclip", specifier = ">=1.9.0" },
{ name = "python-dotenv", specifier = ">=1.1.0" },
{ name = "rich", specifier = ">=13.9.4" },
{ name = "websockets", marker = "extra == 'websockets'", specifier = ">=15.0.1" },