UUniCAS DOCS
Console GitHub
UNICAS / DOCUMENTATIONEdit source

#UniCAS Control-Plane CLI

@unicas/admin-cli (bin unicas) is the operator-facing command line for UniCAS control plane. It exists because DeepSeek Harness's MCP client only supports static headers and therefore cannot complete the OAuth authorization-code flow that protects https://api.unicas.work/mcp. The CLI authenticates through the control-plane BFF instead: it opens the BFF's /admin/auth/cli/authorize, the BFF runs Google OIDC (client secret held server-side) and the email allowlist, then redirects the browser back to the CLI's loopback with a one-time code that the CLI exchanges (PKCE) for a BFF session cookie + CSRF token, persisted to ~/.unicas/session.json (0600). The CLI never talks to Google and needs no client id or secret of its own. Every command calls the typed @unicas/admin-client over the /admin HTTP API with that session; unicas mcp is a local stdio MCP server backed by the same HTTP client (no MCP-to-MCP forwarding).

See packages/admin-cli/README.md for the full command reference.

#Quick start

pnpm --filter @unicas/admin-cli build
pnpm --filter @unicas/admin-cli unicas login        # browser: Google sign-in + consent (via the BFF)
pnpm --filter @unicas/admin-cli unicas whoami
pnpm --filter @unicas/admin-cli unicas stacks list
pnpm --filter @unicas/admin-cli unicas stacks create "Operations" --idempotency-key create-ops-1
pnpm --filter @unicas/admin-cli unicas logout       # ends the BFF session (POST /admin/auth/logout)

#DSH integration (stdio MCP)

Configure DeepSeek Harness's mcp-client with a stdio server:

{
  "transport": "stdio",
  "serverName": "unicas",
  "command": "unicas",
  "args": ["mcp"]
}

unicas mcp advertises the identical tool contract as the remote control plane and forwards calls over the authenticated connection, so DSH can read and operate the control plane without any OAuth implementation of its own. To put unicas on PATH from the checkout, run pnpm --filter @unicas/admin-cli build and then pnpm install --global ./packages/admin-cli (pnpm 10+ removed pnpm link --global), or configure the client with command: "node" and args: ["<checkout>/packages/admin-cli/dist/cli.js", "mcp"].

On Windows the global bin is a .CMD shim; a Node-based MCP client must spawn it with shell: true, reference the shim path directly, or use the node + dist/cli.js form above (a plain spawn("unicas") fails with ENOENT).

Alternatively, skip MCP entirely and have DSH run plain shell commands (unicas stacks list, unicas whoami, …); the CLI prints JSON on stdout.

#Command surface = control-plane MCP tool set

Group Commands
Read (control:read) whoami, stacks list/get, members list, oauth-issuer get, ref-domains list, audit control/root-domain-refs/root-domain-events
Write (control:write) stacks create (idempotency key), stacks update (ETag)
Security (control:security) members invite/remove, oauth-issuer inspect/activate

Creation tools take or auto-generate an idempotency key; mutations on existing resources resolve the current ETag when none is passed; destructive operations require an explicit --confirm-* flag matching the target (or a TTY prompt). oauth-issuer activate accepts only a compact-JWS activation proof signed off-CLI with a key the discovered issuer advertises — private key material is never a valid input, and there is no manual JWK upload path: UniCAS derives keys exclusively from verified issuer JWKS discovery.

The WebUI reads the optional custom issuer with GET /admin/stacks/:stackId/oauth-issuer?optional=true. An authorized member receives 200 null when no custom issuer is configured; membership and stack errors remain errors. Omitting optional preserves the default 404 NOT_FOUND behavior used by the CLI and MCP. Configured issuers still return their metadata and ETag.

The Playground retains opened file-root snapshots and each root's current path in page memory. Switching roots or folders reuses those snapshots without a network request. Refresh reloads the selected root from the server and invalidates other cached roots whose catalog revision or manifest hash changed. Successful edits update the working snapshot; failed edits discard uncommitted changes and evict it. Leaving the Playground or changing stacks clears these working snapshots.

Immutable node metadata and completely read small node content use the independent @unicas/tenant-browser-cache strategy (8 MiB memory, 64 MiB IndexedDB per endpoint/principal, 4 MiB entry limit). These bytes survive page reloads. Keys also include stack, tenant and hash. Session identity discovery, capability issuance and the mutable root catalog remain live server reads before reopening a root; a changed manifest hash loads new content. Refresh reloads the catalog, but may reuse unchanged immutable bytes. Logout clears the current principal's persisted entries across endpoints, including prior page sessions. Storage failures degrade to memory/network; deletion is best-effort when browser storage is unavailable. Tenant capabilities remain memory-only and renew when a request needs an unexpired token. A cached node is not proof of current server existence, permission, lease or retention; usage, GC and other mutable node state are never cached here.

#Testing

pnpm --filter @unicas/admin-cli test
pnpm --filter @unicas/admin-cli typecheck
pnpm check:workspace

Unit tests mock the BFF /admin API (login/exchange, control-plane operations) and a stateless stdio MCP server; they never touch production. A real unicas login + unicas whoami against https://console.unicas.work/admin is a manual verification step (browser Google sign-in + UniCAS consent required).