Localize mcp.php + add mcp-tool.php detail pages for all 19 MCP tools
- Replace all hardcoded English strings in mcp.php with dbnToolsT() calls - Add 44 MCP UI chrome translation keys to includes/i18n.php (en/no/uk/pl) - Generate includes/mcp-tool-translations.php with tool names, descriptions, and parameter docs translated into Norwegian, Ukrainian, and Polish via Azure OpenAI - Create mcp-tool.php: parameterized detail page (?tool=dbn.slug) with parameter table, example request/response JSON, and privacy section, all localized - Add "View details →" links on tool cards in mcp.php (shown on expand) - Add translations/mcp-chrome.php and scripts/generate-mcp-translations.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,8 +50,8 @@ $toolIcons = [
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>MCP — Do Better Norge</title>
|
||||
<meta name="description" content="Connect Claude, Cursor, and other AI tools to all 19 Do Better Norge legal preparation tools via MCP.">
|
||||
<title><?= htmlspecialchars(dbnToolsT('mcp_page_title', $uiLang)) ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars(dbnToolsT('mcp_meta_desc', $uiLang)) ?>">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
@@ -190,7 +190,16 @@ $toolIcons = [
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mcp-tool-card[open] .mcp-tool-card__desc { -webkit-line-clamp: unset; overflow: visible; }
|
||||
.mcp-tool-card[data-expanded="1"] .mcp-tool-card__desc { -webkit-line-clamp: unset; overflow: visible; }
|
||||
.mcp-tool-card__details-link {
|
||||
display: inline-block;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--dbn-blue, #00205b);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
.mcp-tool-card__details-link:hover { text-decoration: underline; }
|
||||
/* ── Gated states ───────────────────────────────────────────── */
|
||||
.mcp-gate {
|
||||
text-align: center;
|
||||
@@ -259,30 +268,34 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
<!-- ── Hero ─────────────────────────────────────────────────── -->
|
||||
<div class="mcp-hero">
|
||||
<div class="mcp-hero__badge">✦ Plus & Pro</div>
|
||||
<h1>Use DBN tools from Claude, Cursor & Copilot</h1>
|
||||
<p>Connect any MCP client to all 19 Do Better Norge tools — transcription, legal analysis, timelines, redaction, and more.</p>
|
||||
<div class="mcp-hero__badge"><?= htmlspecialchars(dbnToolsT('mcp_hero_badge', $uiLang)) ?></div>
|
||||
<h1><?= htmlspecialchars(dbnToolsT('mcp_hero_h1', $uiLang)) ?></h1>
|
||||
<p><?= htmlspecialchars(dbnToolsT('mcp_hero_sub', $uiLang)) ?></p>
|
||||
</div>
|
||||
|
||||
<!-- ── Token management ─────────────────────────────────────── -->
|
||||
<section class="account-section" id="mcpTokenSection">
|
||||
<p class="account-section__title">Your MCP token</p>
|
||||
<p class="account-section__title"><?= htmlspecialchars(dbnToolsT('mcp_token_section_title', $uiLang)) ?></p>
|
||||
|
||||
<?php if (!$isLoggedIn): ?>
|
||||
<div class="mcp-gate">
|
||||
<p>Sign in to create your personal MCP token for Plus or Pro members.</p>
|
||||
<a href="/?return=<?= urlencode('/mcp.php') ?>" class="account-upgrade-btn" style="display:inline-block; text-decoration:none;">Sign in</a>
|
||||
<p><?= htmlspecialchars(dbnToolsT('mcp_gate_guest_p', $uiLang)) ?></p>
|
||||
<a href="/?return=<?= urlencode('/mcp.php') ?>" class="account-upgrade-btn" style="display:inline-block; text-decoration:none;">
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_gate_guest_btn', $uiLang)) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php elseif (!$isPlusPro): ?>
|
||||
<div class="mcp-gate">
|
||||
<p>MCP access is available on Plus and Pro plans. Upgrade to connect your AI tools.</p>
|
||||
<a href="/account.php" class="account-upgrade-btn" style="display:inline-block; text-decoration:none;">Upgrade plan</a>
|
||||
<p><?= htmlspecialchars(dbnToolsT('mcp_gate_free_p', $uiLang)) ?></p>
|
||||
<a href="/account.php" class="account-upgrade-btn" style="display:inline-block; text-decoration:none;">
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_gate_free_btn', $uiLang)) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<p style="color:var(--muted,#667085); font-size:0.88rem; margin-top:0;">
|
||||
Tokens are shown once at creation. Create one per client (Claude, Cursor, VS Code…).
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_token_hint', $uiLang)) ?>
|
||||
</p>
|
||||
|
||||
<div id="mcpTokenList" style="margin:0.85rem 0 0.25rem;"></div>
|
||||
@@ -290,23 +303,27 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
<form id="mcpTokenForm" style="display:flex; gap:0.5rem; flex-wrap:wrap; align-items:center; margin-top:0.85rem;">
|
||||
<input id="mcpTokenName" type="text" maxlength="100" value="DBN MCP" aria-label="Token name"
|
||||
style="min-width:220px; padding:0.65rem 0.75rem; border:1px solid var(--line,#d0d5dd); border-radius:8px; font-size:0.9rem;">
|
||||
<button type="submit" class="account-upgrade-btn" style="border:0; cursor:pointer;">Create token</button>
|
||||
<button type="submit" class="account-upgrade-btn" style="border:0; cursor:pointer;">
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_token_create_btn', $uiLang)) ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div id="mcpTokenReveal" class="mcp-token-reveal">
|
||||
<strong>Copy this token now — it won't be shown again:</strong>
|
||||
<strong><?= htmlspecialchars(dbnToolsT('mcp_token_reveal_label', $uiLang)) ?></strong>
|
||||
<code id="mcpTokenPlain"></code>
|
||||
<button id="mcpTokenCopyBtn" type="button" style="margin-top:0.6rem; border:1px solid #bbf7d0; background:#fff; border-radius:6px; padding:0.35rem 0.7rem; cursor:pointer; font-size:0.82rem;">Copy token</button>
|
||||
<button id="mcpTokenCopyBtn" type="button" style="margin-top:0.6rem; border:1px solid #bbf7d0; background:#fff; border-radius:6px; padding:0.35rem 0.7rem; cursor:pointer; font-size:0.82rem;">
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_token_copy_btn', $uiLang)) ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<!-- ── Client config ─────────────────────────────────────────── -->
|
||||
<section class="account-section">
|
||||
<p class="account-section__title">Client configuration</p>
|
||||
<p class="account-section__title"><?= htmlspecialchars(dbnToolsT('mcp_config_title', $uiLang)) ?></p>
|
||||
<p style="color:var(--muted,#667085); font-size:0.88rem; margin-top:0; margin-bottom:1rem;">
|
||||
Paste your token into the config below after creating it above.
|
||||
<span id="tokenHint" style="display:none;color:#065f46;font-weight:600;"> Token auto-filled.</span>
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_config_hint', $uiLang)) ?>
|
||||
<span id="tokenHint" style="display:none;color:#065f46;font-weight:600;"> <?= htmlspecialchars(dbnToolsT('mcp_config_token_filled', $uiLang)) ?></span>
|
||||
</p>
|
||||
|
||||
<div class="mcp-tabs" role="tablist" aria-label="MCP client">
|
||||
@@ -343,7 +360,7 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
<!-- Claude Code -->
|
||||
<div class="mcp-tab-panel" id="tab-claude-code" role="tabpanel">
|
||||
<p style="font-size:0.85rem; color:var(--muted,#667085); margin:0 0 0.75rem;">
|
||||
Run in your terminal:
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_config_run_terminal', $uiLang)) ?>
|
||||
</p>
|
||||
<div class="mcp-code-wrap">
|
||||
<button class="mcp-copy-btn" type="button" data-copy="claude-code">Copy</button>
|
||||
@@ -427,7 +444,7 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
<div style="margin-top:1.25rem; display:flex; align-items:center; gap:0.75rem; flex-wrap:wrap;">
|
||||
<button id="mcpTestBtn" type="button"
|
||||
style="border:1px solid var(--dbn-blue,#00205b); color:var(--dbn-blue,#00205b); background:#fff; border-radius:8px; padding:0.55rem 1rem; cursor:pointer; font-size:0.88rem;">
|
||||
Test connection
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_test_btn', $uiLang)) ?>
|
||||
</button>
|
||||
<span id="mcpTestResult" style="font-size:0.88rem;"></span>
|
||||
</div>
|
||||
@@ -436,9 +453,9 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
|
||||
<!-- ── Tool catalog ───────────────────────────────────────────── -->
|
||||
<section class="account-section">
|
||||
<p class="account-section__title">Available tools (<?= count($toolCatalog) ?>)</p>
|
||||
<p class="account-section__title"><?= htmlspecialchars(dbnToolsT('mcp_tools_title', $uiLang)) ?> (<?= count($toolCatalog) ?>)</p>
|
||||
<p style="color:var(--muted,#667085); font-size:0.88rem; margin-top:0;">
|
||||
All tools run on your Plus or Pro plan credits. Click a card for full details.
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_tools_sub', $uiLang)) ?>
|
||||
</p>
|
||||
|
||||
<div class="mcp-tool-grid" id="mcpToolGrid">
|
||||
@@ -450,6 +467,7 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
$props = (array)($tool['config']['input_schema']['properties'] ?? []);
|
||||
$req = (array)($tool['config']['input_schema']['required'] ?? []);
|
||||
$paramNames = array_keys($props);
|
||||
$detailUrl = '/mcp-tool.php?tool=' . urlencode((string)($tool['slug'] ?? '')) . '&lang=' . urlencode($uiLang);
|
||||
?>
|
||||
<div class="mcp-tool-card" data-slug="<?= $slug ?>">
|
||||
<div class="mcp-tool-card__icon"><?= $icon ?></div>
|
||||
@@ -465,10 +483,14 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
font-family: monospace;"><?= htmlspecialchars($param) ?><?= in_array($param, $req, true) ? '*' : '' ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<p style="margin:0.5rem 0 0; font-size:0.75rem; color:var(--muted,#667085); display:none;" class="mcp-tool-card__hint">
|
||||
<em>Purple = required</em>
|
||||
<p style="margin:0.3rem 0 0; font-size:0.75rem; color:var(--muted,#667085); display:none;" class="mcp-tool-card__hint">
|
||||
<em><?= htmlspecialchars(dbnToolsT('mcp_tools_param_req_hint', $uiLang)) ?></em>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<a href="<?= htmlspecialchars($detailUrl) ?>" class="mcp-tool-card__details-link" style="display:none;"
|
||||
onclick="event.stopPropagation();">
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_tools_view_details', $uiLang)) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
@@ -476,15 +498,12 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
|
||||
<!-- ── Privacy notice ─────────────────────────────────────────── -->
|
||||
<section class="account-section">
|
||||
<p class="account-section__title">Privacy</p>
|
||||
<p class="account-section__title"><?= htmlspecialchars(dbnToolsT('mcp_privacy_title', $uiLang)) ?></p>
|
||||
<div class="mcp-privacy">
|
||||
<strong>Process-and-forget by default.</strong> All tool calls process your text in memory and return results to your AI client.
|
||||
Nothing is saved to My Case unless you explicitly call <code>dbn.save_to_case</code>.
|
||||
DBN MCP tokens do not retain input text or tool results after the request completes.
|
||||
<strong><?= htmlspecialchars(dbnToolsT('mcp_privacy_text', $uiLang)) ?></strong>
|
||||
</div>
|
||||
<p style="margin-top:1rem; font-size:0.82rem; color:var(--muted,#667085);">
|
||||
Tools provide legal preparation support, not final legal advice.
|
||||
Results are for informational purposes and should be reviewed by a qualified legal professional.
|
||||
<?= htmlspecialchars(dbnToolsT('mcp_privacy_legal', $uiLang)) ?>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -496,6 +515,17 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ── i18n from PHP ─────────────────────────────────────────── */
|
||||
var I = {
|
||||
tokenActive: <?= json_encode(dbnToolsT('mcp_token_active', $uiLang)) ?>,
|
||||
tokenRevoked: <?= json_encode(dbnToolsT('mcp_token_revoked', $uiLang)) ?>,
|
||||
tokenNeverUsed: <?= json_encode(dbnToolsT('mcp_token_never_used', $uiLang)) ?>,
|
||||
tokenLastUsed: <?= json_encode(dbnToolsT('mcp_token_last_used', $uiLang)) ?>,
|
||||
tokenRevokeBtn: <?= json_encode(dbnToolsT('mcp_token_revoke_btn', $uiLang)) ?>,
|
||||
testNoToken: <?= json_encode(dbnToolsT('mcp_test_no_token', $uiLang)) ?>,
|
||||
testTesting: <?= json_encode(dbnToolsT('mcp_test_testing', $uiLang)) ?>,
|
||||
};
|
||||
|
||||
/* ── Helpers ──────────────────────────────────────────────────── */
|
||||
function esc(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, function (c) {
|
||||
@@ -566,19 +596,22 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
document.querySelectorAll('.mcp-tool-card').forEach(function (card) {
|
||||
card.addEventListener('click', function () {
|
||||
var expanded = card.getAttribute('data-expanded') === '1';
|
||||
var params = card.querySelector('.mcp-tool-card__params');
|
||||
var hint = card.querySelector('.mcp-tool-card__hint');
|
||||
var desc = card.querySelector('.mcp-tool-card__desc');
|
||||
var params = card.querySelector('.mcp-tool-card__params');
|
||||
var hint = card.querySelector('.mcp-tool-card__hint');
|
||||
var desc = card.querySelector('.mcp-tool-card__desc');
|
||||
var details = card.querySelector('.mcp-tool-card__details-link');
|
||||
if (expanded) {
|
||||
card.setAttribute('data-expanded', '0');
|
||||
if (params) params.style.display = 'none';
|
||||
if (hint) hint.style.display = 'none';
|
||||
if (desc) { desc.style.webkitLineClamp = '2'; desc.style.overflow = 'hidden'; }
|
||||
if (params) params.style.display = 'none';
|
||||
if (hint) hint.style.display = 'none';
|
||||
if (details) details.style.display = 'none';
|
||||
if (desc) { desc.style.webkitLineClamp = '2'; desc.style.overflow = 'hidden'; }
|
||||
} else {
|
||||
card.setAttribute('data-expanded', '1');
|
||||
if (params) params.style.display = 'flex';
|
||||
if (hint) hint.style.display = 'block';
|
||||
if (desc) { desc.style.webkitLineClamp = 'unset'; desc.style.overflow = 'visible'; }
|
||||
if (params) params.style.display = 'flex';
|
||||
if (hint) hint.style.display = 'block';
|
||||
if (details) details.style.display = 'inline-block';
|
||||
if (desc) { desc.style.webkitLineClamp = 'unset'; desc.style.overflow = 'visible'; }
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -590,20 +623,20 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
function renderTokens(tokens) {
|
||||
if (!tokenList) return;
|
||||
if (!tokens.length) {
|
||||
tokenList.innerHTML = '<p style="color:var(--muted,#667085); font-size:0.88rem;">No MCP tokens yet.</p>';
|
||||
tokenList.innerHTML = '<p style="color:var(--muted,#667085); font-size:0.88rem;"><?= addslashes(dbnToolsT('mcp_token_no_tokens', $uiLang)) ?></p>';
|
||||
return;
|
||||
}
|
||||
tokenList.innerHTML = tokens.map(function (t) {
|
||||
var status = t.is_active ? 'Active' : 'Revoked';
|
||||
var last = t.last_used_at ? 'Last used ' + esc(t.last_used_at) : 'Never used';
|
||||
var status = t.is_active ? esc(I.tokenActive) : esc(I.tokenRevoked);
|
||||
var last = t.last_used_at ? esc(I.tokenLastUsed) + ' ' + esc(t.last_used_at) : esc(I.tokenNeverUsed);
|
||||
var revoke = t.is_active
|
||||
? '<button type="button" class="mcp-token-row__revoke" data-revoke="' + t.id + '">Revoke</button>'
|
||||
? '<button type="button" class="mcp-token-row__revoke" data-revoke="' + t.id + '">' + esc(I.tokenRevokeBtn) + '</button>'
|
||||
: '';
|
||||
return '<div class="mcp-token-row">'
|
||||
+ '<div>'
|
||||
+ '<strong>' + esc(t.name) + '</strong><br>'
|
||||
+ '<code style="font-size:0.8rem;">' + esc(t.token_prefix) + '…</code><br>'
|
||||
+ '<span style="color:var(--muted,#667085); font-size:0.78rem;">' + esc(status) + ' · ' + esc(last) + '</span>'
|
||||
+ '<span style="color:var(--muted,#667085); font-size:0.78rem;">' + status + ' · ' + last + '</span>'
|
||||
+ '</div>'
|
||||
+ revoke
|
||||
+ '</div>';
|
||||
@@ -657,7 +690,6 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
if (reveal) reveal.style.display = 'block';
|
||||
fillToken(tok);
|
||||
loadTokens();
|
||||
// Scroll to config
|
||||
var configSection = document.querySelector('.mcp-tabs');
|
||||
if (configSection) configSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
})
|
||||
@@ -678,11 +710,11 @@ Authorization: Bearer dbn_user_mcp_...</code></pre>
|
||||
if (testBtn) {
|
||||
testBtn.addEventListener('click', function () {
|
||||
if (!activeToken) {
|
||||
if (testResult) { testResult.textContent = 'Create a token first.'; testResult.style.color = '#b45309'; }
|
||||
if (testResult) { testResult.textContent = I.testNoToken; testResult.style.color = '#b45309'; }
|
||||
return;
|
||||
}
|
||||
testBtn.disabled = true;
|
||||
if (testResult) { testResult.textContent = 'Testing…'; testResult.style.color = 'var(--muted,#667085)'; }
|
||||
if (testResult) { testResult.textContent = I.testTesting; testResult.style.color = 'var(--muted,#667085)'; }
|
||||
fetch('/api/mcp/user/session', {
|
||||
headers: { 'Authorization': 'Bearer ' + activeToken }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user