Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/logto-cloud/logto-mcp-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Logto MCP Server is a remote [MCP (Model Context Protocol)](https://modelcontext
Logto MCP Server is built with Logto and [MCP Auth](https://mcp-auth.dev). If you want to build your own remote MCP server, see [Build your own remote MCP server](#build-your-own-remote-mcp-server).
:::

<YouTubeVideo
id="cpjf9b6qt6U"
title="Ship user authentication with one prompt: introducing Logto MCP Server"
/>

## What you can do \{#what-you-can-do}

- **Add authentication to your project**: The AI detects your framework, creates a Logto application, and generates working integration code. Supports 38+ frameworks including React, Next.js, Vue, Nuxt, SvelteKit, Express, Go, Python, iOS, Android, Flutter, and more.
Expand Down
71 changes: 71 additions & 0 deletions src/components/YouTubeVideo/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.video {
position: relative;
width: 100%;
max-width: 560px;
aspect-ratio: 16 / 9;
margin-block: 24px;
margin-inline: auto;
border-radius: 12px;
overflow: hidden;
background: var(--logto-container-neutral-bg);

.player,
.preview {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
}

.preview {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
background: none;
cursor: pointer;

.thumbnail {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}

// Keep the play icon readable regardless of how bright the thumbnail is.
&::after {
content: '';
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 20%);
transition: background 0.2s ease-in-out;
}

&:hover::after,
&:focus-visible::after {
background: rgba(0, 0, 0, 8%);
}

.playIcon {
position: relative;
z-index: 1;
width: 64px;
height: 64px;
color: #fff;
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 50%));
transition: transform 0.2s ease-in-out;

@media (max-width: 480px) {
width: 48px;
height: 48px;
}
}

&:hover .playIcon,
&:focus-visible .playIcon {
transform: scale(1.08);
}
}
}
61 changes: 61 additions & 0 deletions src/components/YouTubeVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { translate } from '@docusaurus/Translate';
import { useState } from 'react';

import PlayIcon from '@site/src/assets/video.svg';

import styles from './index.module.scss';

type Props = {
/** The YouTube video ID, e.g. `cpjf9b6qt6U` of `https://www.youtube.com/watch?v=cpjf9b6qt6U`. */
readonly id: string;
/** The video title. It is used as the accessible name of the video. */
readonly title: string;
};

/**
* An embedded YouTube video.
*
* Only the thumbnail is rendered at first, and the YouTube player is loaded after the user clicks
* play, so pages with a video don't pay the cost of the player (and its cookies) on every visit.
*/
const YouTubeVideo = ({ id, title }: Props) => {
const [isPlaying, setIsPlaying] = useState(false);

return (
<div className={styles.video}>
{isPlaying ? (
// The YouTube player needs both scripts and its own origin to work, which sandboxing
// cannot express; the frame is cross-origin and cannot touch this page anyway.
// eslint-disable-next-line react/iframe-missing-sandbox
<iframe
allowFullScreen
className={styles.player}
src={`https://www.youtube-nocookie.com/embed/${id}?autoplay=1`}
title={title}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
/>
) : (
<button
type="button"
className={styles.preview}
aria-label={translate(
{ id: 'component.youTubeVideo.play', message: 'Play video: {title}' },
{ title }
)}
onClick={() => {
setIsPlaying(true);
}}
>
<img
alt=""
className={styles.thumbnail}
src={`https://i.ytimg.com/vi/${id}/maxresdefault.jpg`}
/>
<PlayIcon className={styles.playIcon} />
</button>
)}
</div>
);
};

export default YouTubeVideo;
2 changes: 2 additions & 0 deletions src/theme/MDXComponents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CloudLink from '@site/src/components/CloudLink';
import MainSiteUrl from '@site/src/components/MainSiteUrl';
import NavGroup from '@site/src/components/NavGroup';
import Url from '@site/src/components/Url';
import YouTubeVideo from '@site/src/components/YouTubeVideo';

import DocCardList from '../DocCardList';

Expand All @@ -16,6 +17,7 @@ const components = {
NavGroup,
Url,
MainSiteUrl,
YouTubeVideo,
table: Table,
};

Expand Down
2 changes: 1 addition & 1 deletion static/_headers
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://akasha.logto.io https://accounts.google.com; connect-src 'self' https://akasha.logto.io https://accounts.google.com https://auth.logto.dev https://*.logto.dev https://auth.logto.io https://*.logto.io https://api.inkeep.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://accounts.google.com https://fonts.googleapis.com; font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com; frame-src https://accounts.google.com; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self' https://*.logto.dev https://*.logto.io https://cloud.logto.dev https://cloud.logto.io
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://akasha.logto.io https://accounts.google.com; connect-src 'self' https://akasha.logto.io https://accounts.google.com https://auth.logto.dev https://*.logto.dev https://auth.logto.io https://*.logto.io https://api.inkeep.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://accounts.google.com https://fonts.googleapis.com; font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com; frame-src https://accounts.google.com https://www.youtube-nocookie.com https://www.youtube.com; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self' https://*.logto.dev https://*.logto.io https://cloud.logto.dev https://cloud.logto.io
Loading