* WIP: new workflow architecture * WIP: further improvements and cleanup * WIP: cleanup and docks, replace old tool with new * WIP: cleanup and docks, replace old tool with new * WIP: new planner implementation using workflow * WIP: precommit tool working as a workflow instead of a basic tool Support for passing False to use_assistant_model to skip external models completely and use Claude only * WIP: precommit workflow version swapped with old * WIP: codereview * WIP: replaced codereview * WIP: replaced codereview * WIP: replaced refactor * WIP: workflow for thinkdeep * WIP: ensure files get embedded correctly * WIP: thinkdeep replaced with workflow version * WIP: improved messaging when an external model's response is received * WIP: analyze tool swapped * WIP: updated tests * Extract only the content when building history * Use "relevant_files" for workflow tools only * WIP: updated tests * Extract only the content when building history * Use "relevant_files" for workflow tools only * WIP: fixed get_completion_next_steps_message missing param * Fixed tests Request for files consistently * Fixed tests Request for files consistently * Fixed tests * New testgen workflow tool Updated docs * Swap testgen workflow * Fix CI test failures by excluding API-dependent tests - Update GitHub Actions workflow to exclude simulation tests that require API keys - Fix collaboration tests to properly mock workflow tool expert analysis calls - Update test assertions to handle new workflow tool response format - Ensure unit tests run without external API dependencies in CI 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * WIP - Update tests to match new tools * WIP - Update tests to match new tools * WIP - Update tests to match new tools * Should help with https://github.com/BeehiveInnovations/zen-mcp-server/issues/97 Clear python cache when running script: https://github.com/BeehiveInnovations/zen-mcp-server/issues/96 Improved retry error logging Cleanup * WIP - chat tool using new architecture and improved code sharing * Removed todo * Removed todo * Cleanup old name * Tweak wordings * Tweak wordings Migrate old tests * Support for Flash 2.0 and Flash Lite 2.0 * Support for Flash 2.0 and Flash Lite 2.0 * Support for Flash 2.0 and Flash Lite 2.0 Fixed test * Improved consensus to use the workflow base class * Improved consensus to use the workflow base class * Allow images * Allow images * Replaced old consensus tool * Cleanup tests * Tests for prompt size * New tool: docgen Tests for prompt size Fixes: https://github.com/BeehiveInnovations/zen-mcp-server/issues/107 Use available token size limits: https://github.com/BeehiveInnovations/zen-mcp-server/issues/105 * Improved docgen prompt Exclude TestGen from pytest inclusion * Updated errors * Lint * DocGen instructed not to fix bugs, surface them and stick to d * WIP * Stop claude from being lazy and only documenting a small handful * More style rules --------- Co-authored-by: Claude <noreply@anthropic.com>
351 lines
13 KiB
Python
351 lines
13 KiB
Python
"""
|
|
Version Tool - Display Zen MCP Server version and system information
|
|
|
|
This tool provides version information about the Zen MCP Server including
|
|
version number, last update date, author, and basic system information.
|
|
It also checks for updates from the GitHub repository.
|
|
"""
|
|
|
|
import logging
|
|
import platform
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
try:
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import urlopen
|
|
|
|
HAS_URLLIB = True
|
|
except ImportError:
|
|
HAS_URLLIB = False
|
|
|
|
from mcp.types import TextContent
|
|
|
|
from config import __author__, __updated__, __version__
|
|
from tools.models import ToolModelCategory, ToolOutput
|
|
from tools.shared.base_models import ToolRequest
|
|
from tools.shared.base_tool import BaseTool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def parse_version(version_str: str) -> tuple[int, int, int]:
|
|
"""
|
|
Parse version string to tuple of integers for comparison.
|
|
|
|
Args:
|
|
version_str: Version string like "5.5.5"
|
|
|
|
Returns:
|
|
Tuple of (major, minor, patch) as integers
|
|
"""
|
|
try:
|
|
parts = version_str.strip().split(".")
|
|
if len(parts) >= 3:
|
|
return (int(parts[0]), int(parts[1]), int(parts[2]))
|
|
elif len(parts) == 2:
|
|
return (int(parts[0]), int(parts[1]), 0)
|
|
elif len(parts) == 1:
|
|
return (int(parts[0]), 0, 0)
|
|
else:
|
|
return (0, 0, 0)
|
|
except (ValueError, IndexError):
|
|
return (0, 0, 0)
|
|
|
|
|
|
def compare_versions(current: str, remote: str) -> int:
|
|
"""
|
|
Compare two version strings.
|
|
|
|
Args:
|
|
current: Current version string
|
|
remote: Remote version string
|
|
|
|
Returns:
|
|
-1 if current < remote (update available)
|
|
0 if current == remote (up to date)
|
|
1 if current > remote (ahead of remote)
|
|
"""
|
|
current_tuple = parse_version(current)
|
|
remote_tuple = parse_version(remote)
|
|
|
|
if current_tuple < remote_tuple:
|
|
return -1
|
|
elif current_tuple > remote_tuple:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
|
|
def fetch_github_version() -> Optional[tuple[str, str]]:
|
|
"""
|
|
Fetch the latest version information from GitHub repository.
|
|
|
|
Returns:
|
|
Tuple of (version, last_updated) if successful, None if failed
|
|
"""
|
|
if not HAS_URLLIB:
|
|
logger.warning("urllib not available, cannot check for updates")
|
|
return None
|
|
|
|
github_url = "https://raw.githubusercontent.com/BeehiveInnovations/zen-mcp-server/main/config.py"
|
|
|
|
try:
|
|
# Set a 10-second timeout
|
|
with urlopen(github_url, timeout=10) as response:
|
|
if response.status != 200:
|
|
logger.warning(f"HTTP error while checking GitHub: {response.status}")
|
|
return None
|
|
|
|
content = response.read().decode("utf-8")
|
|
|
|
# Extract version using regex
|
|
version_match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
|
|
updated_match = re.search(r'__updated__\s*=\s*["\']([^"\']+)["\']', content)
|
|
|
|
if version_match:
|
|
remote_version = version_match.group(1)
|
|
remote_updated = updated_match.group(1) if updated_match else "Unknown"
|
|
return (remote_version, remote_updated)
|
|
else:
|
|
logger.warning("Could not parse version from GitHub config.py")
|
|
return None
|
|
|
|
except HTTPError as e:
|
|
logger.warning(f"HTTP error while checking GitHub: {e.code}")
|
|
return None
|
|
except URLError as e:
|
|
logger.warning(f"URL error while checking GitHub: {e.reason}")
|
|
return None
|
|
except Exception as e:
|
|
logger.warning(f"Error checking GitHub for updates: {e}")
|
|
return None
|
|
|
|
|
|
class VersionTool(BaseTool):
|
|
"""
|
|
Tool for displaying Zen MCP Server version and system information.
|
|
|
|
This tool provides:
|
|
- Current server version
|
|
- Last update date
|
|
- Author information
|
|
- Python version
|
|
- Platform information
|
|
"""
|
|
|
|
def get_name(self) -> str:
|
|
return "version"
|
|
|
|
def get_description(self) -> str:
|
|
return (
|
|
"VERSION & CONFIGURATION - Get server version, configuration details, and list of available tools. "
|
|
"Useful for debugging and understanding capabilities."
|
|
)
|
|
|
|
def get_input_schema(self) -> dict[str, Any]:
|
|
"""Return the JSON schema for the tool's input"""
|
|
return {"type": "object", "properties": {}, "required": []}
|
|
|
|
def get_system_prompt(self) -> str:
|
|
"""No AI model needed for this tool"""
|
|
return ""
|
|
|
|
def get_request_model(self):
|
|
"""Return the Pydantic model for request validation."""
|
|
return ToolRequest
|
|
|
|
async def prepare_prompt(self, request: ToolRequest) -> str:
|
|
"""Not used for this utility tool"""
|
|
return ""
|
|
|
|
def format_response(self, response: str, request: ToolRequest, model_info: dict = None) -> str:
|
|
"""Not used for this utility tool"""
|
|
return response
|
|
|
|
async def execute(self, arguments: dict[str, Any]) -> list[TextContent]:
|
|
"""
|
|
Display Zen MCP Server version and system information.
|
|
|
|
This overrides the base class execute to provide direct output without AI model calls.
|
|
|
|
Args:
|
|
arguments: Standard tool arguments (none required)
|
|
|
|
Returns:
|
|
Formatted version and system information
|
|
"""
|
|
output_lines = ["# Zen MCP Server Version\n"]
|
|
|
|
# Server version information
|
|
output_lines.append("## Server Information")
|
|
output_lines.append(f"**Current Version**: {__version__}")
|
|
output_lines.append(f"**Last Updated**: {__updated__}")
|
|
output_lines.append(f"**Author**: {__author__}")
|
|
|
|
# Get the current working directory (MCP server location)
|
|
current_path = Path.cwd()
|
|
output_lines.append(f"**Installation Path**: `{current_path}`")
|
|
output_lines.append("")
|
|
|
|
# Check for updates from GitHub
|
|
output_lines.append("## Update Status")
|
|
|
|
try:
|
|
github_info = fetch_github_version()
|
|
|
|
if github_info:
|
|
remote_version, remote_updated = github_info
|
|
comparison = compare_versions(__version__, remote_version)
|
|
|
|
output_lines.append(f"**Latest Version (GitHub)**: {remote_version}")
|
|
output_lines.append(f"**Latest Updated**: {remote_updated}")
|
|
|
|
if comparison < 0:
|
|
# Update available
|
|
output_lines.append("")
|
|
output_lines.append("🚀 **UPDATE AVAILABLE!**")
|
|
output_lines.append(
|
|
f"Your version `{__version__}` is older than the latest version `{remote_version}`"
|
|
)
|
|
output_lines.append("")
|
|
output_lines.append("**To update:**")
|
|
output_lines.append("```bash")
|
|
output_lines.append(f"cd {current_path}")
|
|
output_lines.append("git pull")
|
|
output_lines.append("```")
|
|
output_lines.append("")
|
|
output_lines.append("*Note: Restart your Claude session after updating to use the new version.*")
|
|
elif comparison == 0:
|
|
# Up to date
|
|
output_lines.append("")
|
|
output_lines.append("✅ **UP TO DATE**")
|
|
output_lines.append("You are running the latest version.")
|
|
else:
|
|
# Ahead of remote (development version)
|
|
output_lines.append("")
|
|
output_lines.append("🔬 **DEVELOPMENT VERSION**")
|
|
output_lines.append(
|
|
f"Your version `{__version__}` is ahead of the published version `{remote_version}`"
|
|
)
|
|
output_lines.append("You may be running a development or custom build.")
|
|
else:
|
|
output_lines.append("❌ **Could not check for updates**")
|
|
output_lines.append("Unable to connect to GitHub or parse version information.")
|
|
output_lines.append("Check your internet connection or try again later.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during version check: {e}")
|
|
output_lines.append("❌ **Error checking for updates**")
|
|
output_lines.append(f"Error: {str(e)}")
|
|
|
|
output_lines.append("")
|
|
|
|
# Python and system information
|
|
output_lines.append("## System Information")
|
|
output_lines.append(
|
|
f"**Python Version**: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
)
|
|
output_lines.append(f"**Platform**: {platform.system()} {platform.release()}")
|
|
output_lines.append(f"**Architecture**: {platform.machine()}")
|
|
output_lines.append("")
|
|
|
|
# Available tools
|
|
try:
|
|
# Import here to avoid circular imports
|
|
from server import TOOLS
|
|
|
|
tool_names = sorted(TOOLS.keys())
|
|
output_lines.append("## Available Tools")
|
|
output_lines.append(f"**Total Tools**: {len(tool_names)}")
|
|
output_lines.append("\n**Tool List**:")
|
|
|
|
for tool_name in tool_names:
|
|
tool = TOOLS[tool_name]
|
|
# Get the first line of the tool's description for a brief summary
|
|
description = tool.get_description().split("\n")[0]
|
|
# Truncate if too long
|
|
if len(description) > 80:
|
|
description = description[:77] + "..."
|
|
output_lines.append(f"- `{tool_name}` - {description}")
|
|
|
|
output_lines.append("")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Error loading tools list: {e}")
|
|
output_lines.append("## Available Tools")
|
|
output_lines.append("**Error**: Could not load tools list")
|
|
output_lines.append("")
|
|
|
|
# Configuration information
|
|
output_lines.append("## Configuration")
|
|
|
|
# Check for configured providers
|
|
try:
|
|
from providers.base import ProviderType
|
|
from providers.registry import ModelProviderRegistry
|
|
|
|
provider_status = []
|
|
|
|
# Check each provider type
|
|
provider_types = [
|
|
ProviderType.GOOGLE,
|
|
ProviderType.OPENAI,
|
|
ProviderType.XAI,
|
|
ProviderType.OPENROUTER,
|
|
ProviderType.CUSTOM,
|
|
]
|
|
provider_names = ["Google Gemini", "OpenAI", "X.AI", "OpenRouter", "Custom/Local"]
|
|
|
|
for provider_type, provider_name in zip(provider_types, provider_names):
|
|
provider = ModelProviderRegistry.get_provider(provider_type)
|
|
status = "✅ Configured" if provider is not None else "❌ Not configured"
|
|
provider_status.append(f"- **{provider_name}**: {status}")
|
|
|
|
output_lines.append("**Providers**:")
|
|
output_lines.extend(provider_status)
|
|
|
|
# Get total available models
|
|
try:
|
|
available_models = ModelProviderRegistry.get_available_models(respect_restrictions=True)
|
|
output_lines.append(f"\n**Available Models**: {len(available_models)}")
|
|
except Exception:
|
|
output_lines.append("\n**Available Models**: Unknown")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"Error checking provider configuration: {e}")
|
|
output_lines.append("**Providers**: Error checking configuration")
|
|
|
|
output_lines.append("")
|
|
|
|
# Usage information
|
|
output_lines.append("## Usage")
|
|
output_lines.append("- Use `listmodels` tool to see all available AI models")
|
|
output_lines.append("- Use `chat` for interactive conversations and brainstorming")
|
|
output_lines.append("- Use workflow tools (`debug`, `codereview`, `docgen`, etc.) for systematic analysis")
|
|
output_lines.append("- Set DEFAULT_MODEL=auto to let Claude choose the best model for each task")
|
|
|
|
# Format output
|
|
content = "\n".join(output_lines)
|
|
|
|
tool_output = ToolOutput(
|
|
status="success",
|
|
content=content,
|
|
content_type="text",
|
|
metadata={
|
|
"tool_name": self.name,
|
|
"server_version": __version__,
|
|
"last_updated": __updated__,
|
|
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
"platform": f"{platform.system()} {platform.release()}",
|
|
},
|
|
)
|
|
|
|
return [TextContent(type="text", text=tool_output.model_dump_json())]
|
|
|
|
def get_model_category(self) -> ToolModelCategory:
|
|
"""Return the model category for this tool."""
|
|
return ToolModelCategory.FAST_RESPONSE # Simple version info, no AI needed
|