Compare commits

3 Commits
v1.0.0 ... main

Author SHA1 Message Date
Michal
350cd336d2 Add Dockerfile (stdio, python-slim); trim requirements to runtime deps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-03 01:34:28 +01:00
Michal
e4b5ea649f MCP compliance, safety and math fixes; English tool docs; symbolic dsolve
Fork of https://github.com/thinkitpossible/CalcMCP with:
- tools/list now publishes inputSchema (upstream used non-standard 'params'
  key, leaving clients with no parameter information)
- tool descriptions and parameter docs rewritten in English with examples
- JSON-RPC notifications are no longer answered; tool failures set isError
- raw eval() of tool arguments removed (json.loads/ast.literal_eval for
  matrices, sympy.lambdify for the numeric ODE right-hand side)
- complex eigenvalues serialize as 'a+bi' strings instead of crashing
- solve accepts '=' equations; ttest accepts {"popmean": ...} params
- new symbolic_tool operation 'dsolve' for symbolic ODE solutions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-03 01:27:21 +01:00
一条懒狗
a17b0c8017 Add Chinese README (only) 2025-12-07 17:32:49 +08:00
6 changed files with 590 additions and 88 deletions

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
# syntax=docker/dockerfile:1
FROM docker.io/library/python:3.12-slim
LABEL org.opencontainers.image.title="calcmcp" \
org.opencontainers.image.description="Scientific calculator MCP server (SymPy/NumPy/SciPy/pandas), stdio transport" \
org.opencontainers.image.source="https://mysources.co.uk/michal/CalcMCP"
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY calculator.py mcp_server.py ./
USER nobody
ENTRYPOINT ["python", "-u", "mcp_server.py"]

View File

