feat(tools): reposition as Do Better Legal two-track Norwegian-law MCP

De-family-ify shared JSON tools (persona-aware routing + neutral base
prompt), make the verification review pick its engine per track
(family/child-welfare -> dbn-legal-agent-v3, others -> gpt-4o interim),
and route product-name strings through dbnToolsProductName(). Rebrand the
MCP/tools surface (mcp.php + i18n mcp_* strings) to Do Better Legal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 07:45:17 +02:00
parent d156f8cf6b
commit 7fcd317205
12 changed files with 158 additions and 72 deletions
+5 -3
View File
@@ -847,9 +847,10 @@ PROMPT;
: '';
$docExcerpt = mb_substr($docText, 0, 8000, 'UTF-8');
$product = dbnToolsProductName();
$prompt = <<<PROMPT
You are Do Better Norge Legal Tools. Produce a structured Barnevernet case analysis for: {$roleStr}.
You are {$product} Tools. Produce a structured Barnevernet case analysis for: {$roleStr}.
HALLUCINATION RULES — READ FIRST:
- You may ONLY cite statute sections (§), ECHR article numbers, ECHR application numbers, case names, and Bufdir/Statsforvalter circular references that appear verbatim in the numbered corpus sources below.
@@ -984,7 +985,8 @@ PROMPT;
$checkFindings = dbnToolsRunLegalCheck(
(string)($json['advocacy_brief'] ?? ''),
$docType,
$this->resolvedPersonaPrompt
$this->resolvedPersonaPrompt,
$this->personaSlug
);
if (!empty($checkFindings)) {
if (!is_array($json['procedural_red_flags'] ?? null)) {
@@ -1379,7 +1381,7 @@ PROMPT;
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
}
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
}
return $package;
}
+1 -1
View File
@@ -56,7 +56,7 @@ final class DbnMcpRuntime
'mode' => ['type' => 'string', 'enum' => ['standard', 'strict']],
'output_format' => ['type' => 'string', 'enum' => ['contextual', 'generic', 'pseudonym']],
], ['text']),
self::tool('dbn.translate', 'Translate legal document', 'Translate Norwegian family-law text with legal terminology annotations.', [
self::tool('dbn.translate', 'Translate legal document', 'Translate Norwegian legal text with legal terminology annotations.', [
'text' => $text,
'source_lang' => $lang,
'target_lang' => $lang,
+8 -5
View File
@@ -440,7 +440,7 @@ final class DbnDeepResearchAgent
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
}
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
}
return $package;
}
@@ -487,8 +487,9 @@ final class DbnDeepResearchAgent
$priorContextBlock = implode("\n", $parts) . "\n\nNow investigate this branch:\n";
}
$product = dbnToolsProductName();
$prompt = <<<PROMPT
{$rolePrefix}{$priorContextBlock}You are reviewing the input below to set up a deep legal research pass against the Do Better Norge family-law corpus.
{$rolePrefix}{$priorContextBlock}You are reviewing the input below to set up a deep legal research pass against the {$product} Norwegian legal corpus.
Input:
{$seedDescription}
@@ -580,8 +581,9 @@ Rules:
- Write the questions in {$locale}.
PROMPT;
} else {
$product = dbnToolsProductName();
$prompt = <<<PROMPT
You are decomposing a Do Better Norge legal-research request into {$targetCount} focused sub-questions that should each be answered by the legal corpus (Norwegian family law, child welfare, ECHR/Hague).
You are decomposing a {$product} legal-research request into {$targetCount} focused sub-questions that should each be answered by the legal corpus (Norwegian law — e.g. family, child welfare, immigration, labour, consumer/tenancy, ECHR/Hague).
Research brief:
{$brief}
@@ -1064,9 +1066,10 @@ PROMPT;
? "\nKey retrieval signals (statutory/factual terms that drove corpus search — ground your brief in these where sources permit):\n" . implode(', ', $keySignals) . "\n"
: '';
$product = dbnToolsProductName();
if ($advocateRole !== '') {
$prompt = <<<PROMPT
You are Do Better Norge Legal Tools producing a legal preparation brief in {$locale}.
You are {$product} Tools producing a legal preparation brief in {$locale}.
Your client: {$advocateRole}
{$priorContextSection}
User input:
@@ -1106,7 +1109,7 @@ Return JSON:
PROMPT;
} else {
$prompt = <<<PROMPT
You are Do Better Norge Legal Tools running a deep-research synthesis. You MUST ground every claim in the numbered sources below, using inline `[n]` citation markers that map to the source list. Do NOT cite a source you did not use. Do NOT invent statutes, paragraph numbers, case names, dates, or parties.
You are {$product} Tools running a deep-research synthesis. You MUST ground every claim in the numbered sources below, using inline `[n]` citation markers that map to the source list. Do NOT cite a source you did not use. Do NOT invent statutes, paragraph numbers, case names, dates, or parties.
{$priorContextSection}
User input:
{$seedDescription}
+3 -2
View File
@@ -803,9 +803,10 @@ PROMPT;
$docTypeB = $metaB['doc_type'] ?? $nameB;
$docDateB = $metaB['doc_date'] ?? '?';
$authority = $metaA['issuing_authority'] ?? $metaB['issuing_authority'] ?? 'the authority';
$product = dbnToolsProductName();
$prompt = <<<PROMPT
You are Do Better Norge Legal Tools evaluating discrepancies between two Barnevernet document versions.
You are {$product} Tools evaluating discrepancies between two Barnevernet document versions.
HALLUCINATION RULES:
- Only cite statute sections (§), ECHR articles, and case names that appear verbatim in the corpus sources below.
@@ -1040,7 +1041,7 @@ PROMPT;
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
}
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
}
return $package;
}
+4 -3
View File
@@ -282,7 +282,7 @@ PROMPT;
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
$legalCheck = [];
try {
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body);
$legalCheck = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
} catch (Throwable $e) { /* silent — non-critical */ }
// ── Translate to user language (if not Norwegian) ───────────────────────
@@ -777,7 +777,8 @@ EOT,
array $classify,
string $originalDraftNo,
string $jurisdiction,
?callable $emit = null
?callable $emit = null,
?string $persona = null
): array {
$body = $intake['recipient_body'] ?? 'other';
$outputType = $intake['output_type'] ?? 'email';
@@ -808,7 +809,7 @@ EOT,
if ($emit) { $emit('progress', ['detail' => self::L('legal_check', $userLang)]); }
$legalCheckRefine = [];
try {
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body);
$legalCheckRefine = dbnToolsRunLegalCheck($checked['draft'], $body, null, $persona);
} catch (Throwable $e) { /* silent — non-critical */ }
$draftUser = $checked['draft'];
+19 -5
View File
@@ -28,6 +28,8 @@ final class DbnLegalAnalysisAgent
/** Persona system prompt (from the resolved chat profile); null ⇒ use the built-in barnevern prompt. */
private ?string $personaPrompt = null;
/** Persona slug driving the per-track reviewer model; default = child-welfare (family track). */
private string $personaSlug = 'child-welfare';
public function withPersonaPrompt(?string $prompt): self
{
@@ -36,6 +38,15 @@ final class DbnLegalAnalysisAgent
return $this;
}
public function withPersonaSlug(?string $slug): self
{
$slug = is_string($slug) ? trim($slug) : '';
if ($slug !== '') {
$this->personaSlug = $slug;
}
return $this;
}
public function __construct()
{
// On Azure: gpt-4o-mini for extraction/synthesis. On Bedrock: factory picks Haiku/Sonnet.
@@ -408,11 +419,12 @@ PROMPT;
$userMsg .= "\n\n" . $ctxLabel . ': ' . $issue['brief_context'];
}
if ($corpusContext !== '') {
$product = dbnToolsProductName();
$srcLabel = match ($language) {
'no' => 'Relevante kilder fra Do Better Norge-korpuset',
'pl' => 'Istotne źródła z korpusu Do Better Norge',
'uk' => 'Релевантні джерела з корпусу Do Better Norge',
default => 'Relevant sources from the Do Better Norge corpus',
'no' => 'Relevante kilder fra ' . $product . '-korpuset',
'pl' => 'Istotne źródła z korpusu ' . $product,
'uk' => 'Релевантні джерела з корпусу ' . $product,
default => 'Relevant sources from the ' . $product . ' corpus',
};
$userMsg .= "\n\n" . $srcLabel . ":\n" . $corpusContext;
}
@@ -613,7 +625,9 @@ PROMPT;
try {
$legalCheck = dbnToolsRunLegalCheck(
mb_strimwidth($synth['overall_assessment'], 0, 800),
$docType
$docType,
$this->personaPrompt,
$this->personaSlug
);
} catch (Throwable) {}
+33 -21
View File
@@ -39,8 +39,9 @@ final class DbnLegalToolsService
'shared' => 'Legal Library only',
default => 'Legal Library + personal corpus',
};
$product = dbnToolsProductName();
$trace = [
$this->trace('Query interpretation', "Searching Do Better Norge {$scopeLabel}.", 'complete'),
$this->trace('Query interpretation', "Searching {$product} {$scopeLabel}.", 'complete'),
$this->trace('Search tools used', 'ClientRagPipeline::searchAll with keyword mode.', 'running'),
];
@@ -252,12 +253,9 @@ Return JSON only with these keys:
}
PROMPT;
// Persona voice/domain prepended to the JSON-enforcing scaffold (keeps the
// Persona voice/domain folded into the JSON-enforcing scaffold (keeps the
// structured-output contract while applying the persona's legal framing).
$system = $this->legalJsonSystemPrompt($language);
if (!empty($personaResolved['system_prompt'])) {
$system = $personaResolved['system_prompt'] . "\n\n" . $system;
}
$system = $this->legalJsonSystemPrompt($language, $personaResolved['system_prompt'] ?? null);
$askDeployment = $personaModel;
$raw = $gateway->withDeployment($askDeployment)->chatText([
['role' => 'system', 'content' => $system],
@@ -1166,7 +1164,7 @@ PROMPT;
dbnToolsAbort('The family-legal corpus package is not active.', 503, 'package_unavailable');
}
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
dbnToolsAbort('Do Better Norge does not have an active family-legal subscription.', 503, 'subscription_missing');
dbnToolsAbort(dbnToolsProductName() . ' does not have an active family-legal subscription.', 503, 'subscription_missing');
}
return $package;
}
@@ -1191,30 +1189,44 @@ PROMPT;
return [$this->azure, ($engine === 'azure_full') ? 'gpt-4o' : 'gpt-4o-mini'];
}
private function runJsonTool(string $prompt, string $language, int $maxTokens): array
private function runJsonTool(string $prompt, string $language, int $maxTokens, ?array $persona = null): array
{
$raw = $this->azure->chatText([
['role' => 'system', 'content' => $this->legalJsonSystemPrompt($language)],
// With a persona, route to its pinned engine (Track-1 → tuned Qwen, Track-2 → gpt-4o)
// and fold its domain framing into the system prompt. Without one (e.g. pasted-text
// tools), keep the default Azure routing with the neutral base prompt.
$personaPrompt = $persona['system_prompt'] ?? null;
if ($persona !== null) {
[$gateway, $model] = $this->personaGateway($persona, 'azure_mini');
$gateway = $gateway->withDeployment($model);
} else {
$gateway = $this->azure;
}
$raw = $gateway->chatText([
['role' => 'system', 'content' => $this->legalJsonSystemPrompt($language, $personaPrompt)],
['role' => 'user', 'content' => $prompt],
], [
'json' => true,
'temperature' => 0.1,
'max_tokens' => $maxTokens,
]);
$json = $this->azure->decodeJsonObject($raw);
$json = $gateway->decodeJsonObject($raw);
if (!$json) {
dbnToolsAbort('Azure OpenAI did not return valid structured JSON.', 502, 'azure_invalid_json');
dbnToolsAbort('The model did not return valid structured JSON.', 502, 'invalid_json');
}
return $json;
}
private function legalJsonSystemPrompt(string $language): string
private function legalJsonSystemPrompt(string $language, ?string $personaPrompt = null): string
{
$locale = dbnToolsLanguageName($language);
$locale = dbnToolsLanguageName($language);
$product = dbnToolsProductName();
$personaPrompt = is_string($personaPrompt) ? trim($personaPrompt) : '';
// The persona (family, immigration, labour, …) supplies the domain framing; the
// base prompt stays domain-neutral so non-family tracks are not cast as child-welfare.
$personaBlock = $personaPrompt !== '' ? ($personaPrompt . "\n") : '';
return <<<PROMPT
You are Do Better Norge Legal Tools — a source-grounded Norwegian legal preparation assistant.
Norwegian legal context: CPS cases follow the chain Barnevernstjenesten → Fylkesnemnda → Statsforvalteren → Tingrett → Lagmannsrett → Høyesterett. Key order types: akuttvedtak (emergency removal), omsorgsvedtak (care order), samværsvedtak (contact order). Relevant bodies: BUP (child psychiatry), PPT (educational psychology), NAV (welfare).
Use the DBN legal guardrails:
You are {$product} Tools — a source-grounded Norwegian legal preparation assistant covering all areas of Norwegian law.
{$personaBlock}Legal guardrails:
- Answer only from provided source excerpts or pasted text.
- Treat your role as legal information and issue-spotting, not final legal advice.
- Never invent statutes, paragraph numbers, case names, citations, parties, dates, or sources.
@@ -1260,7 +1272,7 @@ PROMPT;
'title' => $title,
'excerpt' => $docSummary ?? $rawExcerpt,
'chunk_text' => $rawExcerpt,
'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? 'Do Better Norge'),
'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? dbnToolsProductName()),
'score' => $score,
'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null,
'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null,
@@ -1354,7 +1366,7 @@ PROMPT;
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as &$row) {
$row['similarity'] = 0.25;
$row['source_name'] = 'Do Better Norge private corpus';
$row['source_name'] = dbnToolsProductName() . ' private corpus';
$row['source_type'] = 'private';
}
return $rows;
@@ -1819,7 +1831,7 @@ PROMPT;
$enriched = $text;
$corpusUsed = $corpusContext !== '';
if ($corpusUsed) {
$enriched = "[Relevant legal context from Do Better Norge corpus]\n"
$enriched = '[Relevant legal context from ' . dbnToolsProductName() . " corpus]\n"
. $corpusContext
. "\n\n---\n\nDocument to summarise:\n"
. $text;
@@ -1874,7 +1886,7 @@ PROMPT;
}
$corpusNote = $corpusUsed
? 'Summary enriched with ' . count(array_filter(explode('=== ', $corpusContext))) . ' passage(s) from the Do Better Norge legal corpus.'
? 'Summary enriched with ' . count(array_filter(explode('=== ', $corpusContext))) . ' passage(s) from the ' . dbnToolsProductName() . ' legal corpus.'
: 'No corpus search performed; summarised from document text only.';
$trace = [
+59 -12
View File
@@ -479,6 +479,39 @@ function dbnToolsDefaultPersonaSlug(): string
return $v !== '' ? $v : 'family';
}
/** Product display name for tool-facing copy. Override via .env DBN_PRODUCT_NAME. */
function dbnToolsProductName(): string
{
$v = trim((string)(dbnToolsEnv('DBN_PRODUCT_NAME', 'Do Better Legal') ?? 'Do Better Legal'));
return $v !== '' ? $v : 'Do Better Legal';
}
/**
* Engine track for a persona slug:
* 'family' → family + child-welfare, served/verified by the fine-tuned Qwen.
* 'general' → all other Norwegian-law personas, served/verified by the interim engine.
*/
function dbnToolsPersonaTrack(?string $slug): string
{
$slug = strtolower(trim((string)$slug));
return in_array($slug, ['family', 'child-welfare'], true) ? 'family' : 'general';
}
/**
* Reviewer/verification model for a persona's track.
* Family track → fine-tuned Qwen (dbn-legal-agent-v3).
* General track → interim gpt-4o; swap to the 2nd fine-tune later via .env
* DBN_REVIEW_MODEL_GENERAL with no code change.
*/
function dbnToolsReviewerModel(?string $slug = null): string
{
if (dbnToolsPersonaTrack($slug) === 'family') {
return trim((string)(dbnToolsEnv('DBN_REVIEW_MODEL_FAMILY', 'dbn-legal-agent-v3') ?? 'dbn-legal-agent-v3'))
?: 'dbn-legal-agent-v3';
}
return trim((string)(dbnToolsEnv('DBN_REVIEW_MODEL_GENERAL', 'gpt-4o') ?? 'gpt-4o')) ?: 'gpt-4o';
}
/**
* Resolve a DBN persona (a caveauAI chat profile for client 57) into a normalized
* runtime bundle the tools can act on:
@@ -544,7 +577,7 @@ function dbnToolsResolvePersona(int $clientId, ?string $slug = null): array
dbnToolsAbort('No persona profile and the family-legal corpus package is not active.', 503, 'package_unavailable');
}
if (!dbnToolsHasActiveSubscription($clientId, (int)$package['id'])) {
dbnToolsAbort('Do Better Norge does not have an active corpus subscription.', 503, 'subscription_missing');
dbnToolsAbort(dbnToolsProductName() . ' does not have an active corpus subscription.', 503, 'subscription_missing');
}
$resolved = [
'slug' => 'family',
@@ -938,7 +971,7 @@ function dbnToolsRequireClient(): array
{
$client = dbnToolsFetchClient();
if (!$client || empty($client['is_active'])) {
dbnToolsAbort('Do Better Norge client tenant is not active or was not found.', 503, 'client_unavailable');
dbnToolsAbort(dbnToolsProductName() . ' client tenant is not active or was not found.', 503, 'client_unavailable');
}
return $client;
}
@@ -1304,26 +1337,33 @@ function dbnToolsLiteLLMEmbedBatch(array $texts, string $model = 'nomic-embed-te
// Strategy: ask ONE targeted question per document type, cap tokens at 350 to cut
// off before the tool-planning loop kicks in, parse the answer for legal findings.
function dbnToolsRunLegalCheck(string $brief, string $docType, ?string $personaPrompt = null): array
function dbnToolsRunLegalCheck(string $brief, string $docType, ?string $personaPrompt = null, ?string $personaSlug = null): array
{
$question = dbnToolsSelectCheckQuestion($docType, $brief);
$track = dbnToolsPersonaTrack($personaSlug);
$model = dbnToolsReviewerModel($personaSlug);
$question = dbnToolsSelectCheckQuestion($docType, $brief, $track);
if ($question === null) {
return [];
}
$opts = [
'model' => 'dbn-legal-agent-v3',
'model' => $model,
'temperature' => 0.1,
'max_tokens' => 350,
'timeout' => 45,
// No 'json' key — plain narrative, no response_format flag
];
// Base prompt from the resolved persona (default = Child-welfare/barnevern).
// Base prompt: resolved persona prompt wins; otherwise a track-appropriate default.
$personaPrompt = is_string($personaPrompt) ? trim($personaPrompt) : '';
$sysMsg = $personaPrompt !== ''
? $personaPrompt
: 'Du er en ekspert på norsk barnevernsloven og EMD-praksis. Svar alltid på norsk med korrekt juridisk terminologi. Bruk terskler fra barnevernsloven 2021: § 4-25 krever «klar nødvendighet». Strand Lobben mot Norge (37283/13) setter krav om rehabiliteringsplan før adopsjon. Aldri oppfinn paragrafnumre, saksnumre eller dommernavn.';
if ($personaPrompt !== '') {
$sysMsg = $personaPrompt;
} elseif ($track === 'family') {
$sysMsg = 'Du er en ekspert på norsk barnevernsloven og EMD-praksis. Svar alltid på norsk med korrekt juridisk terminologi. Bruk terskler fra barnevernsloven 2021: § 4-25 krever «klar nødvendighet». Strand Lobben mot Norge (37283/13) setter krav om rehabiliteringsplan før adopsjon. Aldri oppfinn paragrafnumre, saksnumre eller dommernavn.';
} else {
$sysMsg = 'Du er en erfaren norsk jurist med bred kompetanse i norsk rett (forvaltningsrett, utlendingsrett, arbeidsrett, husleie- og forbrukerrett). Svar alltid på norsk med korrekt juridisk terminologi. Aldri oppfinn paragrafnumre, saksnumre, dommernavn eller kilder. Hvis du er usikker, si det tydelig.';
}
// Prepend a short snippet of the actual synthesis text so v3 answers in context,
// not just as a general law quiz. Strip HTML tags and cap at 350 chars.
@@ -1360,16 +1400,23 @@ function dbnToolsRunLegalCheck(string $brief, string $docType, ?string $personaP
'severity' => dbnToolsInferCheckSeverity($clean),
'legal_basis' => dbnToolsExtractCheckLegalBasis($clean),
'source_refs' => [],
'what_to_check'=> 'Verifiser med norsk familieretsadvokat',
'check_model' => 'dbn-legal-agent-v3',
'what_to_check'=> $track === 'family' ? 'Verifiser med norsk familieretsadvokat' : 'Verifiser med norsk advokat',
'check_model' => $model,
]];
}
function dbnToolsSelectCheckQuestion(string $docType, string $brief): ?string
function dbnToolsSelectCheckQuestion(string $docType, string $brief, string $track = 'family'): ?string
{
$t = mb_strtolower($docType);
$b = mb_strtolower($brief);
// Track 2 (other Norwegian law): neutral verification question, always fires.
if ($track !== 'family') {
return 'Vurder teksten ovenfor opp mot gjeldende norsk rett: er de rettslige påstandene korrekte, '
. 'er det vist til riktige lover og paragrafer, og mangler det viktige rettslige vilkår, frister '
. 'eller prosessuelle krav som burde vært nevnt?';
}
if (str_contains($t, 'akutt') || str_contains($t, 'emergency')) {
return 'Hva er den korrekte rettslige terskelen for midlertidig plassering utenfor hjemmet '
. 'etter barnevernsloven § 4-25 (2021-loven)? Er det forskjell mellom § 4-6 (1992-loven) '
+16 -16
View File
@@ -470,11 +470,11 @@ function dbnToolsTranslations(): array
'doc_picker_modal_title' => 'Select from My Docs',
// MCP setup page + tool detail pages
'mcp_page_title' => 'MCP — Do Better Norge',
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all 19 DBN legal preparation tools via MCP.',
'mcp_page_title' => 'MCP — Do Better Legal',
'mcp_meta_desc' => 'Connect Claude, Cursor, and other AI tools to all Do Better Legal Norwegian-law preparation tools via MCP.',
'mcp_hero_badge' => '✦ Plus & Pro',
'mcp_hero_h1' => 'Use DBN tools from Claude, Cursor & Copilot',
'mcp_hero_sub' => 'Connect any MCP client to all 19 Do Better Norge tools — transcription, legal analysis, timelines, redaction, and more.',
'mcp_hero_h1' => 'Use Do Better Legal tools from Claude, Cursor & Copilot',
'mcp_hero_sub' => 'Connect any MCP client to the full Do Better Legal toolset — transcription, legal analysis, timelines, redaction, and more.',
'mcp_token_section_title' => 'Your MCP token',
'mcp_gate_guest_p' => 'Sign in to create your personal MCP token. Available to Plus and Pro members.',
'mcp_gate_guest_btn' => 'Sign in',
@@ -928,11 +928,11 @@ function dbnToolsTranslations(): array
'doc_picker_modal_title' => 'Velg fra Mine dokumenter',
// MCP setup page + tool detail pages
'mcp_page_title' => 'MCP — Do Better Norge',
'mcp_meta_desc' => 'Koble Claude, Cursor og andre AI-verktøy til alle 19 DBN juridiske forberedelsesverktøy via MCP.',
'mcp_page_title' => 'MCP — Do Better Legal',
'mcp_meta_desc' => 'Koble Claude, Cursor og andre AI-verktøy til alle Do Better Legal-verktøy for norsk juridisk forberedelse via MCP.',
'mcp_hero_badge' => '✦ Plus & Pro',
'mcp_hero_h1' => 'Bruk DBN-verktøy fra Claude, Cursor & Copilot',
'mcp_hero_sub' => 'Koble enhver MCP-klient til alle 19 Do Better Norge-verktøy — transkripsjon, juridisk analyse, tidslinjer, redigering og mer.',
'mcp_hero_h1' => 'Bruk Do Better Legal-verktøy fra Claude, Cursor & Copilot',
'mcp_hero_sub' => 'Koble enhver MCP-klient til hele Do Better Legal-verktøysettet — transkripsjon, juridisk analyse, tidslinjer, redigering og mer.',
'mcp_token_section_title' => 'Din MCP-token',
'mcp_gate_guest_p' => 'Logg inn for å opprette din personlige MCP-token. Tilgjengelig for Plus- og Pro-medlemmer.',
'mcp_gate_guest_btn' => 'Logg inn',
@@ -1386,11 +1386,11 @@ function dbnToolsTranslations(): array
'doc_picker_modal_title' => 'Вибрати з Моїх документів',
// MCP setup page + tool detail pages
'mcp_page_title' => 'MCP — Do Better Norge',
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх 19 юридичних підготовчих інструментів DBN через MCP.',
'mcp_page_title' => 'MCP — Do Better Legal',
'mcp_meta_desc' => 'Підключіть Claude, Cursor та інші інструменти ШІ до всіх інструментів Do Better Legal для підготовки за нормами норвезького права через MCP.',
'mcp_hero_badge' => '✦ Плюс та Профі',
'mcp_hero_h1' => 'Використовуйте інструменти DBN від Claude, Cursor та Copilot',
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до всіх 19 інструментів Do Better Norge — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
'mcp_hero_h1' => 'Використовуйте інструменти Do Better Legal від Claude, Cursor та Copilot',
'mcp_hero_sub' => 'Підключіть будь-якого клієнта MCP до повного набору інструментів Do Better Legal — транскрипція, юридичний аналіз, часові лінії, редагування та інше.',
'mcp_token_section_title' => 'Ваш токен MCP',
'mcp_gate_guest_p' => 'Увійдіть, щоб створити свій особистий токен MCP. Доступно для учасників Плюс та Профі.',
'mcp_gate_guest_btn' => 'Увійти',
@@ -1844,11 +1844,11 @@ function dbnToolsTranslations(): array
'doc_picker_modal_title' => 'Wybierz z Moich dokumentów',
// MCP setup page + tool detail pages
'mcp_page_title' => 'MCP — Do Better Norge',
'mcp_meta_desc' => 'Połącz Claude, Cursor i inne narzędzia AI z wszystkimi 19 narzędziami przygotowania prawnego DBN za pośrednictwem MCP.',
'mcp_page_title' => 'MCP — Do Better Legal',
'mcp_meta_desc' => 'Połącz Claude, Cursor i inne narzędzia AI z wszystkimi narzędziami Do Better Legal do przygotowania w zakresie prawa norweskiego za pośrednictwem MCP.',
'mcp_hero_badge' => '✦ Plus & Pro',
'mcp_hero_h1' => 'Użyj narzędzi DBN z Claude, Cursor i Copilot',
'mcp_hero_sub' => 'Połącz dowolnego klienta MCP ze wszystkimi 19 narzędziami Do Better Norge — transkrypcja, analiza prawna, harmonogramy, redakcja i inne.',
'mcp_hero_h1' => 'Użyj narzędzi Do Better Legal z Claude, Cursor i Copilot',
'mcp_hero_sub' => 'Połącz dowolnego klienta MCP z pełnym zestawem narzędzi Do Better Legal — transkrypcja, analiza prawna, harmonogramy, redakcja i inne.',
'mcp_token_section_title' => 'Twój token MCP',
'mcp_gate_guest_p' => 'Zaloguj się, aby utworzyć swój osobisty token MCP. Dostępny dla członków Plus i Pro.',
'mcp_gate_guest_btn' => 'Zaloguj się',