Files
openseo/README.md
T
pastilhas 14f3f3f1e5 the repository, before any code
Per the rule for a new plugin: the public gitea repository and its infrastructure
come first, so there is somewhere for the work to land rather than a directory
that becomes a repository later.

What is here is the shape and the two decisions it deliberately does not make —
where the container runs, and whether Officer's agents call OpenSEO's /mcp
directly or Officer wraps it as native tools. Both are the owner's, both are
recorded in the todo this came from, and the scaffold keeps both open by
proxying.

Everything in the README was verified against upstream's compose.yaml and its
own skills rather than its marketing: one container, port 3001 on loopback,
SQLite in a volume, AUTH_MODE=local_noauth, and DATAFORSEO_API_KEY as base64 of
login:password.
2026-08-29 15:03:47 +01:00

54 lines
2.7 KiB
Markdown

# OpenSEO for Officer
Keyword research, rank tracking, competitor and backlink data, site audits — from
[`every-app/open-seo`](https://github.com/every-app/open-seo), self-hosted, behind Officer's own
permission gate.
## Why this is a thin plugin
Most integrations need Officer to invent the agent-facing surface. This one arrives with its own: OpenSEO
ships an **MCP server at `/mcp`** and its own Agent Skills, built to be driven by exactly this kind of
client. So the plugin's job is credential plumbing, a permission boundary and a UI surface — not
reimplementing a tool vocabulary that already exists upstream.
## What it runs against
One container, verified from upstream's `compose.yaml` rather than its README:
```
ghcr.io/every-app/open-seo:latest
port 3001, bound to 127.0.0.1
data a Docker volume at /app/.wrangler (Cloudflare D1 — SQLite; Postgres is opt-in only)
auth AUTH_MODE=local_noauth
```
**`local_noauth` is not a shortcut taken here, it is upstream's documented self-hosting mode** — their
docs say to *"only expose it behind your own auth-protected reverse proxy, tunnel, or private network."*
That sentence describes an Officer plugin: mounted behind Officer's permission gate, never published on
its own port. It is the ordinary case here rather than a risk to design around — but it is also why
**nothing in this plugin may expose OpenSEO's port directly**, and why the pass-through is the only door.
## Credentials, and the honest cost
| | |
| --- | --- |
| `DATAFORSEO_API_KEY` | **Required.** Base64 of `login:password`. Paid pay-as-you-go **directly to DataForSEO** |
| `OPENROUTER_API_KEY` | Optional — OpenSEO's own AI features |
| `GOOGLE_CLIENT_ID` / `_SECRET` | Optional — Search Console |
This is free software over a metered API. Nothing here is a subscription to OpenSEO, and nothing here
makes the DataForSEO calls free.
## Decisions this scaffold does NOT make
Both are open in `todo/integrations/openseo-a-plugin-candidate.md`, and both are the owner's:
1. **Where the container runs.** Today a plugin has no primitive for "bring a container" — `osDependencies`
installs OS packages. `todo/infra/opm-officer-s-own-process-manager` is heading toward the Docker
network, at which point a container-backed dependency needs no published host port at all. Until then
this plugin points at an instance rather than owning one, which is what `jellyfin`, `gitea` and
`proxy-manager` already do.
2. **Whether Officer's agents call `/mcp` directly, or Officer wraps it as native MCP tools.** Direct is
simpler and already works. Wrapping is more consistent with how other plugins expose theirs, and puts
every call through one place that can log and refuse. This scaffold proxies, which keeps both open.