# server.py
from fastmcp import FastMCP
import os
import json
import hashlib
import base64
import datetime
from pathlib import Path
mcp = FastMCP("local-utils", version="0.1.0")
@mcp.tool(
description="Convert between °C and °F"
)
async def convert_temp(value: float, unit: str) -> str:
if unit == "C":
return f"{(value * 9/5) + 32:.2f} °F"
if unit == "F":
return f"{(value - 32) * 5/9:.2f} °C"
raise ValueError("unit must be C or F")
@mcp.tool(
description="Read the contents of a text file"
)
async def read_file(file_path: str) -> str:
"""Read and return the contents of a text file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return f"File contents of {file_path}:\n\n{content}"
except FileNotFoundError:
return f"Error: File '{file_path}' not found."
except Exception as e:
return f"Error reading file: {str(e)}"
@mcp.tool(
description="Write text content to a file"
)
async def write_file(file_path: str, content: str) -> str:
"""Write content to a text file."""
try:
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return f"Successfully wrote content to {file_path}"
except Exception as e:
return f"Error writing file: {str(e)}"
@mcp.tool(
description="List files and directories in a given path"
)
async def list_directory(directory_path: str = ".") -> str:
"""List contents of a directory."""
try:
path = Path(directory_path)
if not path.exists():
return f"Error: Directory '{directory_path}' does not exist."
items = []
for item in sorted(path.iterdir()):
if item.is_dir():
items.append(f"📁 {item.name}/")
else:
size = item.stat().st_size
items.append(f"📄 {item.name} ({size} bytes)")
return f"Contents of {directory_path}:\n" + "\n".join(items)
except Exception as e:
return f"Error listing directory: {str(e)}"
@mcp.tool(
description="Calculate hash (MD5, SHA1, SHA256) of text or file"
)
async def calculate_hash(text_or_path: str, hash_type: str = "sha256", is_file: bool = False) -> str:
"""Calculate hash of text or file content."""
try:
hash_functions = {
"md5": hashlib.md5,
"sha1": hashlib.sha1,
"sha256": hashlib.sha256
}
if hash_type.lower() not in hash_functions:
return f"Error: Unsupported hash type. Use: {', '.join(hash_functions.keys())}"
hash_func = hash_functions[hash_type.lower()]()
if is_file:
with open(text_or_path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_func.update(chunk)
result = hash_func.hexdigest()
return f"{hash_type.upper()} hash of file '{text_or_path}': {result}"
else:
hash_func.update(text_or_path.encode('utf-8'))
result = hash_func.hexdigest()
return f"{hash_type.upper()} hash of text: {result}"
except Exception as e:
return f"Error calculating hash: {str(e)}"
@mcp.tool(
description="Encode/decode text using Base64"
)
async def base64_encode_decode(text: str, operation: str = "encode") -> str:
"""Encode or decode text using Base64."""
try:
if operation.lower() == "encode":
encoded = base64.b64encode(text.encode('utf-8')).decode('utf-8')
return f"Base64 encoded: {encoded}"
elif operation.lower() == "decode":
decoded = base64.b64decode(text).decode('utf-8')
return f"Base64 decoded: {decoded}"
else:
return "Error: Operation must be 'encode' or 'decode'"
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool(
description="Get current date and time information"
)
async def get_datetime_info(format_string: str = "%Y-%m-%d %H:%M:%S") -> str:
"""Get current date and time with optional custom format."""
try:
now = datetime.datetime.now()
formatted_time = now.strftime(format_string)
info = f"""Current Date & Time Information:
Formatted: {formatted_time}
ISO Format: {now.isoformat()}
Timestamp: {now.timestamp()}
Weekday: {now.strftime('%A')}
Year: {now.year}
Month: {now.month} ({now.strftime('%B')})
Day: {now.day}
Hour: {now.hour}
Minute: {now.minute}
Second: {now.second}"""
return info
except Exception as e:
return f"Error getting datetime info: {str(e)}"
@mcp.tool(
description="Count words, characters, and lines in text"
)
async def text_stats(text: str) -> str:
"""Calculate statistics for the given text."""
lines = text.split('\n')
words = text.split()
characters = len(text)
characters_no_spaces = len(text.replace(' ', ''))
return f"""Text Statistics:
Lines: {len(lines)}
Words: {len(words)}
Characters (with spaces): {characters}
Characters (without spaces): {characters_no_spaces}
Average words per line: {len(words) / len(lines):.2f}
Average characters per word: {characters / len(words):.2f if words else 0}"""
@mcp.tool(
description="Generate a simple password with specified length and character sets"
)
async def generate_password(length: int = 12, include_uppercase: bool = True,
include_lowercase: bool = True, include_numbers: bool = True,
include_symbols: bool = False) -> str:
"""Generate a random password."""
import random
import string
if length < 1:
return "Error: Password length must be at least 1"
characters = ""
if include_lowercase:
characters += string.ascii_lowercase
if include_uppercase:
characters += string.ascii_uppercase
if include_numbers:
characters += string.digits
if include_symbols:
characters += "!@#$%^&*()_+-=[]{}|;:,.<>?"
if not characters:
return "Error: At least one character set must be selected"
password = ''.join(random.choice(characters) for _ in range(length))
return f"Generated password: {password}"
if __name__ == "__main__":
import sys
import logging
# Set up basic logging to help with debugging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stderr)
]
)
logger = logging.getLogger("local-utils")
logger.info("Starting Local Utilities MCP Server...")
logger.info(f"Python version: {sys.version}")
logger.info(f"Working directory: {os.getcwd()}")
try:
# Transport can be 'stdio' (default), 'http', or 'sse'
mcp.run("stdio") # Explicitly specify stdio transport
except Exception as e:
logger.error(f"Server error: {e}")
sys.exit(1)