fix(mcp): make the LinkedIn redirect URL impossible to miss

Confirmed the architecture is correct as-is: each company already has
its own isolated envs.json, providers.json and LinkedIn OAuth tokens,
swapped in on switch (company_profiles.py) — LinkedIn's own client_id and
secret are meant to be per-company, by design, and nothing about that
changes here.

What was genuinely broken: the exact URL a new company's owner has to add
to their LinkedIn app before Authenticate can work was rendered as small
gray footnote text below the status badges, easy to open LinkedIn
Developer Portal without ever noticing. That's the entire reason a
working setup on one machine is completely unreproducible from memory a
week later on another one.

Moved it to a highlighted box, first thing shown in the card, with the
warning stated in plain words and a one-click Copy button — removing the
one place a manual retype silently drops a character. No backend change:
the URL itself was already computed correctly and consistently; only
where and how it's shown was the problem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
AFFAANh
2026-09-03 19:07:02 +05:30
co-authored by Claude Opus 5
parent 62270b8c30
commit fa86fc53f9
+77 -13
View File
@@ -1,6 +1,6 @@
import { useCallback, useState, useEffect } from "react";
import { Button, Empty, Modal, Card, message } from "@agentscope-ai/design";
import { ExternalLink, RefreshCw, ShieldCheck } from "lucide-react";
import { Copy, ExternalLink, RefreshCw, ShieldCheck } from "lucide-react";
import api, { request } from "../../../api";
import type {
LinkedInOAuthStatus,
@@ -262,6 +262,20 @@ function MCPPage() {
}
};
// The single most common way LinkedIn login breaks for a new client: this
// exact URL was never added to their LinkedIn app's Authorized redirect
// URLs. Copy-to-clipboard removes the one place a manual retype goes
// wrong — a trailing space or a dropped character here fails silently
// until someone clicks Authenticate and gets LinkedIn's own error page.
const handleCopyLinkedInRedirectUri = async (redirectUri: string) => {
try {
await navigator.clipboard.writeText(redirectUri);
message.success("Copied. Paste it into LinkedIn Developer Portal.");
} catch {
message.error("Could not copy automatically — select and copy it by hand.");
}
};
const handleCreateClient = async () => {
try {
const parsed: unknown = JSON.parse(newClientJson);
@@ -454,7 +468,68 @@ function MCPPage() {
<ShieldCheck size={18} />
LinkedIn OAuth
</h3>
<p style={{ margin: "4px 0 0", color: "#475569", fontSize: 13 }}>
{/* Shown first, before anything else in this card: this is
the one step a new client's owner has to get exactly
right before Authenticate can possibly work, and the old
small-print placement below the status badges made it
easy to open LinkedIn Developer Portal without ever
noticing it. */}
<div
style={{
marginTop: 10,
padding: "10px 12px",
borderRadius: 6,
border: "1px solid #fde68a",
background: "#fffbeb",
}}
>
<p
style={{
margin: 0,
fontSize: 13,
fontWeight: 600,
color: "#92400e",
}}
>
Before clicking Authenticate: add this exact URL to this
LinkedIn app's Authorized redirect URLs (Developer Portal
your app Auth tab), or LinkedIn will refuse the login.
</p>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
marginTop: 6,
flexWrap: "wrap",
}}
>
<code
style={{
fontSize: 12,
wordBreak: "break-all",
background: "#fff",
border: "1px solid #fde68a",
borderRadius: 4,
padding: "3px 6px",
}}
>
{linkedinStatus.redirect_uri}
</code>
<Button
size="small"
icon={<Copy size={12} />}
onClick={() =>
handleCopyLinkedInRedirectUri(linkedinStatus.redirect_uri)
}
>
Copy
</Button>
</div>
</div>
<p style={{ margin: "10px 0 0", color: "#475569", fontSize: 13 }}>
{linkedinStatusLine(linkedinStatus)}
</p>
{linkedinStatus.missing_step && (
@@ -487,17 +562,6 @@ function MCPPage() {
linkedinStatus.linkedin_image_post_enabled,
)}
</div>
<p
style={{
margin: "10px 0 0",
color: "#64748b",
fontSize: 12,
wordBreak: "break-all",
}}
>
Add this callback URL in LinkedIn Developer Portal:{" "}
<code>{linkedinStatus.redirect_uri}</code>
</p>
{linkedinStatus.scope.length > 0 && (
<p
style={{