Ops MCP route
read as.md The /ops route surfaces operator-class MCP tools — the same actions the console UI (coming soon) will expose to a human (create an app, rename it, mint a connector key, redeem a coupon, …), exposed as MCP tools so an LLM acting as an operator agent can perform them on the user’s behalf.
This page is the wire reference for the 24 ops-audience handlers across seven domains. The agent-loop surface (handshake / render / consume / …) lives on /mcp and is documented separately — /ops is a strictly disjoint route with no overlap.
What’s on /ops
Section titled “What’s on /ops”/ops is the destination for an operator agent — an LLM acting as the console’s hands. Typical caller: a Claude conversation that the user opens from console.ggui.ai (coming soon) and gives natural-language instructions like “create a new app called Inbox Triage and lock a connector key to it.” The agent calls ggui_ops_create_app followed by ggui_ops_issue_connector_key, never touching the AppSync GraphQL layer directly.
Every tool here mirrors a UI action the console (coming soon) will expose. The handler files in @ggui-ai/mcp-server-handlers are pure over typed seams (AppsSource, OrgsSource, OrgInvitesSource, ConnectorKeysSource, CouponRedeemSource) — the cloud pod binds AppSync-backed adapters; OSS deployments leave the seams unwired and the surface stays narrow.
Endpoint
Section titled “Endpoint”POST http://127.0.0.1:6781/opsSelf-hosted servers register tools on /ops when the operator seams are wired into createGguiServer({opsApps, opsOrgs, opsConnectorKeys, opsCoupon}) (the ops-blueprint family additionally hangs off the opsBlueprint dep bundle on defaultHandlers). With nothing wired, the route still mounts but tools/list rejects with JSON-RPC Method not found — no tools capability is advertised when zero handlers are registered. Hosted ggui (coming soon) will serve the same route at https://mcp.ggui.ai/ops.
Authentication
Section titled “Authentication”Identical to /mcp — bearer-token auth via the same upstream AuthAdapter:
Authorization: Bearer devContent-Type: application/jsonSelf-hosted: with ggui serve --dev-allow-all, any bearer authenticates as the builder identity; default serve requires a pairing-minted bearer. Hosted ggui (coming soon) will run the OAuth 2.0 Dynamic Client Registration ceremony (see OAuth on mcp.ggui.ai). The bearer presented on /ops is the same bearer presented on /mcp — there is no separate “ops token”.
Identity model
Section titled “Identity model”Every handler resolves the calling identity through a single helper:
function resolveOwnerSub(toolName: string, ctx: HandlerContext): string { const sub = ctx.userId ?? ctx.appId; if (!sub) throw new Error(`${toolName}: missing caller identity`); return sub;}- Hosted (multi-tenant):
ctx.userIdis the caller’s Cognito sub, populated by the upstream auth adapter. - OSS (single-tenant):
ctx.userIdis undefined;ctx.appId(resolved by the auth adapter viadefaultAppIdFromIdentity— typicallyworkspaceId ?? userIdfor kind=user identities) serves as the identity. - Neither set: the handler throws — that means an unauthenticated caller slipped past auth, surfaced as a 5xx rather than masked as an empty list.
Tenancy posture
Section titled “Tenancy posture”Every read and write is scoped by the resolved identity at the seam layer (AppsSource.list(ownerSub) returns only the caller’s rows; AppsSource.get returns null for foreign rows). Cross-tenant probes never reveal whether a given id exists on another user’s account — the handlers translate “row exists but you don’t own it” to the same shape as “no such row”:
| Operation | Cross-tenant probe |
|---|---|
ggui_ops_list_apps | Returns only caller’s rows; foreign rows invisible. |
ggui_ops_rename_app / ggui_ops_set_default_app / ggui_ops_update_app_system_prompt | Throws app_not_found — same as a genuinely missing id. |
ggui_ops_delete_app | Returns {deleted: true} without touching the foreign row. Uniform with “row didn’t exist.” |
ggui_ops_invite_to_org / ggui_ops_revoke_invite | Throws org_invite_access_denied for orgs the caller doesn’t administer. |
ggui_ops_revoke_connector_key | Throws connector_key_access_denied for keys owned by other users. |
ggui_ops_redeem_coupon | Throws coupon_access_denied for targetOrgId orgs the caller isn’t a member of. |
One-time secret reveal
Section titled “One-time secret reveal”Tools by domain
Section titled “Tools by domain”Seven domains, 24 handlers total. Each domain is optional: the four console-style domains (apps / orgs / connector keys / coupons) hang off CreateGguiServerOptions; ops-blueprint hangs off the opsBlueprint dep bundle on defaultHandlers; provider-keys and credits are bound by the hosted cloud pod (coming soon). Leaving a domain unwired removes its tools from tools/list at registration time.
Apps (ops-apps, 6 handlers)
Section titled “Apps (ops-apps, 6 handlers)”Operator actions on GguiApp rows — the rows the universal MCP route resolves per-request to scope sessions. Each row carries appId (server-minted base62), displayName, optional systemPrompt override, createdAt, updatedAt. Bound on the cloud pod via the AppSync provisionGguiApp mutation + the GguiApp model.
ggui_ops_list_apps
Section titled “ggui_ops_list_apps”Enumerate every GguiApp row owned by the calling user. Returns metadata only — same data the console’s Apps section renders. Use to discover ids before calling the mutating tools.
Inputs: none.
Returns: { apps: AppRecord[] }
interface AppRecord { readonly appId: string; readonly displayName: string; readonly systemPrompt?: string; readonly createdAt: string; readonly updatedAt: string;}Tenancy: scope is ownerSub from the bearer token. Cross-user listings are impossible by construction.
ggui_ops_create_app
Section titled “ggui_ops_create_app”Provision a fresh GguiApp owned by the calling user. Wraps the cloud’s provisionGguiApp mutation — opaque base62 appId is minted server-side; argument-supplied appId is NEVER honored (tenant-takeover vector).
| Field | Type | Required | Description |
|---|---|---|---|
displayName | string (1–120 chars) | No | Human-friendly label. Defaults to 'My ggui app' when absent — matches the auto-create path in useGguiUser. |
Returns: the full AppRecord shape as above.
Follow-up: call ggui_ops_set_default_app({appId}) to promote the new app to the user’s default.
ggui_ops_rename_app
Section titled “ggui_ops_rename_app”Update an existing app’s displayName. The target app MUST be owned by the calling user.
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Target GguiApp.appId. Discover via ggui_ops_list_apps. |
displayName | string (1–120 chars) | Yes | New display name. Cap matches the cloud provisioning Lambda. |
Returns: the updated AppRecord.
Errors:
| Code | When |
|---|---|
app_not_found | The id doesn’t exist OR exists under another tenant (uniform shape — no existence leak). |
ggui_ops_delete_app
Section titled “ggui_ops_delete_app”Hard-delete an app owned by the calling user. Idempotent — a second delete of the same id resolves cleanly. The cloud adapter additionally cascades per-app keys / blueprints / sessions (orchestrated below the seam).
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Target GguiApp.appId. |
Returns: { deleted: true }
Tenancy: cross-tenant probes return the success shape without touching the foreign row. Uniform with “row didn’t exist.”
ggui_ops_set_default_app
Section titled “ggui_ops_set_default_app”Set the calling user’s GguiUser.defaultAppId — the universal MCP route resolves this on every request to scope the session. The handler first verifies the caller owns the target appId before writing User.defaultAppId.
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Target app — must be owned by the caller. |
Returns: { defaultAppId: string }
Errors:
| Code | When |
|---|---|
app_not_found | Target appId doesn’t exist OR is owned by another tenant. |
ggui_ops_update_app_system_prompt
Section titled “ggui_ops_update_app_system_prompt”Set or clear the per-app system-prompt override. Empty-string input clears the field — the pod’s per-app system-prompt resolution then falls back to the universal default.
| Field | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Target GguiApp.appId. |
systemPrompt | string (≤10,000 chars) | Yes | Replacement text. Pass "" to clear the override. Cap bounds the response payload and matches a reasonable agent-authored prompt length. |
Returns: the updated AppRecord (with systemPrompt omitted when cleared).
Errors:
| Code | When |
|---|---|
app_not_found | Target appId doesn’t exist OR is owned by another tenant. |
Orgs (ops-orgs, 4 handlers)
Section titled “Orgs (ops-orgs, 4 handlers)”Operator actions on GguiOrg + GguiOrgMember + GguiOrgInvite rows. Orgs are the unit of multi-user collaboration; each row carries orgId (ULID), name, ownerUserId, plus per-membership role on the join rows. Bound on the cloud pod via the provisionGguiOrg / fetchMyOrgs / issueOrgInvite / revokeOrgInvite AppSync mutations.
ggui_ops_list_orgs
Section titled “ggui_ops_list_orgs”Enumerate every org the calling user belongs to — owner + admin + member memberships in a single list, each row carrying the caller’s role.
Inputs: none.
Returns: { orgs: OrgMembershipRecord[] }
interface OrgMembershipRecord { readonly orgId: string; readonly name: string; readonly ownerUserId: string; readonly role: "owner" | "admin" | "member"; readonly joinedAt: string;}Mirrors the AppSync fetchMyOrgs custom resolver. Use to discover orgId before calling the invite tools.
ggui_ops_create_org
Section titled “ggui_ops_create_org”Provision a fresh GguiOrg owned by the calling user. Wraps the cloud’s provisionGguiOrg mutation — ULID orgId minted server-side; an owner membership row and a zero-balance credit row are inserted atomically via TransactWrite.
| Field | Type | Required | Description |
|---|---|---|---|
name | string (1–120 chars) | Yes | Human-friendly display name. Required (no default — orgs are intentional creations). |
Returns:
interface CreateOrgOutput { readonly orgId: string; readonly name: string; readonly ownerUserId: string; readonly createdAt: string; readonly updatedAt: string;}ggui_ops_invite_to_org
Section titled “ggui_ops_invite_to_org”Issue an admin- or member-role invite to a GguiOrg the caller can administer. The invite link in the recipient’s email points at the console (coming soon): console.ggui.ai/invites/<inviteId>.
| Field | Type | Required | Description |
|---|---|---|---|
orgId | string | Yes | Target org — caller must own or administer it. Discover via ggui_ops_list_orgs. |
email | string (RFC 5322) | Yes | Recipient email — the invite link is sent here. |
role | 'admin' | 'member' | Yes | Role the recipient holds once they accept. Owner can’t be granted via invite — ownership transfer is a separate flow. |
Returns:
interface InviteToOrgOutput { readonly inviteId: string; readonly orgId: string; readonly email: string; readonly role: "admin" | "member"; readonly inviterUserId: string; readonly status: "pending" | "accepted" | "revoked" | "expired"; readonly expiresAt: string; readonly createdAt: string; readonly reused: boolean;}Anti-double-issue: an existing pending invite for the same (orgId, email) is reused — no new row, no second email. reused: true flags the dedup.
Errors:
| Code | When |
|---|---|
org_invite_access_denied | Caller is not owner/admin of the target org. |
ggui_ops_revoke_invite
Section titled “ggui_ops_revoke_invite”Invalidate a pending org invite — the bearer-secret link in the recipient’s email stops working immediately.
| Field | Type | Required | Description |
|---|---|---|---|
inviteId | string | Yes | Target invite — must belong to an org the caller can administer. |
Returns:
interface RevokeInviteOutput { readonly inviteId: string; readonly status: "pending" | "accepted" | "revoked" | "expired"; readonly alreadyRevoked: boolean;}Concurrency: the adapter flips status from pending → revoked via a CAS ConditionExpression. A racing accept surfaces a clear conflict instead of silently overwriting. Already-revoked invites return alreadyRevoked: true; already-accepted invites reject.
Errors:
| Code | When |
|---|---|
org_invite_access_denied | Caller is not owner/admin of the invite’s org. |
org_invite_not_found | The id doesn’t exist OR isn’t reachable by the caller. |
Connector keys (ops-connector-keys, 3 handlers)
Section titled “Connector keys (ops-connector-keys, 3 handlers)”Operator actions on GguiUserApiKey rows — the user-facing ggui_user_* API key strings that Claude Desktop (and other Connectors) present to call the MCP routes on the user’s behalf. Bound on the cloud pod via the issueGguiUserApiKey AppSync mutation + the apiKeysByUserId GSI + raw DDB UpdateItem for revoke.
ggui_ops_list_connector_keys
Section titled “ggui_ops_list_connector_keys”Read the calling user’s ggui_user_* connector keys. Metadata only — NEVER plaintext.
Inputs: none.
Returns: { keys: ConnectorKeySummary[] }
interface ConnectorKeySummary { readonly id: string; // stable id for revoke readonly apiKeyPrefix: string; // first ~8 chars of the secret (human re-identification) readonly name?: string; // user-supplied label readonly appId?: string; // optional FK — when set the key locks to that app readonly status: "active" | "revoked"; readonly createdAt: string; readonly lastUsedAt?: string; // from the last successful auth lookup readonly expiresAt?: string; // past timestamp ⇒ adapter rejects auth}The hash itself is never returned on any tool.
ggui_ops_issue_connector_key
Section titled “ggui_ops_issue_connector_key”Mint a fresh ggui_user_* connector key.
| Field | Type | Required | Description |
|---|---|---|---|
name | string (1–120 chars) | No | Optional label, e.g. 'MacBook Claude Desktop'. Surfaces on ggui_ops_list_connector_keys. |
appId | string | No | Lock the key to one app. When set, sessions opened with this key scope to the named app and meta-tools (ggui_ops_open_app, ggui_ops_list_apps) are NOT exposed. Absent ⇒ universal key (scopes to User.defaultAppId per request). |
expiresAt | string (ISO 8601) | No | Optional expiry. Past timestamps reject auth from the start. |
Returns:
interface IssueConnectorKeyOutput { // metadata — same shape as a list row readonly id: string; readonly apiKeyPrefix: string; readonly name?: string; readonly appId?: string; readonly status: "active" | "revoked"; readonly createdAt: string; readonly lastUsedAt?: string; readonly expiresAt?: string; // ONE-TIME REVEAL — never returned again readonly plaintextKey: string;}ggui_ops_revoke_connector_key
Section titled “ggui_ops_revoke_connector_key”Soft-revoke a GguiUserApiKey row. The adapter sets status='revoked'; the auth path rejects revoked keys regardless of hash match. Rows are kept for audit (age-based sweep handles cleanup).
| Field | Type | Required | Description |
|---|---|---|---|
keyId | string | Yes | Stable id of the row (NOT the secret string). Discover via ggui_ops_list_connector_keys. |
Returns:
interface RevokeConnectorKeyOutput { readonly id: string; readonly status: "active" | "revoked"; readonly alreadyRevoked: boolean;}Errors:
| Code | When |
|---|---|
connector_key_access_denied | The key belongs to another user. |
connector_key_not_found | No such key reachable by the caller. |
Idempotent — re-revoking returns alreadyRevoked: true.
Coupons (ops-coupon, 1 handler)
Section titled “Coupons (ops-coupon, 1 handler)”Operator action on GguiCoupon rows — bearer-secret promo codes that credit user or org wallets. Bound on the cloud pod via the redeemCoupon AppSync mutation.
ggui_ops_redeem_coupon
Section titled “ggui_ops_redeem_coupon”Redeem a cpn_* coupon code, crediting the caller’s wallet (default) or a target org’s wallet. The adapter runs an atomic three-leg TransactWrite:
- Flip
GguiCoupon.statusfromissued→activated. - Credit the wallet (user or org).
- Insert a ledger row.
Failure of any leg rolls all back — no half-credit, no double-spend.
| Field | Type | Required | Description |
|---|---|---|---|
couponCode | string | Yes | The bearer-secret code in format cpn_<8 chars>. One-time redemption. |
targetOrgId | string | No | When set, credits the named org’s wallet instead of the caller’s personal wallet. Caller MUST be a member of the org. |
Returns:
interface RedeemCouponOutput { readonly couponCode: string; readonly creditCents: number; readonly redeemedByPrincipalType: "user" | "org"; readonly redeemedByPrincipalId: string; readonly activatedAt: string;}Errors:
| Code | When |
|---|---|
coupon_not_found | The code doesn’t exist. |
coupon_already_redeemed | The code was previously activated (one-time semantics). |
coupon_expired | The code is past its expiry. |
coupon_access_denied | targetOrgId was provided but the caller is not a member of that org. |
Blueprints (ops-blueprint, 5 handlers)
Section titled “Blueprints (ops-blueprint, 5 handlers)”Operator blueprint authorship — generate, register, list, update, delete cached blueprints for the calling app. Unlike the four console-style domains, this family registers on the OSS server via the opsBlueprint dep bundle on defaultHandlers (registry + blueprint store + search; generate additionally requires the resolveLlm + blueprints deps the render generation path reads).
ggui_ops_generate_blueprint
Section titled “ggui_ops_generate_blueprint”Author a blueprint via the bound generator (LLM generation + validation).
| Field | Type | Required | Description |
|---|---|---|---|
contract | object | Yes | The DataContract to generate against. |
generator | string | No | Generator slug. Unknown slug fails with generator_not_found. |
persona | string | No | Variance axis — normalized lowercase + trimmed. |
aesthetic | string | No | Variance axis. |
context | string | No | Variance axis. |
seedPrompt | string | No | Variance axis. |
setAsOperatorDefault | boolean | No | Promote the result to the operator default for its contract. |
Returns: { blueprintId, codeHash?, validatorScore?, source } — validatorScore (0–1) only on the advanced generator path; source is the stamped provenance { kind: 'llm', generator, model } from the engine’s own metadata stamp.
Errors: generator_not_found; missing_credentials (BYOK fix: ggui_ops_set_provider_key); generation failure.
ggui_ops_register_blueprint
Section titled “ggui_ops_register_blueprint”Register pre-built component code verbatim — no LLM, no validator. Operator entry point for fixture seeding and export/reimport round-trips.
| Field | Type | Required | Description |
|---|---|---|---|
contract | object | Yes | The DataContract the code implements. |
componentCode | string | Yes | The component code to register verbatim (min 1 char). |
Plus the same optional generator / persona / aesthetic / context / seedPrompt / setAsOperatorDefault fields as ggui_ops_generate_blueprint.
Returns: { blueprintId, codeHash, source } — source is always { kind: 'user' }; hand-supplied bytes carry no engine claim, so none is recorded.
ggui_ops_list_blueprints
Section titled “ggui_ops_list_blueprints”| Field | Type | Required | Description |
|---|---|---|---|
contractHash | string | No | Filter by canonical contract hash. |
generator | string | No | Filter by generator slug. |
persona | string | No | Dispatches semantic search. |
intentKeywords | string[] | No | Dispatches semantic search. Filters are AND-composed. |
Returns: { blueprints: Blueprint[] }
ggui_ops_update_blueprint
Section titled “ggui_ops_update_blueprint”| Field | Type | Required | Description |
|---|---|---|---|
blueprintId | string | Yes | Target blueprint. |
isOperatorDefault | literal true | No | Promote to operator default. |
variance | object | No | Partial-merge of variance axes; {persona: ""} clears the field. |
Returns: { blueprintId, updatedAt }
ggui_ops_delete_blueprint
Section titled “ggui_ops_delete_blueprint”| Field | Type | Required | Description |
|---|---|---|---|
blueprintId | string | Yes | Target blueprint. |
Returns: { deleted: true } — idempotent.
Provider keys (provider-keys, 3 handlers) — BYOK
Section titled “Provider keys (provider-keys, 3 handlers) — BYOK”Operator actions on the caller’s BYOK LLM provider keys. Provider enum: 'anthropic' | 'openai' | 'google' | 'openrouter'. The handler factories ship in @ggui-ai/mcp-server-handlers; they are bound today by the hosted cloud pod (coming soon), which validates keys against the provider and encrypts at rest.
ggui_ops_set_provider_key
Section titled “ggui_ops_set_provider_key”| Field | Type | Required | Description |
|---|---|---|---|
provider | enum | Yes | One of anthropic / openai / google / openrouter. |
plaintextKey | string | Yes | The provider API key (min 1 char). Re-set replaces (rotation). |
label | string | No | Human label. |
Returns: { provider, label?, lastFour, createdAt?, lastUsedAt? } — never echoes the key.
ggui_ops_list_provider_keys
Section titled “ggui_ops_list_provider_keys”Inputs: none.
Returns: { keys: [{ provider, label?, lastFour, createdAt?, lastUsedAt? }] }
ggui_ops_remove_provider_key
Section titled “ggui_ops_remove_provider_key”| Field | Type | Required | Description |
|---|---|---|---|
provider | enum | Yes | Provider to remove. |
Returns: { deleted, provider }
Credits (credits, 2 handlers)
Section titled “Credits (credits, 2 handlers)”Read-only views over the caller’s prepaid credit wallet. Bound by the hosted cloud pod (coming soon); self-hosted deployments have no credit plane.
ggui_ops_get_credit_balance
Section titled “ggui_ops_get_credit_balance”Inputs: none.
Returns: { balanceCents, lifetimeGrantedCents, lifetimeSpentCents, updatedAt }
ggui_ops_list_credit_transactions
Section titled “ggui_ops_list_credit_transactions”| Field | Type | Required | Description |
|---|---|---|---|
limit | number | No | 1–100, default 20. |
cursor | string | No | Pagination cursor. |
Returns: { transactions: [{ transactionId, kind, deltaCents, balanceAfterCents, reason, createdAt, relatedSessionId? }], nextCursor? } — kind is one of free_credit / render_charge / topup / refund.
OSS vs hosted
Section titled “OSS vs hosted”The four console-style domains are wired through optional fields on CreateGguiServerOptions (ops-blueprint hangs off the opsBlueprint dep bundle on defaultHandlers; provider-keys + credits are cloud-pod-bound):
interface CreateGguiServerOptions { readonly opsApps?: { readonly apps: AppsSource; readonly userDefaultApp: UserDefaultAppSource; }; readonly opsOrgs?: { readonly orgs: OrgsSource; readonly invites: OrgInvitesSource; }; readonly opsConnectorKeys?: { readonly connectorKeys: ConnectorKeysSource; }; readonly opsCoupon?: { readonly coupons: CouponRedeemSource; };}- Hosted (
mcp.ggui.ai, coming soon): the cloud pod binds all four — AppSync-backed adapters wrap the corresponding mutations — plus the provider-keys and credits families. The full ops surface is registered on/ops. - OSS (
ggui serve): every field isundefinedby default. The route still mounts buttools/listrejects withMethod not found— no tools capability is advertised when zero handlers are registered. Operator tools only make sense alongside a data model to operate on; the ops-blueprint family is the one most self-hosters wire (via theopsBlueprintdep bundle). - Partial wiring: omit individual fields to drop their tools. A self-hosted deployment with its own
AppsSourcecan registerggui_ops_*_apponly and leave orgs / connector keys / coupons unwired.
The seam interfaces (AppsSource, OrgsSource, OrgInvitesSource, ConnectorKeysSource, CouponRedeemSource) are exported from @ggui-ai/mcp-server-handlers — implementing them against your own backend is the integration path for downstream forks.
Console parity
Section titled “Console parity”The console UI (coming soon) will mirror these tools 1:1 — every tool corresponds to one UI action:
| Tool | Console surface |
|---|---|
ggui_ops_list_apps | Apps section — main list. |
ggui_ops_create_app | Apps section — “New app” button. |
ggui_ops_rename_app | Apps section — inline rename. |
ggui_ops_delete_app | Apps section — row menu → Delete. |
ggui_ops_set_default_app | Apps section — “Set as default” toggle. |
ggui_ops_update_app_system_prompt | Apps section → System Prompt editor. |
ggui_ops_list_orgs | Orgs section — main list. |
ggui_ops_create_org | Orgs section — “New org” button. |
ggui_ops_invite_to_org | Orgs section → Members → Invite. |
ggui_ops_revoke_invite | Orgs section → Members → pending invite row → Revoke. |
ggui_ops_list_connector_keys | Account → Connector Keys list. |
ggui_ops_issue_connector_key | Account → Connector Keys → “Issue new key”. |
ggui_ops_revoke_connector_key | Account → Connector Keys → row menu → Revoke. |
ggui_ops_redeem_coupon | Billing → Redeem coupon. |
The MCP surface and the UI surface are siblings over the same seam — they call the same AppsSource.create, the same OrgInvitesSource.issue, etc. There’s no privileged path on either side.
Example: curl
Section titled “Example: curl”This walkthrough targets a self-hosted server with the ops seams wired (started with --dev-allow-all for the Bearer dev shortcut). On hosted ggui (coming soon), the same calls will go to https://mcp.ggui.ai/ops with an OAuth bearer.
# 1. Initializecurl -X POST http://127.0.0.1:6781/ops \ -H "Authorization: Bearer dev" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"curl","version":"1.0"},"capabilities":{}}}'
# 2. Enumerate the caller's appscurl -X POST http://127.0.0.1:6781/ops \ -H "Authorization: Bearer dev" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ggui_ops_list_apps","arguments":{}}}'
# 3. Create a fresh appcurl -X POST http://127.0.0.1:6781/ops \ -H "Authorization: Bearer dev" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"ggui_ops_create_app","arguments":{"displayName":"Inbox Triage"}}}'
# 4. Promote the new app to default (use the appId from step 3's response)curl -X POST http://127.0.0.1:6781/ops \ -H "Authorization: Bearer dev" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"ggui_ops_set_default_app","arguments":{"appId":"<appId>"}}}'
# 5. Issue a connector key locked to the new app# The response carries `plaintextKey` — surface it to the user immediately.curl -X POST http://127.0.0.1:6781/ops \ -H "Authorization: Bearer dev" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"ggui_ops_issue_connector_key","arguments":{"name":"MacBook Claude Desktop","appId":"<appId>"}}}'The same calls can be made through the @modelcontextprotocol/sdk client by pointing the transport at /ops instead of /mcp — the tool registration shapes are standard.
See Also
Section titled “See Also”- Console — the human-facing surface for the same actions (coming soon).
- Audience Routes — the
agent/runtime/protocol/opstag model and how it projects to routes. - MCP Protocol Reference — the sibling agent-loop surface on
/mcp.