Skip to content

capo689/Controller

Repository files navigation

Singularity SEO Controller

This service exposes connected Singularity SEO WordPress sites to ChatGPT as an authenticated remote MCP server over SSE.

The WordPress plugin is the customer-site adapter. The hosted Controller verifies customer installs, stores customer-site connection records, routes MCP tools to the correct WordPress site, enforces OAuth scopes, protects rank-provider quota, and proxies governed reads/writes to the WordPress REST API.

Architecture

ChatGPT app
  -> OAuth 2.1 / PKCE with Auth0
  -> Bearer access token on MCP tool requests
  -> Controller validates JWT issuer, signature, audience, expiry, and scopes
  -> Controller resolves an authorized Singularity SEO customer/site record
  -> Controller calls that site's WordPress REST API with X-SMSEO-Bridge-Token

The Controller is a resource server only. It does not proxy Auth0 login, issue OAuth codes, store ChatGPT client registrations, or manage Auth0 callbacks.

Production Endpoint

https://controller-nq6p.onrender.com/sse-0-5-40/

Use the versioned SSE URL shown by /healthz.clean_connector_url for new ChatGPT connector registrations. This forces ChatGPT to fetch a fresh tool manifest after launch-surface changes. The canonical /sse/ endpoint remains available, but it can be sticky in ChatGPT connector caches.

Previously issued versioned connector URLs remain mounted so existing clean connectors keep working after a later manifest bump.

Canonical OAuth resource identifier:

https://controller-nq6p.onrender.com

SMSEO_MCP_BASE_URL accepts either the origin above or the canonical /sse endpoint and normalizes it to the origin. SMSEO_AUTH0_AUDIENCE must match the normalized resource URL exactly.

Health

GET https://controller-nq6p.onrender.com/healthz

Current expected health labels include:

{
  "ok": true,
  "service": "singularity-seo-mcp-bridge",
  "version": "0.5.40-tool-annotations",
  "manifest_version": "0-5-40",
  "clean_connector_url": "https://controller-nq6p.onrender.com/sse-0-5-40/",
  "canonical_connector_url": "https://controller-nq6p.onrender.com/sse/",
  "customer_ownership_enforced": true,
  "serpapi_configured": true,
  "serpapi_max_keywords": 3,
  "serpapi_daily_search_limit": 3,
  "google_search_console_oauth_configured": true,
  "tool_count": 32
}

The health payload also returns tool_names; verify this includes s2_getting_started_for_customer, s2_get_capabilities_for_customer, s2_generate_report_for_customer, s2_apply_seo_batch_plan_for_customer, s2_apply_seo_batch_proposal_for_customer, s2_create_seo_batch_proposal_for_customer, s2_forget_seo_customer, s2_adopt_discovered_pages_for_customer, s2_refresh_rankings_for_customer, and s2_rollback_seo_change_for_customer before public launch testing. Generic proposal approve/apply tools remain hidden from the launch tool surface so ChatGPT prefers the batch-first apply path.

When a ChatGPT connector keeps showing stale tools after a deploy, do not keep reconnecting the same URL. Create the next connector against clean_connector_url from /healthz; that URL changes whenever the public tool manifest must be forcibly refreshed.

Customer Onboarding API

POST https://controller-nq6p.onrender.com/onboarding/wordpress
Content-Type: application/json

Request body:

{
  "customer_id": "demo-site",
  "site_url": "https://example.com/",
  "wordpress_rest_status_url": "https://example.com/wp-json/smseo/v1/status",
  "wordpress_rest_pages_url": "https://example.com/wp-json/smseo/v1/pages",
  "chatgpt_app_url": "https://controller-nq6p.onrender.com/sse/",
  "private_connection_key": "generated-by-plugin"
}

Success response:

{
  "ok": true,
  "customer": {
    "customer_id": "demo-site",
    "site_url": "https://example.com/",
    "plugin_name": "Singularity SEO",
    "plugin_version": "0.5.28",
    "managed_page_count": 0,
    "connection_status": "verified"
  }
}

A fresh generic customer may verify with managed_page_count: 0 until setup review/adoption or dashboard Singularity mode brings scanned pages under management. The private connection key is stored for hosted WordPress calls but is never returned by public API responses.

Tenant Routing

Customer-routed MCP tools accept customer_id. If SMSEO_DEFAULT_CUSTOMER_ID is set, or if the authenticated ChatGPT user owns exactly one verified customer and ownership enforcement allows it, ChatGPT can omit customer_id and the Controller routes to the active site automatically.

