-p/--profile to target another one. Four integrations are available in every profile — claude-code (Anthropic API key or Claude Code OAuth credentials), codex (OpenAI API key or Codex OAuth credentials — VMs created with the profile get a logged-in codex plus the profile’s MCP endpoint preconfigured), github (a GitHub App installation for gh and git push/clone — you pick exactly which repositories during connect), and machine0-cli (a machine0 API key so the machine0 CLI works inside a VM) — and you can add any remote HTTP MCP server as a custom integration (for example Linear’s official server at https://mcp.linear.app/mcp). Each is connected per profile.
Credentials are stored encrypted at rest. OAuth connections open your browser for the provider’s consent screen; claude-code also asks you to paste the Claude callback URL or code#state back into the CLI, which exchanges the code with Anthropic locally before uploading the encrypted credential payload to machine0. codex works the same way but captures the browser callback automatically on localhost:1455 (the same loopback codex login uses) — if the callback can’t reach your terminal (running the CLI over SSH, or the port is busy), paste the full callback URL from the browser’s address bar instead; the OpenAI token exchange also runs locally. machine0-cli connects instantly (it mints an API key server-side, visible in your account’s token list as machine0-cli-integration).
integrations ls
List a profile’s integrations with their connection status — system integrations first, then custom MCP integrations, each group alphabetical.
Plain
disconnected covers two cases; the reason differs, so read the row’s message rather than assuming it always means “run connect”. The detail view (integrations get <integration>) shows configured: no when the server has no OAuth credentials for the provider.
Each integration’s credentials record when they were last verified — shown in the Last checked column of the profile-wide integrations check output and carried as a full timestamp in --json. The stamp is set when the integration connects and refreshed by every integrations check; for MCP integrations that authenticate with OAuth, the server also verifies (and silently refreshes) the stored tokens on a periodic background sweep, so it can advance without you running anything — and a token the provider has revoked or expired is detected and cleared in the background too, flipping the integration to disconnected before you next use it. -- means never checked — for custom MCP servers that need no authentication this is permanent, since there are no stored credentials to record a check against (the server is still probed and its tools still served).
integrations get
Show one integration’s details — its status, which external account is connected (an API-key connection shows a masked key label instead, e.g.API key (sk-ant-...abcd)), description, and the last check’s outcome. For MCP integrations the CLI also probes the server live and lists the prompts and tools it exposes through the profile’s MCP endpoint (with any prefix and tool whitelist applied), and the attributes include the configured prefix (-- when unset) and tool whitelist. The live probe updates the integration’s stored health exactly like integrations check does — including the silent OAuth token refresh, and removal of dead credentials when the refresh is rejected upstream. Built-in integrations keep the fast attribute-only view.
integrations new
Add a remote HTTP MCP server to a profile as a custom integration. Aliased asintegrations create and integrations add.
Integration names follow the same rules as machine names (letters, digits, and dashes; max 63 characters), and built-in names and close aliases (claude-code, claude, anthropic, codex, openai, chatgpt, github, machine0-cli, and machine0) are reserved. Each profile holds up to 50 integrations, and the same name can exist in different profiles. Only HTTP(S) MCP servers are supported, and URLs must be public — private, loopback, and link-local addresses are rejected.
Integrations added with integrations new get a default tool/prompt prefix of <name>_ — an integration named deepwiki exposes its tools as deepwiki_<tool> through the profile MCP endpoint, so each integration’s tools are namespaced. Change or remove it with integrations update --prefix. Integrations added before this default existed are unchanged: any prefix you set still applies, and unprefixed ones serve the server’s original names until you set a prefix. profiles duplicate copies each integration’s prefix as-is.
integrations update
Update a custom MCP integration’s settings. Built-in integrations can’t be updated. Pass"" to any flag to clear it.
How the two compose:
- The whitelist matches the server’s original (unprefixed) tool names, case-sensitively —
listadmitslist_pagesandlist_usersbut notmy_listorList_all. Changing the prefix never changes what the whitelist admits. - Because entries are prefixes, the upstream can later add a new tool that matches one and it will be proxied automatically — even a full tool name as an entry also admits future extensions of it (
deleteadmits a laterdelete_all). There is no exact-match mode; pick entries as specific as possible, and re-runintegrations checkafter upstream changes to see exactly what is exposed. - Entries are limited to letters, digits, dots, underscores, and hyphens (max 128 characters). A tool whose name uses other characters (
search$web, non-ASCII names) can only be admitted through a shorter prefix entry that stops before the first unsupported character. - The prefix applies to prompts too; the whitelist applies only to tools.
integrations checkand MCP clients connected to the profile endpoint see exactly the same prefixed, filtered names.- A whitelist that matches nothing (e.g. a sentinel entry like
none) blocks every tool from that server. - The prefix keeps names valid MCP identifiers, but a very long upstream name plus a prefix can still exceed a client’s own name limits.
integrations rm
Remove a custom integration from a profile, deleting its stored credentials. Built-in integrations can’t be removed — usedisconnect instead.
integrations connect
Connect (authenticate) an integration in a profile.claude-code and codex support two authentication methods, and connect asks which one to use — for claude-code the choices are Anthropic Subscription and Anthropic API Key; codex offers API key (its default) and account-based OAuth:
- API key (
claude-code: “Anthropic API Key”) — paste your own Anthropic (sk-ant-...) or OpenAI (sk-...) API key. This is the right choice for Anthropic Console (API-usage billing) accounts — create a key in your Anthropic Console first. The CLI validates it against the provider first (a quick models-list call), then stores it encrypted. Usage bills to your provider account’s API credits. No browser, no token refresh. A key the provider rejects outright (401) is refused; a permission-scoped key that can’t list models (403 — e.g. an OpenAI restricted project key) prompts a confirm-to-store instead, andintegrations checkreports it as valid with a scoped-key note. - Account (OAuth) (
claude-code: “Anthropic Subscription”) — the browser consent flow, using your Anthropic or ChatGPT account.claude-codeasks you to paste the callback URL orcode#stateshown by Claude back into the CLI; the CLI performs the Anthropic token exchange locally and stores the OAuth login as-is. Choose this only for an account with a Claude subscription (Pro/Max/Team): an OAuth login from an Anthropic Console (API-billing) account authenticates but can’t runclaudeon a VM —integrations checkflags such a login and tells you to reconnect with the API-key option.codexopens the same ChatGPT consent screencodex loginuses and captures the callback automatically onlocalhost:1455— no pasting needed unless the browser can’t reach the terminal’s machine (CLI over SSH, port in use), in which case paste the callback URL from the address bar; its token exchange also runs locally.
--auth <api-key|oauth> skips the interactive chooser. In a non-interactive session, --auth api-key reads the key from stdin; without --auth, scripts get the OAuth behavior as before. There is deliberately no flag that takes the key as an argument (it would leak into shell history). In scripts, pass --yes too: it skips the replace-credentials confirmation when the integration is already connected (key rotation) and pre-confirms storing a key the models-list probe couldn’t verify (a scoped key’s 403, or a provider outage).
github opens the browser consent screen. machine0-cli connects immediately without a browser, and so do custom MCP servers that don’t require auth.
connect on an already-connected integration asks for confirmation, then replaces the stored credentials with a fresh grant — including switching between API-key and account-based auth (the next VM create, resume, or profiles deploy injects the new mode and removes the old one’s artifact). Connecting the same integration in another profile is independent — each profile holds its own credentials.
GitHub repository access
Connectinggithub for the first time installs the machine0 GitHub App on your account or an organization, and GitHub’s install screen lets you grant access to all repositories or only the repositories you select. The stored token can only reach what the installation covers.
- Change repo access anytime at github.com/settings/installations — changes take effect immediately, no reconnect needed. VMs already running with the profile’s credentials follow along, since the token’s access tracks the installation.
- Add another organization without reconnecting. Once the app is installed anywhere, re-running
connectcompletes without showing GitHub’s install screen again. To grant access to an additional org or account, use the install link that a passingintegrations checkprints (github.com/apps/<app>/installations/new) — the stored credentials reach new installations automatically, so nothing else is needed afterwards. - Organizations may require admin approval. If your org does, the install becomes a request and the browser page tells you how to finish: either re-run
machine0 integrations connectafter an admin approves it, or — when the page says authorization is already complete — just runmachine0 integrations checkafter approval. Until the admin approves,integrations checkfails because the credentials reach no repositories. - Access is limited to accounts and orgs where the app is installed. (The previous flow’s
read:orgscope covered every org you belonged to; the App model does not.) gh gistis not supported — GitHub Apps have no access to the Gist API. Use a personal access token inside the VM if you need gists.
integrations disconnect
Disconnect an integration, deleting its stored credentials in that profile.github and machine0-cli also get best-effort revocation on the provider side; claude-code, codex, and custom MCP credentials are simply deleted — for API-key connections in particular, the key stays valid at the provider until you revoke it in the Anthropic or OpenAI console. The integration shows as disconnected until you reconnect. Other profiles’ connections are unaffected.
Disconnecting does not reach into VMs that were already provisioned with the profile — see the warning at the top of this page: credentials injected into running VMs, suspend snapshots, and custom images stay there until you remove them on the VM or destroy it.
Because the deletion is unrecoverable (reconnecting needs a fresh grant), the command asks for confirmation naming the integration and profile; pass --yes to skip it.
integrations check
Check integration connections — one integration, or the whole profile at once. Exits non-zero when a check fails, so it’s scriptable. (integrations inspect works as an alias.)
Checking one integration
The output shows astatus line (this run’s result) and a message line explaining it. Status collapses to two values: connected when the check passes, disconnected when it fails. When the run flips a previously-connected integration to failing, the status reads connected -> disconnected in red with the message in red too. The outcome is remembered: after a failed check the integration shows disconnected in red in integrations ls (with the failure and time on the detail view) until a check passes or you reconnect. claude-code’s and codex’s checks run locally in the CLI and report their outcomes back the same way. For MCP servers, the server’s prompts and tools are listed (PROMPTS above TOOLS), with any configured prefix and tool whitelist applied — exactly the names MCP clients see through the profile endpoint. One exception for OAuth-connected MCP servers: an expired access token is refreshed silently during the check, and if the server rejects the refresh (the token was revoked upstream), machine0 removes the dead credentials — the integration returns to disconnected and needs integrations connect again.
What each integration’s live check does:
Checking the whole profile
Omitting the integration checks every integration in parallel, updating each one’s stored status and last-checked time, then lists all the prompts and tools the profile’s MCP endpoint exposes (PROMPTS above TOOLS) — one row per entry with the integration it comes from. The profile’s own prompts are listed first, attributed toprofile; per-integration prefixes and tool whitelists are applied. If two servers expose the same name, the endpoint serves the one from the last-listed integration; the other rows are marked (shadowed) — and a profile prompt always wins over a same-named upstream prompt.
Integrations are listed system-first, then MCP, each group alphabetical — the same order as integrations ls (--json keeps the server’s own order, so key entries by name). Each row’s status is connected (green), connected -> disconnected (red, when this run flipped a previously-connected integration to failing), or disconnected. A disconnected row is red when it fails the run and plain when it’s benign (a built-in never connected, or a provider not configured on this server). A red row without the arrow — a custom MCP server failing, or an integration that was already failing before this run — still fails the run:
--json returns the full per-integration array with real timestamps (the claude-code entry’s timestamp comes from the CLI’s local check, since that check runs on your machine). Each entry also carries a previousStatus field ("connected" or "disconnected") — the integration’s status before this run — so a script can detect a connected→disconnected flip. One freshness nuance for scripting: the profile-wide form returns each integration’s lastCheck* fields as updated by this run, while the single-integration form shows the attributes as they were before its check ran — the status field is the live answer in both.