From efd623cde899541909b5ee14dcbb2dd05f5403bc Mon Sep 17 00:00:00 2001 From: Anna Effort Date: Fri, 14 Aug 2026 16:35:40 -0700 Subject: [PATCH] fix: align MCP server table with light theme and localize its copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP servers table painted rows bg-neutral-50, which is the same oklch(0.985) as the bg-main page beneath it, so in light mode the rows had no visible surface at all. Match the treatment Users/Teams/Tokens already use — white rows on the off-white page, bg-main header — and swap the hardcoded neutrals for semantic tokens so both themes follow the palette. Status colors gain light-mode pairs; emerald-400 and amber-400 are tuned for a dark row and fall under 3:1 on white. ServersTable was also the only one of the four tables with hardcoded English. Localize it along with the rest of the MCP servers surface — the page chrome, the connect/edit form, AdvancedSettings, and the six auth components — adding mcpServer.* keys across en-US, es-ES and pt-BR. Three messages needed rich text (the catalog link, the security checklist link, the bolded security warning) so translations can move the embedded element within the sentence. Also align "View Details" to "View details" in the tools and resources namespaces, which were the last two spellings out of step. locales.test.ts checked key parity but not whether messages parse or whether translations keep the same placeholders and tags. A translation that drops {count} or renames a rich-text tag passes CI and then throws or silently drops content at render time, so add both checks. Signed-off-by: Anna Effort --- e2e/resources.spec.ts | 24 ++-- e2e/servers.spec.ts | 12 +- e2e/tools.spec.ts | 32 ++--- .../mcp-servers/AdvancedSettings.tsx | 42 +++--- .../mcp-servers/AuthComponents.test.tsx | 3 +- src/components/mcp-servers/BasicAuth.tsx | 17 ++- .../mcp-servers/BearerTokenAuth.tsx | 9 +- .../mcp-servers/CACertificateUpload.test.tsx | 2 +- .../mcp-servers/CACertificateUpload.tsx | 25 +++- .../mcp-servers/CustomHeadersAuth.test.tsx | 3 +- .../mcp-servers/CustomHeadersAuth.tsx | 41 ++++-- src/components/mcp-servers/MCPServerForm.tsx | 95 +++++++------ src/components/mcp-servers/NoneAuth.tsx | 30 ++-- .../mcp-servers/OAuth2Auth.test.tsx | 3 +- src/components/mcp-servers/OAuth2Auth.tsx | 102 ++++++++------ .../mcp-servers/QueryParameterAuth.tsx | 27 ++-- .../servers/ServerActionsMenu.test.tsx | 2 +- src/components/servers/ServerActionsMenu.tsx | 24 +++- src/components/servers/ServersTable.tsx | 91 +++++++----- src/i18n/locales.test.ts | 82 +++++++++++ src/i18n/locales/en-US/mcpServer.json | 133 +++++++++++++++++- src/i18n/locales/en-US/resources.json | 2 +- src/i18n/locales/en-US/tools.json | 2 +- src/i18n/locales/es-ES/mcpServer.json | 133 +++++++++++++++++- src/i18n/locales/pt-BR/mcpServer.json | 133 +++++++++++++++++- src/pages/Resources.test.tsx | 30 ++-- src/pages/Servers.test.tsx | 2 +- src/pages/Servers.tsx | 51 ++++--- src/pages/Tools.test.tsx | 42 +++--- 29 files changed, 896 insertions(+), 298 deletions(-) diff --git a/e2e/resources.spec.ts b/e2e/resources.spec.ts index 4470dcd..44dc004 100644 --- a/e2e/resources.spec.ts +++ b/e2e/resources.spec.ts @@ -156,7 +156,7 @@ test.describe("Resources page", () => { await page.getByRole("button", { name: "More options for github-server" }).click(); - await expect(page.getByRole("menuitem", { name: "View Details" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "View details" })).toBeVisible(); }); test("truncates a long gateway name and keeps the overflow menu visible", async ({ page }) => { @@ -197,7 +197,7 @@ test.describe("Resources page", () => { expect(kebabBox!.x + kebabBox!.width).toBeLessThanOrEqual(cardBox!.x + cardBox!.width + 1); await kebab.click(); - await expect(page.getByRole("menuitem", { name: "View Details" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "View details" })).toBeVisible(); }); test("opens details panel when View Details is clicked", async ({ page }) => { @@ -213,7 +213,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await expect(panel).toBeVisible(); @@ -235,7 +235,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await expect(panel).toBeVisible(); @@ -259,7 +259,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await panel.getByRole("button", { name: "More options" }).first().click(); @@ -302,7 +302,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await expect(panel).toBeVisible(); @@ -350,7 +350,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await expect(panel).toBeVisible(); @@ -394,7 +394,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for solo-gateway" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for solo-gateway/i }); await expect(panel).toBeVisible(); @@ -441,7 +441,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for multi-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for multi-gw/i }); await expect(panel).toBeVisible(); @@ -491,7 +491,7 @@ test.describe("Resources page", () => { await expect(page.getByText("github-server")).toBeVisible(); await page.getByRole("button", { name: "More options for lone-gateway" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for lone-gateway/i }); await expect(panel).toBeVisible(); @@ -546,7 +546,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await expect(panel).toBeVisible(); @@ -602,7 +602,7 @@ test.describe("Resources page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Resources for github-server/i }); await panel.getByRole("button", { name: "More options" }).first().click(); diff --git a/e2e/servers.spec.ts b/e2e/servers.spec.ts index 94485e9..d9576c9 100644 --- a/e2e/servers.spec.ts +++ b/e2e/servers.spec.ts @@ -173,7 +173,7 @@ test.describe("MCP Servers page", () => { await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await expect(page.getByRole("menuitem", { name: "View Details" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "View details" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Edit" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Deactivate" })).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Delete" })).toBeVisible(); @@ -336,7 +336,7 @@ test.describe("MCP Servers page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); await expect(page.getByRole("heading", { name: "GitHub MCP Server" })).toBeVisible(); await page.keyboard.press("Escape"); // drawer is a full-height overlay; must close it before clicking table row actions await expect(page.getByRole("button", { name: /close mcp server details/i })).not.toBeVisible(); @@ -402,7 +402,7 @@ test.describe("MCP Servers page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); await expect(page.getByRole("heading", { name: "GitHub MCP Server" })).toBeVisible(); await page.keyboard.press("Escape"); // drawer is a full-height overlay; must close it before clicking table row actions await expect(page.getByRole("button", { name: /close mcp server details/i })).not.toBeVisible(); @@ -469,7 +469,7 @@ test.describe("MCP Servers page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); await expect(page.getByRole("heading", { name: "GitHub MCP Server" })).toBeVisible(); await page.keyboard.press("Escape"); // drawer is a full-height overlay; must close it before clicking table row actions await expect(page.getByRole("button", { name: /close mcp server details/i })).not.toBeVisible(); @@ -484,7 +484,7 @@ test.describe("MCP Servers page", () => { await expect(page.getByRole("row").filter({ hasText: "Slack MCP Server" })).not.toBeVisible(); await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); await expect(page.getByRole("heading", { name: "GitHub MCP Server" })).toBeVisible(); }); @@ -531,7 +531,7 @@ test.describe("MCP Servers page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "Actions for GitHub MCP Server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); // Wait for the details panel to open (heading "GitHub MCP Server" appears in the panel header) await expect(page.getByRole("heading", { name: "GitHub MCP Server" })).toBeVisible(); diff --git a/e2e/tools.spec.ts b/e2e/tools.spec.ts index 518ea7f..6fc4422 100644 --- a/e2e/tools.spec.ts +++ b/e2e/tools.spec.ts @@ -225,7 +225,7 @@ test.describe("Tools page", () => { // And it remains functional. await kebab.click(); - await expect(page.getByRole("menuitem", { name: "View Details" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "View details" })).toBeVisible(); }); test("opens more options dropdown and shows View Details item", async ({ page }) => { @@ -242,7 +242,7 @@ test.describe("Tools page", () => { await page.getByRole("button", { name: "More options for github-server" }).click(); - await expect(page.getByRole("menuitem", { name: "View Details" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "View details" })).toBeVisible(); }); test("opens details panel when View Details is clicked", async ({ page }) => { @@ -258,7 +258,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for github-server/i }); await expect(panel).toBeVisible(); @@ -280,7 +280,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for github-server/i }); await expect(panel).toBeVisible(); @@ -315,7 +315,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for github-server/i }); await expect(panel).toBeVisible(); @@ -366,7 +366,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for github-server/i }); await expect(panel).toBeVisible(); @@ -399,7 +399,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for github-server" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for github-server/i }); await panel.getByRole("button", { name: "More options" }).first().click(); @@ -438,7 +438,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for solo-gateway" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for solo-gateway/i }); await expect(panel).toBeVisible(); @@ -485,7 +485,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for multi-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for multi-gw/i }); await expect(panel).toBeVisible(); @@ -535,7 +535,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for rb-gateway" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for rb-gateway/i }); await expect(panel).toBeVisible(); @@ -583,7 +583,7 @@ test.describe("Tools page", () => { await expect(page.getByText("github-server")).toBeVisible(); await page.getByRole("button", { name: "More options for lone-gateway" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for lone-gateway/i }); await expect(panel).toBeVisible(); @@ -845,7 +845,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for edit-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for edit-gw/i }); await expect(panel).toBeVisible(); @@ -885,7 +885,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for schema-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for schema-gw/i }); await expect(panel).toBeVisible(); @@ -936,7 +936,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for toggle-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for toggle-gw/i }); await expect(panel).toBeVisible(); @@ -985,7 +985,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for activate-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for activate-gw/i }); await expect(panel).toBeVisible(); @@ -1034,7 +1034,7 @@ test.describe("Tools page", () => { await page.waitForLoadState("networkidle"); await page.getByRole("button", { name: "More options for fail-gw" }).click(); - await page.getByRole("menuitem", { name: "View Details" }).click(); + await page.getByRole("menuitem", { name: "View details" }).click(); const panel = page.getByRole("region", { name: /Tools for fail-gw/i }); await expect(panel).toBeVisible(); diff --git a/src/components/mcp-servers/AdvancedSettings.tsx b/src/components/mcp-servers/AdvancedSettings.tsx index bd53ec3..dc544f5 100644 --- a/src/components/mcp-servers/AdvancedSettings.tsx +++ b/src/components/mcp-servers/AdvancedSettings.tsx @@ -220,7 +220,9 @@ export function AdvancedSettings({ id="visibility" className="h-10 w-full border-neutral-300 dark:border-neutral-700" > - + @@ -236,9 +238,11 @@ export function AdvancedSettings({ {visibility === "team" && (

- {selectedTeamId - ? "This server will be scoped to your currently selected team" - : "Please select a team using the team switcher in the sidebar"} + {intl.formatMessage({ + id: selectedTeamId + ? "mcpServer.advanced.teamScoped" + : "mcpServer.advanced.teamNotSelected", + })}

)} @@ -246,26 +250,15 @@ export function AdvancedSettings({ {/* Authentication type */}
{(["none", "basic", "bearer", "custom", "oauth", "query"] as AuthType[]).map((type) => { - const label = - type === "none" - ? "None" - : type === "basic" - ? "Basic" - : type === "bearer" - ? "Bearer token" - : type === "custom" - ? "Custom headers" - : type === "oauth" - ? "OAuth 2.0" - : "Query parameter"; + const label = intl.formatMessage({ id: `mcpServer.advanced.authType.${type}` }); const isLongerLabel = type === "custom" || type === "query"; return (
@@ -300,7 +293,7 @@ export function AdvancedSettings({ htmlFor="one-time-auth" className="text-sm font-medium text-neutral-950 dark:text-white" > - One-time authentication + {intl.formatMessage({ id: "mcpServer.advanced.oneTimeAuthLabel" })}
@@ -309,14 +302,14 @@ export function AdvancedSettings({

- {"Use credentials once, don't store them. Health checks will be disabled."} + {intl.formatMessage({ id: "mcpServer.advanced.oneTimeAuthDescription" })}

{oneTimeAuth && (

- Add passthrough headers when one-time authentication is enabled. + {intl.formatMessage({ id: "mcpServer.advanced.oneTimeAuthWarning" })}

)} @@ -328,17 +321,16 @@ export function AdvancedSettings({ htmlFor="passthrough-headers" className="text-sm font-medium text-neutral-950 dark:text-white" > - Passthrough headers + {intl.formatMessage({ id: "mcpServer.advanced.passthroughLabel" })}

- Add comma-separate headers to forward from client requests. Leave empty to use global - defaults. + {intl.formatMessage({ id: "mcpServer.advanced.passthroughDescription" })}