Ownership enforcement is on by default through SMSEO_ENFORCE_CUSTOMER_OWNERSHIP=true.

Supported product model:

  • One ChatGPT user can connect multiple sites.
  • Multiple users can access one site only through explicit hosted ownership records.
  • A user must not see or operate another customer's site.

Current Tool Surface

Primary read tools:

  • s2_list_seo_customers
  • s2_getting_started_for_customer
  • s2_get_capabilities_for_customer
  • s2_seo_status_for_customer
  • s2_list_seo_pages_for_customer
  • s2_get_page_seo_for_customer
  • s2_get_seo_operating_manual_for_customer
  • s2_get_seo_health_metrics_for_customer
  • s2_get_visibility_config_for_customer
  • s2_get_visibility_metrics_for_customer
  • s2_list_seo_proposals_for_customer
  • s2_get_seo_proposal_for_customer
  • s2_list_seo_changes_for_customer
  • s2_get_seo_change_for_customer
  • s2_run_live_head_audit_for_customer
  • s2_seo_customer_overview
  • s2_get_seo_takeover_report_for_customer
  • s2_list_reports_for_customer
  • s2_get_report_for_customer

Non-destructive setup/write tools:

  • s2_connect_seo_customer
  • s2_claim_seo_customer
  • s2_create_seo_health_snapshot_for_customer
  • s2_create_seo_takeover_scan_for_customer
  • s2_adopt_discovered_pages_for_customer
  • s2_update_visibility_config_for_customer
  • s2_create_visibility_snapshot_for_customer
  • s2_refresh_rankings_for_customer
  • s2_ingest_visibility_snapshot_for_customer
  • s2_generate_report_for_customer
  • s2_create_seo_proposal_for_customer
  • s2_create_seo_batch_proposal_for_customer
  • s2_expire_seo_proposal_for_customer

Connection reset tools:

  • s2_forget_seo_customer

Governed public-output mutation tools:

  • s2_apply_seo_batch_plan_for_customer
  • s2_apply_seo_batch_proposal_for_customer
  • s2_rollback_seo_change_for_customer restores prior controlled metadata from a rollback-capable change record. It is a public-output write, but it is annotated as a non-destructive restore because it records history and does not delete pages, content, or customer data.

Rank-provider tools can consume SerpApi quota. Do not run ranking refreshes casually during testing.

Launch rank refreshes check up to the top 100 organic results per tracked keyword. The default SerpApi request uses num=100, while the daily customer budget remains capped at three searches. Do not paginate beyond the first 100 results in normal daily tracking.

First-Run Customer Welcome

When a customer connects a site or asks what to do next, ChatGPT should call s2_getting_started_for_customer.

That read-only tool returns:

  • A concise welcome.
  • What Singularity SEO can do.
  • Recommended first prompts.
  • Confirmation rules for reads, setup actions, public-output writes and quota-consuming actions.
  • Current connection, GSC, rank-provider and keyword state.
  • Active proposal queue summary.
  • Protected governance status proving the full internal SEO brain is not exposed.

This keeps the public self-serve onboarding flow consistent instead of relying on improvised assistant copy.

Use s2_get_capabilities_for_customer when ChatGPT needs the current feature/limit contract for a connected site. It returns customer-safe support for reports, batch tools, rollback, schema overrides, visibility providers, keyword limits, SerpApi daily budget, proposal queue hygiene, and governance redaction. This is the preferred future-proofing surface: new internal behavior should be reflected through capabilities instead of adding a new tool every time.

Use s2_generate_report_for_customer for report creation. Supported report_type values are baseline, competitive, monthly, and campaign; future report improvements should happen behind this stable tool shape whenever possible.

Singularity Mode Batch Flow

For broad customer requests such as “set up my SEO,” “make me rank better,” “use competitor research,” or “run Singularity mode,” ChatGPT should not create/apply one proposal per page.

Preferred launch flow:

  1. Read status, setup review, managed pages, operating manual, health and visibility configuration.
  2. Run/adopt setup pages if the site has zero managed pages.
  3. Build the SEO strategy and all page metadata locally inside ChatGPT.
  4. If the user wants to review first, call s2_create_seo_batch_proposal_for_customer once.
  5. If the user has asked ChatGPT to proceed immediately, call s2_apply_seo_batch_plan_for_customer once after the public-output confirmation.
  6. If the user reviewed a staged batch proposal and then approves it, call s2_apply_seo_batch_proposal_for_customer once. Do not call generic proposal approve/apply tools for broad SEO work.
  7. Verify with page reads and a live head audit.
  8. Restore with s2_rollback_seo_change_for_customer if needed; a batch apply creates one rollback-capable change record.

