feat(mcp): replace the authorization stepper with direct section tabs

This commit is contained in:
t8y2
2026-10-16 20:16:17 +08:00
parent cb05362a85
commit 8379680404
18 changed files with 211 additions and 450 deletions
@@ -336,7 +336,7 @@ import ChangelogPanel from "@/components/settings/ChangelogPanel.vue";
import SettingsTransferPanel from "@/components/settings/SettingsTransferPanel.vue";
import McpResourceScopePicker from "@/components/settings/McpResourceScopePicker.vue";
import McpDatabaseScopePicker from "@/components/settings/McpDatabaseScopePicker.vue";
import McpAuthorizationStepper from "@/components/settings/McpAuthorizationStepper.vue";
import McpAuthorizationTabs from "@/components/settings/McpAuthorizationTabs.vue";
import ScheduledDatabaseBackupSettings from "@/components/backup/ScheduledDatabaseBackupSettings.vue";
import SqlFormatterSettingsPanel from "./SqlFormatterSettingsPanel.vue";
import { APP_CUSTOM_UI_COLOR_DEFS, APP_THEME_PALETTES, type AppCornerStyle, type AppCustomUiColors, type AppThemeAppearance, type AppThemeMode, type AppThemePalette } from "@/lib/app/appTheme";
@@ -12254,7 +12254,7 @@ LIMIT 100;</pre>
</TabsContent>
<TabsContent value="permissions" class="m-0 space-y-4">
<McpAuthorizationStepper>
<McpAuthorizationTabs>
<template #connections>
<div class="space-y-3">
<p v-if="mcpPolicyLoadError" class="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-600 dark:text-red-400">
@@ -12422,7 +12422,7 @@ LIMIT 100;</pre>
</section>
</div>
</template>
</McpAuthorizationStepper>
</McpAuthorizationTabs>
</TabsContent>
<div v-if="mcpManagementTab === 'access'" class="space-y-5 border-t border-border/60 pt-5 sm:ml-1">
<div class="space-y-3">
@@ -1,101 +0,0 @@
<script setup lang="ts">
import { computed, nextTick, ref } from "vue";
import { useI18n } from "vue-i18n";
type StepId = "capabilities" | "connections" | "databases" | "overrides";
const { t } = useI18n();
const steps = computed<Array<{ id: StepId; title: string; description: string }>>(() => [
{ id: "overrides", title: t("settings.mcpAuthStepOverridesTitle"), description: t("settings.mcpAuthStepOverridesDescription") },
{ id: "connections", title: t("settings.mcpAuthStepConnectionsTitle"), description: t("settings.mcpAuthStepConnectionsDescription") },
{ id: "databases", title: t("settings.mcpAuthStepDatabasesTitle"), description: t("settings.mcpAuthStepDatabasesDescription") },
{ id: "capabilities", title: t("settings.mcpAuthStepCapabilitiesTitle"), description: t("settings.mcpAuthStepCapabilitiesDescription") },
]);
const currentStep = ref<StepId>("overrides");
const currentIndex = computed(() => steps.value.findIndex((step) => step.id === currentStep.value));
// Every step is directly reachable from the nav above and all policy edits stage into the
// dialog draft, so the last step has nothing to advance to and nothing to confirm here —
// persistence is the settings footer Apply button's job, not this stepper's.
const isLastStep = computed(() => currentIndex.value === steps.value.length - 1);
function scrollableAncestor(target: EventTarget | null): HTMLElement | null {
let element = target instanceof HTMLElement ? target.parentElement : null;
while (element) {
const overflowY = window.getComputedStyle(element).overflowY;
if (overflowY === "auto" || overflowY === "scroll") return element;
element = element.parentElement;
}
return null;
}
async function selectStep(step: StepId, event?: Event) {
if (step === currentStep.value) return;
const scroller = scrollableAncestor(event?.currentTarget ?? null);
const scrollTop = scroller?.scrollTop;
currentStep.value = step;
if (!scroller || scrollTop === undefined) return;
await nextTick();
requestAnimationFrame(() => {
if (scroller.isConnected) scroller.scrollTop = scrollTop;
});
}
function previousStep(event: Event) {
const step = steps.value[currentIndex.value - 1];
if (step) void selectStep(step.id, event);
}
function nextStep(event: Event) {
const step = steps.value[currentIndex.value + 1];
if (step) void selectStep(step.id, event);
}
</script>
<template>
<section class="space-y-4 rounded-md border bg-background p-3 sm:p-4">
<header class="space-y-1">
<p class="text-sm font-semibold">{{ t("settings.mcpAuthTitle") }}</p>
<p class="text-xs text-muted-foreground">{{ t("settings.mcpAuthDescription") }}</p>
</header>
<nav class="flex items-stretch gap-1 overflow-x-auto rounded-md border bg-muted/50 p-1 sm:grid sm:grid-cols-4" :aria-label="t('settings.mcpAuthStepsLabel')">
<button
v-for="(step, index) in steps"
:key="step.id"
type="button"
class="group flex min-h-16 min-w-48 flex-none items-start gap-2 rounded px-2.5 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 sm:min-w-0"
:class="currentStep === step.id ? 'bg-background shadow-sm' : 'text-muted-foreground hover:bg-background/70'"
:aria-current="currentStep === step.id ? 'step' : undefined"
@click="selectStep(step.id, $event)"
>
<span
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full border text-[11px] font-semibold"
:class="currentStep === step.id ? 'border-primary bg-primary text-primary-foreground' : index < currentIndex ? 'border-primary/40 bg-primary/10 text-primary' : 'bg-background text-muted-foreground'"
>{{ index + 1 }}</span
>
<span class="min-w-0 flex-1">
<span class="block whitespace-normal text-xs font-medium leading-5">{{ step.title }}</span>
<span class="mt-0.5 block whitespace-normal text-[10px] leading-4 text-muted-foreground">{{ step.description }}</span>
</span>
</button>
</nav>
<div :class="currentStep === 'overrides' ? 'block' : 'hidden'" :aria-hidden="currentStep !== 'overrides'" role="region" :aria-label="t('settings.mcpAuthStepRegionLabel', { step: 1, title: t('settings.mcpAuthStepOverridesTitle') })"><slot name="overrides" /></div>
<div :class="currentStep === 'connections' ? 'block' : 'hidden'" :aria-hidden="currentStep !== 'connections'" role="region" :aria-label="t('settings.mcpAuthStepRegionLabel', { step: 2, title: t('settings.mcpAuthStepConnectionsTitle') })"><slot name="connections" /></div>
<div :class="currentStep === 'databases' ? 'block' : 'hidden'" :aria-hidden="currentStep !== 'databases'" role="region" :aria-label="t('settings.mcpAuthStepRegionLabel', { step: 3, title: t('settings.mcpAuthStepDatabasesTitle') })"><slot name="databases" /></div>
<div :class="currentStep === 'capabilities' ? 'block' : 'hidden'" :aria-hidden="currentStep !== 'capabilities'" role="region" :aria-label="t('settings.mcpAuthStepRegionLabel', { step: 4, title: t('settings.mcpAuthStepCapabilitiesTitle') })"><slot name="capabilities" /></div>
<footer class="flex items-center justify-between gap-3 border-t pt-3">
<button type="button" class="rounded-md border bg-background px-3 py-1.5 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50" :disabled="currentIndex === 0" @click="previousStep($event)">{{ t("settings.mcpAuthPreviousStep") }}</button>
<p class="text-center text-xs text-muted-foreground">{{ t("settings.mcpAuthStepProgress", { current: currentIndex + 1, total: steps.length }) }}</p>
<!-- Kept in the layout (visibility: hidden) so the progress label holds the same offset in
every step: a fixed-width spacer could not match a label whose width varies per locale.
`invisible` also drops the button from the tab order and the accessibility tree. -->
<button type="button" class="rounded-md border bg-background px-3 py-1.5 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50" :class="{ invisible: isLastStep }" :disabled="isLastStep" @click="nextStep($event)">
{{ t("settings.mcpAuthNextStep") }}
</button>
</footer>
</section>
</template>
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
type SectionId = "overrides" | "connections" | "databases" | "capabilities";
const { t } = useI18n();
const sections = computed<Array<{ id: SectionId; title: string }>>(() => [
{ id: "overrides", title: t("settings.mcpAuthTabOverridesTitle") },
{ id: "connections", title: t("settings.mcpAuthTabConnectionsTitle") },
{ id: "databases", title: t("settings.mcpAuthTabDatabasesTitle") },
{ id: "capabilities", title: t("settings.mcpAuthTabCapabilitiesTitle") },
]);
const currentSection = ref<SectionId>("overrides");
</script>
<template>
<section class="space-y-3">
<header class="space-y-1">
<p class="text-sm font-semibold">{{ t("settings.mcpAuthTitle") }}</p>
<p class="text-xs text-muted-foreground">{{ t("settings.mcpAuthDescription") }}</p>
</header>
<!-- The sections are independent views over one staged policy draft (persisted by the settings
footer Apply button), not sequential steps, so every one is a directly reachable tab. Hidden
panels stay mounted (unmount-on-hide=false) so the pickers keep their search/expand state across tab switches. -->
<Tabs v-model="currentSection" :unmount-on-hide="false" class="gap-4">
<TabsList variant="line" class="h-9 w-full justify-start gap-1 overflow-x-auto border-b border-border/60 bg-transparent p-0" :aria-label="t('settings.mcpAuthTitle')">
<TabsTrigger v-for="section in sections" :key="section.id" :value="section.id" class="flex-none px-3">{{ section.title }}</TabsTrigger>
</TabsList>
<TabsContent v-for="section in sections" :key="section.id" :value="section.id" class="m-0">
<slot :name="section.id" />
</TabsContent>
</Tabs>
</section>
</template>
@@ -1,164 +0,0 @@
// @vitest-environment happy-dom
// MCP 授权步骤条回归测试(#9309):
// 末步(第 4 步「汇总与确认」)没有下一页可去,右侧按钮不再显示可点的“下一步”。
// 1) 第 1-3 步:右侧“下一步”可见可点,点击前进一格;左侧“上一步”第 1 步禁用、其后可点。
// 2) 第 4 步:右侧按钮带 invisible 且 disabled,但仍留在 footer 中作同尺寸占位。
// 3) 第 4 步点“上一步”回到第 3 步后,右侧按钮恢复可见。
// 4) 顶部步骤条四个步骤都能直达,末步右侧按钮同样隐藏。
// 5) 末步占位按钮不可聚焦。
// 6) 正向对照:非末步的“下一步”按钮可以聚焦——证明这个环境能表达焦点,第 5 条才不是恒真。
//
// 哪些用例真正守住 #9309:第 2、4 条(断言 invisible 与 footer 计数)。把组件还原到修复前,
// 恰好是这两条失败。第 5 条在修复前也通过——末步按钮本来就带 disabled,所以它是防止后人
// 删掉 disabled 的前瞻守卫,而不是本次缺陷的回归守卫,勿误读。
//
// 说明:happy-dom 无排版引擎、不加载 Tailwind 样式表,因此“进度文案是否真的居中/不位移”
// 这类几何断言在单测里无法表达,只以“占位仍留在 DOM 中”作为可证伪的替代断言。
import { createApp, nextTick, type App } from "vue";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("vue-i18n", () => ({
useI18n: () => ({
t: (key: string, params?: Record<string, unknown>) => (params ? `${key}:${params.current}/${params.total}` : key),
}),
}));
import McpAuthorizationStepper from "@/components/settings/McpAuthorizationStepper.vue";
let harness: { app: App<Element>; root: HTMLElement } | null = null;
function mountStepper() {
const root = document.createElement("div");
document.body.appendChild(root);
const app = createApp(McpAuthorizationStepper);
app.mount(root);
harness = { app, root };
return root;
}
afterEach(() => {
harness?.app?.unmount();
harness = null;
document.body.innerHTML = "";
});
function buttonByText(root: HTMLElement, key: string): HTMLButtonElement {
const button = Array.from(root.querySelectorAll("button")).find((el) => el.textContent?.trim() === key);
if (!button) throw new Error(`button ${key} not found`);
return button as HTMLButtonElement;
}
function nextButton(root: HTMLElement) {
return buttonByText(root, "settings.mcpAuthNextStep");
}
function prevButton(root: HTMLElement) {
return buttonByText(root, "settings.mcpAuthPreviousStep");
}
function navStep(root: HTMLElement, index: number) {
const nav = root.querySelector("nav");
if (!nav) throw new Error("step nav not found");
const button = nav.querySelectorAll("button")[index];
if (!button) throw new Error(`nav step ${index} not found`);
return button as HTMLButtonElement;
}
function progress(root: HTMLElement) {
return root.textContent?.match(/settings\.mcpAuthStepProgress:\d+\/\d+/)?.[0];
}
async function goToLastStep(root: HTMLElement) {
for (let i = 0; i < 3; i += 1) {
nextButton(root).click();
await nextTick();
}
}
describe("McpAuthorizationStepper", () => {
it("第 1 步:右侧为可见可点的“下一步”,左侧“上一步”禁用", () => {
const root = mountStepper();
expect(progress(root)).toBe("settings.mcpAuthStepProgress:1/4");
expect(nextButton(root).classList.contains("invisible")).toBe(false);
expect(nextButton(root).disabled).toBe(false);
expect(prevButton(root).disabled).toBe(true);
});
it("逐级前进:第 2、3 步右侧仍是可见可点的“下一步”,左侧“上一步”可点", async () => {
const root = mountStepper();
nextButton(root).click();
await nextTick();
expect(progress(root)).toBe("settings.mcpAuthStepProgress:2/4");
expect(nextButton(root).classList.contains("invisible")).toBe(false);
expect(nextButton(root).disabled).toBe(false);
expect(prevButton(root).disabled).toBe(false);
nextButton(root).click();
await nextTick();
expect(progress(root)).toBe("settings.mcpAuthStepProgress:3/4");
expect(nextButton(root).classList.contains("invisible")).toBe(false);
expect(nextButton(root).disabled).toBe(false);
expect(prevButton(root).disabled).toBe(false);
});
it("第 4 步:右侧按钮隐藏且禁用,但仍留在 footer 中撑住布局", async () => {
const root = mountStepper();
await goToLastStep(root);
expect(progress(root)).toBe("settings.mcpAuthStepProgress:4/4");
// footer 仍是 [上一步][进度][占位] 三个子元素,即占位按钮没有被 v-if 摘掉——摘掉的话这里只剩 1 个。
expect(root.querySelectorAll("footer button").length).toBe(2);
const next = nextButton(root);
expect(next.classList.contains("invisible")).toBe(true);
expect(next.disabled).toBe(true);
expect(prevButton(root).disabled).toBe(false);
});
it("第 4 步点“上一步”回到第 3 步后,右侧按钮恢复可见可点", async () => {
const root = mountStepper();
await goToLastStep(root);
prevButton(root).click();
await nextTick();
expect(progress(root)).toBe("settings.mcpAuthStepProgress:3/4");
expect(nextButton(root).classList.contains("invisible")).toBe(false);
expect(nextButton(root).disabled).toBe(false);
});
it("顶部步骤条四个步骤均可直达,末步的右侧按钮同样隐藏", async () => {
const root = mountStepper();
for (let index = 0; index < 4; index += 1) {
navStep(root, index).click();
await nextTick();
expect(progress(root)).toBe(`settings.mcpAuthStepProgress:${index + 1}/4`);
expect(nextButton(root).classList.contains("invisible")).toBe(index === 3);
}
expect(nextButton(root).disabled).toBe(true);
});
// 正向对照:证明这个环境里 focus() 确实能生效,下面那条否定断言才不是恒真。
it("正向对照:非末步的“下一步”按钮可以聚焦", () => {
const root = mountStepper();
nextButton(root).focus();
expect(document.activeElement).toBe(nextButton(root));
});
// 每个用例独立挂载、起始焦点都不在任何按钮上——避免“元素已在焦点上、随后才被禁用”这种
// 与断言无关的状态混进来(原生化不会因为元素被禁用就把已有焦点移走)。
it("末步占位按钮不可聚焦", async () => {
const root = mountStepper();
await goToLastStep(root);
const next = nextButton(root);
next.focus();
expect(document.activeElement).not.toBe(next);
expect(next.disabled).toBe(true);
});
});
@@ -0,0 +1,104 @@
// @vitest-environment happy-dom
// MCP 授权设置页签:四个区块是同一份暂存策略的独立视图,不是线性流程,
// 因此不再有“上一步/下一步/第 N 步”,而是可直达的页签。
// 1) 四个页签可直达,默认选中“默认权限”。
// 2) 切换页签只改变可见面板;所有面板保持挂载(子组件的搜索/展开状态不丢)。
// 3) 不再渲染流程式的上一步/下一步/进度控件。
import { createApp, nextTick, type App } from "vue";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("vue-i18n", () => ({
useI18n: () => ({
t: (key: string) => key,
}),
}));
import McpAuthorizationTabs from "@/components/settings/McpAuthorizationTabs.vue";
const SECTIONS = ["overrides", "connections", "databases", "capabilities"] as const;
let harness: { app: App<Element>; root: HTMLElement } | null = null;
function mountTabs() {
const root = document.createElement("div");
document.body.appendChild(root);
const app = createApp({
components: { McpAuthorizationTabs },
template: `
<McpAuthorizationTabs>
<template #overrides><p data-slot-content="overrides">overrides</p></template>
<template #connections><p data-slot-content="connections">connections</p></template>
<template #databases><p data-slot-content="databases">databases</p></template>
<template #capabilities><p data-slot-content="capabilities">capabilities</p></template>
</McpAuthorizationTabs>
`,
});
app.mount(root);
harness = { app, root };
return root;
}
afterEach(() => {
harness?.app?.unmount();
harness = null;
document.body.innerHTML = "";
});
function triggers(root: HTMLElement) {
return Array.from(root.querySelectorAll<HTMLElement>('[role="tab"]'));
}
function panelOf(root: HTMLElement, section: string) {
const content = root.querySelector(`[data-slot-content="${section}"]`);
const panel = content?.closest<HTMLElement>('[role="tabpanel"]');
if (!panel) throw new Error(`panel ${section} not found`);
return panel;
}
async function selectTab(tab: HTMLElement) {
// reka-ui 的触发器在 mousedown / focus 时激活,而不是 click。
tab.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0, ctrlKey: false }));
await nextTick();
}
describe("McpAuthorizationTabs", () => {
it("渲染四个页签,默认选中第一个", () => {
const root = mountTabs();
const tabs = triggers(root);
expect(tabs.map((tab) => tab.textContent?.trim())).toEqual(["settings.mcpAuthTabOverridesTitle", "settings.mcpAuthTabConnectionsTitle", "settings.mcpAuthTabDatabasesTitle", "settings.mcpAuthTabCapabilitiesTitle"]);
expect(tabs[0].getAttribute("aria-selected")).toBe("true");
expect(panelOf(root, "overrides").hidden).toBe(false);
expect(panelOf(root, "databases").hidden).toBe(true);
});
it("每个页签都能直达,且只显示被选中的面板", async () => {
const root = mountTabs();
for (const [index, section] of SECTIONS.entries()) {
await selectTab(triggers(root)[index]);
expect(triggers(root)[index].getAttribute("aria-selected")).toBe("true");
for (const other of SECTIONS) {
// data-state 由选中态直接驱动;hidden 经由 Presence 判定,happy-dom 下没有动画事件,故不以它为准。
expect(panelOf(root, other).getAttribute("data-state"), `${section} selected, ${other}`).toBe(other === section ? "active" : "inactive");
}
}
});
it("所有面板始终保持挂载", async () => {
const root = mountTabs();
await selectTab(triggers(root)[2]);
for (const section of SECTIONS) {
expect(root.querySelector(`[data-slot-content="${section}"]`), section).not.toBeNull();
}
});
it("不再渲染上一步/下一步/进度等流程控件", () => {
const root = mountTabs();
expect(root.querySelector("footer")).toBeNull();
expect(root.textContent).not.toMatch(/PreviousStep|NextStep|StepProgress/);
});
});
+5 -14
View File
@@ -8438,20 +8438,11 @@ export default withEnglishFallback({
mcpRefresh: "Yenidən yoxla",
mcpGuide: "MCP təlimatı",
mcpAuthTitle: "MCP səlahiyyətləndirməsi",
mcpAuthDescription: "Səlahiyyətləndirməni ardıcıllıqla tamamlayın: qlobal standartı təyin edin, qrupları və əlaqələri seçin, verilənlər bazalarını məhdudlaşdırın, sonra yekun siyasəti nəzərdən keçirin.",
mcpAuthStepsLabel: "MCP səlahiyyətləndirmə addımları",
mcpAuthStepConnectionsTitle: "Qruplar və əlaqələr",
mcpAuthStepConnectionsDescription: "MCP-nin nəyi görə biləcəyini seçin və əlaqə icazələrini təyin edin",
mcpAuthStepDatabasesTitle: "Verilənlər bazası əhatəsi",
mcpAuthStepDatabasesDescription: "Hər əlaqənin verilənlər bazalarını məhdudlaşdırın və verilənlər bazası icazələrini təyin edin",
mcpAuthStepOverridesTitle: "Standart icazələr",
mcpAuthStepOverridesDescription: "Bütün sonrakı əhatə dairələri üçün qlobal standartı təyin edin",
mcpAuthStepCapabilitiesTitle: "Xülasə və baxış",
mcpAuthStepCapabilitiesDescription: "Qüvvədə olan icazələri nəzərdən keçirin, alətləri seçin və təsdiqləyin",
mcpAuthStepRegionLabel: "Addım {step}: {title}",
mcpAuthPreviousStep: "Əvvəlki",
mcpAuthNextStep: "Növbəti",
mcpAuthStepProgress: "Addım {current}/{total}",
mcpAuthDescription: "İcazələr pilləli şəkildə üstələnir: verilənlər bazası › əlaqə › qrup › qlobal standart. Əhatə nə qədər dar olarsa, prioritet bir o qədər yüksəkdir.",
mcpAuthTabConnectionsTitle: "Qruplar və əlaqələr",
mcpAuthTabDatabasesTitle: "Verilənlər bazası əhatəsi",
mcpAuthTabOverridesTitle: "Standart icazələr",
mcpAuthTabCapabilitiesTitle: "Qüvvədə olan giriş və alətlər",
mcpPolicyExceptionsTitle: "Saxlanılmış icazə istisnaları",
mcpPolicyExceptionsDescription: "Bağlantı əhatəsinin dəyişdirilməsi icazə istisnalarını saxlayır. Əhatə daxilindəki istisnalar qüvvədə qalır; əhatədən kənardakı istisnalar bağlantı əhatəyə qayıtdıqda tətbiq olunur. Burada icra icazələrini dəyişə və ya irsi icazələri bərpa edə bilərsiniz.",
mcpPolicyExceptionInScope: "Cari əhatə daxilində",
+5 -14
View File
@@ -9956,20 +9956,11 @@ export default {
mcpRefresh: "Check again",
mcpGuide: "MCP guide",
mcpAuthTitle: "MCP authorization",
mcpAuthDescription: "Complete authorization in order: set the global default, choose groups and connections, limit databases, then review the final policy.",
mcpAuthStepsLabel: "MCP authorization steps",
mcpAuthStepConnectionsTitle: "Groups and connections",
mcpAuthStepConnectionsDescription: "Choose what MCP can see and set connection permissions",
mcpAuthStepDatabasesTitle: "Database scope",
mcpAuthStepDatabasesDescription: "Limit each connection's databases and set database permissions",
mcpAuthStepOverridesTitle: "Default permissions",
mcpAuthStepOverridesDescription: "Set the global default for all later scopes",
mcpAuthStepCapabilitiesTitle: "Summary and review",
mcpAuthStepCapabilitiesDescription: "Review effective permissions, choose tools, and confirm",
mcpAuthStepRegionLabel: "Step {step}: {title}",
mcpAuthPreviousStep: "Previous",
mcpAuthNextStep: "Next",
mcpAuthStepProgress: "Step {current} of {total}",
mcpAuthDescription: "Permissions are overridden level by level: database › connection › group › global default. The narrower the scope, the higher its priority.",
mcpAuthTabConnectionsTitle: "Groups and connections",
mcpAuthTabDatabasesTitle: "Database scope",
mcpAuthTabOverridesTitle: "Default permissions",
mcpAuthTabCapabilitiesTitle: "Effective access and tools",
mcpPolicyExceptionsTitle: "Saved permission exceptions",
mcpPolicyExceptionsDescription: "Changing the connection scope preserves permission exceptions. Exceptions inside the scope remain effective; those outside apply when the connection returns to the scope. You can edit execution permissions or restore inherited permissions here.",
mcpPolicyExceptionInScope: "Inside current scope",
+5 -14
View File
@@ -8999,20 +8999,11 @@ export default withEnglishFallback({
mcpRefresh: "Comprobar de nuevo",
mcpGuide: "Guía MCP",
mcpAuthTitle: "Autorización de MCP",
mcpAuthDescription: "Completa la autorización en orden: define el valor global, elige grupos y conexiones, limita las bases de datos y revisa la política final.",
mcpAuthStepsLabel: "Pasos de autorización de MCP",
mcpAuthStepConnectionsTitle: "Grupos y conexiones",
mcpAuthStepConnectionsDescription: "Elige lo que MCP puede ver y configura los permisos",
mcpAuthStepDatabasesTitle: "Alcance de bases de datos",
mcpAuthStepDatabasesDescription: "Limita las bases de datos y configura permisos por base",
mcpAuthStepOverridesTitle: "Permisos predeterminados",
mcpAuthStepOverridesDescription: "Define el permiso global para los ámbitos posteriores",
mcpAuthStepCapabilitiesTitle: "Resumen y confirmación",
mcpAuthStepCapabilitiesDescription: "Revisa los permisos efectivos, elige herramientas y confirma",
mcpAuthStepRegionLabel: "Paso {step}: {title}",
mcpAuthPreviousStep: "Anterior",
mcpAuthNextStep: "Siguiente",
mcpAuthStepProgress: "Paso {current} de {total}",
mcpAuthDescription: "Los permisos se sobrescriben por niveles: base de datos › conexión › grupo › valor global. Cuanto más específico es el alcance, mayor es su prioridad.",
mcpAuthTabConnectionsTitle: "Grupos y conexiones",
mcpAuthTabDatabasesTitle: "Alcance de bases de datos",
mcpAuthTabOverridesTitle: "Permisos predeterminados",
mcpAuthTabCapabilitiesTitle: "Acceso efectivo y herramientas",
mcpPolicyExceptionsTitle: "Excepciones de permisos guardadas",
mcpPolicyExceptionsDescription: "Cambiar el ámbito de conexiones conserva las excepciones de permisos. Las excepciones dentro del ámbito siguen vigentes; las demás se aplican cuando la conexión vuelve al ámbito. Aquí puede editar los permisos de ejecución o restaurar los permisos heredados.",
mcpPolicyExceptionInScope: "Dentro del ámbito actual",
+5 -14
View File
@@ -9254,20 +9254,11 @@ export default withEnglishFallback({
mcpRefresh: "Periksa lagi",
mcpGuide: "Panduan MCP",
mcpAuthTitle: "Otorisasi MCP",
mcpAuthDescription: "Selesaikan otorisasi secara berurutan: atur default global, pilih grup dan koneksi, batasi database, lalu tinjau kebijakan akhir.",
mcpAuthStepsLabel: "Langkah otorisasi MCP",
mcpAuthStepConnectionsTitle: "Grup dan koneksi",
mcpAuthStepConnectionsDescription: "Pilih apa yang dapat dilihat MCP dan atur izin koneksi",
mcpAuthStepDatabasesTitle: "Cakupan database",
mcpAuthStepDatabasesDescription: "Batasi database setiap koneksi dan atur izin database",
mcpAuthStepOverridesTitle: "Izin default",
mcpAuthStepOverridesDescription: "Atur default global untuk semua cakupan berikutnya",
mcpAuthStepCapabilitiesTitle: "Ringkasan dan tinjauan",
mcpAuthStepCapabilitiesDescription: "Tinjau izin efektif, pilih alat, dan konfirmasi",
mcpAuthStepRegionLabel: "Langkah {step}: {title}",
mcpAuthPreviousStep: "Sebelumnya",
mcpAuthNextStep: "Berikutnya",
mcpAuthStepProgress: "Langkah {current} dari {total}",
mcpAuthDescription: "Izin ditimpa secara bertingkat: database › koneksi › grup › default global. Semakin sempit cakupannya, semakin tinggi prioritasnya.",
mcpAuthTabConnectionsTitle: "Grup dan koneksi",
mcpAuthTabDatabasesTitle: "Cakupan database",
mcpAuthTabOverridesTitle: "Izin default",
mcpAuthTabCapabilitiesTitle: "Akses efektif dan alat",
mcpPolicyExceptionsTitle: "Pengecualian izin tersimpan",
mcpPolicyExceptionsDescription: "Mengubah cakupan koneksi mempertahankan pengecualian izin. Pengecualian dalam cakupan tetap berlaku; pengecualian di luar cakupan berlaku saat koneksi kembali ke cakupan. Anda dapat mengedit izin eksekusi atau memulihkan izin yang diwarisi di sini.",
mcpPolicyExceptionInScope: "Dalam cakupan saat ini",
+5 -14
View File
@@ -8749,20 +8749,11 @@ export default withEnglishFallback({
mcpRefresh: "Verifica di nuovo",
mcpGuide: "Guida MCP",
mcpAuthTitle: "Autorizzazione MCP",
mcpAuthDescription: "Completa l'autorizzazione in ordine: imposta il valore globale, scegli gruppi e connessioni, limita i database e rivedi la policy finale.",
mcpAuthStepsLabel: "Passaggi di autorizzazione MCP",
mcpAuthStepConnectionsTitle: "Gruppi e connessioni",
mcpAuthStepConnectionsDescription: "Determina quali connessioni MCP può vedere",
mcpAuthStepDatabasesTitle: "Ambito database",
mcpAuthStepDatabasesDescription: "Limita i database accessibili per ogni connessione",
mcpAuthStepOverridesTitle: "Permessi predefiniti",
mcpAuthStepOverridesDescription: "Imposta il permesso globale per gli ambiti successivi",
mcpAuthStepCapabilitiesTitle: "Riepilogo e conferma",
mcpAuthStepCapabilitiesDescription: "Rivedi i permessi effettivi, scegli gli strumenti e conferma",
mcpAuthStepRegionLabel: "Passaggio {step}: {title}",
mcpAuthPreviousStep: "Indietro",
mcpAuthNextStep: "Avanti",
mcpAuthStepProgress: "Passaggio {current} di {total}",
mcpAuthDescription: "I permessi vengono sovrascritti per livelli: database › connessione › gruppo › valore globale. Più è ristretto l'ambito, più alta è la priorità.",
mcpAuthTabConnectionsTitle: "Gruppi e connessioni",
mcpAuthTabDatabasesTitle: "Ambito database",
mcpAuthTabOverridesTitle: "Permessi predefiniti",
mcpAuthTabCapabilitiesTitle: "Accesso effettivo e strumenti",
mcpPolicyExceptionsTitle: "Eccezioni ai permessi salvate",
mcpPolicyExceptionsDescription: "La modifica dell’ambito delle connessioni conserva le eccezioni ai permessi. Le eccezioni nell’ambito restano attive; le altre si applicano quando la connessione rientra nell’ambito. Qui puoi modificare i permessi di esecuzione o ripristinare quelli ereditati.",
mcpPolicyExceptionInScope: "Nell’ambito attuale",
+5 -14
View File
@@ -8736,20 +8736,11 @@ export default withEnglishFallback({
mcpRefresh: "再確認",
mcpGuide: "MCPガイド",
mcpAuthTitle: "MCP 権限設定",
mcpAuthDescription: "順番に設定します:グローバル既定値、グループと接続、データベース範囲を設定し、最後に最終ポリシーを確認します。",
mcpAuthStepsLabel: "MCP 権限設定の手順",
mcpAuthStepConnectionsTitle: "グループと接続",
mcpAuthStepConnectionsDescription: "MCP に公開する接続を決定します",
mcpAuthStepDatabasesTitle: "データベース範囲",
mcpAuthStepDatabasesDescription: "接続ごとにアクセス可能なデータベースを制限します",
mcpAuthStepOverridesTitle: "既定の権限",
mcpAuthStepOverridesDescription: "後続の範囲に適用するグローバル既定値を設定します",
mcpAuthStepCapabilitiesTitle: "概要と確認",
mcpAuthStepCapabilitiesDescription: "有効な権限を確認し、ツールを選択して確定します",
mcpAuthStepRegionLabel: "ステップ {step}:{title}",
mcpAuthPreviousStep: "前へ",
mcpAuthNextStep: "次へ",
mcpAuthStepProgress: "ステップ {current} / {total}",
mcpAuthDescription: "権限は範囲が狭いものが優先して上書きされます:データベース › 接続 › グループ › グローバル既定値。",
mcpAuthTabConnectionsTitle: "グループと接続",
mcpAuthTabDatabasesTitle: "データベース範囲",
mcpAuthTabOverridesTitle: "既定の権限",
mcpAuthTabCapabilitiesTitle: "有効な権限とツール",
mcpPolicyExceptionsTitle: "保存された権限の例外",
mcpPolicyExceptionsDescription: "接続範囲を変更しても権限の例外は保持されます。範囲内の例外は引き続き有効で、範囲外の例外は接続が範囲に戻ると適用されます。ここで実行権限を変更したり、継承する権限に戻したりできます。",
mcpPolicyExceptionInScope: "現在の範囲内",
+5 -14
View File
@@ -8642,20 +8642,11 @@ export default withEnglishFallback({
mcpRefresh: "다시 확인",
mcpGuide: "MCP 가이드",
mcpAuthTitle: "MCP 권한 설정",
mcpAuthDescription: "순서대로 권한을 설정하세요: 전역 기본값을 정하고 그룹과 연결, 데이터베이스 범위를 설정한 뒤 최종 정책을 확인합니다.",
mcpAuthStepsLabel: "MCP 권한 설정 단계",
mcpAuthStepConnectionsTitle: "그룹 및 연결",
mcpAuthStepConnectionsDescription: "MCP에 노출할 연결을 결정합니다",
mcpAuthStepDatabasesTitle: "데이터베이스 범위",
mcpAuthStepDatabasesDescription: "연결별로 접근 가능한 데이터베이스를 제한합니다",
mcpAuthStepOverridesTitle: "기본 권한",
mcpAuthStepOverridesDescription: "이후 범위에 적용할 전역 기본 권한을 설정합니다",
mcpAuthStepCapabilitiesTitle: "요약 및 확인",
mcpAuthStepCapabilitiesDescription: "최종 권한을 검토하고 도구를 선택한 뒤 확인합니다",
mcpAuthStepRegionLabel: "단계 {step}: {title}",
mcpAuthPreviousStep: "이전",
mcpAuthNextStep: "다음",
mcpAuthStepProgress: "{total}단계 중 {current}단계",
mcpAuthDescription: "권한은 단계별로 덮어씁니다: 데이터베이스 › 연결 › 그룹 › 전역 기본값. 범위가 좁을수록 우선순위가 높습니다.",
mcpAuthTabConnectionsTitle: "그룹 및 연결",
mcpAuthTabDatabasesTitle: "데이터베이스 범위",
mcpAuthTabOverridesTitle: "기본 권한",
mcpAuthTabCapabilitiesTitle: "적용 권한 및 도구",
mcpPolicyExceptionsTitle: "저장된 권한 예외",
mcpPolicyExceptionsDescription: "연결 범위를 변경해도 권한 예외는 유지됩니다. 범위 안의 예외는 계속 유효하며, 범위 밖의 예외는 연결이 범위에 다시 포함되면 적용됩니다. 여기서 실행 권한을 수정하거나 상속된 권한으로 되돌릴 수 있습니다.",
mcpPolicyExceptionInScope: "현재 범위 안",
+5 -14
View File
@@ -8747,20 +8747,11 @@ export default withEnglishFallback({
mcpRefresh: "Verificar novamente",
mcpGuide: "Guia do MCP",
mcpAuthTitle: "Autorização do MCP",
mcpAuthDescription: "Conclua a autorização em ordem: defina o padrão global, escolha grupos e conexões, limite os bancos e revise a política final.",
mcpAuthStepsLabel: "Etapas de autorização do MCP",
mcpAuthStepConnectionsTitle: "Grupos e conexões",
mcpAuthStepConnectionsDescription: "Define quais conexões o MCP pode ver",
mcpAuthStepDatabasesTitle: "Escopo de bancos de dados",
mcpAuthStepDatabasesDescription: "Limita os bancos acessíveis por conexão",
mcpAuthStepOverridesTitle: "Permissões padrão",
mcpAuthStepOverridesDescription: "Defina a permissão global para os escopos seguintes",
mcpAuthStepCapabilitiesTitle: "Resumo e confirmação",
mcpAuthStepCapabilitiesDescription: "Revise as permissões efetivas, escolha as ferramentas e confirme",
mcpAuthStepRegionLabel: "Etapa {step}: {title}",
mcpAuthPreviousStep: "Voltar",
mcpAuthNextStep: "Avançar",
mcpAuthStepProgress: "Etapa {current} de {total}",
mcpAuthDescription: "As permissões são sobrescritas por níveis: banco › conexão › grupo › padrão global. Quanto mais específico o escopo, maior a prioridade.",
mcpAuthTabConnectionsTitle: "Grupos e conexões",
mcpAuthTabDatabasesTitle: "Escopo de bancos de dados",
mcpAuthTabOverridesTitle: "Permissões padrão",
mcpAuthTabCapabilitiesTitle: "Acesso efetivo e ferramentas",
mcpPolicyExceptionsTitle: "Exceções de permissões salvas",
mcpPolicyExceptionsDescription: "Alterar o escopo das conexões preserva as exceções de permissões. As exceções dentro do escopo continuam ativas; as demais se aplicam quando a conexão retorna ao escopo. Aqui você pode editar as permissões de execução ou restaurar as permissões herdadas.",
mcpPolicyExceptionInScope: "Dentro do escopo atual",
+5 -14
View File
@@ -10141,20 +10141,11 @@ export default withEnglishFallback({
mcpRefresh: "Проверить снова",
mcpGuide: "Руководство MCP",
mcpAuthTitle: "Авторизация MCP",
mcpAuthDescription: "Выполните авторизацию по порядку: задайте глобальное значение по умолчанию, выберите группы и подключения, ограничьте базы данных, затем проверьте итоговую политику.",
mcpAuthStepsLabel: "Шаги авторизации MCP",
mcpAuthStepConnectionsTitle: "Группы и подключения",
mcpAuthStepConnectionsDescription: "Выберите, что видит MCP, и задайте разрешения для подключений",
mcpAuthStepDatabasesTitle: "Область баз данных",
mcpAuthStepDatabasesDescription: "Ограничьте базы данных каждого подключения и задайте разрешения для баз данных",
mcpAuthStepOverridesTitle: "Разрешения по умолчанию",
mcpAuthStepOverridesDescription: "Задайте глобальное значение по умолчанию для всех последующих областей",
mcpAuthStepCapabilitiesTitle: "Сводка и проверка",
mcpAuthStepCapabilitiesDescription: "Проверьте действующие разрешения, выберите инструменты и подтвердите",
mcpAuthStepRegionLabel: "Шаг {step}: {title}",
mcpAuthPreviousStep: "Назад",
mcpAuthNextStep: "Далее",
mcpAuthStepProgress: "Шаг {current} из {total}",
mcpAuthDescription: "Права переопределяются по уровням: база данных › подключение › группа › глобальное значение по умолчанию. Чем уже область, тем выше приоритет.",
mcpAuthTabConnectionsTitle: "Группы и подключения",
mcpAuthTabDatabasesTitle: "Область баз данных",
mcpAuthTabOverridesTitle: "Разрешения по умолчанию",
mcpAuthTabCapabilitiesTitle: "Действующие права и инструменты",
mcpPolicyExceptionsTitle: "Сохранённые исключения разрешений",
mcpPolicyExceptionsDescription: "При изменении области подключений исключения разрешений сохраняются. Внутри области они остаются действующими; вне области они применяются при возвращении подключения. Здесь можно изменить разрешения на выполнение или восстановить наследуемые разрешения.",
mcpPolicyExceptionInScope: "В текущей области",
+5 -14
View File
@@ -10581,20 +10581,11 @@ export default withEnglishFallback({
mcpRefresh: "ตรวจสอบอีกครั้ง",
mcpGuide: "คู่มือ MCP",
mcpAuthTitle: "การอนุญาต MCP",
mcpAuthDescription: "ทำการอนุญาตให้ครบตามลำดับ: ตั้งค่าเริ่มต้นรวม เลือกกลุ่มและการเชื่อมต่อ จำกัดฐานข้อมูล แล้วตรวจสอบนโยบายสุดท้าย",
mcpAuthStepsLabel: "ขั้นตอนการอนุญาต MCP",
mcpAuthStepConnectionsTitle: "กลุ่มและการเชื่อมต่อ",
mcpAuthStepConnectionsDescription: "เลือกสิ่งที่ MCP มองเห็นและตั้งค่าสิทธิ์การเชื่อมต่อ",
mcpAuthStepDatabasesTitle: "ขอบเขตฐานข้อมูล",
mcpAuthStepDatabasesDescription: "จำกัดฐานข้อมูลของแต่ละการเชื่อมต่อและตั้งค่าสิทธิ์ฐานข้อมูล",
mcpAuthStepOverridesTitle: "สิทธิ์เริ่มต้น",
mcpAuthStepOverridesDescription: "ตั้งค่าเริ่มต้นรวมสำหรับขอบเขตถัดไปทั้งหมด",
mcpAuthStepCapabilitiesTitle: "สรุปและตรวจสอบ",
mcpAuthStepCapabilitiesDescription: "ตรวจสอบสิทธิ์ที่มีผล เลือกเครื่องมือ และยืนยัน",
mcpAuthStepRegionLabel: "ขั้นตอน {step}: {title}",
mcpAuthPreviousStep: "ก่อนหน้า",
mcpAuthNextStep: "ถัดไป",
mcpAuthStepProgress: "ขั้นตอน {current} จาก {total}",
mcpAuthDescription: "สิทธิ์จะถูกแทนที่ตามลำดับขั้น: ฐานข้อมูล › การเชื่อมต่อ › กลุ่ม › ค่าเริ่มต้นรวม ขอบเขตยิ่งแคบ ลำดับความสำคัญยิ่งสูง",
mcpAuthTabConnectionsTitle: "กลุ่มและการเชื่อมต่อ",
mcpAuthTabDatabasesTitle: "ขอบเขตฐานข้อมูล",
mcpAuthTabOverridesTitle: "สิทธิ์เริ่มต้น",
mcpAuthTabCapabilitiesTitle: "สิทธิ์ที่มีผลและเครื่องมือ",
mcpPolicyExceptionsTitle: "ข้อยกเว้นสิทธิ์ที่บันทึกไว้",
mcpPolicyExceptionsDescription: "การเปลี่ยนขอบเขตการเชื่อมต่อจะคงข้อยกเว้นสิทธิ์ไว้ ข้อยกเว้นภายในขอบเขตยังมีผล ส่วนภายนอกจะมีผลเมื่อการเชื่อมต่อกลับเข้าสู่ขอบเขต คุณแก้ไขสิทธิ์การเรียกใช้หรือคืนค่าสิทธิ์ที่สืบทอดได้ที่นี่",
mcpPolicyExceptionInScope: "ภายในขอบเขตปัจจุบัน",
+5 -14
View File
@@ -8340,20 +8340,11 @@ export default withEnglishFallback({
mcpRefresh: "Yeniden denetle",
mcpGuide: "MCP kılavuzu",
mcpAuthTitle: "MCP yetkilendirmesi",
mcpAuthDescription: "Yetkilendirmeyi sırayla tamamlayın: genel varsayılanı belirleyin, grup ve bağlantıları seçin, veritabanlarını sınırlayın ve son politikayı inceleyin.",
mcpAuthStepsLabel: "MCP yetkilendirme adımları",
mcpAuthStepConnectionsTitle: "Gruplar ve bağlantılar",
mcpAuthStepConnectionsDescription: "MCP'nin hangi bağlantıları görebileceğini belirler",
mcpAuthStepDatabasesTitle: "Veritabanı kapsamı",
mcpAuthStepDatabasesDescription: "Her bağlantının erişebileceği veritabanlarını sınırlayın",
mcpAuthStepOverridesTitle: "Varsayılan izinler",
mcpAuthStepOverridesDescription: "Sonraki kapsamlar için genel varsayılan izni belirleyin",
mcpAuthStepCapabilitiesTitle: "Özet ve onay",
mcpAuthStepCapabilitiesDescription: "Etkin izinleri inceleyin, araçları seçin ve onaylayın",
mcpAuthStepRegionLabel: "Adım {step}: {title}",
mcpAuthPreviousStep: "Geri",
mcpAuthNextStep: "İleri",
mcpAuthStepProgress: "{total} adımdan {current}. adım",
mcpAuthDescription: "İzinler kademeli olarak geçersiz kılınır: veritabanı › bağlantı › grup › genel varsayılan. Kapsam ne kadar dar olursa öncelik o kadar yüksektir.",
mcpAuthTabConnectionsTitle: "Gruplar ve bağlantılar",
mcpAuthTabDatabasesTitle: "Veritabanı kapsamı",
mcpAuthTabOverridesTitle: "Varsayılan izinler",
mcpAuthTabCapabilitiesTitle: "Geçerli erişim ve araçlar",
mcpPolicyExceptionsTitle: "Kaydedilen izin istisnaları",
mcpPolicyExceptionsDescription: "Bağlantı kapsamını değiştirmek izin istisnalarını korur. Kapsamdaki istisnalar geçerli kalır; kapsam dışındakiler bağlantı kapsama döndüğünde uygulanır. Burada yürütme izinlerini düzenleyebilir veya devralınan izinleri geri yükleyebilirsiniz.",
mcpPolicyExceptionInScope: "Geçerli kapsam içinde",
+5 -14
View File
@@ -9938,20 +9938,11 @@ export default withEnglishFallback({
mcpRefresh: "重新检查",
mcpGuide: "MCP 指南",
mcpAuthTitle: "MCP 授权设置",
mcpAuthDescription: "按顺序完成授权:先设置全局默认权限,再选择分组与连接、限定数据库,最后确认最终策略。",
mcpAuthStepsLabel: "MCP 授权步骤",
mcpAuthStepConnectionsTitle: "分组与连接",
mcpAuthStepConnectionsDescription: "决定哪些分组和连接可被 MCP 看见,并设置连接权限",
mcpAuthStepDatabasesTitle: "数据库范围",
mcpAuthStepDatabasesDescription: "为每个连接限定可访问的库,并设置单库权限",
mcpAuthStepOverridesTitle: "默认权限",
mcpAuthStepOverridesDescription: "设置全局默认权限,后续范围可按需覆盖",
mcpAuthStepCapabilitiesTitle: "汇总与确认",
mcpAuthStepCapabilitiesDescription: "查看最终生效权限、选择 MCP 工具并确认策略",
mcpAuthStepRegionLabel: "第 {step} 步:{title}",
mcpAuthPreviousStep: "上一步",
mcpAuthNextStep: "下一步",
mcpAuthStepProgress: "第 {current} 步,共 {total} 步",
mcpAuthDescription: "权限逐级覆盖:数据库 › 连接 › 分组 › 全局默认,范围越小优先级越高。",
mcpAuthTabConnectionsTitle: "分组与连接",
mcpAuthTabDatabasesTitle: "数据库范围",
mcpAuthTabOverridesTitle: "默认权限",
mcpAuthTabCapabilitiesTitle: "生效权限与工具",
mcpPolicyExceptionsTitle: "已保存的权限例外",
mcpPolicyExceptionsDescription: "切换连接范围会保留权限例外。范围内的例外仍然生效;范围外的例外会在连接重新进入范围后生效。可直接修改执行权限,或恢复继承上级权限。",
mcpPolicyExceptionInScope: "当前范围内",
+5 -14
View File
@@ -8124,20 +8124,11 @@ export default withEnglishFallback({
mcpRefresh: "重新檢查",
mcpGuide: "MCP 指南",
mcpAuthTitle: "MCP 授權設定",
mcpAuthDescription: "按順序完成授權:先設定全域預設權限,再選擇群組與連線、限定資料庫,最後確認最終策略。",
mcpAuthStepsLabel: "MCP 授權步驟",
mcpAuthStepConnectionsTitle: "群組與連線",
mcpAuthStepConnectionsDescription: "決定哪些連線可被 MCP 看見",
mcpAuthStepDatabasesTitle: "資料庫範圍",
mcpAuthStepDatabasesDescription: "為每個連線限定可存取的庫",
mcpAuthStepOverridesTitle: "預設權限",
mcpAuthStepOverridesDescription: "設定套用至後續範圍的全域預設權限",
mcpAuthStepCapabilitiesTitle: "摘要與確認",
mcpAuthStepCapabilitiesDescription: "檢視最終權限、選擇工具並確認策略",
mcpAuthStepRegionLabel: "第 {step} 步:{title}",
mcpAuthPreviousStep: "上一步",
mcpAuthNextStep: "下一步",
mcpAuthStepProgress: "第 {current} 步,共 {total} 步",
mcpAuthDescription: "權限逐級覆蓋:資料庫 › 連線 › 群組 › 全域預設,範圍越小優先級越高。",
mcpAuthTabConnectionsTitle: "群組與連線",
mcpAuthTabDatabasesTitle: "資料庫範圍",
mcpAuthTabOverridesTitle: "預設權限",
mcpAuthTabCapabilitiesTitle: "生效權限與工具",
mcpPolicyExceptionsTitle: "已儲存的權限例外",
mcpPolicyExceptionsDescription: "切換連線範圍會保留權限例外。範圍內的例外仍然生效;範圍外的例外會在連線重新進入範圍後生效。可直接修改執行權限,或恢復繼承上層權限。",
mcpPolicyExceptionInScope: "目前範圍內",