#!/usr/bin/env python3
"""
Calculator MCP Server with Authentication
"""
import asyncio
import os
import hashlib
import hmac
from typing import Any
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
# Authentication configuration
API_KEY = os.getenv("CALCULATOR_API_KEY", "")
REQUIRE_AUTH = os.getenv("CALCULATOR_REQUIRE_AUTH", "true").lower() == "true"
# Create server instance
app = Server("calculator-server-auth")
def verify_auth(auth_token: str = None) -> bool:
"""Verify authentication token."""
if not REQUIRE_AUTH:
return True
if not API_KEY:
print("Warning: No API key configured but authentication is required")
return False
if not auth_token:
return False
# Simple comparison (in production, use proper token validation)
return hmac.compare_digest(auth_token, API_KEY)
@app.list_tools()
async def list_tools() -> list[Tool]:
"""List available calculator tools."""
return [
Tool(
name="add",
description="Add two numbers together (requires authentication)",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Second number"},
"auth_token": {"type": "string", "description": "Authentication token"}
},
"required": ["a", "b"]
}
),
Tool(
name="subtract",
description="Subtract second number from first (requires authentication)",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Second number"},
"auth_token": {"type": "string", "description": "Authentication token"}
},
"required": ["a", "b"]
}
),
Tool(
name="multiply",
description="Multiply two numbers (requires authentication)",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Second number"},
"auth_token": {"type": "string", "description": "Authentication token"}
},
"required": ["a", "b"]
}
),
]
@app.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
"""Handle tool calls with authentication."""
# Extract auth token from arguments
auth_token = arguments.get("auth_token", "")
# Verify authentication
if not verify_auth(auth_token):
return [TextContent(
type="text",
text="Error: Authentication failed. Invalid or missing API key."
)]
try:
if name == "add":
a = arguments["a"]
b = arguments["b"]
result = a + b
return [TextContent(
type="text",
text=f"{a} + {b} = {result}"
)]
elif name == "subtract":
a = arguments["a"]
b = arguments["b"]
result = a - b
return [TextContent(
type="text",
text=f"{a} - {b} = {result}"
)]
elif name == "multiply":
a = arguments["a"]
b = arguments["b"]
result = a * b
return [TextContent(
type="text",
text=f"{a} × {b} = {result}"
)]
else:
return [TextContent(
type="text",
text=f"Error: Unknown tool '{name}'"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"Error: {str(e)}"
)]
async def main():
"""Run the calculator MCP server with authentication."""
if REQUIRE_AUTH and not API_KEY:
print("ERROR: CALCULATOR_API_KEY environment variable not set")
print("Set it with: export CALCULATOR_API_KEY='your-secret-key'")
return
print(f"Starting calculator server (Auth: {'enabled' if REQUIRE_AUTH else 'disabled'})")
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())