init: vaultmesh mcp server
This commit is contained in:
325
packages/vaultmesh_mcp/tools/treasury.py
Normal file
325
packages/vaultmesh_mcp/tools/treasury.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""Treasury MCP tools - Budget management operations."""
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import blake3
|
||||
|
||||
# VaultMesh root from env or default
|
||||
VAULTMESH_ROOT = Path(os.environ.get("VAULTMESH_ROOT", Path(__file__).parents[3])).resolve()
|
||||
TREASURY_JSONL = VAULTMESH_ROOT / "receipts/treasury/treasury_events.jsonl"
|
||||
TREASURY_STATE = VAULTMESH_ROOT / "receipts/treasury/budgets.json"
|
||||
|
||||
# Schema version
|
||||
SCHEMA_VERSION = "2.0.0"
|
||||
|
||||
|
||||
def _vmhash_blake3(data: bytes) -> str:
|
||||
"""VaultMesh hash: blake3:<hex>."""
|
||||
return f"blake3:{blake3.blake3(data).hexdigest()}"
|
||||
|
||||
|
||||
def _load_budgets() -> dict[str, dict]:
|
||||
"""Load current budget state from disk."""
|
||||
if not TREASURY_STATE.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(TREASURY_STATE.read_text())
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
|
||||
def _save_budgets(budgets: dict[str, dict]) -> None:
|
||||
"""Persist budget state to disk."""
|
||||
TREASURY_STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
TREASURY_STATE.write_text(json.dumps(budgets, indent=2))
|
||||
|
||||
|
||||
def _emit_receipt(receipt_type: str, body: dict, tags: list[str]) -> dict:
|
||||
"""Emit a treasury receipt to JSONL."""
|
||||
body_json = json.dumps(body, sort_keys=True)
|
||||
root_hash = _vmhash_blake3(body_json.encode())
|
||||
|
||||
receipt = {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"type": receipt_type,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"scroll": "treasury",
|
||||
"tags": tags,
|
||||
"root_hash": root_hash,
|
||||
"body": body,
|
||||
}
|
||||
|
||||
TREASURY_JSONL.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(TREASURY_JSONL, "a") as f:
|
||||
f.write(json.dumps(receipt) + "\n")
|
||||
|
||||
# Update ROOT file
|
||||
_update_root()
|
||||
|
||||
return receipt
|
||||
|
||||
|
||||
def _update_root() -> None:
|
||||
"""Update ROOT.treasury.txt with current Merkle root."""
|
||||
if not TREASURY_JSONL.exists():
|
||||
return
|
||||
|
||||
hashes = []
|
||||
with open(TREASURY_JSONL, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line:
|
||||
hashes.append(_vmhash_blake3(line.encode()))
|
||||
|
||||
if not hashes:
|
||||
root = _vmhash_blake3(b"empty")
|
||||
elif len(hashes) == 1:
|
||||
root = hashes[0]
|
||||
else:
|
||||
current = hashes
|
||||
while len(current) > 1:
|
||||
next_level = []
|
||||
for i in range(0, len(current), 2):
|
||||
if i + 1 < len(current):
|
||||
combined = current[i] + current[i + 1]
|
||||
else:
|
||||
combined = current[i] + current[i]
|
||||
next_level.append(_vmhash_blake3(combined.encode()))
|
||||
current = next_level
|
||||
root = current[0]
|
||||
|
||||
root_file = VAULTMESH_ROOT / "ROOT.treasury.txt"
|
||||
root_file.write_text(root)
|
||||
|
||||
|
||||
def treasury_create_budget(
|
||||
budget_id: str,
|
||||
name: str,
|
||||
allocated: int,
|
||||
currency: str = "EUR",
|
||||
created_by: str = "did:vm:mcp:treasury",
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Create a new budget.
|
||||
|
||||
Args:
|
||||
budget_id: Unique identifier for the budget
|
||||
name: Human-readable budget name
|
||||
allocated: Initial allocation amount (cents/smallest unit)
|
||||
currency: Currency code (default: EUR)
|
||||
created_by: DID of the actor creating the budget
|
||||
|
||||
Returns:
|
||||
Created budget with receipt info
|
||||
"""
|
||||
budgets = _load_budgets()
|
||||
|
||||
if budget_id in budgets:
|
||||
return {"error": f"Budget already exists: {budget_id}"}
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
budget = {
|
||||
"id": budget_id,
|
||||
"name": name,
|
||||
"currency": currency,
|
||||
"allocated": allocated,
|
||||
"spent": 0,
|
||||
"created_at": now.isoformat(),
|
||||
"created_by": created_by,
|
||||
}
|
||||
|
||||
budgets[budget_id] = budget
|
||||
_save_budgets(budgets)
|
||||
|
||||
# Emit receipt
|
||||
receipt_body = {
|
||||
"budget_id": budget_id,
|
||||
"name": name,
|
||||
"currency": currency,
|
||||
"allocated": allocated,
|
||||
"created_by": created_by,
|
||||
}
|
||||
receipt = _emit_receipt(
|
||||
"treasury_budget_create",
|
||||
receipt_body,
|
||||
["treasury", "budget", "create", budget_id],
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"budget": budget,
|
||||
"receipt_hash": receipt["root_hash"],
|
||||
"message": f"Created budget '{name}' with {allocated} {currency}",
|
||||
}
|
||||
|
||||
|
||||
def treasury_debit(
|
||||
budget_id: str,
|
||||
amount: int,
|
||||
description: str,
|
||||
debited_by: str = "did:vm:mcp:treasury",
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Debit (spend) from a budget.
|
||||
|
||||
Args:
|
||||
budget_id: Budget to debit from
|
||||
amount: Amount to debit (cents/smallest unit)
|
||||
description: Description of the expenditure
|
||||
debited_by: DID of the actor making the debit
|
||||
|
||||
Returns:
|
||||
Updated budget with receipt info
|
||||
"""
|
||||
budgets = _load_budgets()
|
||||
|
||||
if budget_id not in budgets:
|
||||
return {"error": f"Budget not found: {budget_id}"}
|
||||
|
||||
budget = budgets[budget_id]
|
||||
remaining = budget["allocated"] - budget["spent"]
|
||||
|
||||
if amount > remaining:
|
||||
return {
|
||||
"error": "Insufficient funds",
|
||||
"budget_id": budget_id,
|
||||
"requested": amount,
|
||||
"available": remaining,
|
||||
}
|
||||
|
||||
budget["spent"] += amount
|
||||
budgets[budget_id] = budget
|
||||
_save_budgets(budgets)
|
||||
|
||||
# Emit receipt
|
||||
receipt_body = {
|
||||
"budget_id": budget_id,
|
||||
"amount": amount,
|
||||
"currency": budget["currency"],
|
||||
"description": description,
|
||||
"debited_by": debited_by,
|
||||
"new_spent": budget["spent"],
|
||||
"new_remaining": budget["allocated"] - budget["spent"],
|
||||
}
|
||||
receipt = _emit_receipt(
|
||||
"treasury_debit",
|
||||
receipt_body,
|
||||
["treasury", "debit", budget_id],
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"budget": budget,
|
||||
"remaining": budget["allocated"] - budget["spent"],
|
||||
"receipt_hash": receipt["root_hash"],
|
||||
"message": f"Debited {amount} from '{budget['name']}' - {description}",
|
||||
}
|
||||
|
||||
|
||||
def treasury_credit(
|
||||
budget_id: str,
|
||||
amount: int,
|
||||
description: str,
|
||||
credited_by: str = "did:vm:mcp:treasury",
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Credit (add funds) to a budget.
|
||||
|
||||
Args:
|
||||
budget_id: Budget to credit
|
||||
amount: Amount to add (cents/smallest unit)
|
||||
description: Description of the credit (refund, adjustment, etc.)
|
||||
credited_by: DID of the actor making the credit
|
||||
|
||||
Returns:
|
||||
Updated budget with receipt info
|
||||
"""
|
||||
budgets = _load_budgets()
|
||||
|
||||
if budget_id not in budgets:
|
||||
return {"error": f"Budget not found: {budget_id}"}
|
||||
|
||||
budget = budgets[budget_id]
|
||||
budget["allocated"] += amount
|
||||
budgets[budget_id] = budget
|
||||
_save_budgets(budgets)
|
||||
|
||||
# Emit receipt
|
||||
receipt_body = {
|
||||
"budget_id": budget_id,
|
||||
"amount": amount,
|
||||
"currency": budget["currency"],
|
||||
"description": description,
|
||||
"credited_by": credited_by,
|
||||
"new_allocated": budget["allocated"],
|
||||
}
|
||||
receipt = _emit_receipt(
|
||||
"treasury_credit",
|
||||
receipt_body,
|
||||
["treasury", "credit", budget_id],
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"budget": budget,
|
||||
"remaining": budget["allocated"] - budget["spent"],
|
||||
"receipt_hash": receipt["root_hash"],
|
||||
"message": f"Credited {amount} to '{budget['name']}' - {description}",
|
||||
}
|
||||
|
||||
|
||||
def treasury_balance(budget_id: Optional[str] = None) -> dict[str, Any]:
|
||||
"""
|
||||
Get budget balance(s).
|
||||
|
||||
Args:
|
||||
budget_id: Specific budget ID (optional, returns all if omitted)
|
||||
|
||||
Returns:
|
||||
Budget balance(s) with current state
|
||||
"""
|
||||
budgets = _load_budgets()
|
||||
|
||||
if budget_id:
|
||||
if budget_id not in budgets:
|
||||
return {"error": f"Budget not found: {budget_id}"}
|
||||
budget = budgets[budget_id]
|
||||
return {
|
||||
"budget_id": budget_id,
|
||||
"name": budget["name"],
|
||||
"currency": budget["currency"],
|
||||
"allocated": budget["allocated"],
|
||||
"spent": budget["spent"],
|
||||
"remaining": budget["allocated"] - budget["spent"],
|
||||
}
|
||||
|
||||
# Return all budgets
|
||||
result = []
|
||||
total_allocated = 0
|
||||
total_spent = 0
|
||||
for bid, budget in budgets.items():
|
||||
remaining = budget["allocated"] - budget["spent"]
|
||||
total_allocated += budget["allocated"]
|
||||
total_spent += budget["spent"]
|
||||
result.append({
|
||||
"budget_id": bid,
|
||||
"name": budget["name"],
|
||||
"currency": budget["currency"],
|
||||
"allocated": budget["allocated"],
|
||||
"spent": budget["spent"],
|
||||
"remaining": remaining,
|
||||
})
|
||||
|
||||
return {
|
||||
"budgets": result,
|
||||
"count": len(result),
|
||||
"totals": {
|
||||
"allocated": total_allocated,
|
||||
"spent": total_spent,
|
||||
"remaining": total_allocated - total_spent,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user