This is the launch UX target: one coherent SEO build, one public-output approval, one fast WordPress apply operation, one rollback point.

Confirmation Policy

Read-only tools should not require confirmation.

Low-friction setup tools may store setup snapshots, health snapshots, visibility configuration or proposal records, but do not change public SEO output.

Public-output mutation tools require explicit confirmation:

  • Apply a batch SEO plan.
  • Restore prior metadata from a rollback-capable change record.
  • Apply an approved single-page proposal.
  • Any future tool that changes rendered public SEO output.

Quota-consuming tools should clearly state that one rank-provider search may be consumed per tracked keyword.

Registry Persistence

The onboarding endpoint writes verified customer records to SMSEO_CUSTOMER_REGISTRY_PATH.

Production uses persistent disk:

SMSEO_CUSTOMER_REGISTRY_PATH=/var/data/singularity-seo-customers.json

Google Search Console token storage should also use persistent disk:

SMSEO_GOOGLE_SEARCH_CONSOLE_STORE_PATH=/var/data/singularity-seo-google-search-console.json

Seeded secret JSON remains supported for emergency recovery or migration:

SMSEO_CUSTOMER_REGISTRY_JSON={"demo-site":{"customer_id":"demo-site","site_url":"https://example.com/","wordpress_rest_status_url":"https://example.com/wp-json/smseo/v1/status","wordpress_rest_pages_url":"https://example.com/wp-json/smseo/v1/pages","chatgpt_app_url":"https://controller-nq6p.onrender.com/sse/","private_connection_key":"...","plugin_name":"Singularity SEO","plugin_version":"0.5.28","managed_page_count":0,"connection_status":"verified"}}

Seeded records are merged with file records. File records win when the same customer_id exists in both places.

Environment Variables

Required:

SMSEO_MCP_BASE_URL=https://controller-nq6p.onrender.com
SMSEO_AUTH0_CONFIG_URL=https://YOUR_AUTH0_DOMAIN/.well-known/openid-configuration
SMSEO_AUTH0_AUDIENCE=https://controller-nq6p.onrender.com
SMSEO_WORDPRESS_BASE_URL=https://example.com
SMSEO_WORDPRESS_USERNAME=YOUR_WORDPRESS_USERNAME
SMSEO_WORDPRESS_APP_PASSWORD=YOUR_WORDPRESS_APPLICATION_PASSWORD

Recommended production:

SMSEO_CUSTOMER_REGISTRY_PATH=/var/data/singularity-seo-customers.json
SMSEO_GOOGLE_SEARCH_CONSOLE_STORE_PATH=/var/data/singularity-seo-google-search-console.json
SMSEO_ONBOARDING_ADMIN_TOKEN=YOUR_INTERNAL_ADMIN_TOKEN
SMSEO_ENFORCE_CUSTOMER_OWNERSHIP=true
SMSEO_SERPAPI_KEY=YOUR_SERPAPI_KEY
SMSEO_SERPAPI_MAX_KEYWORDS=3
SMSEO_SERPAPI_DAILY_SEARCH_LIMIT=3
SMSEO_SERPAPI_USAGE_PATH=/var/data/singularity-seo-serpapi-usage.json
SMSEO_REQUEST_TIMEOUT=45
SMSEO_LOG_LEVEL=INFO

Optional / migration:

SMSEO_WORDPRESS_BRIDGE_TOKEN=YOUR_SHARED_WORDPRESS_PLUGIN_BRIDGE_TOKEN
SMSEO_CUSTOMER_REGISTRY_JSON=YOUR_SEEDED_CUSTOMER_RECORDS_JSON
SMSEO_DEFAULT_CUSTOMER_ID=

Google Search Console OAuth:

SMSEO_GOOGLE_OAUTH_CLIENT_ID=YOUR_GOOGLE_OAUTH_CLIENT_ID
SMSEO_GOOGLE_OAUTH_CLIENT_SECRET=YOUR_GOOGLE_OAUTH_CLIENT_SECRET
SMSEO_GOOGLE_OAUTH_STATE_SECRET=LONG_RANDOM_SECRET

Google redirect URI:

https://controller-nq6p.onrender.com/integrations/google/search-console/callback

Keyword Cap And SerpApi Quota

Launch WordPress product rule:

Do not exceed 3 tracked keywords on the WordPress plan.

