YYayaw

Control Plane

Production MCP and local stdio control-plane access for MCP clients and automation.

Overview

Yayaw exposes a production control plane through MCP. Codex is one supported client, but the endpoint is designed for any trusted MCP client or automation runner.

  • Production and staging use Streamable HTTP at /api/mcp.
  • Local development can use stdio with bun --conditions react-server --env-file=.env src/lib/scripts/mcp/yayaw-mcp-server.ts.
  • Production requests can authenticate with either a Better Auth API key or a Better Auth OAuth access token. Both paths use explicit control-plane permissions.
  • The control-plane-mcp-enabled site setting is the server-side kill switch.
  • Underlying Yayaw authorization still runs through can(...) or equivalent scoped membership checks.

Control-Plane Permissions

API keys use Better Auth API key permissions under the control-plane resource:

  • read: status, discovery, resources, list/get tools.
  • write: draft/data/media/design-token mutations.
  • publish: publish/archive lifecycle tools.
  • admin: audit and feature-flag administration.

Admin does not bypass Yayaw resource authorization. It only satisfies the control-plane permission gate.

Production MCP Connection

Create a production MCP key, store it in your client environment as YAYAW_MCP_API_KEY, then configure the MCP client. Codex can use:

[mcp_servers.yayaw-prod]
url = "https://yayaw.app/api/mcp"
bearer_token_env_var = "YAYAW_MCP_API_KEY"

Before connecting, enable the site setting:

bun run seed

Then set control-plane-mcp-enabled to true from Admin settings or with the control-plane flag tool using an existing admin key.

OAuth MCP Connection

Yayaw also exposes MCP-compatible OAuth 2.1 discovery for ChatGPT Apps and other clients that follow the MCP authorization flow. OAuth supports authorization code with PKCE and refresh tokens; machine-to-machine access should continue to use MCP API keys.

  • Protected resource metadata: /.well-known/oauth-protected-resource/api/mcp
  • Authorization server metadata: /.well-known/oauth-authorization-server/api/auth
  • OpenID metadata: /.well-known/openid-configuration/api/auth
  • Authorization server base: /api/auth
  • MCP resource/audience: /api/mcp

The MCP endpoint returns a WWW-Authenticate challenge on unauthenticated or invalid OAuth requests so clients can discover the protected resource metadata. OAuth tokens are verified against the Better Auth JWKS, issuer, audience, expiry, and control-plane scopes before a tool runs.

The OAuth provider intentionally advertises the full MCP permission set:

control-plane:read control-plane:write control-plane:publish control-plane:admin

This keeps the MCP tool surface unchanged for trusted linked clients. Tool handlers still enforce the required control-plane permission and the underlying Yayaw resource authorization for every operation, so OAuth scopes never replace application authorization.

For published ChatGPT Apps, ensure NEXT_PUBLIC_BASE_URL is the public HTTPS origin before generating metadata. ChatGPT redirects back to https://chatgpt.com/connector/oauth/{callback_id}; the registered OAuth client must allow that redirect URI.

Local MCP Connection

Local stdio is useful for development and does not require opening a public endpoint. Codex can register it with:

codex mcp add yayaw-local -- bun --conditions react-server --env-file=.env src/lib/scripts/mcp/yayaw-mcp-server.ts

For local stdio, set YAYAW_MCP_API_KEY to test production-like API key verification. Without a key, the launcher creates a local development actor using YAYAW_MCP_LOCAL_USER_ID or local-codex.

Key Management

For a fully trusted MCP operator key, use the full control-plane permission set:

control-plane:read,write,publish,admin

The dashboard Developer settings page uses Yayaw's custom API key block rather than the generic Better Auth UI, because Better Auth treats API key permissions as server-only fields. Use the Yayaw block to create new MCP keys with explicit control-plane scopes, or to repair an existing key by setting the Full MCP access preset. The preset writes:

control-plane:read,write,publish,admin

The UI can update permissions on an existing key without revealing or rotating the secret. Use that only when the current secret is already stored safely, because Better Auth still shows the secret only once.

