Merge pull request #165 from bensenescu/codex/update-ai-tab
Update the AI tab for MCP setup and usage guidance
This commit is contained in:
commit
36b60f240f
34
src/client/features/ai-mcp/AgentIcons.tsx
Normal file
34
src/client/features/ai-mcp/AgentIcons.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
export function ClaudeIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 257"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#D97757"
|
||||
d="m50.228 170.321 50.357-28.257.843-2.463-.843-1.361h-2.462l-8.426-.518-28.775-.778-24.952-1.037-24.175-1.296-6.092-1.297L0 125.796l.583-3.759 5.12-3.434 7.324.648 16.202 1.101 24.304 1.685 17.629 1.037 26.118 2.722h4.148l.583-1.685-1.426-1.037-1.101-1.037-25.147-17.045-27.22-18.017-14.258-10.37-7.713-5.25-3.888-4.925-1.685-10.758 7-7.713 9.397.649 2.398.648 9.527 7.323 20.35 15.75L94.817 91.9l3.889 3.24 1.555-1.102.195-.777-1.75-2.917-14.453-26.118-15.425-26.572-6.87-11.018-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0 82.05 1.426l4.472 3.888 6.61 15.101 10.694 23.786 16.591 32.34 4.861 9.592 2.592 8.879.973 2.722h1.685v-1.556l1.36-18.211 2.528-22.36 2.463-28.776.843-8.1 4.018-9.722 7.971-5.25 6.222 2.981 5.12 7.324-.713 4.73-3.046 19.768-5.962 30.98-3.889 20.739h2.268l2.593-2.593 10.499-13.934 17.628-22.036 7.778-8.749 9.073-9.657 5.833-4.601h11.018l8.1 12.055-3.628 12.443-11.342 14.388-9.398 12.184-13.48 18.147-8.426 14.518.778 1.166 2.01-.194 30.46-6.481 16.462-2.982 19.637-3.37 8.88 4.148.971 4.213-3.5 8.62-20.998 5.184-24.628 4.926-36.682 8.685-.454.324.519.648 16.526 1.555 7.065.389h17.304l32.21 2.398 8.426 5.574 5.055 6.805-.843 5.184-12.962 6.611-17.498-4.148-40.83-9.721-14-3.5h-1.944v1.167l11.666 11.406 21.387 19.314 26.767 24.887 1.36 6.157-3.434 4.86-3.63-.518-23.526-17.693-9.073-7.972-20.545-17.304h-1.36v1.814l4.73 6.935 25.017 37.59 1.296 11.536-1.814 3.76-6.481 2.268-7.13-1.297-14.647-20.544-15.1-23.138-12.185-20.739-1.49.843-7.194 77.448-3.37 3.953-7.778 2.981-6.48-4.925-3.436-7.972 3.435-15.749 4.148-20.544 3.37-16.333 3.046-20.285 1.815-6.74-.13-.454-1.49.194-15.295 20.999-23.267 31.433-18.406 19.702-4.407 1.75-7.648-3.954.713-7.064 4.277-6.286 25.47-32.405 15.36-20.092 9.917-11.6-.065-1.686h-.583L44.07 198.125l-12.055 1.555-5.185-4.86.648-7.972 2.463-2.593 20.35-13.999-.064.065Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CodexIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
89
src/client/features/ai-mcp/AvailableTools.tsx
Normal file
89
src/client/features/ai-mcp/AvailableTools.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
type McpTool = {
|
||||
name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type ToolCategory = {
|
||||
label: string;
|
||||
tools: McpTool[];
|
||||
};
|
||||
|
||||
const toolCategories: ToolCategory[] = [
|
||||
{
|
||||
label: "Keywords",
|
||||
tools: [
|
||||
{
|
||||
name: "research_keywords",
|
||||
title: "Research keywords",
|
||||
description: "Get keyword ideas with volume, difficulty, and CPC.",
|
||||
},
|
||||
{
|
||||
name: "get_serp_results",
|
||||
title: "Get SERP results",
|
||||
description: "See live Google results for a keyword.",
|
||||
},
|
||||
{
|
||||
name: "get_rank_tracker",
|
||||
title: "Get rank tracking positions",
|
||||
description: "Read tracked keyword positions.",
|
||||
},
|
||||
{
|
||||
name: "list_saved_keywords",
|
||||
title: "Get saved keywords",
|
||||
description: "Pull your saved keyword lists.",
|
||||
},
|
||||
{
|
||||
name: "save_keywords",
|
||||
title: "Save keywords",
|
||||
description: "Save keywords back to OpenSEO.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Domain",
|
||||
tools: [
|
||||
{
|
||||
name: "get_domain_overview",
|
||||
title: "Get domain overview",
|
||||
description: "Summarize a domain's organic footprint.",
|
||||
},
|
||||
{
|
||||
name: "get_domain_keyword_suggestions",
|
||||
title: "Get domain keywords",
|
||||
description: "Find keywords a domain already ranks for.",
|
||||
},
|
||||
{
|
||||
name: "get_backlinks_overview",
|
||||
title: "Get backlinks overview",
|
||||
description: "Check backlink and referring-domain stats.",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function AvailableTools() {
|
||||
return (
|
||||
<div className="grid gap-x-8 gap-y-8 md:grid-cols-2">
|
||||
{toolCategories.map((cat) => (
|
||||
<div key={cat.label}>
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-base-content/50">
|
||||
{cat.label}
|
||||
</h3>
|
||||
<ul className="mt-3 space-y-3">
|
||||
{cat.tools.map((tool) => (
|
||||
<li key={tool.name} className="flex flex-col gap-0.5">
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
{tool.title}
|
||||
</span>
|
||||
<p className="text-xs text-base-content/60 leading-relaxed">
|
||||
{tool.description}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -60,14 +60,15 @@ const projectNavItems = [
|
||||
icon: MessageSquare,
|
||||
matchSegment: "/prompt-explorer",
|
||||
},
|
||||
{
|
||||
to: "/p/$projectId/ai" as const,
|
||||
label: "AI & Agents",
|
||||
icon: Bot,
|
||||
matchSegment: "/ai",
|
||||
},
|
||||
] as const;
|
||||
|
||||
const aiNavItem = linkOptions({
|
||||
to: "/ai" as const,
|
||||
label: "AI & MCP",
|
||||
icon: Bot,
|
||||
matchSegment: "/ai",
|
||||
});
|
||||
|
||||
function getProjectNavItems(projectId: string) {
|
||||
return linkOptions(
|
||||
projectNavItems.map((item) => ({
|
||||
@ -114,7 +115,7 @@ export function getProjectNavGroups(projectId: string) {
|
||||
},
|
||||
{
|
||||
type: "standalone" as const,
|
||||
item: bySegment("/ai"),
|
||||
item: aiNavItem,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import { Route as AuthSignInRouteImport } from './routes/_auth.sign-in'
|
||||
import { Route as AppSupportRouteImport } from './routes/_app/support'
|
||||
import { Route as AppSettingsRouteImport } from './routes/_app/settings'
|
||||
import { Route as AppBillingRouteImport } from './routes/_app/billing'
|
||||
import { Route as AppAiRouteImport } from './routes/_app/ai'
|
||||
import { Route as DotwellKnownOpenidConfigurationRouteImport } from './routes/[.]well-known/openid-configuration'
|
||||
import { Route as DotwellKnownOauthAuthorizationServerRouteImport } from './routes/[.]well-known/oauth-authorization-server'
|
||||
import { Route as ApiAutumnSplatRouteImport } from './routes/api/autumn/$'
|
||||
@ -40,7 +41,6 @@ import { Route as ProjectPProjectIdDomainRouteImport } from './routes/_project/p
|
||||
import { Route as ProjectPProjectIdBrandLookupRouteImport } from './routes/_project/p/$projectId/brand-lookup'
|
||||
import { Route as ProjectPProjectIdBacklinksRouteImport } from './routes/_project/p/$projectId/backlinks'
|
||||
import { Route as ProjectPProjectIdAuditRouteImport } from './routes/_project/p/$projectId/audit'
|
||||
import { Route as ProjectPProjectIdAiRouteImport } from './routes/_project/p/$projectId/ai'
|
||||
import { Route as DotwellKnownOauthAuthorizationServerApiAuthRouteImport } from './routes/[.]well-known/oauth-authorization-server/api/auth'
|
||||
import { Route as ProjectPProjectIdRankTrackingIndexRouteImport } from './routes/_project/p/$projectId/rank-tracking/index'
|
||||
import { Route as ProjectPProjectIdAuditIndexRouteImport } from './routes/_project/p/$projectId/audit/index'
|
||||
@ -119,6 +119,11 @@ const AppBillingRoute = AppBillingRouteImport.update({
|
||||
path: '/billing',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
const AppAiRoute = AppAiRouteImport.update({
|
||||
id: '/ai',
|
||||
path: '/ai',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
const DotwellKnownOpenidConfigurationRoute =
|
||||
DotwellKnownOpenidConfigurationRouteImport.update({
|
||||
id: '/.well-known/openid-configuration',
|
||||
@ -207,11 +212,6 @@ const ProjectPProjectIdAuditRoute = ProjectPProjectIdAuditRouteImport.update({
|
||||
path: '/audit',
|
||||
getParentRoute: () => ProjectPProjectIdRouteRoute,
|
||||
} as any)
|
||||
const ProjectPProjectIdAiRoute = ProjectPProjectIdAiRouteImport.update({
|
||||
id: '/ai',
|
||||
path: '/ai',
|
||||
getParentRoute: () => ProjectPProjectIdRouteRoute,
|
||||
} as any)
|
||||
const DotwellKnownOauthAuthorizationServerApiAuthRoute =
|
||||
DotwellKnownOauthAuthorizationServerApiAuthRouteImport.update({
|
||||
id: '/api/auth',
|
||||
@ -250,6 +250,7 @@ export interface FileRoutesByFullPath {
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
|
||||
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
|
||||
'/ai': typeof AppAiRoute
|
||||
'/billing': typeof AppBillingRoute
|
||||
'/settings': typeof AppSettingsRoute
|
||||
'/support': typeof AppSupportRoute
|
||||
@ -263,7 +264,6 @@ export interface FileRoutesByFullPath {
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/api/autumn/$': typeof ApiAutumnSplatRoute
|
||||
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
|
||||
'/p/$projectId/ai': typeof ProjectPProjectIdAiRoute
|
||||
'/p/$projectId/audit': typeof ProjectPProjectIdAuditRouteWithChildren
|
||||
'/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute
|
||||
'/p/$projectId/brand-lookup': typeof ProjectPProjectIdBrandLookupRoute
|
||||
@ -285,6 +285,7 @@ export interface FileRoutesByTo {
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
|
||||
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
|
||||
'/ai': typeof AppAiRoute
|
||||
'/billing': typeof AppBillingRoute
|
||||
'/settings': typeof AppSettingsRoute
|
||||
'/support': typeof AppSupportRoute
|
||||
@ -297,7 +298,6 @@ export interface FileRoutesByTo {
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/api/autumn/$': typeof ApiAutumnSplatRoute
|
||||
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
|
||||
'/p/$projectId/ai': typeof ProjectPProjectIdAiRoute
|
||||
'/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute
|
||||
'/p/$projectId/brand-lookup': typeof ProjectPProjectIdBrandLookupRoute
|
||||
'/p/$projectId/domain': typeof ProjectPProjectIdDomainRoute
|
||||
@ -321,6 +321,7 @@ export interface FileRoutesById {
|
||||
'/verify-email': typeof VerifyEmailRoute
|
||||
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
|
||||
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
|
||||
'/_app/ai': typeof AppAiRoute
|
||||
'/_app/billing': typeof AppBillingRoute
|
||||
'/_app/settings': typeof AppSettingsRoute
|
||||
'/_app/support': typeof AppSupportRoute
|
||||
@ -335,7 +336,6 @@ export interface FileRoutesById {
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/api/autumn/$': typeof ApiAutumnSplatRoute
|
||||
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
|
||||
'/_project/p/$projectId/ai': typeof ProjectPProjectIdAiRoute
|
||||
'/_project/p/$projectId/audit': typeof ProjectPProjectIdAuditRouteWithChildren
|
||||
'/_project/p/$projectId/backlinks': typeof ProjectPProjectIdBacklinksRoute
|
||||
'/_project/p/$projectId/brand-lookup': typeof ProjectPProjectIdBrandLookupRoute
|
||||
@ -359,6 +359,7 @@ export interface FileRouteTypes {
|
||||
| '/verify-email'
|
||||
| '/.well-known/oauth-authorization-server'
|
||||
| '/.well-known/openid-configuration'
|
||||
| '/ai'
|
||||
| '/billing'
|
||||
| '/settings'
|
||||
| '/support'
|
||||
@ -372,7 +373,6 @@ export interface FileRouteTypes {
|
||||
| '/api/auth/$'
|
||||
| '/api/autumn/$'
|
||||
| '/.well-known/oauth-authorization-server/api/auth'
|
||||
| '/p/$projectId/ai'
|
||||
| '/p/$projectId/audit'
|
||||
| '/p/$projectId/backlinks'
|
||||
| '/p/$projectId/brand-lookup'
|
||||
@ -394,6 +394,7 @@ export interface FileRouteTypes {
|
||||
| '/verify-email'
|
||||
| '/.well-known/oauth-authorization-server'
|
||||
| '/.well-known/openid-configuration'
|
||||
| '/ai'
|
||||
| '/billing'
|
||||
| '/settings'
|
||||
| '/support'
|
||||
@ -406,7 +407,6 @@ export interface FileRouteTypes {
|
||||
| '/api/auth/$'
|
||||
| '/api/autumn/$'
|
||||
| '/.well-known/oauth-authorization-server/api/auth'
|
||||
| '/p/$projectId/ai'
|
||||
| '/p/$projectId/backlinks'
|
||||
| '/p/$projectId/brand-lookup'
|
||||
| '/p/$projectId/domain'
|
||||
@ -429,6 +429,7 @@ export interface FileRouteTypes {
|
||||
| '/verify-email'
|
||||
| '/.well-known/oauth-authorization-server'
|
||||
| '/.well-known/openid-configuration'
|
||||
| '/_app/ai'
|
||||
| '/_app/billing'
|
||||
| '/_app/settings'
|
||||
| '/_app/support'
|
||||
@ -443,7 +444,6 @@ export interface FileRouteTypes {
|
||||
| '/api/auth/$'
|
||||
| '/api/autumn/$'
|
||||
| '/.well-known/oauth-authorization-server/api/auth'
|
||||
| '/_project/p/$projectId/ai'
|
||||
| '/_project/p/$projectId/audit'
|
||||
| '/_project/p/$projectId/backlinks'
|
||||
| '/_project/p/$projectId/brand-lookup'
|
||||
@ -581,6 +581,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppBillingRouteImport
|
||||
parentRoute: typeof AppRouteRoute
|
||||
}
|
||||
'/_app/ai': {
|
||||
id: '/_app/ai'
|
||||
path: '/ai'
|
||||
fullPath: '/ai'
|
||||
preLoaderRoute: typeof AppAiRouteImport
|
||||
parentRoute: typeof AppRouteRoute
|
||||
}
|
||||
'/.well-known/openid-configuration': {
|
||||
id: '/.well-known/openid-configuration'
|
||||
path: '/.well-known/openid-configuration'
|
||||
@ -693,13 +700,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ProjectPProjectIdAuditRouteImport
|
||||
parentRoute: typeof ProjectPProjectIdRouteRoute
|
||||
}
|
||||
'/_project/p/$projectId/ai': {
|
||||
id: '/_project/p/$projectId/ai'
|
||||
path: '/ai'
|
||||
fullPath: '/p/$projectId/ai'
|
||||
preLoaderRoute: typeof ProjectPProjectIdAiRouteImport
|
||||
parentRoute: typeof ProjectPProjectIdRouteRoute
|
||||
}
|
||||
'/.well-known/oauth-authorization-server/api/auth': {
|
||||
id: '/.well-known/oauth-authorization-server/api/auth'
|
||||
path: '/api/auth'
|
||||
@ -739,6 +739,7 @@ declare module '@tanstack/react-router' {
|
||||
}
|
||||
|
||||
interface AppRouteRouteChildren {
|
||||
AppAiRoute: typeof AppAiRoute
|
||||
AppBillingRoute: typeof AppBillingRoute
|
||||
AppSettingsRoute: typeof AppSettingsRoute
|
||||
AppSupportRoute: typeof AppSupportRoute
|
||||
@ -747,6 +748,7 @@ interface AppRouteRouteChildren {
|
||||
}
|
||||
|
||||
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||
AppAiRoute: AppAiRoute,
|
||||
AppBillingRoute: AppBillingRoute,
|
||||
AppSettingsRoute: AppSettingsRoute,
|
||||
AppSupportRoute: AppSupportRoute,
|
||||
@ -794,7 +796,6 @@ const ProjectPProjectIdRankTrackingRouteWithChildren =
|
||||
)
|
||||
|
||||
interface ProjectPProjectIdRouteRouteChildren {
|
||||
ProjectPProjectIdAiRoute: typeof ProjectPProjectIdAiRoute
|
||||
ProjectPProjectIdAuditRoute: typeof ProjectPProjectIdAuditRouteWithChildren
|
||||
ProjectPProjectIdBacklinksRoute: typeof ProjectPProjectIdBacklinksRoute
|
||||
ProjectPProjectIdBrandLookupRoute: typeof ProjectPProjectIdBrandLookupRoute
|
||||
@ -808,7 +809,6 @@ interface ProjectPProjectIdRouteRouteChildren {
|
||||
|
||||
const ProjectPProjectIdRouteRouteChildren: ProjectPProjectIdRouteRouteChildren =
|
||||
{
|
||||
ProjectPProjectIdAiRoute: ProjectPProjectIdAiRoute,
|
||||
ProjectPProjectIdAuditRoute: ProjectPProjectIdAuditRouteWithChildren,
|
||||
ProjectPProjectIdBacklinksRoute: ProjectPProjectIdBacklinksRoute,
|
||||
ProjectPProjectIdBrandLookupRoute: ProjectPProjectIdBrandLookupRoute,
|
||||
|
||||
352
src/routes/_app/ai.tsx
Normal file
352
src/routes/_app/ai.tsx
Normal file
@ -0,0 +1,352 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { ArrowUpRight, Check, ChevronDown, Copy } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ClaudeIcon, CodexIcon } from "@/client/features/ai-mcp/AgentIcons";
|
||||
import { AvailableTools } from "@/client/features/ai-mcp/AvailableTools";
|
||||
|
||||
const DISCORD_URL = "https://discord.gg/c9uGs3cFXr";
|
||||
const SUPPORT_EMAIL = "ben@openseo.so";
|
||||
const SAM_GITHUB_URL = "https://github.com/every-app/sam";
|
||||
|
||||
export const Route = createFileRoute("/_app/ai")({
|
||||
component: AiPage,
|
||||
});
|
||||
|
||||
function AiPage() {
|
||||
const mcpUrl =
|
||||
typeof window === "undefined"
|
||||
? "https://app.openseo.so/mcp"
|
||||
: `${window.location.origin}/mcp`;
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-auto bg-base-100 px-4 py-12 md:px-6 md:py-16 pb-24 md:pb-12">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<h1 className="text-2xl font-semibold">AI & MCP</h1>
|
||||
<p className="mt-2 text-sm text-base-content/70 leading-relaxed">
|
||||
Connect your AI agent to OpenSEO. Run keyword research, SERP analysis,
|
||||
and domain lookups from your editor or chat.
|
||||
</p>
|
||||
|
||||
<section className="mt-8">
|
||||
<div className="rounded-lg border border-base-300 bg-base-200 px-4 py-3.5">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-base-content/50">
|
||||
MCP server URL
|
||||
</p>
|
||||
<CopyButton
|
||||
value={mcpUrl}
|
||||
successMessage="MCP URL copied"
|
||||
label="Copy"
|
||||
/>
|
||||
</div>
|
||||
<code className="mt-2 block break-all font-mono text-sm text-base-content">
|
||||
{mcpUrl}
|
||||
</code>
|
||||
</div>
|
||||
<p className="mt-2.5 text-xs text-base-content/55 leading-relaxed">
|
||||
Paste this into any MCP client. Sign in with OpenSEO when prompted.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-10">
|
||||
<h2 className="text-base font-semibold">Setup guides</h2>
|
||||
<p className="mt-1.5 text-sm text-base-content/70">
|
||||
Pick your agent.
|
||||
</p>
|
||||
<div className="mt-4 divide-y divide-base-300 overflow-hidden rounded-lg border border-base-300 bg-base-200">
|
||||
<Collapsible
|
||||
id="claude-code"
|
||||
title="Claude Code"
|
||||
subtitle="Add with the CLI"
|
||||
icon={<ClaudeIcon className="size-5" />}
|
||||
>
|
||||
<p className="text-sm text-base-content/70">
|
||||
Run this in your terminal:
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`claude mcp add --transport http --scope user openseo ${mcpUrl}`}
|
||||
/>
|
||||
<p className="text-sm text-base-content/70">
|
||||
Approve the login when prompted.
|
||||
</p>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible
|
||||
id="claude-desktop"
|
||||
title="Claude Desktop"
|
||||
subtitle="Add a custom connector"
|
||||
icon={<ClaudeIcon className="size-5" />}
|
||||
>
|
||||
<ol className="ml-5 list-decimal space-y-1.5 text-sm text-base-content/70 leading-relaxed">
|
||||
<li>
|
||||
Open <span className="text-base-content">Settings</span> →{" "}
|
||||
<span className="text-base-content">Connectors</span>.
|
||||
</li>
|
||||
<li>
|
||||
Click{" "}
|
||||
<span className="font-medium text-base-content">
|
||||
Add custom connector
|
||||
</span>
|
||||
.
|
||||
</li>
|
||||
<li>Paste the MCP URL above and click Add.</li>
|
||||
<li>Approve the OpenSEO login when prompted.</li>
|
||||
</ol>
|
||||
<p className="text-xs text-base-content/55 leading-relaxed">
|
||||
Requires a Claude Pro, Max, Team, or Enterprise plan.
|
||||
</p>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible
|
||||
id="codex"
|
||||
title="Codex"
|
||||
subtitle="Add with the CLI"
|
||||
icon={<CodexIcon className="size-5" />}
|
||||
>
|
||||
<p className="text-sm text-base-content/70">
|
||||
Run this in your terminal:
|
||||
</p>
|
||||
<CodeBlock code={`codex mcp add openseo --url ${mcpUrl}`} />
|
||||
<p className="text-sm text-base-content/70">
|
||||
Approve the login when prompted.
|
||||
</p>
|
||||
</Collapsible>
|
||||
|
||||
<Collapsible
|
||||
id="codex-desktop"
|
||||
title="Codex Desktop"
|
||||
subtitle="Settings → Integrations & MCP"
|
||||
icon={<CodexIcon className="size-5" />}
|
||||
>
|
||||
<ol className="ml-5 list-decimal space-y-1.5 text-sm text-base-content/70 leading-relaxed">
|
||||
<li>
|
||||
Open{" "}
|
||||
<span className="text-base-content">
|
||||
Settings → Integrations & MCP
|
||||
</span>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Click{" "}
|
||||
<span className="font-medium text-base-content">
|
||||
Add your own
|
||||
</span>
|
||||
.
|
||||
</li>
|
||||
<li>Paste the MCP URL above.</li>
|
||||
<li>Approve the OpenSEO login when prompted.</li>
|
||||
</ol>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-base font-semibold">Available tools</h2>
|
||||
<div className="mt-5">
|
||||
<AvailableTools />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-base font-semibold">Sam: AI SEO teammate</h2>
|
||||
<p className="mt-1.5 text-sm text-base-content/70 leading-relaxed">
|
||||
Sam is an experimental content workflow for Claude Code and other
|
||||
coding agents. It combines keyword research, source discovery,
|
||||
drafting, and QA.
|
||||
</p>
|
||||
<a
|
||||
href={SAM_GITHUB_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-3 inline-flex items-center gap-1.5 text-sm font-medium text-base-content transition-colors hover:text-base-content/60"
|
||||
>
|
||||
View Sam on GitHub
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-base font-semibold">Roadmap</h2>
|
||||
<ul className="mt-4 space-y-3">
|
||||
{[
|
||||
{
|
||||
title: "In-app SEO Research Agent",
|
||||
description:
|
||||
"Ask questions and run research without leaving OpenSEO",
|
||||
},
|
||||
{
|
||||
title: "Content Assistant",
|
||||
description:
|
||||
"Generate drafts using saved keywords and business context",
|
||||
},
|
||||
].map((item) => (
|
||||
<li key={item.title} className="flex gap-2.5 text-sm">
|
||||
<span className="mt-[2px] shrink-0 text-base-content/40">
|
||||
—
|
||||
</span>
|
||||
<span className="text-base-content/70">
|
||||
<span className="font-medium text-base-content">
|
||||
{item.title}
|
||||
</span>
|
||||
<br />
|
||||
{item.description}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<p className="mt-12 text-xs text-base-content/55 leading-relaxed">
|
||||
Have feedback? Reach out on{" "}
|
||||
<a
|
||||
className="link link-primary"
|
||||
href={DISCORD_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Discord
|
||||
</a>{" "}
|
||||
or email{" "}
|
||||
<a className="link link-primary" href={`mailto:${SUPPORT_EMAIL}`}>
|
||||
{SUPPORT_EMAIL}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Collapsible({
|
||||
id,
|
||||
title,
|
||||
subtitle,
|
||||
icon,
|
||||
children,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const contentId = `collapsible-${id}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
aria-expanded={open}
|
||||
aria-controls={contentId}
|
||||
className="flex w-full items-center justify-between gap-3 px-4 py-3.5 text-left transition-colors hover:bg-base-300/50"
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
{icon ? (
|
||||
<span className="flex size-5 shrink-0 items-center justify-center text-base-content">
|
||||
{icon}
|
||||
</span>
|
||||
) : null}
|
||||
<div className="flex min-w-0 flex-col gap-0.5">
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
{title}
|
||||
</span>
|
||||
{subtitle ? (
|
||||
<span className="text-xs text-base-content/55">{subtitle}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`size-4 shrink-0 text-base-content/50 transition-transform ${
|
||||
open ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
{open ? (
|
||||
<div id={contentId} className="space-y-3 px-4 pb-4 pt-1">
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlock({ code }: { code: string }) {
|
||||
return (
|
||||
<div className="flex items-stretch overflow-hidden rounded-md border border-base-300 bg-base-100">
|
||||
<pre className="min-w-0 flex-1 overflow-x-auto p-3 text-xs leading-relaxed text-base-content">
|
||||
<code className="font-mono">{code}</code>
|
||||
</pre>
|
||||
<div className="flex shrink-0 items-start border-l border-base-300 p-1.5">
|
||||
<CopyButton
|
||||
value={code}
|
||||
successMessage="Copied to clipboard"
|
||||
iconOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyButton({
|
||||
value,
|
||||
successMessage,
|
||||
label,
|
||||
iconOnly = false,
|
||||
}: {
|
||||
value: string;
|
||||
successMessage: string;
|
||||
label?: string;
|
||||
iconOnly?: boolean;
|
||||
}) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
||||
toast.error("Clipboard not available");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
toast.success(successMessage);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
toast.error("Could not copy to clipboard");
|
||||
}
|
||||
};
|
||||
|
||||
if (iconOnly) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy"
|
||||
className="flex size-7 items-center justify-center rounded-md text-base-content/60 transition-colors hover:bg-base-200 hover:text-base-content"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="size-3.5 text-success" />
|
||||
) : (
|
||||
<Copy className="size-3.5" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="inline-flex items-center gap-1.5 rounded-md border border-base-300 bg-base-100 px-2 py-1 text-xs font-medium text-base-content/70 transition-colors hover:bg-base-300/50 hover:text-base-content"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="size-3 text-success" />
|
||||
) : (
|
||||
<Copy className="size-3" />
|
||||
)}
|
||||
{label ?? "Copy"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
|
||||
const DISCORD_URL = "https://discord.gg/c9uGs3cFXr";
|
||||
const SUPPORT_EMAIL = "ben@openseo.so";
|
||||
const SAM_GITHUB_URL = "https://github.com/every-app/sam";
|
||||
const DATAFORSEO_MCP_DOCS_URL =
|
||||
"https://dataforseo.com/help-center/setting-up-the-official-dataforseo-mcp-server-simple-guide";
|
||||
|
||||
export const Route = createFileRoute("/_project/p/$projectId/ai")({
|
||||
component: AiPage,
|
||||
});
|
||||
|
||||
function AiPage() {
|
||||
return (
|
||||
<div className="px-4 py-12 md:px-6 md:py-20 pb-24 md:pb-8 overflow-auto">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<h1 className="text-2xl font-semibold">AI & Agents</h1>
|
||||
<p className="mt-2 text-sm text-base-content/70 leading-relaxed">
|
||||
OpenSEO is brand new. Our goal is to build the most powerful and
|
||||
intuitive tool to help your websites rank better. AI will play a large
|
||||
role in that.
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-base-content/70 leading-relaxed">
|
||||
Lots of other SEO tools have an overwhelming number of
|
||||
“features”. We think many of these can be hidden by
|
||||
default, only shown to you when you want them.
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-base-content/70 leading-relaxed">
|
||||
This is community-driven — we want to hear what matters to you. Reach
|
||||
out on{" "}
|
||||
<a
|
||||
className="link link-primary"
|
||||
href={DISCORD_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Discord
|
||||
</a>{" "}
|
||||
or email{" "}
|
||||
<a className="link link-primary" href={`mailto:${SUPPORT_EMAIL}`}>
|
||||
{SUPPORT_EMAIL}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
{/* Sam */}
|
||||
<section className="mt-12">
|
||||
<h2 className="text-xl font-semibold">Sam: AI SEO Teammate</h2>
|
||||
<p className="mt-2 text-sm text-base-content/70 leading-relaxed">
|
||||
Sam turns your Claude Code or favorite coding agent into a powerful
|
||||
SEO teammate. Sam has a specialized workflow for writing great
|
||||
content, but you can teach Sam about your SEO workflow and needs and
|
||||
it will customize itself for you.
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-base-content/70 leading-relaxed">
|
||||
Sam handles keyword research, source discovery, and drafting — with
|
||||
a built-in QA loop that checks quality and accuracy before you
|
||||
publish.
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-base-content/70 leading-relaxed">
|
||||
Works with DataForSEO MCP for real SEO data. Model-agnostic — use it
|
||||
with Claude Code, GPT, or others.
|
||||
</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{[
|
||||
"Keyword research & topic analysis",
|
||||
"Source discovery & verification",
|
||||
"Content drafting with QA",
|
||||
].map((item) => (
|
||||
<li
|
||||
key={item}
|
||||
className="flex gap-2.5 text-sm text-base-content/70"
|
||||
>
|
||||
<span className="mt-[2px] shrink-0 text-base-content/40">
|
||||
—
|
||||
</span>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a
|
||||
href={SAM_GITHUB_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-4 inline-flex items-center gap-1.5 text-sm font-medium text-base-content hover:text-base-content/60 transition-colors"
|
||||
>
|
||||
View on GitHub
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</a>
|
||||
</section>
|
||||
|
||||
{/* Use any coding agent */}
|
||||
<section className="mt-12">
|
||||
<h2 className="text-xl font-semibold">Use any coding agent</h2>
|
||||
<p className="mt-2 text-sm text-base-content/70 leading-relaxed">
|
||||
Pair your preferred agent with the DataForSEO MCP server to access
|
||||
keyword volumes, SERP results, backlink profiles, and more.
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-base-content/60">
|
||||
Claude Code · Cursor · Windsurf · Codex
|
||||
· any MCP-compatible agent
|
||||
</p>
|
||||
<a
|
||||
href={DATAFORSEO_MCP_DOCS_URL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-4 inline-flex items-center gap-1.5 text-sm font-medium text-base-content hover:text-base-content/60 transition-colors"
|
||||
>
|
||||
DataForSEO MCP setup guide
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</a>
|
||||
</section>
|
||||
|
||||
{/* Roadmap */}
|
||||
<section className="mt-12">
|
||||
<h2 className="text-xl font-semibold">Roadmap</h2>
|
||||
<ul className="mt-4 space-y-4">
|
||||
{[
|
||||
{
|
||||
title: "In-app SEO Research Agent",
|
||||
description:
|
||||
"Ask questions and run research without leaving OpenSEO",
|
||||
},
|
||||
{
|
||||
title: "Content Assistant",
|
||||
description:
|
||||
"Generate drafts using saved keywords and business context",
|
||||
},
|
||||
{
|
||||
title: "More MCP integrations",
|
||||
},
|
||||
].map((item) => (
|
||||
<li key={item.title}>
|
||||
<p className="flex gap-2.5 text-sm text-base-content/70">
|
||||
<span className="mt-[2px] shrink-0 text-base-content/40">
|
||||
—
|
||||
</span>
|
||||
<span>
|
||||
<span className="font-medium text-base-content">
|
||||
{item.title}
|
||||
</span>
|
||||
{item.description ? (
|
||||
<>
|
||||
<br />
|
||||
{item.description}
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user