whisper-money/app/Models
Víctor Falcón 5d7b655111
feat(mcp): add write tools (Phase 2) (#690)
## MCP Phase 2 — write tools

> **Stacked on #689** (`mcp-functionality`). Base this PR on
`mcp-functionality`, not `main`, and merge it **after** #689.

Phase 1 shipped a read-only MCP server for Pro accounts. This adds the
**write** surface and re-enables the read/read-write token scope the UI
dropped in PR1.

### Write tools
A new `WriteTool` base extends `McpTool`: on top of the Pro-plan gate it
requires the calling token to carry `mcp:write`, returning a clear error
for read-only tokens. Each concrete tool is annotated `#[IsDestructive]`
(PHP attributes aren't inherited, so the annotation lives on each tool,
not the base — a docblock on `WriteTool` notes this).

- `create_transaction` — manual (non-connected) accounts only; forces
`source = manually_created`.
- `update_transaction` / `delete_transaction` — manually-created
transactions only; bank/imported ones stay locked.
- `categorize_transaction` — sets/clears the category on **any**
transaction (imported included), marking it `category_source = manual`.
- `label_transaction` — add/remove labels on **any** transaction.
- `create_balance` — balance snapshot on manual accounts only.
- `create_category` / `update_category` / `delete_category` — mirrors
the settings controller (parent/depth/cycle rules, cashflow derivation,
child strategies).
- `create_label` / `update_label` / `delete_label`.
- `create_automation_rule` / `update_automation_rule` /
`delete_automation_rule` — JsonLogic conditions + category/label
actions, at least one action required.
- `list_labels` — a small **read** tool added so label ids are
discoverable (label/automation tools are unusable without it).

### Guardrails
Write tools never touch bank-sourced data: the existing
`TransactionSource` enum and `Account::isConnected()` are the barriers,
reused not reinvented. There is no server-side write confirmation
(client-controlled, accepted decision) — hence `#[IsDestructive]`.

### Token scope
`StoreMcpTokenRequest` re-adds `scope` (`read` | `read_write`); the
controller grants `['mcp:read']` or `['mcp:read', 'mcp:write']`. The
settings page gets its scope selector back with honest copy (new strings
added to `lang/es.json`). The `/mcp` route stays gated on
`abilities:mcp:read` — any MCP token can connect and read; the per-tool
`mcp:write` check is what blocks writes.

### Tests
Happy path + guardrail failures for every write tool, the
read-only-token rejection (via a real read-only PAT so the `tokenCan`
gate runs exactly as over HTTP), cross-user isolation, the inherited Pro
gate, and read/read_write scope validation.

### Notes
- `AutomationRule::labels()` gained a generic return annotation (needed
for larastan level 5 on the new label mapping).

### Verification
- `vendor/bin/pint --test` 
- `vendor/bin/phpstan analyse` (larastan level 5) — 0 errors 
- `php artisan test tests/Feature/Mcp
tests/Feature/Settings/McpTokenTest.php
tests/Feature/LocalizationTest.php` 
- `prettier --check` / `eslint` on `settings/mcp.tsx` 
2026-07-17 15:25:03 +00:00
..
Concerns feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
Account.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
AccountBalance.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
AiConsent.php feat(email): follow up after post-onboarding AI consent (#596) 2026-06-26 17:56:06 +00:00
AutomationRule.php feat(mcp): add write tools (Phase 2) (#690) 2026-07-17 15:25:03 +00:00
AutomationRuleLabel.php feat: add transaction labels feature (#24) 2025-12-13 13:02:19 +01:00
Bank.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
BankingConnection.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
BankingSyncLog.php fix(banking): retry failed sync connections and log every sync attempt (#251) 2026-03-31 11:34:35 +01:00
Budget.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
BudgetPeriod.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
BudgetTransaction.php fix(static-analysis): clear phpstan-baseline by fixing all suppressed errors (#183) 2026-03-02 12:22:30 +00:00
Category.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
CategoryCorrection.php feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
EncryptedMessage.php fix(static-analysis): clear phpstan-baseline by fixing all suppressed errors (#183) 2026-03-02 12:22:30 +00:00
ExchangeRate.php Preload exchange rates (#362) 2026-05-06 16:35:49 +01:00
IntegrationRequest.php feat(integration-requests): add not-doable status with a public comment (#552) 2026-06-17 16:36:32 +02:00
IntegrationRequestVote.php feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00
Label.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
LabelTransaction.php feat: add transaction labels feature (#24) 2025-12-13 13:02:19 +01:00
LoanDetail.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
RealEstateDetail.php refactor(api): standardize serialization via model $hidden (#492) 2026-06-05 13:57:34 +02:00
RuleSuggestion.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
SavedFilter.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
Space.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
SpaceInvitation.php feat(spaces): phase 0 — multi-tenant Space foundation (no behaviour change) (#650) 2026-07-09 14:26:07 +02:00
StuckCohortSnapshot.php feat(stats): weekly paywall stuck-cohort report to Discord (#563) 2026-06-19 14:12:51 +00:00
SuggestionRun.php feat(ai): suggest automation rules during onboarding (#523) 2026-06-13 22:51:15 +02:00
Transaction.php feat(transactions): allow editing all fields of manual transactions (#683) 2026-07-16 08:59:10 +02:00
User.php feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
UserLead.php feat(leads): add user lead re-invite campaign (#432) 2026-05-26 08:35:31 +02:00
UserMailLog.php feat: Send custom emails to users (#52) 2026-01-09 09:33:19 +01:00
UserSetting.php feat(settings): let users disable bank transactions email (#472) 2026-06-02 12:24:39 +02:00