OpenSEO ships its own MCP server and its own web UI, so this plugin holds the address, holds the credential, and is the door. There are no curated routes in front of it and there should not be: proxy-manager's router already records where that leads — a curated surface cannot keep up, and the plugin becomes a worse version of the thing it wraps. The door IS the security model, not a convenience. Upstream self-hosts with AUTH_MODE=local_noauth, so every request reaching the instance is trusted by design and their docs say to put it behind auth. Officer's gate is that auth. Two things follow and neither is optional: nothing here may publish OpenSEO's own port, and /_config is owner-only, because the address alone is enough to bypass this plugin entirely. The iframe therefore points at /api/openseo/ — this Officer's origin — and never at the instance, since an iframe src is a URL anybody can read out of the page and open directly. The probe asks /mcp for tools/list rather than fetching the home page. A web root answers 200 for a great many things that are not this; a JSON-RPC reply carrying tool names is the instance identifying itself. Saving with an empty key field KEEPS the stored one — absent means keep, empty string means clear. Without that distinction, changing the address from a form that cannot display the credential would silently erase it, and it would surface days later as an instance that had stopped answering. One table, deliberately: every project, keyword and crawl lives in the instance, so uninstalling this cannot destroy anybody's research. The skill says so too, because the temptation to cache a project list is exactly what would break it. The skill leads with cost. This is free software over DataForSEO, metered and billed to the owner per lookup, so it tells the agent not to sweep and to say what a thorough answer would spend before spending it. Tags are `Development` and `Homelab`. `Marketing` and `Research` would both be more accurate and neither exists in the union — extending it is a platform change rather than a plugin's to make.
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* OpenSEO — self-hosted keyword research, rank tracking and site audits, inside Officer.
|
|
*
|
|
* ── It points at an instance; it does not own one ──
|
|
*
|
|
* OpenSEO is a container, and a plugin has no primitive for "bring a container": `osDependencies`
|
|
* installs OS packages. So this follows the shape `jellyfin`, `gitea` and `proxy-manager` already use —
|
|
* a stored connection to a service running somewhere, with the credential write-only across the
|
|
* boundary.
|
|
*
|
|
* A decision to revisit rather than a permanent one. `todo/infra/opm-officer-s-own-process-manager` is
|
|
* heading toward the Docker network, and on the day a plugin can declare a container, the connection
|
|
* becomes something this provisions rather than something somebody types.
|
|
*
|
|
* ── The permission split, and why it is not the same as proxy-manager's ──
|
|
*
|
|
* Proxy Manager carves out `/_config` because its credential can redirect any hostname on the machine.
|
|
* The shape here is the same and the reason is different: OpenSEO self-hosts with
|
|
* `AUTH_MODE=local_noauth`, so ANY reachable request to it is unauthenticated by design — the whole of
|
|
* Officer's gate is the authentication. A member who could read the connection could reach the instance
|
|
* directly and bypass this plugin entirely.
|
|
*
|
|
* So `/_config` is owner-only, and the pass-through is the only door. Nothing here may publish
|
|
* OpenSEO's own port, and that is not a hardening measure — it is the security model upstream
|
|
* documents: *"only expose it behind your own auth-protected reverse proxy, tunnel, or private
|
|
* network."*
|
|
*
|
|
* ── Why the tools are not reimplemented here ──
|
|
*
|
|
* OpenSEO ships its own MCP server at `/mcp`. Most integrations need Officer to invent the agent-facing
|
|
* surface; this one arrives with one, built for exactly this kind of client. Wrapping it in
|
|
* Officer-native tools is a real option and an open decision — but inventing a second vocabulary for
|
|
* tools that already exist would be work whose only product is drift.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '0.1.0',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'OpenSEO',
|
|
summary: 'Keyword research, rank tracking, competitors, backlinks and site audits — self-hosted',
|
|
/*
|
|
`Marketing` and `Research` would both be more accurate and neither exists — the union is closed, and
|
|
extending it is a platform change rather than a plugin's to make. `Development` is the least wrong of
|
|
what is there: this is a tool about websites, used by whoever builds them. Worth revisiting if the
|
|
store ever grows a marketing shelf.
|
|
*/
|
|
tags: ['Development', 'Homelab'],
|
|
icon: 'TrendingUp',
|
|
color: '#22c55e',
|
|
|
|
permissions: [
|
|
{
|
|
key: 'openseo',
|
|
label: 'OpenSEO',
|
|
description: 'Keyword research, rank tracking, competitor and backlink data, and site audits',
|
|
|
|
/*
|
|
The connection form, the probe, and the DataForSEO key.
|
|
|
|
Owner-only by PREFIX, so `/_config/test` is covered too. That matters here for the same reason it
|
|
does in proxy-manager: the test request carries a credential in its body, and a member who can
|
|
call it has an oracle pointed at somebody else's key.
|
|
*/
|
|
ownerOnly: ['/_config'],
|
|
},
|
|
],
|
|
};
|
|
|
|
export default manifest;
|