feat(web): finesse features section

pull/715/head
Travis Fischer 2025-06-25 09:33:55 -05:00
rodzic 422da87f1c
commit 52302243cd
8 zmienionych plików z 428 dodań i 157 usunięć

Wyświetl plik

@ -2,14 +2,10 @@ import Link from 'next/link'
import { DotsSection } from '@/components/dots-section'
import { ExampleAgenticConfigs } from '@/components/example-agentic-configs'
import { Features } from '@/components/features'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { SupplySideCTA } from '@/components/supply-side-cta'
import {
calendarBookingUrl,
docsUrl,
githubUrl,
twitterUrl
} from '@/lib/config'
import { githubUrl, twitterUrl } from '@/lib/config'
export default function MCPAuthorsPage() {
return (
@ -64,147 +60,10 @@ export default function MCPAuthorsPage() {
{/* Features section */}
<section className='flex flex-col gap-8 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Production-Ready and Extremely Flexible
Production-Ready MCP Gateway
</h2>
<div className='grid gap-6 max-w-2xl'>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Auth
</h4>
<p className='text-sm'>
Ship to production fast with Agentic's free, built-in
authentication. Email & password, OAuth, GitHub, Google, Twitter,
etc if your origin API requires OAuth credentials, Agentic
likely already supports it, and if not,{' '}
<Link
href={calendarBookingUrl}
target='_blank'
rel='noopener'
className='link'
>
let me know
</Link>
.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Stripe Billing
</h4>
<p className='text-sm'>
Charge for your MCP products with a flexible, declarative pricing
model built on top of Stripe. Agentic supports almost any
combination of fixed and{' '}
<span className='font-semibold'>usage-based billing</span> models,
both at the MCP level, at the tool-call level, and at the custom
metric level (e.g., tokens, image transformations, etc).
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Support both MCP and HTTP
</h4>
<p className='text-sm'>
All agentic products support being used both as a standard MCP
server <em>and</em> as an extremely simple HTTP API. MCP is
important for interop, discoverability, and future-proofing,
whereas being able to call your agentic tools via simple{' '}
<em>HTTP POST</em> requests makes tool use easy to debug and makes
integration with existing LLM tool calling patterns a breeze. With
Agentic, you get the best of both worlds, including future support
for unreleased MCP features and related specs.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
API keys
</h4>
<p className='text-sm'>
When a customer subscribes to your product, they're given a unique
API key. MCP URLs are appended with this API key to correlate
usage with their subscription. Customer HTTP tool calls use the
same API key as a standard HTTP <em>Authorization</em> header.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Rate-limiting
</h4>
<p className='text-sm'>
All agentic products are protected by durable rate-limiting built
on top of Cloudflare's global infrastructure. Customize the
default rate-limits, change them based on a customer's pricing
plan, or create custom tool-specific overrides. REST assured that
your origin API will be safe behind Agentic's robust API gateway.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Caching
</h4>
<p className='text-sm'>
Opt-in to caching with familiar <em>cache-control</em> and{' '}
<em>stale-while-revalidate</em> options. MCP tool calls include
caching information in their <em>_meta</em> fields, providing
parity with standard HTTP headers. All caching takes place in
Cloudflare's global edge cache, and caching will only be enabled
if you choose to enable it for your product or individual tools.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Analytics
</h4>
<p className='text-sm'>
Agentic tracks all tool calls for usage-based billing and
analytics at a fine-grained level, so you can drill in and deeply
understand how your customers are using your product.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
Versioning
</h4>
<p className='text-sm'>
Just like Vercel, Agentic uses immutable deployments, so every
time you make a change to your product's config, pricing, or docs,
a unique preview deployment is created for that change. This
enables instant rollbacks if there are problems with a deployment.
Publishing deployments publicly uses semantic versioning (
<em>semver</em>), so your customers can choose how to handle
breaking changes.
</p>
</div>
<div className='flex flex-col gap-2'>
<h4 className='text-center text-balance text-lg font-heading'>
That's just the start
</h4>
<p className='text-sm'>
<Link href={docsUrl} className='link'>
Check out our docs
</Link>{' '}
for more details on Agentic's MCP API gateway.
</p>
</div>
</div>
<Features />
</section>
{/* Open source section */}
@ -214,7 +73,6 @@ export default function MCPAuthorsPage() {
</h2>
<p className=''>
Open source is very dear to my heart, and I couldn't be happier that
Agentic is fully OSS. It's written in{' '}
<span className='font-semibold'>TypeScript</span> and has a small but
vibrant developer community.{' '}
@ -242,13 +100,15 @@ export default function MCPAuthorsPage() {
</section>
{/* CTA section */}
<section className='flex flex-col gap-12 mb-16'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Deploy Your MCP Today
</h2>
<DotsSection className='mb-16'>
<div className='flex flex-col gap-12 z-10'>
<h2 className='text-center text-balance leading-snug md:leading-none text-3xl font-heading'>
Deploy Your MCP Today
</h2>
<SupplySideCTA variant='github-2' />
</section>
<SupplySideCTA variant='github-2' />
</div>
</DotsSection>
</>
)
}

Wyświetl plik

@ -105,7 +105,6 @@ export default async function TheBestDamnLandingPageEver() {
</h2>
<p className=''>
Open source is very dear to my heart, and I couldn't be happier that
Agentic is fully OSS. It's written in{' '}
<span className='font-semibold'>TypeScript</span> and has a small but
vibrant developer community.{' '}

Wyświetl plik

@ -6,7 +6,7 @@ import { docsMarketplaceUrl } from '@/lib/config'
export function DemandSideCTA() {
return (
<div className='flex justify-center items-center gap-8'>
<div className='flex justify-center items-center gap-12'>
<HeroButton asChild className='h-full'>
<Link href='/marketplace' className='font-mono'>
gotoTools();

Wyświetl plik

@ -0,0 +1,336 @@
'use client'
import type { ComponentType, ReactNode } from 'react'
import {
ChartNoAxesCombinedIcon,
CheckCheckIcon,
CreditCardIcon,
DatabaseZapIcon,
HistoryIcon,
KeyRoundIcon,
ShieldCheckIcon,
TextSelectIcon,
UserIcon
} from 'lucide-react'
import {
motion,
type MotionValue,
useMotionTemplate,
useMotionValue
} from 'motion/react'
import Link from 'next/link'
import { calendarBookingUrl, docsUrl } from '@/lib/config'
import { GridPattern } from './grid-pattern'
type Feature = {
name: string
description: ReactNode
icon: ComponentType<{ className?: string }>
pattern: Omit<GridPattern, 'width' | 'height' | 'x'>
href?: string
}
const FEATURES: Feature[] = [
{
name: 'Auth',
description: (
<>
Ship to production fast with Agentic's free, built-in authentication.
Email & password, OAuth, GitHub, Google, Twitter, etc if your origin
API requires OAuth credentials, Agentic likely already supports it, and
if not,{' '}
<Link
href={calendarBookingUrl}
target='_blank'
rel='noopener'
className='link'
>
let me know
</Link>
.
</>
),
icon: UserIcon,
pattern: {
y: 16,
squares: [
[0, 1],
[1, 3]
]
}
},
{
name: 'Stripe Billing',
description: (
<>
Charge for your MCP products with a flexible, declarative pricing model
built on top of Stripe. Agentic supports almost any combination of fixed
and <span className='font-semibold'>usage-based billing</span> models,
both at the MCP level, at the tool-call level, and at the custom metric
level (e.g., tokens, image transformations, etc).
</>
),
icon: CreditCardIcon,
pattern: {
y: -6,
squares: [
[-1, 2],
[1, 3]
]
}
},
{
name: 'Support both MCP and HTTP',
description: (
<>
All agentic products support being used both as a standard MCP server{' '}
<em>and</em> as an extremely simple HTTP API. MCP is important for
interop and future-proofing, whereas being able to call your agentic
tools via simple <em>HTTP POST</em> requests makes tool use easy to
debug and makes integration with existing LLM tool calling patterns a
breeze.
</>
),
icon: CheckCheckIcon,
pattern: {
y: 32,
squares: [
[0, 2],
[1, 4]
]
}
},
{
name: 'API Keys',
description: (
<>
When a customer subscribes to your product, they're given a unique API
key. MCP URLs are appended with this API key to correlate usage with
their subscription. Customer HTTP tool calls use the same API key as a
standard HTTP <em>Authorization</em> header.
</>
),
icon: KeyRoundIcon,
pattern: {
y: 22,
squares: [[0, 1]]
}
},
{
name: 'Rate-limiting',
description: (
<>
All agentic products are protected by durable rate-limiting built on top
of Cloudflare's global infrastructure. Customize the default
rate-limits, change them based on a customer's pricing plan, or create
custom tool-specific overrides. REST assured that your origin API will
be safe behind Agentic's API gateway.
</>
),
icon: ShieldCheckIcon,
pattern: {
y: 2,
squares: [
[-2, 3],
[1, 4]
]
}
},
{
name: 'Caching',
description: (
<>
Opt-in to caching with familiar <em>cache-control</em> and{' '}
<em>stale-while-revalidate</em> options. MCP tool calls include caching
information in their <em>_meta</em> fields, providing parity with
standard HTTP headers. All caching takes place in Cloudflare's global
edge cache, and caching will only be enabled if you choose to enable it
for your product or individual tools.
</>
),
icon: DatabaseZapIcon,
pattern: {
y: 8,
squares: [
[0, 2],
[-1, 1]
]
}
},
{
name: 'Analytics',
description: (
<>
Agentic tracks all tool calls for usage-based billing and analytics at a
fine-grained level, so you can drill in and deeply understand how your
customers are using your product.
</>
),
icon: ChartNoAxesCombinedIcon,
pattern: {
y: -2,
squares: [[-2, 2]]
}
},
{
name: 'Versioning',
description: (
<>
Just like Vercel, Agentic uses immutable deployments, so every time you
make a change to your product's config, pricing, or docs, a unique
preview deployment is created for that change. This enables instant
rollbacks if there are problems with a deployment. Publishing
deployments publicly uses semantic versioning (<em>semver</em>), so your
customers can choose how to handle breaking changes.
</>
),
icon: HistoryIcon,
pattern: {
y: 26,
squares: [
[2, 4],
[-2, 3]
]
}
},
{
name: "That's just the start",
description: (
<>Check out our docs for more details on Agentic's MCP gateway.</>
),
href: docsUrl,
icon: TextSelectIcon,
pattern: {
y: 13,
squares: [
[0, 2],
[0, 2]
]
}
}
]
function Feature({ name, description, icon, pattern, href }: Feature) {
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
function onMouseMove({
currentTarget,
clientX,
clientY
}: React.MouseEvent<HTMLElement>) {
const { left, top } = currentTarget.getBoundingClientRect()
mouseX.set(clientX - left)
mouseY.set(clientY - top)
}
const content = (
<>
<FeaturePattern {...pattern} mouseX={mouseX} mouseY={mouseY} />
<div className='ring-gray-900/7.5 group-hover:ring-gray-900/10 absolute inset-0 rounded-2xl ring-1 ring-inset dark:ring-white/10 dark:group-hover:ring-white/20' />
<div className='relative rounded-2xl px-4 pb-4 pt-16'>
<FeatureIcon icon={icon} />
<h3 className='text-gray-900 mt-4 text-[0.875rem] font-semibold leading-7 dark:text-white'>
{/* <span className='absolute inset-0 rounded-2xl' /> */}
{name}
</h3>
<p className='text-gray-600 dark:text-gray-400 mt-1 text-[0.875rem] leading-[1.5rem]'>
{description}
</p>
</div>
</>
)
const className =
'dark:bg-white/2.5 bg-gray-50 hover:shadow-gray-900/5 group relative flex rounded-2xl transition-shadow hover:shadow-md dark:hover:shadow-black/5'
if (href) {
return (
<Link
href={href}
key={name}
onMouseMove={onMouseMove}
className={className}
>
{content}
</Link>
)
} else {
return (
<div key={name} onMouseMove={onMouseMove} className={className}>
{content}
</div>
)
}
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function FeatureIcon({ icon: Icon }: { icon: Feature['icon'] }) {
return (
<div className='dark:bg-white/7.5 bg-gray-900/5 ring-gray-900/25 group-hover:ring-gray-900/25 dark:group-hover:bg-sky-300/10 dark:group-hover:ring-sky-400 flex size-7 items-center justify-center rounded-full ring-1 backdrop-blur-[2px] transition duration-300 group-hover:bg-white/50 dark:ring-white/15'>
<Icon className='fill-gray-700/10 stroke-gray-700 group-hover:stroke-gray-900 dark:stroke-gray-400 dark:group-hover:stroke-sky-400 dark:group-hover:fill-sky-300/10 size-5 transition-colors duration-300 dark:fill-white/10' />
</div>
)
}
function FeaturePattern({
mouseX,
mouseY,
...gridProps
}: Feature['pattern'] & {
mouseX: MotionValue<number>
mouseY: MotionValue<number>
}) {
const maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)`
const style = { maskImage, WebkitMaskImage: maskImage }
return (
<div className='pointer-events-none'>
<div className='absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50'>
<GridPattern
width={72}
height={56}
x='50%'
className='dark:fill-white/1 dark:stroke-white/2.5 absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5'
{...gridProps}
/>
</div>
<motion.div
className='from-sky-100 to-sky-300 dark:from-sky-500 dark:to-sky-300 absolute inset-0 rounded-2xl bg-gradient-to-r opacity-0 transition duration-300 group-hover:opacity-50 dark:group-hover:opacity-15'
style={style}
/>
<motion.div
className='absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100'
style={style}
>
<GridPattern
width={72}
height={56}
x='50%'
className='dark:fill-white/2.5 absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 dark:stroke-white/10'
{...gridProps}
/>
</motion.div>
</div>
)
}
export function Features() {
return (
<div className='xl:max-w-none'>
<div className='not-prose grid grid-cols-1 gap-8 sm:grid-cols-2 xl:grid-cols-3'>
{FEATURES.map((feature) => (
<Feature key={feature.name} {...feature} />
))}
</div>
</div>
)
}

Wyświetl plik

@ -3,7 +3,7 @@ import Link from 'next/link'
import { ActiveLink } from '@/components/active-link'
import { GitHubIcon } from '@/icons/github'
import { TwitterIcon } from '@/icons/twitter'
import { copyright, githubUrl, twitterUrl } from '@/lib/config'
import { copyright, docsUrl, githubUrl, twitterUrl } from '@/lib/config'
export function Footer() {
return (
@ -21,12 +21,24 @@ export function Footer() {
</ActiveLink>
</span>
<span>
<ActiveLink href='/marketplace' className='link'>
MCP Marketplace
</ActiveLink>
</span>
<span>
<ActiveLink href='/mcp-authors' className='link'>
For MCP Authors
</ActiveLink>
</span>
<span>
<ActiveLink href={docsUrl} className='link'>
Docs
</ActiveLink>
</span>
<span>
<ActiveLink href='/about' className='link'>
About

Wyświetl plik

@ -0,0 +1,64 @@
import { type SVGProps, useId } from 'react'
export type GridPattern = Omit<
SVGProps<SVGSVGElement>,
'width' | 'height' | 'x' | 'y'
> & {
width: number
height: number
x: string | number
y: string | number
squares: Array<[x: number, y: number]>
}
export function GridPattern({
width,
height,
x,
y,
squares,
...props
}: GridPattern) {
const patternId = useId()
return (
<svg aria-hidden='true' {...props}>
<defs>
<pattern
id={patternId}
width={width}
height={height}
patternUnits='userSpaceOnUse'
x={x}
y={y}
>
<path d={`M.5 ${height}V.5H${width}`} fill='none' />
</pattern>
</defs>
<rect
width='100%'
height='100%'
strokeWidth={0}
fill={`url(#${patternId})`}
/>
{squares && (
<svg x={x} y={y} className='overflow-visible'>
<title>square</title>
{squares.map(([x, y]) => (
<rect
strokeWidth='0'
key={`${x}-${y}`}
width={width + 1}
height={height + 1}
x={x * width}
y={y * height}
/>
))}
</svg>
)}
</svg>
)
}

Wyświetl plik

@ -19,7 +19,7 @@ export function Header() {
<div className='flex justify-end items-center h-full gap-4'>
<ActiveLink href='/marketplace' className='link'>
Marketplace
MCP Marketplace
</ActiveLink>
<ActiveLink href={docsUrl} className='link'>

Wyświetl plik

@ -19,7 +19,7 @@ export function SupplySideCTA({
const ctx = useAgentic()
return (
<div className='flex justify-center items-center gap-8'>
<div className='flex justify-center items-center gap-12'>
<HeroButton asChild className=''>
<Link
href={