fix(copilot): replace MCP jargon with user-friendly language (#12381)

Closes SECRT-2105

### Changes 🏗️

Replace all user-facing MCP technical terminology with plain, friendly
language across the CoPilot UI and LLM prompting.

**Backend (`run_mcp_tool.py`)**
- Added `_service_name()` helper that extracts a readable name from an
MCP host (`mcp.sentry.dev` → `Sentry`)
- `agent_name` in `SetupRequirementsResponse`: `"MCP: mcp.sentry.dev"` →
`"Sentry"`
- Auth message: `"The MCP server at X requires authentication. Please
connect your credentials to continue."` → `"To continue, sign in to
Sentry and approve access."`

**Backend (`mcp_tool_guide.md`)**
- Added "Communication style" section with before/after examples to
teach the LLM to avoid "MCP server", "OAuth", "credentials" jargon in
responses to users

**Frontend (`MCPSetupCard.tsx`)**
- Button: `"Connect to mcp.sentry.dev"` → `"Connect Sentry"`
- Connected state: `"Connected to mcp.sentry.dev!"` → `"Connected to
Sentry!"`
- Retry message: `"I've connected the MCP server credentials. Please
retry."` → `"I've connected. Please retry."`

**Frontend (`helpers.tsx`)**
- Added `serviceNameFromHost()` helper (exported, mirrors the backend
logic)
- Run text: `"Discovering MCP tools on mcp.sentry.dev"` → `"Connecting
to Sentry…"`
- Run text: `"Connecting to MCP server"` → `"Connecting…"`
- Run text: `"Connect to MCP: mcp.sentry.dev"` → `"Connect Sentry"`
(uses `agent_name` which is now just `"Sentry"`)
- Run text: `"Discovered N tool(s) on mcp.sentry.dev"` → `"Connected to
Sentry"`
- Error text: `"MCP error"` → `"Connection error"`

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [ ] Open CoPilot and ask it to connect to a service (e.g. Sentry,
Notion)
- [ ] Verify the run text accordion title shows `"Connecting to
Sentry…"` instead of `"Discovering MCP tools on mcp.sentry.dev"`
- [ ] Verify the auth card button shows `"Connect Sentry"` instead of
`"Connect to mcp.sentry.dev"`
- [ ] Verify the connected state shows `"Connected to Sentry!"` instead
of `"Connected to mcp.sentry.dev!"`
- [ ] Verify the LLM response text avoids "MCP server", "OAuth",
"credentials" terminology
This commit is contained in:
Zamil Majdy
2026-03-12 15:54:15 +07:00
committed by GitHub
parent ade2baa58f
commit 755bc84b1a
7 changed files with 61 additions and 44 deletions

View File

@@ -26,3 +26,17 @@ For other services, search the MCP registry at https://registry.modelcontextprot
If the server requires credentials, a `SetupRequirementsResponse` is returned with an OAuth
login prompt. Once the user completes the flow and confirms, retry the same call immediately.
### Communication style
Avoid technical jargon like "MCP server", "OAuth", or "credentials" when talking to the user.
Use plain, friendly language instead:
| Instead of… | Say… |
|---|---|
| "Let me connect to Sentry's MCP server and discover what tools are available." | "I can connect to Sentry and help identify important issues." |
| "Let me connect to Sentry's MCP server now." | "Next, I'll connect to Sentry." |
| "The MCP server at mcp.sentry.dev requires authentication. Please connect your credentials to continue." | "To continue, sign in to Sentry and approve access." |
| "Sentry's MCP server needs OAuth authentication. You should see a prompt to connect your Sentry account…" | "You should see a prompt to sign in to Sentry. Once connected, I can help surface critical issues right away." |
Use **"connect to [Service]"** or **"sign in to [Service]"** — never "MCP server", "OAuth", or "credentials".

View File

@@ -34,6 +34,11 @@ logger = logging.getLogger(__name__)
_AUTH_STATUS_CODES = {401, 403}
def _service_name(host: str) -> str:
"""Strip the 'mcp.' prefix from an MCP hostname: 'mcp.sentry.dev''sentry.dev'"""
return host[4:] if host.startswith("mcp.") else host
class RunMCPToolTool(BaseTool):
"""
Tool for discovering and executing tools on any MCP server.
@@ -303,8 +308,8 @@ class RunMCPToolTool(BaseTool):
)
return ErrorResponse(
message=(
f"The MCP server at {server_host(server_url)} requires authentication, "
"but no credential configuration was found."
f"Unable to connect to {_service_name(server_host(server_url))} "
" no credentials configured."
),
session_id=session_id,
)
@@ -312,15 +317,13 @@ class RunMCPToolTool(BaseTool):
missing_creds_list = list(missing_creds_dict.values())
host = server_host(server_url)
service = _service_name(host)
return SetupRequirementsResponse(
message=(
f"The MCP server at {host} requires authentication. "
"Please connect your credentials to continue."
),
message=(f"To continue, sign in to {service} and approve access."),
session_id=session_id,
setup_info=SetupInfo(
agent_id=server_url,
agent_name=f"MCP: {host}",
agent_name=service,
user_readiness=UserReadiness(
has_all_credentials=False,
missing_credentials=missing_creds_dict,

View File

@@ -756,4 +756,4 @@ async def test_build_setup_requirements_returns_setup_response():
)
assert isinstance(result, SetupRequirementsResponse)
assert result.setup_info.agent_id == _SERVER_URL
assert "authentication" in result.message.lower()
assert "sign in" in result.message.lower()

View File

@@ -235,8 +235,8 @@ describe("getAnimationText", () => {
state: "input-streaming",
...BASE,
});
expect(text).toContain("Discovering");
expect(text).toContain("mcp.example.com");
expect(text).toContain("Connecting");
expect(text).toContain("example.com");
});
it("shows tool call text when tool_name is set", () => {
@@ -245,7 +245,7 @@ describe("getAnimationText", () => {
input: { server_url: "https://mcp.example.com/mcp", tool_name: "fetch" },
});
expect(text).toContain("fetch");
expect(text).toContain("mcp.example.com");
expect(text).toContain("example.com");
});
it("includes query argument preview when tool_arguments has a query key", () => {
@@ -282,7 +282,7 @@ describe("getAnimationText", () => {
tool_arguments: {},
},
});
expect(text).toBe("Calling list_users on mcp.example.com");
expect(text).toBe("Calling list_users on example.com");
});
it("truncates long argument previews to 60 chars with ellipsis", () => {
@@ -327,8 +327,8 @@ describe("getAnimationText", () => {
output: DISCOVERY,
input: { server_url: "https://mcp.example.com/mcp" },
});
expect(text).toContain("Discovered");
expect(text).toContain("1");
expect(text).toContain("Connected");
expect(text).toContain("example.com");
});
it("shows setup label on output-available for setup requirements", () => {

View File

@@ -12,8 +12,6 @@ import { CredentialsProvidersContext } from "@/providers/agent-credentials/crede
import { useContext, useEffect, useRef, useState } from "react";
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
import { ContentMessage } from "../../../../components/ToolAccordion/AccordionContent";
import { serverHost } from "../../helpers";
interface Props {
output: SetupRequirementsResponse;
/**
@@ -38,7 +36,8 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
// setup_info.agent_id is set to the server_url in the backend
const serverUrl = output.setup_info.agent_id;
const host = serverHost(serverUrl);
// agent_name is computed by the backend as the display name for the service
const service = output.setup_info.agent_name;
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -95,10 +94,7 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
}
setConnected(true);
onSend(
retryInstruction ??
"I've connected the MCP server credentials. Please retry.",
);
onSend(retryInstruction ?? "I've connected. Please retry.");
} catch (e: unknown) {
const err = e as Record<string, unknown>;
if (err?.status === 400) {
@@ -137,10 +133,7 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
if (!(res.status >= 200 && res.status < 300))
throw new Error("Failed to store token");
setConnected(true);
onSend(
retryInstruction ??
"I've connected the MCP server credentials. Please retry.",
);
onSend(retryInstruction ?? "I've connected. Please retry.");
} catch (e: unknown) {
const err = e as Record<string, unknown>;
setError(
@@ -155,7 +148,7 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
if (connected) {
return (
<div className="mt-2 rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700">
Connected to {host}!
Connected to {service}!
</div>
);
}
@@ -171,7 +164,7 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
onClick={handleConnect}
disabled={loading}
>
{loading ? "Connecting…" : `Connect to ${host}`}
{loading ? "Connecting…" : `Connect ${service}`}
</Button>
{error && (
@@ -184,7 +177,7 @@ export function MCPSetupCard({ output, retryInstruction }: Props) {
<div className="mt-3 flex gap-2">
<input
type="password"
aria-label={`API token for ${host}`}
aria-label={`API token for ${service}`}
placeholder="Paste API token"
value={manualToken}
onChange={(e) => setManualToken(e.target.value)}

View File

@@ -32,11 +32,11 @@ vi.mock("@/app/api/__generated__/endpoints/mcp/mcp", () => ({
function makeSetupOutput(serverUrl = "https://mcp.example.com/mcp") {
return {
type: "setup_requirements" as const,
message: "The MCP server at mcp.example.com requires authentication.",
message: "To continue, sign in to example.com and approve access.",
session_id: "test-session",
setup_info: {
agent_id: serverUrl,
agent_name: "MCP: mcp.example.com",
agent_name: "example.com",
user_readiness: {
has_all_credentials: false,
missing_credentials: {},
@@ -58,9 +58,9 @@ describe("MCPSetupCard", () => {
it("renders setup message and connect button", () => {
render(<MCPSetupCard output={makeSetupOutput()} />);
expect(screen.getByText(/requires authentication/)).toBeDefined();
expect(screen.getByText(/sign in to example\.com/i)).toBeDefined();
expect(
screen.getByRole("button", { name: /connect to mcp.example.com/i }),
screen.getByRole("button", { name: /connect example\.com/i }),
).toBeDefined();
});
@@ -76,7 +76,7 @@ describe("MCPSetupCard", () => {
render(<MCPSetupCard output={makeSetupOutput()} />);
fireEvent.click(
screen.getByRole("button", { name: /connect to mcp.example.com/i }),
screen.getByRole("button", { name: /connect example\.com/i }),
);
await waitFor(() => {
@@ -100,7 +100,7 @@ describe("MCPSetupCard", () => {
render(<MCPSetupCard output={makeSetupOutput()} />);
fireEvent.click(
screen.getByRole("button", { name: /connect to mcp.example.com/i }),
screen.getByRole("button", { name: /connect example\.com/i }),
);
await waitFor(() => {
@@ -127,7 +127,7 @@ describe("MCPSetupCard", () => {
fireEvent.click(screen.getByRole("button", { name: /use token/i }));
await waitFor(() => {
expect(screen.getByText(/connected to mcp.example.com/i)).toBeDefined();
expect(screen.getByText(/connected to example\.com/i)).toBeDefined();
});
});
});

View File

@@ -125,6 +125,11 @@ export function serverHost(url: string): string {
}
}
/** Strip the 'mcp.' prefix from an MCP hostname: 'mcp.sentry.dev' → 'sentry.dev' */
export function serviceNameFromHost(host: string): string {
return host.startsWith("mcp.") ? host.slice(4) : host;
}
/**
* Return a short preview of the most meaningful argument value, e.g. `"my query"`.
* Checks common "query" key names first, then falls back to the first string value.
@@ -174,28 +179,30 @@ export function getAnimationText(part: {
const host = input?.server_url ? serverHost(input.server_url) : "";
const toolName = input?.tool_name?.trim();
const service = host ? serviceNameFromHost(host) : "";
switch (part.state) {
case "input-streaming":
case "input-available": {
if (!toolName) return `Discovering MCP tools${host ? ` on ${host}` : ""}`;
if (!toolName) return `Connecting to ${service || "integration"}`;
const argPreview = getArgPreview(input?.tool_arguments);
return `Calling ${toolName}${argPreview ? `(${argPreview})` : ""}${host ? ` on ${host}` : ""}`;
return `Calling ${toolName}${argPreview ? `(${argPreview})` : ""}${service ? ` on ${service}` : ""}`;
}
case "output-available": {
const output = getRunMCPToolOutput(part);
if (!output) return "Connecting to MCP server";
if (!output) return "Connecting";
if (isSetupRequirementsOutput(output))
return `Connect to ${output.setup_info.agent_name}`;
return `Connect ${output.setup_info.agent_name}`;
if (isMCPToolOutput(output))
return `Ran ${output.tool_name}${host ? ` on ${host}` : ""}`;
return `Ran ${output.tool_name}${service ? ` on ${service}` : ""}`;
if (isDiscoveryOutput(output))
return `Discovered ${output.tools.length} tool(s) on ${serverHost(output.server_url)}`;
return "MCP error";
return `Connected to ${serviceNameFromHost(serverHost(output.server_url))}`;
return "Connection error";
}
case "output-error":
return "MCP error";
return "Connection error";
default:
return "Connecting to MCP server";
return "Connecting";
}
}