Files
sure/app/models/assistant.rb
T
Brandon WolfandClaude Fable 5 00b7252fbf feat(mcp): Add MCP budget update tool (#2908)
* feat(mcp): Add MCP budget update tool

Adds an update_budget assistant/MCP function so AI assistants can write
monthly budgets: total budgeted spending, expected income, and
per-category allocations in one transactional call.

- Month resolution and slug format mirror get_budget (YYYY-MM or
  MMM-YYYY, custom month start respected); targeting a valid month with
  no budget row bootstraps it via Budget.find_or_bootstrap, same as the
  budgets UI.
- Category allocations accept an exact (case-insensitive) name or id and
  go through BudgetCategory#update_budgeted_spending!, so subcategory
  writes keep the parent total in sync.
- All writes in one call share a transaction: an invalid category rolls
  back a totals change from the same call.
- Family-scoped like the budgets UI; amounts validated non-negative.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(mcp): harden update_budget per review feedback

- Extract shared month resolution into Assistant::Function::MonthResolvable
  so get_budget and update_budget can't drift on custom month starts
- Run budget bootstrap inside the update transaction so a failed entry
  no longer leaves a newly created budget behind
- Apply explicit parent amounts after subcategory syncs so results don't
  depend on the caller's array order
- Reject non-finite amounts (NaN/Infinity)
- Explain the synthetic Uncategorized bucket instead of a generic
  category-not-found error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:49:43 +02:00

72 lines
2.1 KiB
Ruby

module Assistant
Error = Class.new(StandardError)
REGISTRY = {
"builtin" => Assistant::Builtin,
"external" => Assistant::External
}.freeze
# Statement Vault + provenance tools, for users who opted into preview features
# in Settings -> Preferences. They back the wealth agent-harness workflow
# documented in docs/llm-guides/wealth-agent-harness.md.
PREVIEW_FUNCTION_CLASSES = [
Function::UploadAccountStatement,
Function::ListAccountStatements,
Function::GetAccountStatement,
Function::GetStatementCoverage,
Function::RecordValuation
].freeze
class << self
def for_chat(chat)
implementation_for(chat).for_chat(chat)
end
def config_for(chat)
raise Error, "chat is required" if chat.blank?
Assistant::Builtin.config_for(chat)
end
def available_types
REGISTRY.keys
end
# The single registry behind both the builtin chat and the /mcp endpoint's
# tools/list — a function class added here is immediately callable by an
# external agent, so pass the user to keep preview tools out of the default
# surface.
def function_classes(user = nil)
classes = [
Function::GetTransactions,
Function::GetAccounts,
Function::GetHoldings,
Function::GetBalanceSheet,
Function::GetIncomeStatement,
Function::GetBudget,
Function::ImportBankStatement,
Function::SearchFamilyFiles,
Function::CreateGoal,
Function::GetTags,
Function::CreateTag,
Function::UpdateTag,
Function::GetCategories,
Function::CreateCategory,
Function::UpdateCategory,
Function::UpdateTransaction,
Function::UpdateBudget
]
classes += PREVIEW_FUNCTION_CLASSES if user&.preview_features_enabled?
classes
end
private
def implementation_for(chat)
raise Error, "chat is required" if chat.blank?
type = ENV["ASSISTANT_TYPE"].presence || chat.user&.family&.assistant_type.presence || "builtin"
REGISTRY.fetch(type) { REGISTRY["builtin"] }
end
end
end