feat(mcp): ask the bank first: connect link deep-starts the named bank's consent (#1951)

* feat(mcp): ask the bank first: connect link deep-starts the named bank's consent

The connect card used to open the generic picker page; the user then chose
the bank there. The agent now asks 'vilken bank har företaget?' among the
opening questions and passes it to gnubok_connect_bank, whose connect_url
becomes /import?mode=psd2&bank=<name>. BankSelector resolves the name
(exact, then unique prefix, then unique substring: ambiguous names fall
back to the prefilled picker rather than guessing an institution) and
auto-starts that bank's consent through the same onConnect handler, so
the duplicate-pending and renew-instead guards stay fully interactive.
The param is stripped via history.replaceState after the one-shot so an
aborted bank flow plus back-navigation does not silently relaunch.

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

* fix(mcp): first-year suggestion keeps the AB vs enskild firma distinction

Compliance-review finding on #1949: the suggestion text collapsed both
forms onto a 31 December end. Only an enskild firma's first year MUST end
31 December; an AB may pick any end within BFL 3 kap 3 §'s 18-month cap,
with 31 December as the common default. The lookup tool's still_to_ask
line and the skill now say so explicitly, and first-year-defaults
documents that fiscalYear-null is a strong-not-perfect filed-report
signal that must only ever feed confirm-question suggestions.

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

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-26 14:31:45 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent 9a56b7aff9
commit ec9cab24cc
7 changed files with 168 additions and 11 deletions
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react'
import { ChevronRight, Landmark, Loader2, Search } from 'lucide-react'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { matchBankByName } from '../lib/bank-match'
export interface Bank {
name: string
@@ -128,6 +129,41 @@ export function BankSelector({
const [error, setError] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const searchRef = useRef<HTMLInputElement>(null)
const autoStartHandled = useRef(false)
// Deep-link preselect (?bank=<name>, set by the MCP connect card when the
// user already named their bank in chat): auto-start that bank's consent
// the way clicking its row would, so the flow feels like Skatteverket's
// one-click authorize. The param is stripped immediately so an abort at
// the bank followed by back-navigation does not silently re-launch; an
// unknown or ambiguous name just prefills the search instead of guessing.
// Guards further down (duplicate pending, renew-instead-409) stay fully
// interactive because this runs through the same onConnect handler.
useEffect(() => {
if (autoStartHandled.current || isLoading || banks.length === 0 || isConnecting) return
const params = new URLSearchParams(window.location.search)
const requested = params.get('bank')
if (!requested) {
autoStartHandled.current = true
return
}
autoStartHandled.current = true
params.delete('bank')
const query = params.toString()
window.history.replaceState(
null,
'',
`${window.location.pathname}${query ? `?${query}` : ''}${window.location.hash}`
)
const match = matchBankByName(banks, requested)
if (match) {
onConnect(match)
} else {
setSearchQuery(requested)
searchRef.current?.focus()
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot after the bank list arrives
}, [isLoading, banks, isConnecting])
useEffect(() => {
async function fetchBanks() {