Skip to content

Commit c7cdf5f

Browse files
fix(site): fix MCP page interactions and links (#7756)
* fix(site): fix MCP page agent cards, prompt bubbles, and CTAs - Agent cards: add tooltips, copy-to-clipboard, correct deep links for Cursor/VS Code, and proper external links - Prompt bubbles: make copyable with tooltip feedback - CTA section: differentiate "Add MCP Server" and "Read Docs" hrefs - "Want to see your tool listed?": open Tally form modal instead of broken link * fix(site): enlarge tally dialog and fix prompt bubble text alignment * fix(site): make Cursor and VS Code cards copy to clipboard instead of navigating * fix(site): use absolute URL for Read Docs CTA
1 parent b919d81 commit c7cdf5f

File tree

5 files changed

+190
-104
lines changed

5 files changed

+190
-104
lines changed
Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,75 @@
1+
"use client";
2+
13
import Image from "next/image";
4+
import { useState } from "react";
5+
6+
import { Tooltip, TooltipContent, TooltipTrigger } from "@prisma/eclipse";
27

38
export function AgentCard({
49
logo,
510
alt,
611
icon,
712
href,
13+
copyText,
814
}: {
915
logo: string | null;
1016
alt: string;
1117
icon: string | null;
12-
href: string;
18+
href?: string;
19+
copyText?: string;
1320
}) {
21+
const [copied, setCopied] = useState(false);
22+
23+
const isCopyAction = !!copyText;
24+
const isExternalLink = !isCopyAction && href?.startsWith("http");
25+
26+
const handleClick = (e: React.MouseEvent) => {
27+
if (!isCopyAction) return;
28+
e.preventDefault();
29+
navigator.clipboard.writeText(copyText);
30+
setCopied(true);
31+
setTimeout(() => setCopied(false), 1500);
32+
};
33+
34+
const Tag = isCopyAction ? "button" : "a";
35+
const linkProps = isCopyAction
36+
? { type: "button" as const, onClick: handleClick }
37+
: {
38+
href,
39+
...(isExternalLink ? { target: "_blank", rel: "noopener noreferrer" } : {}),
40+
};
41+
1442
return (
15-
<a
16-
href={href}
17-
title={alt}
18-
aria-label={alt}
19-
className="group relative flex h-30 w-full max-w-[165px] items-center justify-center rounded-[12px] border border-stroke-neutral bg-background-neutral-weaker shadow-box-low no-underline outline-offset-4 transition-[border-color,background-color,box-shadow] hover:border-stroke-ppg/60 hover:bg-background-default hover:shadow-box-high focus-visible:ring-2 focus-visible:ring-stroke-ppg dark:bg-background-neutral-weaker dark:hover:bg-background-neutral"
20-
>
21-
{logo ? (
22-
<Image
23-
src={logo}
24-
alt=""
25-
width={48}
26-
height={48}
27-
className="size-12 object-contain brightness-0 opacity-45 transition-opacity group-hover:opacity-65 dark:opacity-55 dark:invert"
28-
unoptimized
29-
/>
30-
) : (
31-
<span className="font-mono text-lg text-foreground-neutral-weak">
32-
Any AI agent
33-
</span>
34-
)}
35-
{icon ? (
36-
<span
37-
className="absolute right-1.75 top-1.75 text-foreground-neutral-weaker opacity-50 transition-opacity group-hover:opacity-100"
38-
aria-hidden
43+
<Tooltip open={copied || undefined}>
44+
<TooltipTrigger asChild>
45+
<Tag
46+
aria-label={alt}
47+
className="group relative flex h-30 w-full max-w-[165px] cursor-pointer items-center justify-center rounded-[12px] border border-stroke-neutral bg-background-neutral-weaker shadow-box-low no-underline outline-offset-4 transition-[border-color,background-color,box-shadow] hover:border-stroke-ppg/60 hover:bg-background-default hover:shadow-box-high focus-visible:ring-2 focus-visible:ring-stroke-ppg dark:bg-background-neutral-weaker dark:hover:bg-background-neutral"
48+
{...linkProps}
3949
>
40-
<i className={`${icon} text-[16px]`} />
41-
</span>
42-
) : null}
43-
</a>
50+
{logo ? (
51+
<Image
52+
src={logo}
53+
alt=""
54+
width={48}
55+
height={48}
56+
className="size-12 object-contain brightness-0 opacity-45 transition-opacity group-hover:opacity-65 dark:opacity-55 dark:invert dark:group-hover:opacity-80"
57+
unoptimized
58+
/>
59+
) : (
60+
<span className="font-mono text-lg text-foreground-neutral-weak">Any AI agent</span>
61+
)}
62+
{icon ? (
63+
<span
64+
className="absolute right-1.75 top-1.75 text-foreground-neutral-weaker opacity-50 transition-opacity group-hover:opacity-100"
65+
aria-hidden
66+
>
67+
<i className={`${icon} text-[16px]`} />
68+
</span>
69+
) : null}
70+
</Tag>
71+
</TooltipTrigger>
72+
<TooltipContent>{copied ? "Copied!" : alt}</TooltipContent>
73+
</Tooltip>
4474
);
4575
}

apps/site/src/app/mcp/_components/mcp-agents-section.tsx

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
import { Button } from "@prisma/eclipse";
1+
"use client";
2+
3+
import Script from "next/script";
4+
5+
import {
6+
Dialog,
7+
DialogContent,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger,
11+
TooltipProvider,
12+
} from "@prisma/eclipse";
213

314
import { AgentCard } from "./agent-card";
415

516
export type McpAgent = {
617
logo: string | null;
718
alt: string;
819
icon: string | null;
9-
href: string;
20+
href?: string;
21+
copyText?: string;
1022
};
1123

12-
export function McpAgentsSection({
13-
docsHref,
14-
agents,
15-
}: {
16-
docsHref: string;
17-
agents: readonly McpAgent[];
18-
}) {
24+
export function McpAgentsSection({ agents }: { docsHref: string; agents: readonly McpAgent[] }) {
1925
return (
2026
<section className="px-4 py-12 md:px-0">
2127
<div className="mx-auto flex max-w-[790px] flex-col items-center gap-12 text-center">
@@ -29,19 +35,44 @@ export function McpAgentsSection({
2935
</p>
3036
</div>
3137

32-
<div className="grid w-full max-w-[368px] grid-cols-2 justify-items-center gap-4 min-[400px]:gap-8 md:max-w-[790px] md:grid-cols-4 md:gap-8">
33-
{agents.map(({ logo, alt, icon, href }) => (
34-
<AgentCard key={alt} logo={logo} alt={alt} icon={icon} href={href} />
35-
))}
36-
</div>
38+
<TooltipProvider>
39+
<div className="grid w-full max-w-[368px] grid-cols-2 justify-items-center gap-4 min-[400px]:gap-8 md:max-w-[790px] md:grid-cols-4 md:gap-8">
40+
{agents.map((agent) => (
41+
<AgentCard key={agent.alt} {...agent} />
42+
))}
43+
</div>
44+
</TooltipProvider>
3745

38-
<Button
39-
href={docsHref}
40-
variant={"link"}
41-
className="text-sm font-semibold text-foreground-ppg underline"
42-
>
43-
Want to see your tool listed?
44-
</Button>
46+
<Dialog>
47+
<DialogTrigger asChild>
48+
<button
49+
type="button"
50+
className="cursor-pointer text-sm font-semibold text-foreground-ppg underline"
51+
>
52+
Want to see your tool listed?
53+
</button>
54+
</DialogTrigger>
55+
<DialogContent className="max-w-4xl">
56+
<DialogHeader>
57+
<DialogTitle>Want to see your favorite AI tool listed on prisma.io/mcp?</DialogTitle>
58+
</DialogHeader>
59+
<iframe
60+
data-tally-src="https://tally.so/r/wA1R1N"
61+
title="Tool listing request"
62+
width="100%"
63+
height="600"
64+
className="border-0"
65+
/>
66+
<Script
67+
src="https://tally.so/widgets/embed.js"
68+
onLoad={() => {
69+
if (typeof window !== "undefined" && (window as any).Tally) {
70+
(window as any).Tally.loadEmbeds();
71+
}
72+
}}
73+
/>
74+
</DialogContent>
75+
</Dialog>
4576
</div>
4677
</section>
4778
);

apps/site/src/app/mcp/_components/mcp-bubble.tsx

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { useId, type ReactNode } from "react";
1+
"use client";
2+
3+
import { useId, useState, type ReactNode } from "react";
4+
5+
import {
6+
Tooltip,
7+
TooltipContent,
8+
TooltipProvider,
9+
TooltipTrigger,
10+
} from "@prisma/eclipse";
211

312
const bubbleShadow = "shadow-box-low dark:shadow-box-high";
413

@@ -46,7 +55,7 @@ const promptConfig: Record<McpPromptBubbleVariant, string> = {
4655
};
4756

4857
const promptTextClass =
49-
"inline-block w-full break-words text-pretty font-mono text-[14px] font-normal leading-5 text-background-ppg-reverse-strong dark:text-foreground-ppg-reverse-weak";
58+
"inline-block w-full break-words text-pretty text-left font-mono text-[14px] font-normal leading-5 text-background-ppg-reverse-strong dark:text-foreground-ppg-reverse-weak";
5059

5160
function BubbleTail({ side }: { side: "left" | "right" }) {
5261
const positionClass =
@@ -92,14 +101,34 @@ export function McpPromptBubble({
92101
variant: McpPromptBubbleVariant;
93102
children: ReactNode;
94103
}) {
104+
const [copied, setCopied] = useState(false);
105+
106+
const handleCopy = () => {
107+
if (typeof children === "string") {
108+
navigator.clipboard.writeText(children);
109+
setCopied(true);
110+
setTimeout(() => setCopied(false), 1500);
111+
}
112+
};
113+
95114
return (
96115
<div className="relative w-full">
97-
<div
98-
className={`relative z-10 flex w-full items-center rounded-[12px] border bg-background-ppg text-background-ppg-reverse-strong transition-colors duration-300 ${promptConfig[variant]} [--mcp-bubble-fill:var(--color-background-ppg)] dark:[--mcp-bubble-fill:var(--color-background-ppg)] [--mcp-bubble-stroke:var(--color-stroke-ppg)] bg-(--mcp-bubble-fill) border-(--mcp-bubble-stroke)`}
99-
>
100-
<code className={promptTextClass}>{children}</code>
101-
<BubbleTail side="right" />
102-
</div>
116+
<TooltipProvider>
117+
<Tooltip open={copied || undefined}>
118+
<TooltipTrigger asChild>
119+
<button
120+
type="button"
121+
onClick={handleCopy}
122+
aria-label={copied ? "Copied!" : "Copy prompt"}
123+
className={`relative z-10 flex w-full cursor-pointer items-center rounded-[12px] border bg-background-ppg text-background-ppg-reverse-strong transition-colors duration-300 ${promptConfig[variant]} [--mcp-bubble-fill:var(--color-background-ppg)] dark:[--mcp-bubble-fill:var(--color-background-ppg)] [--mcp-bubble-stroke:var(--color-stroke-ppg)] bg-(--mcp-bubble-fill) border-(--mcp-bubble-stroke) hover:brightness-110`}
124+
>
125+
<code className={promptTextClass}>{children}</code>
126+
<BubbleTail side="right" />
127+
</button>
128+
</TooltipTrigger>
129+
<TooltipContent>{copied ? "Copied!" : "Copy prompt"}</TooltipContent>
130+
</Tooltip>
131+
</TooltipProvider>
103132
</div>
104133
);
105134
}

apps/site/src/app/mcp/_components/mcp-cta-section.tsx

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Button } from "@prisma/eclipse";
22

3-
export function McpCtaSection({ docsHref }: { docsHref: string }) {
3+
export function McpCtaSection({
4+
docsHref,
5+
readDocsHref,
6+
}: {
7+
docsHref: string;
8+
readDocsHref: string;
9+
}) {
410
return (
511
<section className="bg-radial from-background-ppg/50 from-0% to-background-default to-70% px-4 py-12">
612
<div className="mx-auto max-w-360 rounded-2xl bg-[url('/illustrations/homepage/footer_grid.svg')] bg-cover bg-center px-4 py-12">
@@ -11,36 +17,25 @@ export function McpCtaSection({ docsHref }: { docsHref: string }) {
1117
Start Building with AI
1218
</h3>
1319
<p className="max-w-[463px] text-base leading-6 text-foreground-neutral-weak">
14-
Join thousands of developers, and agents, already using Prisma MCP
15-
for faster, more intuitive database workflows.
20+
Join thousands of developers, and agents, already using Prisma MCP for faster, more
21+
intuitive database workflows.
1622
</p>
1723
</div>
1824

1925
<div className="flex flex-col items-center gap-4">
2026
<div className="flex w-full flex-col items-center justify-center gap-4 min-[720px]:flex-row min-[720px]:gap-6">
21-
<Button
22-
href={docsHref}
23-
variant={"ppg"}
24-
size={"3xl"}
25-
className="gap-3"
26-
>
27+
<Button href={docsHref} variant={"ppg"} size={"3xl"} className="gap-3">
2728
Add MCP Server
28-
<i
29-
className="fa-regular fa-arrow-right shrink-0 text-[16px]"
30-
aria-hidden
31-
/>
29+
<i className="fa-regular fa-arrow-right shrink-0 text-[16px]" aria-hidden />
3230
</Button>
3331
<Button
34-
href={docsHref}
32+
href={readDocsHref}
3533
variant={"default-stronger"}
3634
size={"3xl"}
3735
className="gap-3"
3836
>
3937
Read Docs
40-
<i
41-
className="fa-regular fa-book-open shrink-0 text-[16px]"
42-
aria-hidden
43-
/>
38+
<i className="fa-regular fa-book-open shrink-0 text-[16px]" aria-hidden />
4439
</Button>
4540
</div>
4641

0 commit comments

Comments
 (0)