Organization-owned keys are the default choice for MCP clients operating a customer/workspace scope. They are issued for the active organization and can be managed by Better Auth organization owners, admins, and managers. They carry issuer metadata so MCP can keep running Yayaw authorization as the issuing user, and they are constrained to that organization for scoped page, CMS data, and media operations.

Personal/user-owned keys are superadmin-only and are intended for global Yayaw administration. Use them for global content, feature flags, or cross-organization operations only when that is intentional.

Issue a user-owned MCP API key:

bun run mcp:key -- issue --email admin@example.com --permissions read,write,publish,admin

Issue an organization-owned MCP API key:

bun run mcp:key -- issue --email owner@example.com --organization-slug acme --permissions read,write,publish,admin

List a user's keys and their stored permissions:

bun run mcp:key -- list --email admin@example.com

List an organization's keys and their stored permissions:

bun run mcp:key -- list --organization-slug acme

Inspect one key by id:

bun run mcp:key -- inspect --key-id <api-key-id>

Inspect the secret currently configured for a local MCP client:

YAYAW_MCP_API_KEY=<secret> bun run mcp:key -- inspect --token-env YAYAW_MCP_API_KEY

Revoke a key:

bun run mcp:key -- revoke --key-id <api-key-id>

The issue command prints the secret once. Store it in a secret manager or the local MCP client environment; do not commit it.

Tools

Core:

  • yayaw_capabilities
  • yayaw_status
  • yayaw_audit_list
  • yayaw_deploy_status

CMS and pages:

  • yayaw_pages_list, yayaw_pages_get, yayaw_pages_validate, yayaw_pages_diff
  • yayaw_pages_save_draft, yayaw_pages_publish, yayaw_pages_archive
  • yayaw_pages_create_draft
  • yayaw_components_list, yayaw_component_get, yayaw_component_import
  • yayaw_components_sync_registry, yayaw_component_recompile
  • yayaw_component_publish, yayaw_component_delete
  • yayaw_sections_list, yayaw_sections_get, yayaw_sections_create_draft
  • yayaw_sections_validate, yayaw_sections_save_draft
  • yayaw_sections_publish, yayaw_sections_archive
  • yayaw_kibo_sections_generate
  • yayaw_cms_page_design
  • yayaw_data_models_list, yayaw_data_schema_get, yayaw_data_entry_get
  • yayaw_data_entry_upsert, yayaw_data_entry_publish
  • yayaw_email_templates_list, yayaw_email_template_get
  • yayaw_email_template_save, yayaw_email_templates_sync_system
  • yayaw_org_domains_list, yayaw_org_domain_add, yayaw_org_domain_check
  • yayaw_org_domain_set_primary, yayaw_org_domain_archive

Site operations:

  • yayaw_billing_products_list, yayaw_billing_product_update
  • yayaw_stripe_discounts_list, yayaw_stripe_discounts_sync
  • yayaw_media_list, yayaw_media_generate
  • yayaw_design_tokens_get, yayaw_design_tokens_save
  • yayaw_flags_list, yayaw_flags_update

When adding a tool, prefer a service-layer implementation that can also be used by UI server actions or scripts. Tool handlers should stay thin: validate input, check control-plane permissions, call the shared service, and write audit data.

Resources

  • yayaw://status
  • yayaw://schemas
  • yayaw://pages/{scope}/{slug}
  • yayaw://data/{scope}/{modelSlug}/{entrySlug}
  • yayaw://email-templates/{slug}
  • yayaw://audit/recent

Billing, discount, media, design-token, feature-flag, and organization-domain workflows are exposed as tools rather than MCP resources. Organization-scoped page and data reads should use the tools when an explicit orgId is required.

Safety Rules

Production writes require all of the following:

  • control-plane-mcp-enabled is enabled.
  • A valid Better Auth API key or OAuth access token is supplied as Authorization: Bearer <token>.
  • The key or OAuth token has the required control-plane permission.
  • The actor passes the underlying Yayaw resource authorization checks.
  • The tool input includes reason.

