feat(app): add MCP feature to user profile page

This commit is contained in:
Fatih Kadir Akın
2025-12-17 01:14:34 +03:00
parent 892fb0a32e
commit ea0725205e
12 changed files with 612 additions and 247 deletions
+2
View File
@@ -62,6 +62,8 @@ export default defineConfig({
aiSearch: true, aiSearch: true,
// Enable AI-powered generation features (requires OPENAI_API_KEY) // Enable AI-powered generation features (requires OPENAI_API_KEY)
aiGeneration: true, aiGeneration: true,
// Enable MCP (Model Context Protocol) features including API key generation
mcp: true,
}, },
// Homepage customization // Homepage customization
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="4" fill="black" style="fill:black;fill-opacity:1;"/>
<path d="M10.5724 15.2565C10.5724 17.5025 9.6613 19.3778 8.17805 21.1047H11.6319L11.6319 22.7786H6.33459V21.1895C7.95557 19.3566 8.58065 17.8628 8.58065 15.2565L10.5724 15.2565Z" fill="white" style="fill:white;fill-opacity:1;"/>
<path d="M17.4276 15.2565C17.4276 17.5025 18.3387 19.3778 19.822 21.1047H16.3681V22.7786H21.6654V21.1895C20.0444 19.3566 19.4194 17.8628 19.4194 15.2565H17.4276Z" fill="white" style="fill:white;fill-opacity:1;"/>
<path d="M10.5724 12.7435C10.5724 10.4975 9.66131 8.62224 8.17807 6.89532L11.6319 6.89532V5.22137L6.33461 5.22137V6.81056C7.95558 8.64343 8.58066 10.1373 8.58066 12.7435L10.5724 12.7435Z" fill="white" style="fill:white;fill-opacity:1;"/>
<path d="M17.4276 12.7435C17.4276 10.4975 18.3387 8.62224 19.822 6.89532L16.3681 6.89532L16.3681 5.22138L21.6654 5.22138V6.81056C20.0445 8.64343 19.4194 10.1373 19.4194 12.7435H17.4276Z" fill="white" style="fill:white;fill-opacity:1;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+3 -2
View File
@@ -7,6 +7,7 @@ import { getPromptUrl } from "@/lib/urls";
import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin, BadgeCheck, Users, ShieldCheck } from "lucide-react"; import { Calendar, ArrowBigUp, FileText, Settings, GitPullRequest, Clock, Check, X, Pin, BadgeCheck, Users, ShieldCheck } from "lucide-react";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import config from "@/../prompts.config";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -326,7 +327,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
</div> </div>
{/* Actions - desktop only */} {/* Actions - desktop only */}
<div className="hidden md:flex items-center gap-2 shrink-0"> <div className="hidden md:flex items-center gap-2 shrink-0">
<McpServerPopup initialUsers={[user.username]} /> {config.features.mcp !== false && <McpServerPopup initialUsers={[user.username]} />}
{isOwner && ( {isOwner && (
<Button variant="outline" size="sm" asChild> <Button variant="outline" size="sm" asChild>
<Link href="/settings"> <Link href="/settings">
@@ -363,7 +364,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
{/* Actions - mobile only */} {/* Actions - mobile only */}
<div className="md:hidden flex gap-2"> <div className="md:hidden flex gap-2">
<McpServerPopup initialUsers={[user.username]} /> {config.features.mcp !== false && <McpServerPopup initialUsers={[user.username]} />}
{isOwner && ( {isOwner && (
<Button variant="outline" size="sm" asChild className="flex-1"> <Button variant="outline" size="sm" asChild className="flex-1">
<Link href="/settings"> <Link href="/settings">
+260
View File
@@ -0,0 +1,260 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { Download, Copy, Check } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useBranding } from "@/components/providers/branding-provider";
import { notFound } from "next/navigation";
interface AssetCardProps {
title: string;
description: string;
bgClass: string;
children: React.ReactNode;
downloadUrl: string;
filename: string;
}
function AssetCard({ title, description, bgClass, children, downloadUrl, filename }: AssetCardProps) {
const handleDownload = async () => {
try {
const response = await fetch(downloadUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error("Download failed:", error);
}
};
return (
<div className="border rounded-lg overflow-hidden">
<div className={`${bgClass} h-40 flex items-center justify-center p-8`}>
{children}
</div>
<div className="p-4 flex items-center justify-between bg-background">
<div>
<p className="font-medium text-sm">{title}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
<Button variant="outline" size="sm" onClick={handleDownload}>
<Download className="h-3 w-3 mr-1" />
SVG
</Button>
</div>
</div>
);
}
interface ColorCardProps {
color: string;
name: string;
description: string;
}
function ColorCard({ color, name, description }: ColorCardProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(color);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<button
onClick={handleCopy}
className="flex items-center justify-between p-3 rounded-md transition-colors hover:opacity-80"
style={{ backgroundColor: color }}
>
<div className="text-left">
<p className="font-mono text-sm font-medium" style={{ color: isLight(color) ? "#000" : "#fff" }}>
{color}
</p>
<p className="text-xs opacity-80" style={{ color: isLight(color) ? "#000" : "#fff" }}>
{description}
</p>
</div>
{copied ? (
<Check className="h-4 w-4" style={{ color: isLight(color) ? "#000" : "#fff" }} />
) : (
<Copy className="h-4 w-4 opacity-60" style={{ color: isLight(color) ? "#000" : "#fff" }} />
)}
</button>
);
}
function isLight(color: string): boolean {
const hex = color.replace("#", "");
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 128;
}
export default function BrandAssetsPage() {
const branding = useBranding();
// Redirect if using clone branding
if (branding.useCloneBranding) {
notFound();
}
return (
<div className="container max-w-4xl py-10">
<h1 className="text-3xl font-bold mb-2">Brand Assets</h1>
<p className="text-muted-foreground mb-10">
Official logos, colors, and brand guidelines for {branding.name}. Free to use for press, partnerships, and community projects.
</p>
<div className="space-y-10">
{/* Logos Section */}
<section>
<h2 className="text-xl font-semibold mb-4">Logos</h2>
<div className="grid md:grid-cols-2 gap-4">
{/* Logo for light backgrounds */}
<AssetCard
title="Logo"
description="For light backgrounds"
bgClass="bg-gray-100"
downloadUrl="/logo.svg"
filename="prompts-chat-logo.svg"
>
<Image
src="/logo.svg"
alt="prompts.chat logo"
width={80}
height={80}
className="h-20 w-auto"
/>
</AssetCard>
{/* Logo for dark backgrounds */}
<AssetCard
title="Logo"
description="For dark backgrounds"
bgClass="bg-gray-900"
downloadUrl="/logo-dark.svg"
filename="prompts-chat-logo-dark.svg"
>
<Image
src="/logo-dark.svg"
alt="prompts.chat logo dark"
width={80}
height={80}
className="h-20 w-auto"
/>
</AssetCard>
{/* Logo with text - light */}
<AssetCard
title="Logo with Name"
description="For light backgrounds"
bgClass="bg-gray-100"
downloadUrl="/logo.svg"
filename="prompts-chat-logo.svg"
>
<div className="flex items-center gap-3">
<Image
src="/logo.svg"
alt="prompts.chat logo"
width={48}
height={48}
className="h-12 w-auto"
/>
<span className="text-2xl font-bold text-gray-900">{branding.name}</span>
</div>
</AssetCard>
{/* Logo with text - dark */}
<AssetCard
title="Logo with Name"
description="For dark backgrounds"
bgClass="bg-gray-900"
downloadUrl="/logo-dark.svg"
filename="prompts-chat-logo-dark.svg"
>
<div className="flex items-center gap-3">
<Image
src="/logo-dark.svg"
alt="prompts.chat logo dark"
width={48}
height={48}
className="h-12 w-auto"
/>
<span className="text-2xl font-bold text-white">{branding.name}</span>
</div>
</AssetCard>
</div>
</section>
{/* Colors Section */}
<section>
<h2 className="text-xl font-semibold mb-4">Brand Colors</h2>
<p className="text-sm text-muted-foreground mb-4">Click to copy hex value</p>
<div className="grid gap-2">
<ColorCard
color="#000000"
name="Primary"
description="Primary brand color"
/>
<ColorCard
color="#ffffff"
name="Background"
description="Light background"
/>
<ColorCard
color="#6366f1"
name="Accent"
description="Indigo accent color"
/>
<ColorCard
color="#71717a"
name="Muted"
description="Muted text color"
/>
</div>
</section>
{/* Usage Guidelines */}
<section>
<h2 className="text-xl font-semibold mb-4">Usage Guidelines</h2>
<div className="prose prose-neutral dark:prose-invert max-w-none">
<ul className="list-disc list-inside text-muted-foreground space-y-2">
<li>Do not stretch, distort, or rotate the logo</li>
<li>Maintain adequate spacing around the logo</li>
<li>Use the dark logo on light backgrounds and vice versa</li>
<li>Do not add effects like shadows or gradients to the logo</li>
<li>The logo should be clearly visible against the background</li>
</ul>
</div>
</section>
{/* License */}
<section>
<h2 className="text-xl font-semibold mb-4">License</h2>
<p className="text-muted-foreground">
The {branding.name} brand assets are provided under{" "}
<a
href="https://creativecommons.org/publicdomain/zero/1.0/"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
CC0 1.0 Universal
</a>
. You are free to use these assets for any purpose without attribution.
</p>
</section>
</div>
</div>
);
}
+2 -1
View File
@@ -5,6 +5,7 @@ import { getTranslations } from "next-intl/server";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import config from "@/../prompts.config";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PromptList } from "@/components/prompts/prompt-list"; import { PromptList } from "@/components/prompts/prompt-list";
import { SubscribeButton } from "@/components/categories/subscribe-button"; import { SubscribeButton } from "@/components/categories/subscribe-button";
@@ -139,7 +140,7 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
<span>{category._count.subscribers} subscribers</span> <span>{category._count.subscribers} subscribers</span>
</div> </div>
</div> </div>
<McpServerPopup initialCategories={[slug]} /> {config.features.mcp !== false && <McpServerPopup initialCategories={[slug]} />}
</div> </div>
</div> </div>
+162 -166
View File
@@ -10,10 +10,11 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { McpConfigTabs } from "@/components/mcp/mcp-config-tabs"; import { McpConfigTabs } from "@/components/mcp/mcp-config-tabs";
import config from "@/../prompts.config";
export const metadata = { export const metadata = {
title: "API Documentation - prompts.chat", title: "API Documentation - prompts.chat",
description: "MCP-first API for searching and discovering AI prompts programmatically", description: "API for searching and discovering AI prompts programmatically",
}; };
export default async function ApiDocsPage() { export default async function ApiDocsPage() {
@@ -25,83 +26,87 @@ export default async function ApiDocsPage() {
<div className="container max-w-4xl py-10"> <div className="container max-w-4xl py-10">
<h1 className="text-2xl font-bold mb-2">API Documentation</h1> <h1 className="text-2xl font-bold mb-2">API Documentation</h1>
<p className="text-muted-foreground mb-8"> <p className="text-muted-foreground mb-8">
prompts.chat provides an MCP-first API for searching and discovering AI prompts programmatically. {config.features.mcp !== false
Use the MCP endpoint directly with any MCP-compatible client, or make standard HTTP requests. ? "prompts.chat provides an MCP-first API for searching and discovering AI prompts programmatically. Use the MCP endpoint directly with any MCP-compatible client, or make standard HTTP requests."
: "prompts.chat provides an API for searching and discovering AI prompts programmatically."
}
</p> </p>
<div className="prose prose-neutral dark:prose-invert max-w-none space-y-10"> <div className="prose prose-neutral dark:prose-invert max-w-none space-y-10">
{/* MCP Overview */} {config.features.mcp !== false && (
<section className="space-y-4"> <>
<h2 className="text-lg font-semibold flex items-center gap-2"> {/* MCP Overview */}
<Zap className="h-5 w-5" /> <section className="space-y-4">
MCP-First API <h2 className="text-lg font-semibold flex items-center gap-2">
</h2> <Zap className="h-5 w-5" />
<p className="text-muted-foreground"> MCP-First API
Our API is built on the{" "} </h2>
<Link
href="https://modelcontextprotocol.io"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Model Context Protocol (MCP)
</Link>
, enabling seamless integration with AI assistants, IDEs, and automation tools.
The same endpoint works for both MCP clients and traditional REST-style requests.
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
<p className="text-muted-foreground"># MCP Endpoint</p>
<p>POST {baseUrl}/api/mcp</p>
</div>
</section>
{/* Using with MCP Clients */}
<section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Terminal className="h-5 w-5" />
Using with MCP Clients
</h2>
<p className="text-muted-foreground">
Add prompts.chat to your MCP client configuration. Choose your client and connection type below:
</p>
<McpConfigTabs baseUrl={baseUrl} className="[&_button]:text-sm [&_button]:px-3 [&_button]:py-1.5 [&_pre]:text-sm [&_pre]:p-4" />
<p className="text-muted-foreground text-sm">
<strong>Remote</strong> connects directly to prompts.chat API. <strong>Local</strong> runs the MCP server locally via npx.
</p>
</section>
{/* Authentication */}
<section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Key className="h-5 w-5" />
Authentication
</h2>
<p className="text-muted-foreground">
Most API features work without authentication. However, to save prompts via MCP or access your private prompts,
you need to authenticate using an API key.
</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
<div>
<p className="font-medium">Generate an API Key</p>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Go to{" "} Our API is built on the{" "}
<Link href="/settings" className="underline hover:text-foreground"> <Link
Settings href="https://modelcontextprotocol.io"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Model Context Protocol (MCP)
</Link> </Link>
{" "}to generate your API key. Keys start with <code className="bg-muted px-1.5 py-0.5 rounded">pchat_</code>. , enabling seamless integration with AI assistants, IDEs, and automation tools.
The same endpoint works for both MCP clients and traditional REST-style requests.
</p> </p>
</div> <div className="bg-muted rounded-lg p-4 font-mono text-sm">
<div> <p className="text-muted-foreground"># MCP Endpoint</p>
<p className="font-medium">Using the API Key</p> <p>POST {baseUrl}/api/mcp</p>
<p className="text-muted-foreground"> </div>
Pass your API key via the <code className="bg-muted px-1.5 py-0.5 rounded">PROMPTS_API_KEY</code> header. </section>
</p>
</div>
</div>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> {/* Using with MCP Clients */}
<pre>{`# Remote: HTTP transport with headers <section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Terminal className="h-5 w-5" />
Using with MCP Clients
</h2>
<p className="text-muted-foreground">
Add prompts.chat to your MCP client configuration. Choose your client and connection type below:
</p>
<McpConfigTabs baseUrl={baseUrl} className="[&_button]:text-sm [&_button]:px-3 [&_button]:py-1.5 [&_pre]:text-sm [&_pre]:p-4" />
<p className="text-muted-foreground text-sm">
<strong>Remote</strong> connects directly to prompts.chat API. <strong>Local</strong> runs the MCP server locally via npx.
</p>
</section>
{/* Authentication */}
<section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Key className="h-5 w-5" />
Authentication
</h2>
<p className="text-muted-foreground">
Most API features work without authentication. However, to save prompts via MCP or access your private prompts,
you need to authenticate using an API key.
</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
<div>
<p className="font-medium">Generate an API Key</p>
<p className="text-muted-foreground">
Go to{" "}
<Link href="/settings" className="underline hover:text-foreground">
Settings
</Link>
{" "}to generate your API key. Keys start with <code className="bg-muted px-1.5 py-0.5 rounded">pchat_</code>.
</p>
</div>
<div>
<p className="font-medium">Using the API Key</p>
<p className="text-muted-foreground">
Pass your API key via the <code className="bg-muted px-1.5 py-0.5 rounded">PROMPTS_API_KEY</code> header.
</p>
</div>
</div>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`# Remote: HTTP transport with headers
"prompts-chat": { "prompts-chat": {
"url": "${baseUrl}/api/mcp", "url": "${baseUrl}/api/mcp",
"headers": { "headers": {
@@ -123,30 +128,30 @@ curl -X POST ${baseUrl}/api/mcp \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "PROMPTS_API_KEY: pchat_your_api_key_here" \\ -H "PROMPTS_API_KEY: pchat_your_api_key_here" \\
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'`}</pre> -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'`}</pre>
</div> </div>
<p className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm">
<strong>Remote (HTTP)</strong> sends requests to prompts.chat with the API key in headers. <strong>Remote (HTTP)</strong> sends requests to prompts.chat with the API key in headers.
<strong> Local (stdio)</strong> runs the MCP server locally via npx with the API key as an environment variable. <strong> Local (stdio)</strong> runs the MCP server locally via npx with the API key as an environment variable.
With authentication, you can use the <code className="bg-muted px-1.5 py-0.5 rounded">save_prompt</code> tool With authentication, you can use the <code className="bg-muted px-1.5 py-0.5 rounded">save_prompt</code> tool
and search results will include your private prompts. and search results will include your private prompts.
</p> </p>
</section> </section>
{/* MCP Prompts */} {/* MCP Prompts */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h2 className="text-lg font-semibold flex items-center gap-2">
<Terminal className="h-5 w-5" /> <Terminal className="h-5 w-5" />
MCP Prompts MCP Prompts
</h2> </h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
All public prompts are exposed as native MCP prompts. This allows MCP clients to list All public prompts are exposed as native MCP prompts. This allows MCP clients to list
and use prompts directly via slash commands or prompt pickers. You can filter prompts and use prompts directly via slash commands or prompt pickers. You can filter prompts
by category or tag using URL query parameters. by category or tag using URL query parameters.
</p> </p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`# Filter by users (one or more usernames) <pre>{`# Filter by users (one or more usernames)
"prompts-chat": { "prompts-chat": {
"url": "${baseUrl}/api/mcp?users=f,torvalds" "url": "${baseUrl}/api/mcp?users=f,torvalds"
} }
@@ -165,24 +170,24 @@ curl -X POST ${baseUrl}/api/mcp \\
"prompts-chat": { "prompts-chat": {
"url": "${baseUrl}/api/mcp?users=f&categories=coding&tags=js" "url": "${baseUrl}/api/mcp?users=f&categories=coding&tags=js"
}`}</pre> }`}</pre>
</div> </div>
<div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3"> <div className="bg-muted/50 rounded-lg p-4 text-sm space-y-3">
<div> <div>
<p className="font-medium">prompts/list</p> <p className="font-medium">prompts/list</p>
<p className="text-muted-foreground">Browse all available prompts with pagination support.</p> <p className="text-muted-foreground">Browse all available prompts with pagination support.</p>
</div> </div>
<div> <div>
<p className="font-medium">prompts/get</p> <p className="font-medium">prompts/get</p>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Retrieve a prompt by ID. Variables ({"${name}"} or {"${name:default}"}) are automatically Retrieve a prompt by ID. Variables ({"${name}"} or {"${name:default}"}) are automatically
substituted with provided arguments. substituted with provided arguments.
</p> </p>
</div> </div>
</div> </div>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`# List prompts <pre>{`# List prompts
curl -X POST ${baseUrl}/api/mcp \\ curl -X POST ${baseUrl}/api/mcp \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Accept: application/json, text/event-stream" \\ -H "Accept: application/json, text/event-stream" \\
@@ -201,15 +206,15 @@ curl -X POST ${baseUrl}/api/mcp \\
"arguments": { "topic": "AI safety" } "arguments": { "topic": "AI safety" }
} }
}'`}</pre> }'`}</pre>
</div> </div>
</section> </section>
{/* Available Tools */} {/* Available Tools */}
<section className="space-y-6"> <section className="space-y-6">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h2 className="text-lg font-semibold flex items-center gap-2">
<Box className="h-5 w-5" /> <Box className="h-5 w-5" />
Available Tools Available Tools
</h2> </h2>
{/* search_prompts Tool */} {/* search_prompts Tool */}
<div className="space-y-4"> <div className="space-y-4">
@@ -408,17 +413,17 @@ curl -X POST ${baseUrl}/api/mcp \\
</div> </div>
</section> </section>
{/* MCP Protocol Example */} {/* MCP Protocol Example */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h2 className="text-lg font-semibold flex items-center gap-2">
<Code className="h-5 w-5" /> <Code className="h-5 w-5" />
MCP Protocol Examples MCP Protocol Examples
</h2> </h2>
<div className="space-y-3"> <div className="space-y-3">
<h3 className="font-medium">Initialize Connection</h3> <h3 className="font-medium">Initialize Connection</h3>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`curl -X POST ${baseUrl}/api/mcp \\ <pre>{`curl -X POST ${baseUrl}/api/mcp \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Accept: application/json, text/event-stream" \\ -H "Accept: application/json, text/event-stream" \\
-d '{ -d '{
@@ -431,13 +436,13 @@ curl -X POST ${baseUrl}/api/mcp \\
"clientInfo": { "name": "my-app", "version": "1.0.0" } "clientInfo": { "name": "my-app", "version": "1.0.0" }
} }
}'`}</pre> }'`}</pre>
</div> </div>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<h3 className="font-medium">List Available Tools</h3> <h3 className="font-medium">List Available Tools</h3>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`curl -X POST ${baseUrl}/api/mcp \\ <pre>{`curl -X POST ${baseUrl}/api/mcp \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Accept: application/json, text/event-stream" \\ -H "Accept: application/json, text/event-stream" \\
-d '{ -d '{
@@ -445,13 +450,13 @@ curl -X POST ${baseUrl}/api/mcp \\
"id": 2, "id": 2,
"method": "tools/list" "method": "tools/list"
}'`}</pre> }'`}</pre>
</div> </div>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<h3 className="font-medium">Search Prompts</h3> <h3 className="font-medium">Search Prompts</h3>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`curl -X POST ${baseUrl}/api/mcp \\ <pre>{`curl -X POST ${baseUrl}/api/mcp \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Accept: application/json, text/event-stream" \\ -H "Accept: application/json, text/event-stream" \\
-d '{ -d '{
@@ -466,18 +471,18 @@ curl -X POST ${baseUrl}/api/mcp \\
} }
} }
}'`}</pre> }'`}</pre>
</div> </div>
</div> </div>
</section> </section>
{/* Response Format */} {/* Response Format */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-lg font-semibold">Response Format</h2> <h2 className="text-lg font-semibold">Response Format</h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
The <code className="bg-muted px-1.5 py-0.5 rounded text-sm">search_prompts</code> tool returns results in the following format: The <code className="bg-muted px-1.5 py-0.5 rounded text-sm">search_prompts</code> tool returns results in the following format:
</p> </p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`{ <pre>{`{
"query": "code review", "query": "code review",
"count": 2, "count": 2,
"prompts": [ "prompts": [
@@ -495,14 +500,16 @@ curl -X POST ${baseUrl}/api/mcp \\
} }
] ]
}`}</pre> }`}</pre>
</div> </div>
</section> </section>
</>
)}
{/* REST API Alternative */} {/* REST API Alternative */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-lg font-semibold">REST API Alternative</h2> <h2 className="text-lg font-semibold">REST API</h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
For simpler integrations, you can also use the standard REST endpoint: Use the standard REST endpoint to search and retrieve prompts:
</p> </p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-x-auto">
<pre>{`# Search prompts via REST <pre>{`# Search prompts via REST
@@ -513,17 +520,6 @@ curl "${baseUrl}/api/prompts/{id}"`}</pre>
</div> </div>
</section> </section>
{/* Server Info */}
<section className="space-y-4">
<h2 className="text-lg font-semibold">Server Information</h2>
<p className="text-muted-foreground">
Get MCP server information and available tools:
</p>
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
<p>GET {baseUrl}/api/mcp</p>
</div>
</section>
{/* Rate Limits */} {/* Rate Limits */}
<section className="space-y-4"> <section className="space-y-4">
<h2 className="text-lg font-semibold">Rate Limits</h2> <h2 className="text-lg font-semibold">Rate Limits</h2>
+109 -71
View File
@@ -2,6 +2,7 @@ import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { Server, Database, Key, Palette, Globe, Settings, HardDrive, Cpu, BookOpen } from "lucide-react"; import { Server, Database, Key, Palette, Globe, Settings, HardDrive, Cpu, BookOpen } from "lucide-react";
import DeepWikiIcon from "@/../public/deepwiki.svg"; import DeepWikiIcon from "@/../public/deepwiki.svg";
import Context7Icon from "@/../public/context7.svg";
import { import {
Table, Table,
TableBody, TableBody,
@@ -41,47 +42,83 @@ export default function SelfHostingPage() {
</ul> </ul>
</section> </section>
{/* DeepWiki */} {/* Using Documentation AI-Agents */}
<section className="space-y-4"> <section className="space-y-6">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h2 className="text-xl font-bold">Using Documentation AI-Agents</h2>
<Image src={DeepWikiIcon} alt="" width={20} height={20} />
DeepWiki <div className="grid md:grid-cols-2 gap-6">
</h2> {/* DeepWiki */}
<p className="text-muted-foreground"> <div className="space-y-4">
<Link <h3 className="text-lg font-semibold flex items-center gap-2">
href="https://deepwiki.com/f/awesome-chatgpt-prompts" <Image src={DeepWikiIcon} alt="" width={20} height={20} />
target="_blank" DeepWiki
rel="noopener noreferrer" </h3>
className="underline hover:text-foreground" <p className="text-muted-foreground">
> <Link
DeepWiki href="https://deepwiki.com/f/awesome-chatgpt-prompts"
</Link> target="_blank"
{" "}provides AI-powered documentation and insights for this repository. It automatically generates comprehensive documentation from the codebase, making it easier to understand the project structure, APIs, and implementation details. rel="noopener noreferrer"
</p> className="underline hover:text-foreground"
<ul className="list-disc list-inside text-muted-foreground space-y-1"> >
<li>AI-generated documentation from source code</li> DeepWiki
<li>Interactive codebase exploration</li> </Link>
<li>Architecture diagrams and component relationships</li> {" "}provides AI-powered documentation and insights for this repository.
<li>Ask questions about the codebase</li> </p>
</ul> <ul className="list-disc list-inside text-muted-foreground space-y-1 text-sm">
<li>AI-generated documentation from source code</li>
<li>Interactive codebase exploration</li>
<li>Architecture diagrams and component relationships</li>
<li>Available as an MCP server</li>
</ul>
</div>
{/* Context7 */}
<div className="space-y-4">
<h3 className="text-lg font-semibold flex items-center gap-2">
<Image src={Context7Icon} alt="" width={20} height={20} className="rounded" />
Context7
</h3>
<p className="text-muted-foreground">
<Link
href="https://context7.com/f/awesome-chatgpt-prompts?tab=chat"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Context7
</Link>
{" "}is an AI-powered chat interface for exploring and understanding this repository.
</p>
<ul className="list-disc list-inside text-muted-foreground space-y-1 text-sm">
<li>Chat with the codebase using natural language</li>
<li>Get answers with code references</li>
<li>Understand implementation details quickly</li>
<li>Available as an MCP server</li>
</ul>
</div>
</div>
</section> </section>
{/* Prerequisites */} {/* Manually */}
<section className="space-y-4"> <section className="space-y-6">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h2 className="text-xl font-bold">Manually</h2>
<Database className="h-5 w-5" />
Prerequisites
</h2>
<ul className="list-disc list-inside text-muted-foreground space-y-1">
<li>Node.js 18+</li>
<li>PostgreSQL database</li>
<li>npm or yarn</li>
</ul>
</section>
{/* Installation */} {/* Prerequisites */}
<section className="space-y-4"> <div className="space-y-4">
<h2 className="text-lg font-semibold">Quick Start</h2> <h3 className="text-lg font-semibold flex items-center gap-2">
<Database className="h-5 w-5" />
Prerequisites
</h3>
<ul className="list-disc list-inside text-muted-foreground space-y-1">
<li>Node.js 18+</li>
<li>PostgreSQL database</li>
<li>npm or yarn</li>
</ul>
</div>
{/* Installation */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">Quick Start</h3>
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1 overflow-x-auto"> <div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1 overflow-x-auto">
<p className="text-muted-foreground"># Clone the repository</p> <p className="text-muted-foreground"># Clone the repository</p>
<p>git clone https://github.com/f/awesome-chatgpt-prompts.git</p> <p>git clone https://github.com/f/awesome-chatgpt-prompts.git</p>
@@ -96,14 +133,14 @@ export default function SelfHostingPage() {
<p className="text-muted-foreground mt-3"># Start development server</p> <p className="text-muted-foreground mt-3"># Start development server</p>
<p>npm run dev</p> <p>npm run dev</p>
</div> </div>
</section> </div>
{/* Environment Variables */} {/* Environment Variables */}
<section className="space-y-6"> <div className="space-y-6">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h3 className="text-lg font-semibold flex items-center gap-2">
<Key className="h-5 w-5" /> <Key className="h-5 w-5" />
Environment Variables Environment Variables
</h2> </h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Create a <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env</code> file based on <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env.example</code>: Create a <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env</code> file based on <code className="bg-muted px-1.5 py-0.5 rounded text-sm">.env.example</code>:
</p> </p>
@@ -280,14 +317,14 @@ export default function SelfHostingPage() {
</Table> </Table>
</div> </div>
</div> </div>
</section> </div>
{/* Configuration */} {/* Configuration */}
<section className="space-y-6"> <div className="space-y-6">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h3 className="text-lg font-semibold flex items-center gap-2">
<Palette className="h-5 w-5" /> <Palette className="h-5 w-5" />
Configuration (prompts.config.ts) Configuration (prompts.config.ts)
</h2> </h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Customize your instance by editing <code className="bg-muted px-1.5 py-0.5 rounded text-sm">prompts.config.ts</code>: Customize your instance by editing <code className="bg-muted px-1.5 py-0.5 rounded text-sm">prompts.config.ts</code>:
</p> </p>
@@ -508,14 +545,14 @@ export default function SelfHostingPage() {
</Table> </Table>
</div> </div>
</div> </div>
</section> </div>
{/* White-label */} {/* White-label */}
<section className="space-y-4"> <div className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h3 className="text-lg font-semibold flex items-center gap-2">
<Settings className="h-5 w-5" /> <Settings className="h-5 w-5" />
White-Label Mode White-Label Mode
</h2> </h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Set <code className="bg-muted px-1.5 py-0.5 rounded text-sm">useCloneBranding = true</code> at the top of the config to: Set <code className="bg-muted px-1.5 py-0.5 rounded text-sm">useCloneBranding = true</code> at the top of the config to:
</p> </p>
@@ -530,14 +567,14 @@ export default function SelfHostingPage() {
<p className="text-muted-foreground">// prompts.config.ts</p> <p className="text-muted-foreground">// prompts.config.ts</p>
<p>const useCloneBranding = true;</p> <p>const useCloneBranding = true;</p>
</div> </div>
</section> </div>
{/* Production */} {/* Production */}
<section className="space-y-4"> <div className="space-y-4">
<h2 className="text-lg font-semibold flex items-center gap-2"> <h3 className="text-lg font-semibold flex items-center gap-2">
<Globe className="h-5 w-5" /> <Globe className="h-5 w-5" />
Production Deployment Production Deployment
</h2> </h3>
<div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1"> <div className="bg-muted rounded-lg p-4 font-mono text-sm space-y-1">
<p>npm run build</p> <p>npm run build</p>
<p>npm run start</p> <p>npm run start</p>
@@ -545,11 +582,11 @@ export default function SelfHostingPage() {
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Deploy to Vercel, Railway, Render, or any Node.js hosting platform. Make sure to set all environment variables in your hosting provider&apos;s dashboard. Deploy to Vercel, Railway, Render, or any Node.js hosting platform. Make sure to set all environment variables in your hosting provider&apos;s dashboard.
</p> </p>
</section> </div>
{/* Support */} {/* Support */}
<section className="space-y-4"> <div className="space-y-4">
<h2 className="text-lg font-semibold">Support</h2> <h3 className="text-lg font-semibold">Support</h3>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
For issues and questions, please open a{" "} For issues and questions, please open a{" "}
<Link <Link
@@ -570,7 +607,8 @@ export default function SelfHostingPage() {
SELF-HOSTING.md SELF-HOSTING.md
</Link> </Link>
{" "}file in the repository. {" "}file in the repository.
</p> </p>
</div>
</section> </section>
</div> </div>
</div> </div>
+1 -1
View File
@@ -183,7 +183,7 @@ export default async function PromptsPage({ searchParams }: PromptsPageProps) {
{!config.homepage?.useCloneBranding && ( {!config.homepage?.useCloneBranding && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<HFDataStudioDropdown aiGenerationEnabled={aiGenerationAvailable} /> <HFDataStudioDropdown aiGenerationEnabled={aiGenerationAvailable} />
<McpServerPopup /> {config.features.mcp !== false && <McpServerPopup />}
</div> </div>
)} )}
<Button size="sm" className="h-8 text-xs w-full sm:w-auto" asChild> <Button size="sm" className="h-8 text-xs w-full sm:w-auto" asChild>
+7 -4
View File
@@ -2,6 +2,7 @@ import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import config from "@/../prompts.config";
import { ProfileForm } from "@/components/settings/profile-form"; import { ProfileForm } from "@/components/settings/profile-form";
import { ApiKeySettings } from "@/components/settings/api-key-settings"; import { ApiKeySettings } from "@/components/settings/api-key-settings";
@@ -41,10 +42,12 @@ export default async function SettingsPage() {
<div className="space-y-6"> <div className="space-y-6">
<ProfileForm user={user} /> <ProfileForm user={user} />
<ApiKeySettings {config.features.mcp !== false && (
initialApiKey={user.apiKey} <ApiKeySettings
initialPublicByDefault={user.mcpPromptsPublicByDefault} initialApiKey={user.apiKey}
/> initialPublicByDefault={user.mcpPromptsPublicByDefault}
/>
)}
</div> </div>
</div> </div>
); );
+2 -1
View File
@@ -4,6 +4,7 @@ import { getTranslations } from "next-intl/server";
import { ArrowLeft, Tag } from "lucide-react"; import { ArrowLeft, Tag } from "lucide-react";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import config from "@/../prompts.config";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { PromptCard } from "@/components/prompts/prompt-card"; import { PromptCard } from "@/components/prompts/prompt-card";
import { McpServerPopup } from "@/components/mcp/mcp-server-popup"; import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
@@ -124,7 +125,7 @@ export default async function TagPage({ params, searchParams }: TagPageProps) {
{total} {t("prompts")} {total} {t("prompts")}
</span> </span>
</div> </div>
<McpServerPopup initialTags={[slug]} /> {config.features.mcp !== false && <McpServerPopup initialTags={[slug]} />}
</div> </div>
</div> </div>
+55 -1
View File
@@ -3,6 +3,7 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation";
import { useSession, signOut } from "next-auth/react"; import { useSession, signOut } from "next-auth/react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { import {
@@ -15,6 +16,8 @@ import {
Globe, Globe,
Moon, Moon,
Sun, Sun,
Copy,
ExternalLink,
} from "lucide-react"; } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -66,10 +69,33 @@ export function Header({ authProvider = "credentials", allowRegistration = true
const t = useTranslations(); const t = useTranslations();
const { theme, setTheme } = useTheme(); const { theme, setTheme } = useTheme();
const branding = useBranding(); const branding = useBranding();
const router = useRouter();
const user = session?.user; const user = session?.user;
const isAdmin = user?.role === "ADMIN"; const isAdmin = user?.role === "ADMIN";
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [logoMenuOpen, setLogoMenuOpen] = useState(false);
const [logoMenuPosition, setLogoMenuPosition] = useState({ x: 0, y: 0 });
const handleLogoContextMenu = (e: React.MouseEvent) => {
if (branding.useCloneBranding) return;
e.preventDefault();
setLogoMenuPosition({ x: e.clientX, y: e.clientY });
setLogoMenuOpen(true);
};
const handleCopyLogoSvg = async () => {
try {
const logoUrl = theme === "dark" ? (branding.logoDark || branding.logo) : branding.logo;
if (!logoUrl) return;
const response = await fetch(logoUrl);
const svgContent = await response.text();
await navigator.clipboard.writeText(svgContent);
setLogoMenuOpen(false);
} catch (error) {
console.error("Failed to copy logo:", error);
}
};
return ( return (
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
@@ -168,7 +194,7 @@ export function Header({ authProvider = "credentials", allowRegistration = true
</Sheet> </Sheet>
{/* Logo */} {/* Logo */}
<Link href="/" className="flex gap-2"> <Link href="/" className="flex gap-2" onContextMenu={handleLogoContextMenu}>
{branding.logo && ( {branding.logo && (
<> <>
<Image <Image
@@ -190,6 +216,34 @@ export function Header({ authProvider = "credentials", allowRegistration = true
<span className="font-semibold leading-none">{branding.name}</span> <span className="font-semibold leading-none">{branding.name}</span>
</Link> </Link>
{/* Logo context menu */}
{!branding.useCloneBranding && (
<DropdownMenu open={logoMenuOpen} onOpenChange={setLogoMenuOpen}>
<DropdownMenuTrigger asChild>
<span className="sr-only">Logo menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent
style={{
position: "fixed",
left: logoMenuPosition.x,
top: logoMenuPosition.y,
}}
>
<DropdownMenuItem onClick={handleCopyLogoSvg}>
<Copy className="mr-2 h-4 w-4" />
Copy Logo SVG
</DropdownMenuItem>
<DropdownMenuItem onClick={() => {
router.push("/brand");
setLogoMenuOpen(false);
}}>
<ExternalLink className="mr-2 h-4 w-4" />
Brand Assets
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
{/* Desktop nav */} {/* Desktop nav */}
<nav className="hidden md:flex items-center gap-1 text-sm"> <nav className="hidden md:flex items-center gap-1 text-sm">
{user && ( {user && (
+1
View File
@@ -45,6 +45,7 @@ export interface FeaturesConfig {
tags: boolean; tags: boolean;
aiSearch?: boolean; aiSearch?: boolean;
aiGeneration?: boolean; aiGeneration?: boolean;
mcp?: boolean;
} }
export interface Sponsor { export interface Sponsor {