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-enabledsite 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 seedThen 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:adminThis 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.tsFor 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,adminThe 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,adminThe 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,adminIssue an organization-owned MCP API key:
bun run mcp:key -- issue --email owner@example.com --organization-slug acme --permissions read,write,publish,adminList a user's keys and their stored permissions:
bun run mcp:key -- list --email admin@example.comList an organization's keys and their stored permissions:
bun run mcp:key -- list --organization-slug acmeInspect 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_KEYRevoke 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_capabilitiesyayaw_statusyayaw_audit_listyayaw_deploy_status
CMS and pages:
yayaw_pages_list,yayaw_pages_get,yayaw_pages_validate,yayaw_pages_diffyayaw_pages_save_draft,yayaw_pages_publish,yayaw_pages_archiveyayaw_pages_create_draftyayaw_components_list,yayaw_component_get,yayaw_component_importyayaw_components_sync_registry,yayaw_component_recompileyayaw_component_publish,yayaw_component_deleteyayaw_sections_list,yayaw_sections_get,yayaw_sections_create_draftyayaw_sections_validate,yayaw_sections_save_draftyayaw_sections_publish,yayaw_sections_archiveyayaw_kibo_sections_generateyayaw_cms_page_designyayaw_data_models_list,yayaw_data_schema_get,yayaw_data_entry_getyayaw_data_entry_upsert,yayaw_data_entry_publishyayaw_email_templates_list,yayaw_email_template_getyayaw_email_template_save,yayaw_email_templates_sync_systemyayaw_org_domains_list,yayaw_org_domain_add,yayaw_org_domain_checkyayaw_org_domain_set_primary,yayaw_org_domain_archive
Site operations:
yayaw_billing_products_list,yayaw_billing_product_updateyayaw_stripe_discounts_list,yayaw_stripe_discounts_syncyayaw_media_list,yayaw_media_generateyayaw_design_tokens_get,yayaw_design_tokens_saveyayaw_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://statusyayaw://schemasyayaw://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-enabledis 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-planewhen MCP clients should operate or inspect the feature. - Require
reasonfor writes,dryRunwhere practical,expectedRevisionIdfor optimistic publish flows, andconfirm: truefor destructive actions. - Gate API-key access with Better Auth
control-plane:*permissions and still run Yayaw resource authorization withcan(...)or scoped membership checks. - Attempt audit-event recording for all control-plane operations and surface
auditIdwhen storage succeeds. - Update this documentation and
content/llm/llm-source.md, then regenerate assistant files withbun 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