Organization-owned keys are additionally limited to their owning organization for scoped content/media operations. Personal keys still require the seeded Yayaw apikey:manage permission to be issued from the dashboard.

Organization public-domain tools follow the same organization boundary. Organization-owned keys operate only on their owning organization; personal keys must pass an explicit orgId. Domain mutations require organization:update or organization-settings:manage, a reason, and use dryRun for preflight checks. PUBLIC_DOMAIN_PROVIDER=vercel attaches the hostname to the shared Vercel project, reads recommended DNS records, and verifies Vercel ownership challenges. PUBLIC_DOMAIN_PROVIDER=manual-dns generates a TXT ownership token and checks public DNS for the expected TXT plus configured CNAME/A hints.

yayaw_deploy_status returns provider-neutral deployment metadata. On Vercel it reports Vercel runtime fields; on self-hosted deployments it reports static DEPLOYMENT_URL, DEPLOYMENT_ENV, DEPLOYMENT_GIT_COMMIT_SHA, and DEPLOYMENT_GIT_COMMIT_REF values when configured.

Component writes are DB-backed catalog imports or recompiles from stored source snapshots. MCP component tools never mutate repository files in production and never execute DB-stored source code at runtime.

Reusable section and CMS page design tools create drafts first. yayaw_cms_page_design can assemble draft components, assets, sections, and a page from a prompt when dryRun=false, but it does not publish. Use the section and page publish tools after validation diagnostics are clean and the caller explicitly requests publication.

yayaw_kibo_sections_generate creates or updates CMS-native kibo-ui-block recipes for the tracked Kibo UI block catalog. It never stores arbitrary third-party TSX as executable runtime content: every generated section is a Yayaw recipe section rendered by checked-in Kibo block examples and their vendored Kibo/shadcn dependencies. Generated recipes expose editable text, link, media, and JSON list props so pages can bind localized Yayaw content through PageSection.inputBindings while the Kibo defaults remain the fallback preview. Use dryRun=true to inspect the target slugs and set publish=true only when the caller explicitly wants the validated revisions published.

Page publish/archive, data entry publish, and section publish tools require expectedRevisionId to preserve optimistic locking. Section archive accepts expectedRevisionId when the caller has a known latest revision. Destructive archive tools, including page, section, component, and organization public domain archive, require confirm: true.

Every MCP tool call attempts to write control_plane_audit_events with actor, API key, operation, target, environment, transport, dry-run state, reason, status, error summary, and result metadata. A successful audit insert returns auditId in the tool response; if audit storage is unavailable, the server logs the failure and the response is returned without auditId.

Future Feature Contract

New operational features should be designed as control-plane-capable unless they are intentionally UI-only.

When a feature needs content, configuration, publish, status, or audit workflows, implement it through the shared control-plane pattern:

  • Put business logic in a reusable service entrypoint that accepts an explicit actor/context argument.
  • Keep cookie-session reads in UI adapters, not in the reusable service layer.
  • Have UI server actions, MCP tools, CLI scripts, and optional HTTP APIs call the same permission-checked service logic.
  • Register typed MCP tools/resources in src/lib/server/services/control-plane when MCP clients should operate or inspect the feature.
  • Require reason for writes, dryRun where practical, expectedRevisionId for optimistic publish flows, and confirm: true for destructive actions.
  • Gate API-key access with Better Auth control-plane:* permissions and still run Yayaw resource authorization with can(...) or scoped membership checks.
  • Attempt audit-event recording for all control-plane operations and surface auditId when storage succeeds.
  • Update this documentation and content/llm/llm-source.md, then regenerate assistant files with bun run docs:llm:generate.
  • Add focused tests for schemas, permission mapping, dry-run/write guards, audit insertion, and MCP discovery or tool-call smoke coverage.

If a feature is intentionally excluded from MCP/CLI, document the reason in the feature docs or PR summary.

Validation

Useful checks after control-plane changes:

bun test src/lib/server/services/control-plane/control-plane.test.ts
bun test src/lib/server/services/control-plane/oauth-metadata.test.ts
bun run mcp:stdio