#!/usr/bin/env python3
"""
MCP server for exact math operations.
Tools:
- mikey_math_eval: Evaluate expression with exact arithmetic
- mikey_math_solve: Solve equation exactly
- mikey_math_verify: Verify answer with checks
- mikey_math_crt: Chinese Remainder Theorem
- mikey_math_mod: Modular arithmetic
- mikey_math_export: Export helpers for notebook
"""
import json
import sys
from typing import Any
# Add src to path for imports
sys.path.insert(0, str(__file__).replace('/server.py', ''))
from mcp.server import Server
from mcp.types import Tool, TextContent
from core.exact import (
exact_eval, exact_solve, exact_simplify, exact_factor,
crt, mod_exp, mod_inv, comb, perm, gcd_many, lcm_many,
prime_factors, all_divisors, euler_phi, format_answer, frac
)
from core.verify import (
is_integer, is_positive, is_in_range, check_mod,
verify_answer, sanity_check, verify_equation
)
server = Server("mcp-math")
@server.list_tools()
async def list_tools():
return [
Tool(
name="mikey_math_eval",
description="Evaluate mathematical expression with exact arithmetic. Returns exact fractions/integers, not floats.",
inputSchema={
"type": "object",
"properties": {
"expr": {"type": "string", "description": "Expression like '1/3 + 1/4' or 'sqrt(2) * 3'"},
"exact": {"type": "boolean", "default": True, "description": "Return exact form (True) or float (False)"}
},
"required": ["expr"]
}
),
Tool(
name="mikey_math_solve",
description="Solve equation exactly. Returns list of solutions.",
inputSchema={
"type": "object",
"properties": {
"equation": {"type": "string", "description": "Equation like 'x^2 - 4 = 0' or expression (= 0 assumed)"},
"var": {"type": "string", "default": "x", "description": "Variable to solve for"}
},
"required": ["equation"]
}
),
Tool(
name="mikey_math_verify",
description="Verify an answer with multiple checks (integer, range, mod, equation, etc.)",
inputSchema={
"type": "object",
"properties": {
"value": {"description": "The answer to verify"},
"checks": {
"type": "object",
"description": "Checks to run: {integer: true, positive: true, range: [0, 100], mod: {modulus: 7, expected: 3}, equation: 'x^2=16'}"
},
"problem_type": {
"type": "string",
"enum": ["counting", "probability", "geometry", "number_theory", "general"],
"default": "general"
}
},
"required": ["value"]
}
),
Tool(
name="mikey_math_crt",
description="Chinese Remainder Theorem: find x where x ≡ residues[i] (mod moduli[i])",
inputSchema={
"type": "object",
"properties": {
"residues": {"type": "array", "items": {"type": "integer"}, "description": "List of remainders"},
"moduli": {"type": "array", "items": {"type": "integer"}, "description": "List of moduli"}
},
"required": ["residues", "moduli"]
}
),
Tool(
name="mikey_math_mod",
description="Modular arithmetic: power, inverse, or simple mod",
inputSchema={
"type": "object",
"properties": {
"op": {"type": "string", "enum": ["pow", "inv", "mod"], "description": "Operation"},
"a": {"type": "integer", "description": "Base or value"},
"b": {"type": "integer", "description": "Exponent (for pow) or ignored (for inv/mod)"},
"m": {"type": "integer", "description": "Modulus"}
},
"required": ["op", "a", "m"]
}
),
Tool(
name="mikey_math_factor",
description="Factorize integer or algebraic expression",
inputSchema={
"type": "object",
"properties": {
"value": {"description": "Integer or expression string"},
"type": {"type": "string", "enum": ["integer", "algebraic"], "default": "integer"}
},
"required": ["value"]
}
),
Tool(
name="mikey_math_comb",
description="Combinatorics: binomial coefficient C(n,k), permutation P(n,k), or factorial",
inputSchema={
"type": "object",
"properties": {
"op": {"type": "string", "enum": ["comb", "perm", "factorial"]},
"n": {"type": "integer"},
"k": {"type": "integer", "description": "Required for comb and perm"}
},
"required": ["op", "n"]
}
),
Tool(
name="mikey_math_export",
description="Export the core math helpers as Python code for Jupyter notebooks",
inputSchema={
"type": "object",
"properties": {
"include": {
"type": "array",
"items": {"type": "string"},
"description": "Which modules: ['exact', 'verify', 'all']",
"default": ["all"]
}
}
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
try:
if name == "mikey_math_eval":
expr = arguments["expr"]
exact = arguments.get("exact", True)
result = exact_eval(expr, exact=exact)
return [TextContent(type="text", text=f"Result: {result}\nType: {type(result).__name__}")]
elif name == "mikey_math_solve":
equation = arguments["equation"]
var = arguments.get("var", "x")
solutions = exact_solve(equation, var)
return [TextContent(type="text", text=f"Solutions: {solutions}")]
elif name == "mikey_math_verify":
value = arguments["value"]
checks = arguments.get("checks", {})
problem_type = arguments.get("problem_type", "general")
# Run sanity checks
sanity_ok, sanity_msg = sanity_check(value, problem_type)
# Run specific checks if provided
if checks:
check_ok, check_msg = verify_answer(value, checks)
else:
check_ok, check_msg = True, "No specific checks requested"
overall = sanity_ok and check_ok
return [TextContent(type="text", text=f"Overall: {'PASS' if overall else 'FAIL'}\nSanity: {sanity_msg}\nChecks: {check_msg}")]
elif name == "mikey_math_crt":
residues = arguments["residues"]
moduli = arguments["moduli"]
result = crt(residues, moduli)
return [TextContent(type="text", text=f"x ≡ {result} (mod {lcm_many(moduli)})")]
elif name == "mikey_math_mod":
op = arguments["op"]
a = arguments["a"]
m = arguments["m"]
if op == "pow":
b = arguments.get("b", 1)
result = mod_exp(a, b, m)
elif op == "inv":
result = mod_inv(a, m)
else: # mod
result = a % m
return [TextContent(type="text", text=f"Result: {result}")]
elif name == "mikey_math_factor":
value = arguments["value"]
typ = arguments.get("type", "integer")
if typ == "integer":
factors = prime_factors(int(value))
factor_str = " × ".join(f"{p}^{e}" if e > 1 else str(p) for p, e in sorted(factors.items()))
return [TextContent(type="text", text=f"{value} = {factor_str}\nFactors: {factors}")]
else:
result = exact_factor(str(value))
return [TextContent(type="text", text=f"Factored: {result}")]
elif name == "mikey_math_comb":
op = arguments["op"]
n = arguments["n"]
if op == "factorial":
from math import factorial
result = factorial(n)
elif op == "comb":
k = arguments["k"]
result = comb(n, k)
elif op == "perm":
k = arguments["k"]
result = perm(n, k)
return [TextContent(type="text", text=f"Result: {result}")]
elif name == "mikey_math_export":
include = arguments.get("include", ["all"])
# Read the source files
import os
src_dir = os.path.dirname(__file__)
code_parts = []
code_parts.append('"""Math helpers for exact arithmetic and verification."""\n')
code_parts.append("from fractions import Fraction")
code_parts.append("from functools import reduce")
code_parts.append("from math import gcd, factorial")
code_parts.append("")
if "all" in include or "exact" in include:
with open(os.path.join(src_dir, "core", "exact.py")) as f:
code_parts.append("# === EXACT ARITHMETIC ===")
# Skip imports, get just the functions
content = f.read()
# Extract functions after the imports
start = content.find("# ===")
if start != -1:
code_parts.append(content[start:])
if "all" in include or "verify" in include:
with open(os.path.join(src_dir, "core", "verify.py")) as f:
code_parts.append("\n# === VERIFICATION ===")
content = f.read()
start = content.find("# ===")
if start != -1:
code_parts.append(content[start:])
return [TextContent(type="text", text="\n".join(code_parts))]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
except Exception as e:
return [TextContent(type="text", text=f"Error: {type(e).__name__}: {e}")]
async def main():
from mcp.server.stdio import stdio_server
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())