readme: update roadmap + add MCP and skills (#213)

This commit is contained in:
Ben Senescu 2026-05-22 09:55:42 -04:00 committed by GitHub
parent 5cef024c68
commit 90f369ad46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 416 additions and 215 deletions

192
README.md
View File

@ -12,13 +12,15 @@ Easy to self-host and extend, but we have a managed version too:
- [Why Use This](#why-use-this)
- [Main SEO Workflows](#main-seo-workflows)
- [OpenSEO MCP](#openseo-mcp)
- [OpenSEO Skills](#openseo-skills)
- [Roadmap](#roadmap)
- [Community](#community)
- [Pricing / Costs (Free + API costs)](#pricing--costs)
- [DataForSEO API Key Setup](#dataforseo-api-key-setup)
- [Self-hosting](#self-hosting)
- [Cloudflare Self-Hosting](#cloudflare-self-hosting)
- [Docker Self Hosting](#docker-self-hosting)
- [Cloudflare Self-Hosting](#cloudflare-self-hosting)
- [Local Development](#local-development)
- [Contributing](#contributing)
- [SEO API Cost Reference](#seo-api-cost-reference)
@ -30,6 +32,7 @@ Easy to self-host and extend, but we have a managed version too:
- Pay only for what you use.
- Fork and vibe code your own features.
- Focused workflows instead of a bloated, complex SEO suite.
- Best in class MCP and AI Skills.
## Main SEO Workflows
@ -43,20 +46,122 @@ Easy to self-host and extend, but we have a managed version too:
- See who links to your site, which pages attract links, and where links are newly won or lost.
- Site Audits
- Catch technical issues early so your site is easier for search engines to crawl and rank.
- AI brand visibility
- See how your brand appears in AI answers, including competitor mentions and source coverage.
- AI search prompt explorer
- Track and explore the prompts people might use when they ask AI tools for recommendations in your market.
## OpenSEO MCP
OpenSEO exposes an MCP server so AI agents can use your SEO data directly.
Connect Codex, Claude Code, Claude Desktop, or another MCP client to:
- Run keyword research
- Inspect SERPs
- Compare domains
- Review backlinks
- Work through SEO decisions from your editor or chat
In the app, open **AI & MCP** and copy your MCP server URL. Point your agent at whichever OpenSEO instance you use.
Hosted app:
```sh
codex mcp add openseo --url https://app.openseo.so/mcp
claude mcp add --transport http --scope user openseo https://app.openseo.so/mcp
```
Cloudflare self-hosted:
```sh
codex mcp add openseo --url https://your-openseo-domain.com/mcp
claude mcp add --transport http --scope user openseo https://your-openseo-domain.com/mcp
```
Local Docker:
```sh
codex mcp add openseo --url http://localhost:3001/mcp
claude mcp add --transport http --scope user openseo http://localhost:3001/mcp
```
Approve the OpenSEO login when your agent asks.
## OpenSEO Skills
OpenSEO skills are reusable workflows for Codex and Claude Code. They guide your agent through SEO tasks and can use the OpenSEO MCP for live keyword, SERP, backlink, and domain data.
### Installation Options
Install with `skills add`:
```sh
npx skills add every-app/open-seo
```
Auto-accept each OpenSEO skill:
```sh
npx skills add every-app/open-seo --skill '*'
```
Install for Claude Code only:
```sh
npx skills add every-app/open-seo --skill '*' --agent claude-code
```
Install for OpenAI Codex only:
```sh
npx skills add every-app/open-seo --skill '*' --agent codex
```
You can also pick skills directly from the GitHub repo and copy them into your agent's skills folder:
```sh
git clone https://github.com/every-app/open-seo.git
# Codex
mkdir -p ~/.codex/skills
cp -R open-seo/.agents/skills/* ~/.codex/skills/
# Claude Code
mkdir -p ~/.claude/skills
cp -R open-seo/.agents/skills/* ~/.claude/skills/
```
Start with `/onboarding-checklist`. It will ask about your project and help configure your workspace.
### Available Skills
- `onboarding-checklist`
- `seo-coach`
- `keyword-research`
- `keyword-clustering`
- `competitive-landscape`
- `competitor-analysis`
- `link-prospecting`
## Roadmap
Top priorities:
- AI SEO, GEO, LLM Visibility
- MCP for Claude
- Making the best agentic workflows for SEO
- Google Search Console Integration + MCP
- Custom Reports for Clients
- Improved and Scheduled Site Audits
- In App AI Agent
- Support Multiple Projects
Our top priority is always refining the current product and making existing features better based on user feedback.
If something important is missing, please join the [Discord](https://discord.gg/c9uGs3cFXr) or email me at ben@openseo.so and request it.
## Community
Email me: ben@openseo.so
Join Discord to chat: [Discord](https://discord.gg/c9uGs3cFXr)
Follow along for updates:
@ -99,8 +204,8 @@ printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64
OpenSEO supports two self-hosting paths:
- Docker for your homelab or local use (Recommended).
- Cloudflare for use across multiple devices or for your team.
- Docker for personal use and testing (Recommended for local use).
- Cloudflare for internet-facing self-hosting across multiple devices or for your team.
_Docker_
@ -112,6 +217,9 @@ If you love OpenSEO and want to use it across multiple devices or with your team
## Docker Self Hosting
> [!WARNING]
> By default, the Docker version is intended for local use only. It runs in single-user mode with no authentication. For internet-facing self-hosting, use Cloudflare (free plan compatible). Or read [`docs/SELF_HOSTING_DOCKER.md`](./docs/SELF_HOSTING_DOCKER.md) before exposing to the internet.
Prerequisites:
- Install Docker: https://www.docker.com/products/docker-desktop/
@ -140,12 +248,6 @@ Or use a single command:
docker compose up -d --pull always
```
Use a pinned version tag in `.env` if preferred:
```sh
OPEN_SEO_IMAGE=ghcr.io/every-app/open-seo:v1.2.3
```
For more info, see [`docs/SELF_HOSTING_DOCKER.md`](./docs/SELF_HOSTING_DOCKER.md).
## Cloudflare Self-Hosting
@ -160,71 +262,7 @@ Reference these docs while deploying since the Cloudflare UI doesn't indicate wh
## Local Development
### Prerequisites
- Node.js 20+
- [pnpm](https://pnpm.io/)
- A DataForSEO account/API credentials
### Local Development Workflow
```sh
pnpm install
# Run once per fresh local DB
pnpm run db:migrate:local
```
Configure .env.local:
1. `cp .env.example .env.local`
2. Add `DATAFORSEO_API_KEY` as a base64-encoded `login:password` value:
`printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64`
Run Locally:
```sh
# Option 1
pnpm run dev
# Option 2 (Recommended)
# This log file makes it easier for your coding agent to debug.
mkdir .logs
touch .logs/dev-server.log
# This command uses portless, which is great for worktrees. It also pipes logs to that fixed file, which is helpful for agent debugging output.
pnpm dev:agents
```
`pnpm dev:agents` runs through [portless](https://github.com/vercel-labs/portless) at `http://open-seo.localhost:1355` by default.
When using a git worktree, [portless](https://github.com/vercel-labs/portless) prefixes the branch name, for example `http://feature-name.open-seo.localhost:1355`.
### Database Commands
Generate migration:
```sh
pnpm run db:generate
```
Migrate local DB:
```sh
pnpm run db:migrate:local
```
### Auth modes
- `AUTH_MODE=cloudflare_access` (default): validates Cloudflare Access JWTs (`cf-access-jwt-assertion`) using `TEAM_DOMAIN` + `POLICY_AUD`.
- `AUTH_MODE=local_noauth`: local trusted mode, no auth check, injects `admin@localhost`.
- `AUTH_MODE=hosted`: Better Auth-backed email/password mode. Requires Better Auth schema generation plus `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL`.
Local scripts (`pnpm dev` and `pnpm dev:agents`) set `AUTH_MODE=local_noauth` automatically.
Use `AUTH_MODE=cloudflare_access pnpm dev` when you specifically want to test Access validation locally.
For Cloudflare deployments, ensure Cloudflare Access is enabled on your Worker route/domain and provide `TEAM_DOMAIN` + `POLICY_AUD` in environment variables.
See [`docs/LOCAL_DEVELOPMENT.md`](./docs/LOCAL_DEVELOPMENT.md).
## Contributing

67
docs/LOCAL_DEVELOPMENT.md Normal file
View File

@ -0,0 +1,67 @@
# Local Development
## Prerequisites
- Node.js 20+
- [pnpm](https://pnpm.io/)
- A DataForSEO account/API credentials
## Local Development Workflow
```sh
pnpm install
# Run once per fresh local DB
pnpm run db:migrate:local
```
Configure `.env.local`:
1. `cp .env.example .env.local`
2. Add `DATAFORSEO_API_KEY` as a base64-encoded `login:password` value:
`printf '%s' 'YOUR_LOGIN:YOUR_PASSWORD' | base64`
Run locally:
```sh
# Option 1
pnpm run dev
# Option 2 (Recommended)
# This log file makes it easier for your coding agent to debug.
mkdir .logs
touch .logs/dev-server.log
# This command uses portless, which is great for worktrees. It also pipes logs to that fixed file, which is helpful for agent debugging output.
pnpm dev:agents
```
`pnpm dev:agents` runs through [portless](https://github.com/vercel-labs/portless) at `http://open-seo.localhost:1355` by default.
When using a git worktree, [portless](https://github.com/vercel-labs/portless) prefixes the branch name, for example `http://feature-name.open-seo.localhost:1355`.
## Database Commands
Generate migration:
```sh
pnpm run db:generate
```
Migrate local DB:
```sh
pnpm run db:migrate:local
```
## Auth Modes
- `AUTH_MODE=cloudflare_access` (default): validates Cloudflare Access JWTs (`cf-access-jwt-assertion`) using `TEAM_DOMAIN` + `POLICY_AUD`.
- `AUTH_MODE=local_noauth`: local trusted mode, no auth check, injects `admin@localhost`.
- `AUTH_MODE=hosted`: Better Auth-backed email/password mode. Requires Better Auth schema generation plus `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL`.
Local scripts (`pnpm dev` and `pnpm dev:agents`) set `AUTH_MODE=local_noauth` automatically.
Use `AUTH_MODE=cloudflare_access pnpm dev` when you specifically want to test Access validation locally.
For Cloudflare deployments, ensure Cloudflare Access is enabled on your Worker route/domain and provide `TEAM_DOMAIN` + `POLICY_AUD` in environment variables.

View File

@ -0,0 +1,136 @@
import { Check, ChevronDown, Copy } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export 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-4 px-4 pb-6 pt-3">
{children}
</div>
) : null}
</div>
);
}
export 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>
);
}
export 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>
);
}

View File

@ -1,13 +1,38 @@
import { createFileRoute } from "@tanstack/react-router";
import { ArrowUpRight, Check, ChevronDown, Copy } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { ArrowUpRight } from "lucide-react";
import { ClaudeIcon, CodexIcon } from "@/client/features/ai-mcp/AgentIcons";
import { AvailableTools } from "@/client/features/ai-mcp/AvailableTools";
import {
CodeBlock,
Collapsible,
CopyButton,
} from "@/client/features/ai-mcp/SetupControls";
const DISCORD_URL = "https://discord.gg/c9uGs3cFXr";
const SUPPORT_EMAIL = "ben@openseo.so";
const SAM_GITHUB_URL = "https://github.com/every-app/sam";
const SKILL_NAMES = [
"onboarding-checklist",
"seo-coach",
"keyword-research",
"keyword-clustering",
"competitive-landscape",
"competitor-analysis",
"link-prospecting",
];
const SKILLS_INSTALL = `npx skills add every-app/open-seo`;
const ALL_SKILLS_INSTALL = `npx skills add every-app/open-seo --skill '*'`;
const CLAUDE_CODE_SKILLS_INSTALL = `npx skills add every-app/open-seo --skill '*' --agent claude-code`;
const CODEX_SKILLS_INSTALL = `npx skills add every-app/open-seo --skill '*' --agent codex`;
const SKILLS_MANUAL_INSTALL = `git clone https://github.com/every-app/open-seo.git
# Codex
mkdir -p ~/.codex/skills
cp -R open-seo/.agents/skills/* ~/.codex/skills/
# Claude Code
mkdir -p ~/.claude/skills
cp -R open-seo/.agents/skills/* ~/.claude/skills/`;
export const Route = createFileRoute("/_app/ai")({
component: AiPage,
@ -25,7 +50,7 @@ function AiPage() {
<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.
domain lookups, and backlink reviews from your editor or chat.
</p>
<section className="mt-8">
@ -45,7 +70,9 @@ function AiPage() {
</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.
Paste this into any MCP client. This URL points at the OpenSEO
instance you are using now, whether hosted, self-hosted, or local.
Sign in with OpenSEO when prompted.
</p>
</section>
@ -152,6 +179,72 @@ function AiPage() {
</div>
</section>
<section className="mt-12">
<h2 className="text-base font-semibold">OpenSEO Skills</h2>
<p className="mt-1.5 text-sm text-base-content/70 leading-relaxed">
Skills give Codex and Claude Code reusable SEO workflows that can
call your OpenSEO MCP tools when live SERP, keyword, backlink, or
domain data is needed.
</p>
<div className="mt-4 divide-y divide-base-300 overflow-hidden rounded-lg border border-base-300 bg-base-200">
<Collapsible
id="skills-add"
title="Install with skills add"
subtitle="Recommended cross-agent installer"
>
<CodeBlock code={SKILLS_INSTALL} />
<p className="text-sm text-base-content/70">
You can also auto-accept each OpenSEO skill:
</p>
<CodeBlock code={ALL_SKILLS_INSTALL} />
</Collapsible>
<Collapsible
id="claude-code-skills"
title="Install for Claude Code"
subtitle="Target Claude Code only"
icon={<ClaudeIcon className="size-5" />}
>
<CodeBlock code={CLAUDE_CODE_SKILLS_INSTALL} />
</Collapsible>
<Collapsible
id="codex-skills"
title="Install for Codex"
subtitle="Target OpenAI Codex only"
icon={<CodexIcon className="size-5" />}
>
<CodeBlock code={CODEX_SKILLS_INSTALL} />
</Collapsible>
<Collapsible
id="manual-skills"
title="Manual GitHub install"
subtitle="Clone the repo and copy the skills"
>
<CodeBlock code={SKILLS_MANUAL_INSTALL} />
</Collapsible>
</div>
<div className="mt-5">
<p className="text-sm text-base-content/70 leading-relaxed">
Start with{" "}
<span className="font-mono text-base-content">
/onboarding-checklist
</span>
. It will ask about your project and help configure your
workspace.
</p>
<p className="mt-4 text-xs font-medium uppercase tracking-wide text-base-content/50">
Available skills
</p>
<ul className="mt-2 grid gap-1.5 text-sm text-base-content/70 sm:grid-cols-2">
{SKILL_NAMES.map((skill) => (
<li key={skill} className="flex gap-2">
<span className="text-base-content/35">-</span>
<span>{skill}</span>
</li>
))}
</ul>
</div>
</section>
<section className="mt-12">
<h2 className="text-base font-semibold">Available tools</h2>
<div className="mt-5">
@ -228,136 +321,3 @@ function AiPage() {
</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>
);
}