#!/usr/bin/env python3
"""
MCP Server with Calculator Tools
"""
import asyncio
import math
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from mcp.server.fastmcp import FastMCP
# Create a dataclass for our application context
@dataclass
class CalculatorContext:
"""Context for the Calculator MCP server."""
pass
@asynccontextmanager
async def calculator_lifespan(server: FastMCP) -> AsyncIterator[CalculatorContext]:
"""
Manages the Calculator server lifecycle.
Args:
server: The FastMCP server instance
Yields:
CalculatorContext: The context for Calculator operations
"""
try:
yield CalculatorContext()
finally:
# No explicit cleanup needed
pass
# Initialize the MCP server
mcp = FastMCP(
"calculator-server",
lifespan=calculator_lifespan
)
# Path to the meeting summary template (relative to script location)
SCRIPT_DIR = Path(__file__).parent
# Path to the TypeScript SDK documentation (optional - set via TYPESDK_PATH env var)
# Default: typesdk.md in the same directory as this script
TYPESDK_PATH = Path(os.getenv("TYPESDK_PATH", str(SCRIPT_DIR / "typesdk.md")))
MEETING_TEMPLATE_PATH = SCRIPT_DIR / "meeting_summary_template.md"
@mcp.tool()
def add(a: float, b: float) -> float:
"""
Add two numbers.
Args:
a: First number
b: Second number
Returns:
The sum of a and b
"""
return a + b
@mcp.tool()
def subtract(a: float, b: float) -> float:
"""
Subtract second number from first number.
Args:
a: First number (minuend)
b: Second number (subtrahend)
Returns:
The difference of a and b
"""
return a - b
@mcp.tool()
def multiply(a: float, b: float) -> float:
"""
Multiply two numbers.
Args:
a: First number
b: Second number
Returns:
The product of a and b
"""
return a * b
@mcp.tool()
def divide(a: float, b: float) -> float:
"""
Divide first number by second number.
Args:
a: Dividend
b: Divisor
Returns:
The quotient of a divided by b
"""
if b == 0:
raise ValueError("Division by zero is not allowed")
return a / b
@mcp.tool()
def power(base: float, exponent: float) -> float:
"""
Raise base to the power of exponent.
Args:
base: Base number
exponent: Exponent
Returns:
base raised to the power of exponent
"""
return base ** exponent
@mcp.tool()
def square_root(number: float) -> float:
"""
Calculate the square root of a number.
Args:
number: Number to find square root of (must be non-negative)
Returns:
The square root of the number
"""
if number < 0:
raise ValueError("Cannot calculate square root of negative number")
return math.sqrt(number)
@mcp.tool()
def modulo(a: float, b: float) -> float:
"""
Calculate the remainder when a is divided by b.
Args:
a: Dividend
b: Divisor
Returns:
The remainder of a divided by b
"""
if b == 0:
raise ValueError("Modulo by zero is not allowed")
return a % b
@mcp.tool()
def calculate(expression: str) -> str:
"""
Evaluate a mathematical expression.
Supports addition (+), subtraction (-), multiplication (*), division (/),
power (**), parentheses, and common math functions.
Args:
expression: Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5', '(3 + 4) * 2')
Returns:
The result of the expression as a string
"""
if not expression:
raise ValueError("Expression is required")
# Create a safe evaluation context
safe_dict = {
"__builtins__": {},
"abs": abs,
"round": round,
"min": min,
"max": max,
"sum": sum,
"pow": pow,
"sqrt": math.sqrt,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"log": math.log,
"log10": math.log10,
"exp": math.exp,
"pi": math.pi,
"e": math.e,
}
try:
result = eval(expression, safe_dict)
return f"Result: {result}"
except ZeroDivisionError:
raise ValueError("Division by zero")
except Exception as e:
raise ValueError(f"Invalid expression: {str(e)}")
@mcp.resource("typesdk://documentation")
def get_typesdk_documentation() -> str:
"""
Read the TypeScript SDK documentation markdown file.
Returns:
The contents of the TypeScript SDK documentation as a string
"""
try:
if not TYPESDK_PATH.exists():
raise FileNotFoundError(f"Documentation file not found at {TYPESDK_PATH}")
with open(TYPESDK_PATH, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
raise RuntimeError(f"Failed to read documentation: {str(e)}")
@mcp.prompt()
def meeting_summary(meeting_date: str = "", meeting_title: str = "", transcript: str = "") -> str:
"""
Generate a meeting summary prompt using the template.
Args:
meeting_date: The date of the meeting
meeting_title: The title or subject of the meeting
transcript: The meeting transcript or notes
Returns:
A formatted prompt string with template variables replaced
"""
try:
if not MEETING_TEMPLATE_PATH.exists():
raise FileNotFoundError(f"Template file not found at {MEETING_TEMPLATE_PATH}")
# Read the template
with open(MEETING_TEMPLATE_PATH, "r", encoding="utf-8") as f:
template = f.read()
# Replace template variables with provided values
formatted_prompt = template.replace("{{ meeting_date }}", meeting_date or "{{ meeting_date }}")
formatted_prompt = formatted_prompt.replace("{{ meeting_title }}", meeting_title or "{{ meeting_title }}")
formatted_prompt = formatted_prompt.replace("{{ transcript }}", transcript or "{{ transcript }}")
return formatted_prompt
except Exception as e:
raise RuntimeError(f"Failed to generate meeting summary prompt: {str(e)}")
async def main():
"""Main entry point for the MCP server."""
import sys
transport = os.getenv("TRANSPORT", "stdio").lower()
print("MCP Calculator Server starting...", file=sys.stderr)
print(f"Transport: {transport}", file=sys.stderr)
print("Available tools: add, subtract, multiply, divide, power, square_root, modulo, calculate", file=sys.stderr)
print(f"Available resources: typesdk://documentation", file=sys.stderr)
print("Available prompts: meeting_summary", file=sys.stderr)
if transport == "streamable-http":
# Run the MCP server with Streamable HTTP transport
host = os.getenv("HOST", "0.0.0.0")
port = int(os.getenv("PORT", "8000"))
print(f"Starting Streamable HTTP server on {host}:{port}", file=sys.stderr)
try:
# Try with host and port parameters
await mcp.run_streamable_http_async(host=host, port=port)
except TypeError:
# If host/port not supported, try without them
print("Warning: host/port parameters not supported, using defaults", file=sys.stderr)
await mcp.run_streamable_http_async()
else:
# Run the MCP server with stdio transport (default)
print("Starting stdio server...", file=sys.stderr)
await mcp.run_stdio_async()
# Run the MCP server
if __name__ == "__main__":
asyncio.run(main())