@@ -1,9 +1,26 @@
# Scientific Calculator MCP Server # Scientific Calculator MCP Server
> A production-ready Model Context Protocol (MCP) server providing advanced mathematical calculation capabilities for AI models. Supports symbolic math (SymPy), numerical computing (NumPy/SciPy), data analysis (pandas), and image processing. > A Model Context Protocol (MCP) server providing advanced mathematical calculation capabilities for AI models. Supports symbolic math (SymPy), numerical computing (NumPy/SciPy), data analysis (pandas), and image processing.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
> **Fork notice** — this is a patched fork of
> [thinkitpossible/CalcMCP](https://github.com/thinkitpossible/CalcMCP).
> Changes relative to upstream:
>
> - **MCP compliance**: tools are published with a proper `inputSchema` (upstream used a
> non-standard `params` key, so clients saw no parameters at all); JSON-RPC notifications
> (`notifications/initialized`) are no longer answered with an error; tool failures set
> `isError: true` on the result.
> - **Tool descriptions rewritten in English**, with per-parameter docs and inline examples.
> - **Security**: removed all raw `eval()` of tool arguments (matrix inputs now parse via
> `json.loads`/`ast.literal_eval`, the numeric-ODE right-hand side via `sympy.lambdify`).
> - **Bug fixes**: eigenvalue/eigenvector results with complex values no longer crash JSON
> serialization (rendered as `a+bi` strings); `solve` accepts equations written with `=`;
> one-sample t-test accepts `params` as `{"popmean": ...}` or a bare number.
> - **New**: `symbolic_tool` operation `dsolve` — symbolic ODE solving
> (e.g. `Derivative(y(x), x, 2) + y(x) = 0` → `y(x) = C1*sin(x) + C2*cos(x)`).
## Quick Start ## Quick Start
### 1. Install Dependencies ### 1. Install Dependencies

415
README_ZH.md Normal file
View File

@@ -0,0 +1,415 @@
# Scientific Calculator MCP 服务器
> 一个生产级别的 Model Context Protocol(MCP)服务器,为 AI 模型提供高级数学计算功能。支持符号数学(SymPy)、数值计算(NumPy/SciPy)、数据分析(pandas)和图像处理。
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.8+](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://www.python.org/downloads/)
## 📋 目录
- [快速开始](#快速开始)
- [安装指南](#安装指南)
- [配置说明](#配置说明)
- [使用示例](#使用示例)
- [工具参考](#工具参考)
- [常见问题](#常见问题)
## 🚀 快速开始
### 第一步:安装依赖
```bash
pip install sympy numpy scipy pandas
```
### 第二步:配置 Claude Desktop
编辑 Claude Desktop 的配置文件 `claude_desktop_config.json`:
**Windows 用户:**
```json
{
"mcpServers": {
"scientific-calculator": {
"command": "python",
"args": ["-u", "F:\\AAchengguoofAI\\cuz_caculat\\mcp_server.py"]
}
}
}
```
**macOS/Linux 用户:**
```json
{
"mcpServers": {
"scientific-calculator": {
"command": "python3",
"args": ["-u", "/path/to/mcp_server.py"]
}
}
}
```
### 第三步:重启 Claude Desktop
在 Claude 中就能使用计算工具了。
---
## 📦 安装指南
### 方式 1:通过 pip 安装(推荐)
```bash
pip install mcp-scientific-calculator
```
### 方式 2:从源码安装
```bash
git clone https://github.com/thinkitpossible/CalcMCP
cd CalcMCP
pip install -r requirements.txt
```
### 方式 3:本地开发安装
```bash
git clone https://github.com/thinkitpossible/CalcMCP
cd CalcMCP
pip install -e .
```
---
## ⚙️ 配置说明
### 基础配置
确保在 `claude_desktop_config.json` 中配置了服务器路径:
```json
{
"mcpServers": {
"scientific-calculator": {
"command": "python",
"args": ["-u", "path/to/mcp_server.py"],
"env": {}
}
}
}
```
### 环境变量(可选)
```bash
# 设置日志级别(可选)
export LOG_LEVEL=DEBUG
# Windows PowerShell
$env:LOG_LEVEL = "DEBUG"
```
---
## 💡 使用示例
### 1. 符号数学运算
**简化表达式:**
```
用户:简化 (x**2 + 2*x + 1) / (x + 1)
工具调用:symbolic_tool
参数:
operation: "simplify"
expression: "(x**2 + 2*x + 1) / (x + 1)"
返回:x + 1
```
**求导数:**
```
用户:求 sin(x) + x^3 对 x 的导数
工具调用:symbolic_tool
参数:
operation: "derivative"
expression: "sin(x) + x**3"
variable: "x"
返回:cos(x) + 3*x**2
```
**求积分:**
```
用户:求 2*x 的不定积分
工具调用:symbolic_tool
参数:
operation: "integral"
expression: "2*x"
variable: "x"
返回:x**2
```
**解方程:**
```
用户:解方程 x^2 - 5*x + 6 = 0
工具调用:symbolic_tool
参数:
operation: "solve_equation"
equation: "x**2 - 5*x + 6"
variable: "x"
返回:[2, 3]
```
### 2. 数值计算
**线性方程组求解:**
```
用户:求解线性方程组:2x + y = 5, x + 3y = 7
工具调用:numpy_tool
参数:
operation: "linear_algebra"
array_data: [[2, 1], [1, 3]]
array_data_b: [5, 7]
返回:[2.0, 1.0]
```
**矩阵运算:**
```
用户:计算矩阵 [[1, 2], [3, 4]] 的行列式
工具调用:numpy_tool
参数:
operation: "determinant"
array_data: [[1, 2], [3, 4]]
返回:-2.0
```
### 3. 科学计算
**常微分方程求解:**
```
用户:求解 dy/dx = 2*x,初始条件 y(0)=0
工具调用:scipy_tool
参数:
operation: "solve_ode"
expression: "2*x"
initial_condition: 0
返回:数值解向量
```
**函数积分:**
```
用户:计算 ∫sin(x)dx 从 0 到 π
工具调用:scipy_tool
参数:
operation: "integrate"
expression: "sin(x)"
limits: "0:pi"
返回:2.0
```
---
## 🛠️ 工具参考
### symbolic_tool(符号数学)
提供以下操作:
| 操作 | 参数 | 返回 | 说明 |
|------|------|------|------|
| `simplify` | expression | str | 简化数学表达式 |
| `expand` | expression | str | 展开表达式 |
| `factor` | expression | str | 因式分解 |
| `derivative` | expression, variable, order | str | 求导数(可指定阶数) |
| `integral` | expression, variable, lower_limit, upper_limit | str | 求积分(定积分或不定积分) |
| `limit` | expression, variable, point | str | 求极限 |
| `solve_equation` | equation, variable | str | 解方程 |
| `matrix_multiply` | matrix1, matrix2 | str | 矩阵乘法 |
| `matrix_inverse` | matrix | str | 矩阵求逆 |
**使用示例:**
```json
{
"tool": "symbolic_tool",
"input": {
"operation": "simplify",
"expression": "x**2 + 2*x + 1"
}
}
```
### numpy_tool(数值计算)
提供以下操作:
| 操作 | 参数 | 返回 | 说明 |
|------|------|------|------|
| `linear_algebra` | array_data, array_data_b | list | 求解线性方程组 Ax=b |
| `determinant` | array_data | float | 计算矩阵行列式 |
| `eigenvalues` | array_data | str | 计算特征值 |
| `eigenvectors` | array_data | str | 计算特征向量 |
| `statistics` | array_data | dict | 统计信息(均值、标准差等) |
| `fft` | array_data | list | 快速傅里叶变换 |
| `correlation` | array1, array2 | float | 计算两个数组的相关系数 |
| `pandas_read` | file_path, file_type | str | 读取数据文件 |
| `pandas_describe` | file_path | str | 数据摘要统计 |
**使用示例:**
```json
{
"tool": "numpy_tool",
"input": {
"operation": "linear_algebra",
"array_data": [[2, 1], [1, 3]],
"array_data_b": [5, 7]
}
}
```
### scipy_tool(科学计算)
提供以下操作:
| 操作 | 参数 | 返回 | 说明 |
|------|------|------|------|
| `solve_ode` | expression, initial_condition, t_span | str | 求解常微分方程 |
| `integrate` | expression, limits | float | 数值积分 |
| `optimize` | expression, initial_guess | str | 函数优化 |
| `interpolate` | x_data, y_data, x_new | str | 插值 |
| `fft` | array_data | list | 快速傅里叶变换 |
| `root_find` | expression, initial_guess | float | 求根 |
| `curve_fit` | x_data, y_data | str | 曲线拟合 |
| `special_function` | func_type, value | float | 特殊函数计算 |
**使用示例:**
```json
{
"tool": "scipy_tool",
"input": {
"operation": "integrate",
"expression": "sin(x)",
"limits": "0:pi"
}
}
```
---
## ❓ 常见问题
### Q1:如何添加到 Claude Desktop?
**A:**
1. 打开 Claude Desktop 配置文件夹
- **Windows**: `%APPDATA%\Claude\`
- **macOS**: `~/Library/Application Support/Claude/`
- **Linux**: `~/.config/Claude/`
2. 编辑 `claude_desktop_config.json`
3. 在 `mcpServers` 字段添加配置
4. 保存并重启 Claude Desktop
### Q2:提示 "Python not found" 怎么办?
**A:** 确保 Python 已正确安装并在系统 PATH 中:
```bash
python --version
# 或
python3 --version
```
如果没有找到,使用完整路径:
```json
{
"command": "C:\\Python312\\python.exe",
"args": ["-u", "path/to/mcp_server.py"]
}
```
### Q3:模块导入错误怎样修复?
**A:** 重新安装依赖:
```bash
pip install --upgrade sympy numpy scipy pandas
```
### Q4:如何运行测试?
**A:**
```bash
python verify_production_readiness.py
```
### Q5:如何调试问题?
**A:** 使用完整路径并添加日志:
```json
{
"command": "python",
"args": ["-u", "-u", "path/to/mcp_server.py"]
}
```
---
## 📊 项目结构
```
.
├── calculator.py # 核心计算模块(3个工具函数)
├── mcp_server.py # MCP 服务器实现
├── mcp_protocols.py # MCP 协议定义(可选)
├── requirements.txt # 依赖列表
├── setup.py # 打包配置
├── pyproject.toml # 项目元数据
├── advanced_math_problems.py # 测试用例
├── advanced_math_problems.json# 测试数据
├── verify_production_readiness.py # 验证脚本
├── README.md # 英文文档
├── README_ZH.md # 中文文档(本文件)
├── QUICKSTART.md # 快速入门
├── DEPLOYMENT.md # 部署指南
└── .vscode/ # VS Code 配置
```
---
## 📝 版本信息
- **版本**: 1.0.0(初始版本)
- **发布日期**: 2025 年 12 月 7 日
- **Python 版本**: 3.8+
- **MCP 版本**: 兼容最新版本
- **许可证**: MIT
---
## 🔗 相关链接
- **GitHub**: https://github.com/thinkitpossible/CalcMCP
- **PyPI**: https://pypi.org/project/mcp-scientific-calculator/
- **MCP 协议**: https://modelcontextprotocol.io/
- **SymPy 文档**: https://docs.sympy.org/
- **NumPy 文档**: https://numpy.org/doc/
- **SciPy 文档**: https://docs.scipy.org/
---
## 📧 反馈与支持
如有问题或建议,请提出 Issue 或联系维护者。
**GitHub Issues**: https://github.com/thinkitpossible/CalcMCP/issues
---
**祝您使用愉快!** 🎉

View File

@@ -13,8 +13,18 @@ except Exception:
from scipy.integrate import odeint from scipy.integrate import odeint
from scipy.fftpack import fft, rfft from scipy.fftpack import fft, rfft
from typing import Optional from typing import Optional
import ast
import json import json
def _json_default(obj):
"""JSON fallback for numpy/complex values (eigenvalues are often complex)."""
if isinstance(obj, complex):
return obj.real if obj.imag == 0 else f"{obj.real}{obj.imag:+}i"
if hasattr(obj, "item"):
return _json_default(obj.item())
return str(obj)
# ============================================================================ # ============================================================================
# SYMBOLIC MATHEMATICS (SymPy) # SYMBOLIC MATHEMATICS (SymPy)
# ============================================================================ # ============================================================================
@@ -91,9 +101,14 @@ def symbolic_limit(expression: str, variable: str, point: str) -> str:
def solve_equation(equation: str, variable: str) -> str: def solve_equation(equation: str, variable: str) -> str:
"""解方程""" """Solve an equation. Accepts 'expr' (treated as expr = 0), 'lhs = rhs' or 'lhs == rhs'."""
try: try:
expr = sp.sympify(equation) eq_str = equation.replace("==", "=")
if "=" in eq_str:
lhs, rhs = eq_str.split("=", 1)
expr = sp.Eq(sp.sympify(lhs), sp.sympify(rhs))
else:
expr = sp.sympify(eq_str)
var = sp.Symbol(variable) var = sp.Symbol(variable)
result = sp.solve(expr, var) result = sp.solve(expr, var)
return str(result) return str(result)
@@ -101,6 +116,22 @@ def solve_equation(equation: str, variable: str) -> str:
return f"Error: {str(e)}" return f"Error: {str(e)}"
def symbolic_dsolve(equation: str, variable: str = "x") -> str:
"""Solve an ODE symbolically. Unknown function must be written as y(x),
e.g. 'Derivative(y(x), x, 2) + y(x) = 0' -> y(x) = C1*sin(x) + C2*cos(x)."""
try:
eq_str = equation.replace("==", "=")
if "=" in eq_str:
lhs, rhs = eq_str.split("=", 1)
eq = sp.Eq(sp.sympify(lhs), sp.sympify(rhs))
else:
eq = sp.sympify(eq_str)
result = sp.dsolve(eq)
return str(result)
except Exception as e:
return f"Error: {str(e)}"
def taylor_series(expression: str, variable: str, point: str, n: int) -> str: def taylor_series(expression: str, variable: str, point: str, n: int) -> str:
"""泰勒级数展开""" """泰勒级数展开"""
try: try:
@@ -185,8 +216,8 @@ def numpy_linear_algebra(matrix_a, operation: str, matrix_b=None) -> str:
if isinstance(matrix_a, str): if isinstance(matrix_a, str):
try: try:
mat1 = np.array(json.loads(matrix_a), dtype=float) mat1 = np.array(json.loads(matrix_a), dtype=float)
except: except Exception:
mat1 = np.array(eval(matrix_a), dtype=float) mat1 = np.array(ast.literal_eval(matrix_a), dtype=float)
else: else:
mat1 = np.array(matrix_a, dtype=float) mat1 = np.array(matrix_a, dtype=float)
@@ -195,27 +226,19 @@ def numpy_linear_algebra(matrix_a, operation: str, matrix_b=None) -> str:
if isinstance(matrix_b, str): if isinstance(matrix_b, str):
try: try:
mat2 = np.array(json.loads(matrix_b), dtype=float) mat2 = np.array(json.loads(matrix_b), dtype=float)
except: except Exception:
mat2 = np.array(eval(matrix_b), dtype=float) mat2 = np.array(ast.literal_eval(matrix_b), dtype=float)
else: else:
mat2 = np.array(matrix_b, dtype=float) mat2 = np.array(matrix_b, dtype=float)
# 执行操作 # 执行操作
if operation == "eigenvalues": if operation in ("eigenvalues", "eigenvectors"):
eigenvalues, eigenvectors = np.linalg.eig(mat1) eigenvalues, eigenvectors = np.linalg.eig(mat1)
result = { result = {
"eigenvalues": eigenvalues.tolist(), "eigenvalues": eigenvalues.tolist(),
"eigenvectors": eigenvectors.tolist() "eigenvectors": eigenvectors.tolist()
} }
return json.dumps(result, ensure_ascii=False) return json.dumps(result, ensure_ascii=False, default=_json_default)
elif operation == "eigenvectors":
eigenvalues, eigenvectors = np.linalg.eig(mat1)
result = {
"eigenvalues": eigenvalues.tolist(),
"eigenvectors": eigenvectors.tolist()
}
return json.dumps(result, ensure_ascii=False)
elif operation == "determinant": elif operation == "determinant":
result = float(np.linalg.det(mat1)) result = float(np.linalg.det(mat1))
@@ -434,21 +457,17 @@ def scipy_solve_ode(expression: str, initial_conditions: str, t_values: str, var
else: else:
reverse_order = False reverse_order = False
# 评估 ODE 函数表达式 # Parse dy/dt = f(t, y) as a SymPy expression (no eval of raw Python)
try: var_list = [sp.Symbol(v.strip()) for v in (variables or "y").split(",")]
# 尝试作为 lambda 函数字符串 t_sym = sp.Symbol('t')
ode_func = eval(expression) expr = sp.sympify(expression)
except:
# 如果失败,尝试作为符号表达式 if len(var_list) == 1:
var_list = [sp.Symbol(v.strip()) for v in variables.split(",")] f = sp.lambdify([t_sym, var_list[0]], expr, modules=['numpy'])
t_sym = sp.Symbol('t') # odeint calls func(y, t)
expr = sp.sympify(expression) ode_func = lambda y, t: f(t, y)
else:
if len(var_list) == 1: return "Error: System ODEs not yet fully supported"
ode_func = sp.lambdify([t_sym, var_list[0]], expr, modules=['numpy'])
ode_func_wrapped = lambda y, t: ode_func(t, y)
else:
return "Error: System ODEs not yet fully supported"
# 调用 odeint # 调用 odeint
if isinstance(y0, (int, float)): if isinstance(y0, (int, float)):
@@ -489,6 +508,8 @@ def scipy_statistics(operation: str, data: str, params: Optional[str] = None) ->
return str(float(np.std(data_arr))) return str(float(np.std(data_arr)))
elif operation == "ttest": elif operation == "ttest":
popmean = json.loads(params) if params else 0 popmean = json.loads(params) if params else 0
if isinstance(popmean, dict):
popmean = popmean.get("popmean", 0)
result = stats.ttest_1samp(data_arr, popmean) result = stats.ttest_1samp(data_arr, popmean)
return f"statistic={result.statistic}, pvalue={result.pvalue}" return f"statistic={result.statistic}, pvalue={result.pvalue}"
elif operation == "pearsonr" and params: elif operation == "pearsonr" and params:
@@ -560,6 +581,8 @@ def symbolic_tool(
return symbolic_limit(expression, variable, point or point_value) return symbolic_limit(expression, variable, point or point_value)
if op in ("solve", "solve_equation"): if op in ("solve", "solve_equation"):
return solve_equation(equation or expression, variable) return solve_equation(equation or expression, variable)
if op in ("dsolve", "ode", "solve_ode", "differential_equation"):
return symbolic_dsolve(equation or expression, variable or "x")
if op in ("taylor", "taylor_series"): if op in ("taylor", "taylor_series"):
return taylor_series(expression, variable, point or "0", order) return taylor_series(expression, variable, point or "0", order)
if op in ("matrix", "symbolic_matrix_operations"): if op in ("matrix", "symbolic_matrix_operations"):

View File

@@ -58,55 +58,85 @@ class MCPCalculatorServer:
# Consolidated tool definitions (3 tools) # Consolidated tool definitions (3 tools)
tool_definitions = { tool_definitions = {
"symbolic_tool": { "symbolic_tool": {
"description": "统一符号计算入口:simplify/expand/factor/derivative/integral/limit/solve/taylor/matrix", "description": (
"Exact symbolic math (SymPy). Set operation to one of: simplify, expand, factor, "
"derivative, integral, limit, solve, dsolve, taylor, matrix. "
"Write expressions in Python/SymPy syntax, e.g. 'x**2 * exp(-x)'; use 'oo' for infinity. "
"integral: optional lower_limit/upper_limit for definite integrals. "
"solve: pass the equation, '=' is allowed (e.g. 'x**2 = 4' or 'x**3 - 6*x**2 + 11*x - 6'). "
"dsolve: solves an ODE symbolically — write the unknown function as y(x), "
"e.g. equation='Derivative(y(x), x, 2) + y(x) = 0'. "
"taylor: expression + variable + point + order. "
"matrix: matrix_data (JSON) with variable=determinant|inverse|rank|trace. "
"Results are exact (e.g. '2', 'E', 'C1*sin(x) + C2*cos(x)')."
),
"params": [ "params": [
{"name": "operation", "type": "string", "description": "操作类型,如 derivative, integral, solve 等"}, {"name": "operation", "type": "string", "description": "One of: simplify, expand, factor, derivative, integral, limit, solve, dsolve, taylor, matrix"},
{"name": "expression", "type": "string", "description": "表达式", "optional": True}, {"name": "expression", "type": "string", "description": "Expression in SymPy syntax, e.g. 'sin(x)/x'", "optional": True},
{"name": "variable", "type": "string", "description": "变量", "optional": True}, {"name": "variable", "type": "string", "description": "Variable name, e.g. 'x' (for matrix: the operation name)", "optional": True},
{"name": "order", "type": "integer", "description": "阶数(导数/泰勒)", "optional": True}, {"name": "order", "type": "integer", "description": "Order for derivative/taylor (default 1 / 5)", "optional": True},
{"name": "lower_limit", "type": "string", "description": "积分下限", "optional": True}, {"name": "lower_limit", "type": "string", "description": "Lower bound for definite integral, e.g. '0'", "optional": True},
{"name": "upper_limit", "type": "string", "description": "积分上限", "optional": True}, {"name": "upper_limit", "type": "string", "description": "Upper bound for definite integral, e.g. 'oo'", "optional": True},
{"name": "equation", "type": "string", "description": "方程", "optional": True}, {"name": "equation", "type": "string", "description": "Equation for solve/dsolve; '=' allowed", "optional": True},
{"name": "point", "type": "string", "description": "极限或泰勒展开点", "optional": True}, {"name": "point", "type": "string", "description": "Point for limit/taylor, e.g. '0' or 'oo'", "optional": True},
{"name": "matrix_data", "type": "string", "description": "符号矩阵(JSON)", "optional": True} {"name": "matrix_data", "type": "string", "description": "Matrix as JSON, e.g. '[[1,2],[3,4]]'", "optional": True}
] ]
}, },
"numpy_tool": { "numpy_tool": {
"description": "统一 NumPy 入口:数组归约、线性代数、三角、多项式", "description": (
"Numeric arrays and linear algebra (NumPy). Set operation to one of: "
"sum, mean, std, max, min (array_data, optional axis); "
"eigenvalues, eigenvectors, determinant, inverse, solve, norm, rank, trace, "
"svd, qr, cholesky, matmul, dot, hadamard (matrix_a, and matrix_b where two matrices are needed); "
"sin, cos, tan, arcsin, arccos, arctan, sinh, cosh, tanh (values, optional use_degrees); "
"poly_eval, poly_derivative, poly_integral (coefficients highest-degree first, optional x_values). "
"All array/matrix inputs are JSON strings, e.g. matrix_a='[[2,1],[1,2]]'. Results are numeric."
),
"params": [ "params": [
{"name": "operation", "type": "string", "description": "操作类型,如 eigenvalues, sum, poly_eval 等"}, {"name": "operation", "type": "string", "description": "Operation name, e.g. 'eigenvalues', 'matmul', 'mean', 'poly_eval'"},
{"name": "array_data", "type": "string", "description": "数组数据(JSON)", "optional": True}, {"name": "array_data", "type": "string", "description": "Array as JSON, e.g. '[1,2,3]' or '[[1,2],[3,4]]'", "optional": True},
{"name": "axis", "type": "string", "description": "轴参数", "optional": True}, {"name": "axis", "type": "string", "description": "Axis for reductions: '0' or '1'", "optional": True},
{"name": "matrix_a", "type": "string", "description": "矩阵A(JSON)", "optional": True}, {"name": "matrix_a", "type": "string", "description": "First matrix as JSON", "optional": True},
{"name": "matrix_b", "type": "string", "description": "矩阵B(JSON)", "optional": True}, {"name": "matrix_b", "type": "string", "description": "Second matrix as JSON (solve/matmul/dot/hadamard)", "optional": True},
{"name": "values", "type": "string", "description": "三角函数输入(JSON)", "optional": True}, {"name": "values", "type": "string", "description": "Input values for trig functions as JSON, e.g. '[0, 90, 180]'", "optional": True},
{"name": "use_degrees", "type": "boolean", "description": "角度制", "optional": True}, {"name": "use_degrees", "type": "boolean", "description": "Treat trig inputs as degrees (default radians)", "optional": True},
{"name": "coefficients", "type": "string", "description": "多项式系数(JSON)", "optional": True}, {"name": "coefficients", "type": "string", "description": "Polynomial coefficients as JSON, highest degree first", "optional": True},
{"name": "x_values", "type": "string", "description": "多项式自变量数组(JSON)", "optional": True} {"name": "x_values", "type": "string", "description": "x values for poly_eval as JSON", "optional": True}
] ]
}, },
"scipy_tool": { "scipy_tool": {
"description": "统一 SciPy 入口:积分、优化、插值、特殊函数、ODE、统计、FFT、特征分解", "description": (
"Numerical methods (SciPy). Set operation to one of: "
"integrate (numeric definite integral: expression, variable, lower, upper); "
"optimize_minimize, optimize_root (expression, initial_value, optional method); "
"interpolate_linear, interpolate_cubic, interpolate_spline (x_data, y_data, x_eval); "
"special (function name + parameters JSON, e.g. gamma, besselj); "
"solve_ode (NUMERIC simulation of dy/dt = f(t,y): expression e.g. '-2*y + t', "
"initial_conditions, t_values; for exact general solutions use symbolic_tool with operation='dsolve'); "
"statistics ops mean, std, describe, ttest, pearsonr (function + data, optional params); "
"fft, rfft (data_series or data); matrix_eigensystem (matrix_a). "
"All list inputs are JSON strings. Results are numeric."
),
"params": [ "params": [
{"name": "operation", "type": "string", "description": "操作类型,如 integrate, optimize_minimize, fft 等"}, {"name": "operation", "type": "string", "description": "Operation name, e.g. 'integrate', 'optimize_minimize', 'solve_ode', 'fft'"},
{"name": "expression", "type": "string", "description": "表达式/函数", "optional": True}, {"name": "expression", "type": "string", "description": "Expression/function of the variable, e.g. 'x**2 * exp(-x)'", "optional": True},
{"name": "variable", "type": "string", "description": "变量", "optional": True}, {"name": "variable", "type": "string", "description": "Variable name, e.g. 'x'", "optional": True},
{"name": "lower", "type": "string", "description": "积分下限", "optional": True}, {"name": "lower", "type": "string", "description": "Lower integration bound (number)", "optional": True},
{"name": "upper", "type": "string", "description": "积分上限", "optional": True}, {"name": "upper", "type": "string", "description": "Upper integration bound (number or 'oo')", "optional": True},
{"name": "initial_value", "type": "string", "description": "初值/起点", "optional": True}, {"name": "initial_value", "type": "string", "description": "Starting point for optimization/root finding", "optional": True},
{"name": "method", "type": "string", "description": "算法方法", "optional": True}, {"name": "method", "type": "string", "description": "Algorithm, e.g. 'BFGS', 'brentq'", "optional": True},
{"name": "x_data", "type": "string", "description": "插值 x 数据(JSON)", "optional": True}, {"name": "x_data", "type": "string", "description": "Interpolation x data as JSON", "optional": True},
{"name": "y_data", "type": "string", "description": "插值 y 数据(JSON)", "optional": True}, {"name": "y_data", "type": "string", "description": "Interpolation y data as JSON", "optional": True},
{"name": "x_eval", "type": "string", "description": "插值求值点(JSON)", "optional": True}, {"name": "x_eval", "type": "string", "description": "Points to evaluate the interpolant at, JSON", "optional": True},
{"name": "function", "type": "string", "description": "特殊函数名/统计操作名", "optional": True}, {"name": "function", "type": "string", "description": "Special-function or statistics operation name", "optional": True},
{"name": "parameters", "type": "string", "description": "特殊函数参数(JSON)", "optional": True}, {"name": "parameters", "type": "string", "description": "Special-function parameters as JSON", "optional": True},
{"name": "initial_conditions", "type": "string", "description": "ODE 初值(JSON)", "optional": True}, {"name": "initial_conditions", "type": "string", "description": "ODE initial value(s) as JSON, e.g. '[1.0]'", "optional": True},
{"name": "t_values", "type": "string", "description": "ODE 时间点(JSON)", "optional": True}, {"name": "t_values", "type": "string", "description": "ODE time points as JSON, e.g. '[0, 0.1, 0.2]'", "optional": True},
{"name": "variables", "type": "string", "description": "ODE 变量名", "optional": True}, {"name": "variables", "type": "string", "description": "ODE dependent variable name, default 'y'", "optional": True},
{"name": "data", "type": "string", "description": "统计或 FFT 数据(JSON)", "optional": True}, {"name": "data", "type": "string", "description": "Statistics/FFT data as JSON", "optional": True},
{"name": "params", "type": "string", "description": "统计附加参数(JSON)", "optional": True}, {"name": "params", "type": "string", "description": "Extra statistics parameter as JSON: for ttest the population mean ('2.3' or '{\"popmean\": 2.3}'), for pearsonr the second data series", "optional": True},
{"name": "data_series", "type": "string", "description": "FFT 数据(JSON)", "optional": True}, {"name": "data_series", "type": "string", "description": "FFT data as JSON", "optional": True},
{"name": "matrix_a", "type": "string", "description": "矩阵(JSON)用于特征分解", "optional": True} {"name": "matrix_a", "type": "string", "description": "Matrix as JSON for matrix_eigensystem", "optional": True}
] ]
}, },
} }
@@ -116,7 +146,7 @@ class MCPCalculatorServer:
tools.append({ tools.append({
"name": tool_name, "name": tool_name,
"description": definition["description"], "description": definition["description"],
"params": { "inputSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
param["name"]: { param["name"]: {
@@ -170,7 +200,11 @@ class MCPCalculatorServer:
method = request_dict.get("method") method = request_dict.get("method")
params = request_dict.get("params", {}) params = request_dict.get("params", {})
req_id = request_dict.get("id") req_id = request_dict.get("id")
# JSON-RPC notifications (no id) must not be answered
if req_id is None and isinstance(method, str) and method.startswith("notifications/"):
return None
# 处理特殊方法 # 处理特殊方法
if method == "initialize": if method == "initialize":
return { return {
@@ -200,7 +234,10 @@ class MCPCalculatorServer:
tool_params = params.get("arguments", {}) tool_params = params.get("arguments", {})
result = self.call_tool(tool_name, tool_params) result = self.call_tool(tool_name, tool_params)
is_error = isinstance(result, str) and result.startswith(
("Error", "Parameter Error", "Execution Error")
)
return { return {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"result": { "result": {
@@ -209,7 +246,8 @@ class MCPCalculatorServer:
"type": "text", "type": "text",
"text": result "text": result
} }
] ],
"isError": is_error
}, },
"id": req_id "id": req_id
} }
@@ -252,10 +290,11 @@ class MCPCalculatorServer:
try: try:
request = json.loads(line) request = json.loads(line)
response = self.handle_rpc_request(request) response = self.handle_rpc_request(request)
# 写入 JSON-RPC 响应到标准输出 # Notifications return None — nothing must be written back
sys.stdout.write(json.dumps(response, ensure_ascii=False) + '\n') if response is not None:
sys.stdout.flush() sys.stdout.write(json.dumps(response, ensure_ascii=False) + '\n')
sys.stdout.flush()
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
# 发送 JSON 解析错误 # 发送 JSON 解析错误

View File

@@ -1,8 +1,4 @@
Flask>=3.0.0 sympy>=1.12
Flask-CORS>=4.0.0 numpy>=1.24.0
requests>=2.31.0 scipy>=1.11.0
fastmcp>=0.1.0 pandas>=2.0.0
sympy>=1.12
scipy>=1.11.0
numpy>=1.24.0
pytest>=7.0.0