* fix(mcp-oauth): allow ChatGPT connector callbacks and resume OAuth after login Add chatgpt.com/connector/oauth/* (per-instance) and the legacy chatgpt.com/connector_platform_oauth_redirect to the built-in OAuth redirect allowlist so ChatGPT MCP connectors can register and authorize. Fix the login page dropping the ?next= destination: an OAuth-initiated visit that required login previously ended on the dashboard and the connection flow silently died. Login now resumes to the sanitized next path (hard navigation, since the consent page is route-handler HTML), carries it through the MFA step-up as returnTo, and /mfa/verify hard-navigates for /api/ destinations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): dedup incoming feed rows against booked hand-entered twins Users who bookkeep via MCP/chat first and connect their bank afterwards got the same movement twice: the synced row's external_id lives in a different namespace, the free-form manual title never text-bridges the bank's raw string, and the cross-channel mirror deliberately excluded manual/mcp rows. Extend the mirror with a booked-hand-entered track: an incoming feed row is skipped when a BOOKED manual/mcp row shares its (date, ore) bucket count- symmetrically. Gates beyond the feed-vs-feed mirror: stored row must be booked (staged rows never consume an import), currencies must not contradict (bucket key is date+ore only), the cash-account guard applies to the count exactly as to consumption, and symmetry uses the Layer-1-unmatched incoming count so an already-stored row cannot inflate it. Consumption stamps the batch cash_account_id onto an account-unbound hand row, so one hand row can never consume feed rows on other accounts in later syncs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bookkeeping): inline verifikat rattelse (strike lines + text/date edit) Second sanctioned correction track under BFL 5 kap 5/9 pp, Fortnox-style: strike lines inside a posted verifikat with replacements in the same voucher, and correct description/entry_date without an andringsverifikat. Envelope: posted entries, open unlocked periods, company lock date, same-period date moves, structural/FX/doc-linked lines excluded, and a reconciliation guard preserving per-account net on bank/reskontra sides of externally linked entries. Every rattelse writes an immutable who/when row (journal_entry_rattelse_log, WORM, archived as rakenskapsinformation) and struck originals render struck-through in the verifikat; list rows and the detail header carry a Rattad marker. CLAUDE.md hard rule 1 and the swedish-accounting-compliance skill are amended to state the two-track rule. Staging carries the DDL; prod gets it on merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: live saldo in booking form, prior-year window comparison, hideable assistant FAB - Manual journal entry: saldo column now shows before -> after computed from the typed debit/credit amounts (direction feedback while booking) - Resultatrapport: a narrowed date range now compares against the same window shifted one year back (#862), merged across fiscal periods for brutet rakenskapsar; P&L rows report window activity instead of rolled-forward YTD closing - Assistant FAB: per-user hide toggle (user_preferences.hide_assistant_fab, settings > assistant), sidebar entry unaffected; collapsed sessions keep their reopen handle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(stripe): sync balance transactions as a bank feed on 1686 Import the connected Stripe balance into the transactions inbox, opt-in per connection (transaction_sync_enabled on stripe_connections): - Balance transactions map to feed rows with the two-row gross+fee split and frozen external_id formats (stripe_{acct}_{txn} / _fee), dated on created, bound to a provisioned "Stripe-saldo" cash account on 1686 so booking settles against the clearing account by construction. - Double-booking protection: settled payment-link charges import pre-linked to their settlement entry; payout rows import pre-linked to the payout entry; processPayoutPaidEvent claims the payout's fee rows at booking time (linkPayoutFeedRows, idempotent from both directions). - Cursor last_balance_txn_synced_at with 24h overlap; first run backfills 90 days floored at the day after the company lock date. - Nightly cron /api/extensions/stripe/transactions/cron (03:30), transaction-sync toggle route, "Synka nu" covers both feeds, settings panel toggle with last-synced/backfill note, sv+en strings. - Migration 20260723200000 (applied to staging). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): offer match-to-voucher on unbooked history rows Unbooked transactions with is_business already set (e.g. left behind when a voucher was removed without a full uncategorize) land in the history list instead of the inbox, where the match-against-existing-voucher action did not exist, leaving them with no path back to voucher matching. Add the same menu item to the history list for unbooked rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(transactions): enhance ownership checks and error handling in journal entry routes --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
4.9 KiB
paths
| paths | ||
|---|---|---|
|
MCP Server
Accounted exposes its bookkeeping engine as an MCP server for Claude Desktop/Code.
MCP extension (extensions/general/mcp-server/): 90+ tools covering transactions, categorization, customers/suppliers, invoices, accounts, fiscal periods, reports (trial balance, GL, BS, IS, AR/supplier ledger, VAT, KPI), reconciliation, salary runs, AGI, year-end, document upload, and loadable skills. JSON-RPC 2.0. Endpoint: /api/extensions/ext/mcp-server/mcp.
OAuth 2.1 for Claude and ChatGPT connectors: .well-known/oauth-protected-resource + .well-known/oauth-authorization-server discovery; /api/mcp-oauth/authorize, /token (PKCE), /register. Stateless AES-256-GCM auth codes (lib/auth/oauth-codes.ts). Single-use via oauth_used_codes. Allowlist: claude.ai/api/*, claude.com/api/*, chatgpt.com/connector/oauth/*, chatgpt.com/connector_platform_oauth_redirect, localhost.
npm package (packages/gnubok-mcp): Stdio-to-HTTP bridge; users run npx gnubok-mcp with API key.
Tool authoring conventions (enforced by tests)
- Every
inputSchemamust declareadditionalProperties: falseat the top level. Guarded byextensions/general/mcp-server/__tests__/strict-schemas.test.ts. - Tool descriptions must be ≤ 280 chars (guarded by
output-schema.test.ts). NoArgs:/Returns:/Examples:blocks: those belong in JSON Schema, not description prose. Use agent-native hints like "Use to…" / "Call X first" instead. - Completion-signal pattern: tools that stage operations return
STAGED_OPERATION_SCHEMA:{ staged, risk_level, actor, message, preview, period_status?, next? }. Thestaged: trueboolean is the explicit completion signal; agents must not infer completion from prose. Do NOT introduce a parallel{ success, shouldContinue, output }envelope. - Machine-readable staging contract:
tools/list(andgnubok_search_toolsdetail=full) attach a derived_metato staging writes so an agent knows the contract WITHOUT reading prose.deriveToolMeta()keys offoutputSchema === STAGED_OPERATION_SCHEMAand emits{ requires_approval: true, approve_tool: 'gnubok_approve_pending_operation', preflight? }; it merges under any literal_meta(e.g. UI widget hints), which wins on collision. Add toTOOL_PREFLIGHT_MAPwhen a write has a genuine read-only pre-flight (e.g.gnubok_run_year_end→gnubok_year_end_readiness). A new staging tool inherits_metafor free: just keep its description declaring it stages (guarded by__tests__/staging-meta.test.ts).confirmed=truebelongs on the APPROVE call for high-risk ops, never on the staging tool; only some tools acceptdry_run/idempotency_key: never imply they are universal. - Skill/atom summaries:
gnubok_list_skillsandgnubok_get_agent_briefingpass registrydescriptionfields throughtoSummary()(skills/atoms.ts): the raw SKILL.md frontmatter is a long keyword-stuffed trigger list authored for CLI matching, not display copy, and gets truncated mid-sentence otherwise. Full bodies are fetched viagnubok_load_skill. The local.claude/skills/*are the Claude-Code surface; theagent_atom_registryrows seeded from the same bodies are the canonical connector surface: when they overlap, the connector atom is authoritative for MCP users. - Tools that touch a fiscal-period-bound date (categorize, mark paid, create voucher, correct/reverse entry, approve supplier invoice) pass
dateForPeriodChecktostagePendingOperationso the response includesperiod_status: { period_id, status: open|locked|closed, lock_date }. Widgets and agents use this to disable writes without round-trips. - Qualified identifiers: no bare
idin tool OUTPUT schemas: every identifier is fully qualified (transaction_id,journal_entry_id,fact_id,dimension_value_id, …) so agents never guess which entity an id belongs to. Guarded by__tests__/qualified-ids.test.ts(a shrinking grandfathered list carries the deprecatedidaliases; new tools must use qualified names only). - Error envelope: every tool failure flows through the single dispatch point (
toToolError→getStructuredError) and returns{ error: { code, message_sv, message_en, retryable, remediation? } }.retryableis ALWAYS an explicit boolean:truemeans transient (back off and retry the identical call, pairing withidempotency_keywhere the tool accepts one);falsemeans permanent for these inputs (fix arguments/state, never blind-retry). Unclassified transient failures (deadlock, statement timeout, connection drop, upstream 429/5xx) surface as codeTRANSIENT_ERROR. Don't wrap errors in ad-hoc shapes inside tools: throw (typed errors or plainError; SQLSTATE/message inference handles classification) and let the dispatch layer build the envelope. Client-side failures (e.g. the claude.ai approval elicitation's "No approval received") never reach this envelope: idempotency keys are what make those blind retries safe.