feat: show MCP URLs and API access info on public collection/agent pages

- Always show McpUrlSection on public collection pages (not just owners)
- Add note for non-owners about providing their own credentials
- Add API usage example for non-owners on collections
- Add AgentApiSection component for public agent pages
- Show conversation API endpoint and usage example
- Make Chat button available to everyone on public agent pages
This commit is contained in:
Ajax Davis 2026-01-16 16:57:50 +10:00
parent 23d5159b28
commit 839e1b3bef
2 changed files with 182 additions and 25 deletions

View file

@ -2,6 +2,7 @@
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import Link from 'next/link';
import { notFound, useParams } from 'next/navigation';
@ -71,6 +72,113 @@ interface PublicAgent {
} | null;
}
function AgentApiSection({
agentId,
provider,
isOwner,
}: {
agentId: string;
provider: string;
isOwner: boolean;
}) {
const [showExample, setShowExample] = useState(false);
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
const apiUrl = `${baseUrl}/api/agents/${agentId}/conversation`;
const apiExampleSnippet = `// Create a conversation and send a message
const createConvo = await fetch("${apiUrl}", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
},
body: JSON.stringify({
name: "My Conversation"
})
});
const { data: { conversation } } = await createConvo.json();
// Send a message (include providerApiKey for non-owners)
const response = await fetch(\`${apiUrl}/\${conversation.id}\`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
},
body: JSON.stringify({
message: "Hello!",
providerApiKey: "YOUR_${provider.toUpperCase()}_API_KEY", // Required for non-owners
env: {
// Your env vars for any tools the agent uses
"SOME_API_KEY": "your-key-here"
}
})
});`;
return (
<section className="p-4 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 border border-primary/20 rounded-xl">
<div className="flex items-center gap-2 mb-4">
<div className="p-1.5 bg-primary/10 rounded-lg">
<Icon icon="link" className="w-4 h-4 text-primary" />
</div>
<h3 className="font-semibold text-foreground">API Access</h3>
</div>
<div className="space-y-3">
<div className="group">
<div className="flex items-center gap-2 mb-1.5">
<span className="text-xs font-medium text-foreground-secondary uppercase tracking-wide">
Conversation API
</span>
</div>
<div className="flex items-center gap-2">
<div className="flex-1 px-3 py-2 bg-surface border border-border rounded-lg font-mono text-sm text-foreground-secondary overflow-x-auto">
{apiUrl}
</div>
</div>
</div>
</div>
{/* Note for non-owners */}
{!isOwner && (
<div className="mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg">
<p className="text-sm text-warning-foreground">
<Icon icon="info" className="w-4 h-4 inline mr-1" />
You&apos;ll need to provide your own <strong>{provider}</strong> API key via the{' '}
<code className="font-mono text-xs bg-surface px-1 rounded">providerApiKey</code> field,
plus any tool credentials via the{' '}
<code className="font-mono text-xs bg-surface px-1 rounded">env</code> parameter.
</p>
</div>
)}
<div className="mt-4 pt-4 border-t border-border/50">
<button
type="button"
onClick={() => setShowExample(!showExample)}
className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
>
<Icon icon={showExample ? 'chevronDown' : 'chevronRight'} className="w-4 h-4" />
<span>Show API usage example</span>
</button>
{showExample && (
<div className="mt-3">
<CodeBlock language="typescript" code={apiExampleSnippet} />
</div>
)}
</div>
<p className="mt-3 text-xs text-foreground-tertiary">
<Link href="/docs/tutorials/agents" className="text-primary hover:underline">
Learn more about the Agent Conversation API
</Link>
</p>
</section>
);
}
export default function PrettyAgentDetailPage(): React.ReactElement {
const params = useParams();
const rawUsername = params.username as string;
@ -154,14 +262,12 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
<div className="flex items-center gap-2">
<LikeButton entityType="agent" entityId={agent.id} initialCount={agent.likeCount} />
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} />
{isOwner && (
<Link href={`/${username}/agents/${uid}/chat`}>
<Button>
<Icon icon="message" className="w-4 h-4 mr-2" />
Chat
</Button>
</Link>
)}
<Link href={`/${username}/agents/${uid}/chat`}>
<Button>
<Icon icon="message" className="w-4 h-4 mr-2" />
Chat
</Button>
</Link>
</div>
</div>
@ -185,10 +291,8 @@ export default function PrettyAgentDetailPage(): React.ReactElement {
<span>Temperature: {agent.temperature}</span>
</div>
{/* Fork CTA for non-owners */}
{!isOwner && (
<ForkButton type="agent" sourceId={agent.id} sourceName={agent.name} variant="full" />
)}
{/* API Access - Available to everyone (non-owners must provide their own credentials) */}
<AgentApiSection agentId={agent.id} provider={agent.provider} isOwner={!!isOwner} />
{/* System Prompt */}
{agent.systemPrompt && (

View file

@ -74,9 +74,18 @@ interface PublicCollection {
useCasesGeneratedAt: string | null;
}
function McpUrlSection({ username, slug }: { username: string; slug: string }) {
function McpUrlSection({
username,
slug,
isOwner,
}: {
username: string;
slug: string;
isOwner: boolean;
}) {
const [copiedUrl, setCopiedUrl] = useState<'http' | 'sse' | null>(null);
const [showConfig, setShowConfig] = useState(false);
const [showApiExample, setShowApiExample] = useState(false);
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://tpmjs.com';
const httpUrl = `${baseUrl}/api/mcp/${username}/${slug}/http`;
@ -100,6 +109,28 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
}
}`;
const apiExampleSnippet = `// Call a tool with your own credentials
const response = await fetch("${httpUrl}", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TPMJS_API_KEY"
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "tools/call",
params: {
name: "tool-name",
arguments: { /* tool args */ },
env: {
// Your env vars for the tools
"API_KEY": "your-key-here"
}
},
id: 1
})
});`;
return (
<section className="p-4 bg-gradient-to-br from-primary/5 via-transparent to-primary/5 border border-primary/20 rounded-xl">
<div className="flex items-center gap-2 mb-4">
@ -159,8 +190,20 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
</div>
</div>
{/* Note for non-owners */}
{!isOwner && (
<div className="mt-4 p-3 bg-warning/10 border border-warning/20 rounded-lg">
<p className="text-sm text-warning-foreground">
<Icon icon="info" className="w-4 h-4 inline mr-1" />
You&apos;ll need to provide your own API keys for any tools that require them. Pass
credentials via the <code className="font-mono text-xs bg-surface px-1 rounded">env</code>{' '}
parameter in your API calls.
</p>
</div>
)}
{/* Config snippet toggle */}
<div className="mt-4 pt-4 border-t border-border/50">
<div className="mt-4 pt-4 border-t border-border/50 space-y-2">
<button
type="button"
onClick={() => setShowConfig(!showConfig)}
@ -175,6 +218,25 @@ function McpUrlSection({ username, slug }: { username: string; slug: string }) {
<CodeBlock language="json" code={configSnippet} />
</div>
)}
{!isOwner && (
<>
<button
type="button"
onClick={() => setShowApiExample(!showApiExample)}
className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
>
<Icon icon={showApiExample ? 'chevronDown' : 'chevronRight'} className="w-4 h-4" />
<span>Show API usage example</span>
</button>
{showApiExample && (
<div className="mt-3">
<CodeBlock language="typescript" code={apiExampleSnippet} />
</div>
)}
</>
)}
</div>
<p className="mt-3 text-xs text-foreground-tertiary">
@ -310,17 +372,8 @@ export default function PrettyCollectionDetailPage(): React.ReactElement {
)}
</div>
{/* MCP Server URLs - Only shown for owner */}
{isOwner ? (
<McpUrlSection username={username} slug={collection.slug} />
) : (
<ForkButton
type="collection"
sourceId={collection.id}
sourceName={collection.name}
variant="full"
/>
)}
{/* MCP Server URLs - Available to everyone (non-owners must provide their own credentials) */}
<McpUrlSection username={username} slug={collection.slug} isOwner={!!isOwner} />
{/* Tools */}
{collection.tools.length > 0 ? (