Implementation guardrails:

  • WordPress caps public visibility config to three tracked keywords.
  • Controller accepts legacy SMSEO_SERPAPI_MAX_KEYWORDS values up to five but clamps runtime usage and health reporting to three.
  • Controller rejects SMSEO_SERPAPI_DAILY_SEARCH_LIMIT values above three, so the service cannot start with a higher daily budget.
  • Controller stores daily per-customer SerpApi usage in SMSEO_SERPAPI_USAGE_PATH and reserves searches before calling SerpApi.
  • Standard cadence is one measured rank refresh per site per day.
  • One tracked keyword normally consumes one SerpApi search.
  • Manual forced refreshes are still quota-limited and cannot exceed the daily three-search customer/site budget.
  • Enterprise expansion requires an explicit product/pricing decision before raising caps.

Auth Expectations

  • The MCP server publishes protected-resource metadata at /.well-known/oauth-protected-resource.
  • MCP discovery can run without OAuth for mixed-auth compatibility.
  • Actual tool calls require an Auth0 bearer token.
  • Read tools require smseo:read; write tools require smseo:write.
  • Tool responses must not expose bridge credentials, OAuth tokens, API keys, WordPress passwords or provider secrets.
  • Tool responses must not expose full internal operating manuals, hidden prompts, or proprietary SEO brain content verbatim. Operating-manual reads return protected customer-safe governance summaries only.
  • First-run and "what next" flows should use s2_getting_started_for_customer so customers receive consistent onboarding, confirmation policy, quota, and next-prompt guidance.

WordPress REST Contract

Core endpoints used by Controller:

GET   /wp-json/smseo/v1/status
GET   /wp-json/smseo/v1/pages
GET   /wp-json/smseo/v1/pages/{key}
PATCH /wp-json/smseo/v1/pages/{key}
POST  /wp-json/smseo/v1/pages/{key}/reset
POST  /wp-json/smseo/v1/audit/run
GET   /wp-json/smseo/v1/operating-manual
GET   /wp-json/smseo/v1/proposals
POST  /wp-json/smseo/v1/proposals
GET   /wp-json/smseo/v1/proposals/{id}
POST  /wp-json/smseo/v1/proposals/{id}/approve
POST  /wp-json/smseo/v1/proposals/{id}/apply
POST  /wp-json/smseo/v1/proposals/{id}/expire
GET   /wp-json/smseo/v1/changes
GET   /wp-json/smseo/v1/changes/{id}
POST  /wp-json/smseo/v1/changes/{id}/rollback
GET   /wp-json/smseo/v1/metrics
POST  /wp-json/smseo/v1/metrics/snapshot
GET   /wp-json/smseo/v1/visibility
PUT   /wp-json/smseo/v1/visibility/config
POST  /wp-json/smseo/v1/visibility/snapshot
PUT   /wp-json/smseo/v1/visibility/snapshot
GET   /wp-json/smseo/v1/takeover
POST  /wp-json/smseo/v1/takeover/scan
POST  /wp-json/smseo/v1/takeover/adopt

Batch proposals use the existing POST /proposals and POST /proposals/{id}/apply endpoints with a changes array. The WordPress plugin remains the storage, validation, apply, audit and rollback authority.

Deployment Verification

After every Controller deploy:

  1. Check /healthz returns JSON, not a Render 502 page.
  2. Confirm the expected version string.
  3. Confirm customer_ownership_enforced is true.
  4. Confirm serpapi_max_keywords and serpapi_daily_search_limit are both three for launch.
  5. Confirm expected tool names are present.
  6. Confirm non-prefixed compatibility aliases do not appear in tool_names.
  7. Invoke one read-only S2.2 tool against the current test customer, or list customers when no site is connected yet.
  8. Do not treat immediate post-deploy 502s as success; retry after deploy warm-up and inspect before continuing.

Public Launch Docs

Keep these local docs aligned with Controller behavior:

  • docs/singularity-seo-privacy-retention-policy-draft.md
  • docs/singularity-seo-customer-setup.md
  • docs/singularity-seo-troubleshooting-and-rollback.md
  • docs/singularity-seo-safety-boundaries.md
  • docs/singularity-seo-chatgpt-app-submission-prep.md
  • docs/singularity-seo-security-logging-and-data-policy.md
  • docs/singularity-seo-launch-reporting-spec.md

About

Production MCP controller for Singularity SEO: OAuth-scoped tenant routing, governed WordPress mutations, Search Console reporting, rank tracking, and rollback.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors