whisper-money/resources/js
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
..
components feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
contexts Move transaction filtering from client-side to server-side (#126) 2026-02-16 12:49:04 +01:00
hooks fix(dashboard): stop negative asset balances inflating net worth (#660) 2026-07-08 15:22:16 +00:00
layouts feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
lib fix(accounts): show credit cards as positive and exclude them from net worth (#673) 2026-07-13 07:27:52 +00:00
pages feat(mcp): add write tools (Phase 2) (#690) 2026-07-17 15:25:03 +00:00
providers fix: move community link to user menu (#442) 2026-05-27 17:39:26 +02:00
services feat(transactions): allow editing all fields of manual transactions (#683) 2026-07-16 08:59:10 +02:00
types feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
utils feat(currencies): add Swedish Krona (SEK) (#684) 2026-07-16 14:25:33 +00:00
app.tsx feat(onboarding): clarify the "categorize at least 5" goal in the categorizer (#616) 2026-07-01 08:34:51 +02:00
ssr.tsx Remove plaintext-transactions feature flag & E2E references (#116) 2026-02-13 11:10:21 +01:00