"""
MCP 工具實作 - 記帳相關的 MCP Tools
實作所有記帳相關的 MCP 工具,需要先執行 conda activate myenv,才能使用下列工具:
- add_transaction: 新增交易記錄
- get_balance: 取得帳戶餘額
- list_transactions: 查詢交易記錄
- get_monthly_summary: 取得月度彙總
"""
from datetime import date, datetime
from decimal import Decimal
from typing import Dict, Any, List, Optional, Union
import json
from .models import Transaction, TransactionType
from .storage import StorageManager
class AccountingTools:
"""記帳工具類 - 實作所有 MCP Tools"""
def __init__(self, storage_manager: Optional[StorageManager] = None):
self.storage = storage_manager or StorageManager()
async def add_transaction(
self,
amount: Union[float, int, str],
category: str,
description: str = "",
date_str: Optional[str] = None
) -> Dict[str, Any]:
"""
新增交易記錄
Args:
amount: 金額,正數為收入,負數為支出
category: 分類 ID
description: 交易描述
date_str: 交易日期 (YYYY-MM-DD 格式,可選)
Returns:
Dict containing transaction details and updated balance
"""
try:
# 轉換金額為 Decimal
amount_decimal = Decimal(str(amount))
# 解析日期
transaction_date = date.today()
if date_str:
transaction_date = datetime.fromisoformat(date_str).date()
# 建立交易記錄
transaction = Transaction(
amount=amount_decimal,
category=category,
description=description,
date=transaction_date
)
# 新增交易到儲存
self.storage.add_transaction(transaction)
# 取得更新後的餘額
new_balance = self.storage.get_balance()
return {
"success": True,
"transaction": {
"id": transaction.id,
"amount": float(transaction.amount),
"category": transaction.category,
"description": transaction.description,
"date": transaction.date.isoformat(),
"type": transaction.transaction_type.value
},
"new_balance": float(new_balance),
"message": f"已新增{transaction.transaction_type.value == 'income' and '收入' or '支出'} NTD {abs(float(transaction.amount))}"
}
except ValueError as e:
return {
"success": False,
"error": f"參數錯誤: {str(e)}",
"error_type": "validation_error"
}
except Exception as e:
return {
"success": False,
"error": f"新增交易失敗: {str(e)}",
"error_type": "storage_error"
}
async def get_balance(self, detailed: bool = False) -> Dict[str, Any]:
"""
取得帳戶餘額資訊
Args:
detailed: 是否回傳詳細統計資訊
Returns:
Dict containing balance and optionally detailed statistics
"""
try:
account = self.storage.account.load_account()
result = {
"success": True,
"balance": float(account.balance),
"total_transactions": account.total_transactions,
"last_updated": account.last_updated.isoformat() if account.last_updated else None
}
if detailed:
# 取得本月交易統計
now = datetime.now()
monthly_summary = self.storage.get_monthly_summary(now.year, now.month)
result["monthly_stats"] = {
"income": monthly_summary["total_income"],
"expense": monthly_summary["total_expense"],
"net_flow": monthly_summary["net_flow"],
"transaction_count": monthly_summary["transaction_count"]
}
return result
except Exception as e:
return {
"success": False,
"error": f"取得餘額失敗: {str(e)}",
"error_type": "storage_error"
}
async def list_transactions(
self,
limit: int = 20,
offset: int = 0,
category: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None
) -> Dict[str, Any]:
"""
查詢交易記錄清單
Args:
limit: 回傳記錄數限制
offset: 偏移量(用於分頁)
category: 分類篩選
start_date: 開始日期 (YYYY-MM-DD)
end_date: 結束日期 (YYYY-MM-DD)
Returns:
Dict containing transaction list and metadata
"""
try:
# 解析日期篩選條件
start_date_obj = None
end_date_obj = None
if start_date:
start_date_obj = datetime.fromisoformat(start_date).date()
if end_date:
end_date_obj = datetime.fromisoformat(end_date).date()
# 取得交易記錄
transactions = self.storage.transactions.get_transactions(
limit=limit,
offset=offset,
category=category,
start_date=start_date_obj,
end_date=end_date_obj
)
# 轉換為字典格式
transaction_list = [
{
"id": t.id,
"amount": float(t.amount),
"category": t.category,
"description": t.description,
"date": t.date.isoformat(),
"timestamp": t.timestamp.isoformat(),
"type": t.transaction_type.value
}
for t in transactions
]
# 取得總數(用於分頁資訊)
all_transactions = self.storage.transactions.load_transactions()
total_count = len(all_transactions)
return {
"success": True,
"transactions": transaction_list,
"pagination": {
"limit": limit,
"offset": offset,
"total_count": total_count,
"returned_count": len(transaction_list),
"has_more": offset + len(transaction_list) < total_count
},
"filters": {
"category": category,
"start_date": start_date,
"end_date": end_date
}
}
except Exception as e:
return {
"success": False,
"error": f"查詢交易記錄失敗: {str(e)}",
"error_type": "storage_error"
}
async def get_monthly_summary(
self,
year: Optional[int] = None,
month: Optional[int] = None
) -> Dict[str, Any]:
"""
取得月度財務彙總
Args:
year: 年份(預設當前年)
month: 月份(預設當前月)
Returns:
Dict containing monthly financial summary
"""
try:
# 預設使用目前年月
now = datetime.now()
year = year or now.year
month = month or now.month
# 驗證日期有效性
if not (1 <= month <= 12):
raise ValueError("月份必須在 1-12 之間")
if year < 1900 or year > 2100:
raise ValueError("年份必須在 1900-2100 之間")
# 取得月度彙總
summary = self.storage.get_monthly_summary(year, month)
# 取得分類資訊
categories = self.storage.categories.load_categories()
category_info = {cat.id: {"name": cat.name, "icon": cat.icon} for cat in categories}
# 強化分類統計資訊
enhanced_breakdown = {}
for cat_name, amount in summary["category_breakdown"].items():
# 查找對應的分類資訊
cat_info = None
for cat in categories:
if cat.name == cat_name:
cat_info = cat
break
enhanced_breakdown[cat_name] = {
"amount": amount,
"icon": cat_info.icon if cat_info else "📝",
"id": cat_info.id if cat_info else "unknown"
}
return {
"success": True,
"summary": {
"period": {
"year": year,
"month": month,
"month_name": datetime(year, month, 1).strftime("%B")
},
"totals": {
"income": summary["total_income"],
"expense": summary["total_expense"],
"net_flow": summary["net_flow"]
},
"transaction_count": summary["transaction_count"],
"category_breakdown": enhanced_breakdown,
"top_categories": sorted(
enhanced_breakdown.items(),
key=lambda x: x[1]["amount"],
reverse=True
)[:5] # 前 5 大支出分類
}
}
except ValueError as e:
return {
"success": False,
"error": f"參數錯誤: {str(e)}",
"error_type": "validation_error"
}
except Exception as e:
return {
"success": False,
"error": f"取得月度彙總失敗: {str(e)}",
"error_type": "storage_error"
}
async def get_categories(self) -> Dict[str, Any]:
"""
取得所有分類清單(額外工具)
Returns:
Dict containing all available categories
"""
try:
categories = self.storage.categories.load_categories()
category_list = [
{
"id": cat.id,
"name": cat.name,
"description": cat.description,
"type": cat.category_type.value,
"color": cat.color,
"icon": cat.icon
}
for cat in categories
]
# 按類型分組
expense_categories = [cat for cat in category_list if cat["type"] == "expense"]
income_categories = [cat for cat in category_list if cat["type"] == "income"]
return {
"success": True,
"categories": {
"all": category_list,
"expense": expense_categories,
"income": income_categories
},
"total_count": len(category_list)
}
except Exception as e:
return {
"success": False,
"error": f"取得分類清單失敗: {str(e)}",
"error_type": "storage_error"
}
# 工具函式 - 用於 MCP 伺服器註冊
def get_tool_definitions() -> List[Dict[str, Any]]:
"""回傳所有工具的 MCP 定義"""
return [
{
"name": "add_transaction",
"description": "新增一筆收入或支出記錄到帳本中",
"readOnly": False,
"destructive": False,
"inputSchema": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"description": "金額,正數為收入,負數為支出"
},
"category": {
"type": "string",
"description": "分類:food(餐飲), transport(交通), entertainment(娛樂), shopping(購物), healthcare(醫療), education(教育), income(收入), other(其他)"
},
"description": {
"type": "string",
"description": "交易描述資訊(可選)",
"default": ""
},
"date": {
"type": "string",
"description": "交易日期,格式為 YYYY-MM-DD,預設為今天",
"format": "date"
}
},
"required": ["amount", "category"]
}
},
{
"name": "get_balance",
"description": "取得帳戶目前餘額與基本統計資訊",
"readOnly": True,
"destructive": False,
"inputSchema": {
"type": "object",
"properties": {
"detailed": {
"type": "boolean",
"description": "是否回傳詳細的月度統計資訊",
"default": False
}
}
}
},
{
"name": "list_transactions",
"description": "查詢交易記錄,支援分頁與條件篩選",
"readOnly": True,
"destructive": False,
"inputSchema": {
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "回傳記錄數限制,預設 20 筆",
"default": 20,
"minimum": 1,
"maximum": 100
},
"offset": {
"type": "integer",
"description": "跳過的記錄數,用於分頁",
"default": 0,
"minimum": 0
},
"category": {
"type": "string",
"description": "按分類篩選交易記錄"
},
"start_date": {
"type": "string",
"description": "開始日期,格式為 YYYY-MM-DD",
"format": "date"
},
"end_date": {
"type": "string",
"description": "結束日期,格式為 YYYY-MM-DD",
"format": "date"
}
}
}
},
{
"name": "get_monthly_summary",
"description": "取得指定月份的收支彙總統計,包括分類統計",
"readOnly": True,
"destructive": False,
"inputSchema": {
"type": "object",
"properties": {
"year": {
"type": "integer",
"description": "年份,預設為當前年份",
"minimum": 1900,
"maximum": 2100
},
"month": {
"type": "integer",
"description": "月份 (1-12),預設為當前月份",
"minimum": 1,
"maximum": 12
}
}
}
},
{
"name": "get_categories",
"description": "取得所有可用的支出與收入分類清單",
"readOnly": True,
"destructive": False,
"inputSchema": {
"type": "object",
"properties": {}
}
}
]