Skip to main content
An installed app has one bundle identity and one or more account connections. Shared code is installed once. Credentials, account identity, and permission choices belong to each connection.

Choose the authentication flow

The CLI drives the setup plan supplied by firmware. Provider endpoints, client registration, scopes, and callback URLs must match the provider’s requirements. Supporting a setup card does not establish that every provider works with it.

Separate installation from account setup

For a process-based app, use truffile_version: 4 and connections: multi when multiple accounts are supported. Install-scoped steps prepare shared code; connection-scoped steps collect or validate credentials for the first account, additional accounts, and reconnections.
This is a setup fragment, not a complete app. Add a provider validation step before reporting the account as connected. The selected connection’s runtime receives its own SERVICE_API_KEY; do not embed secrets in the manifest or shared app files. For custom Python apps, expose provider-confirmed identity through truffle://user-info so account labels are meaningful.

A remote MCP connector with OAuth

A native connector uses truffile_version: 5 and metadata.foreground.connector. Firmware connects to the upstream MCP server; you do not need to write a Python proxy for this example. Create a directory containing icon.png and this truffile.yaml. Use your own unique bundle ID if you adapt the example for distribution.
This follows the Todoist flow verified with two separate accounts. The connector and OAuth step use the same app_var_key, which identifies connection-scoped token state. The identity mapping reads Todoist’s user-info tool; other providers need their own verified identity mapping.
The permissions command prompts for the account. Repeat it for each account you want to restrict. The provider’s OAuth scopes and Truffle’s account permissions are separate: granting a provider scope does not override Truffle’s permission settings.

OAuth callbacks and credentials

If the browser runs on the same computer as the CLI, the local callback listener can receive a registered localhost redirect. If you open the browser on another computer, copy its final localhost callback URL into the waiting CLI prompt, even if that browser cannot load the callback page. The CLI validates the state against the pending request. Use the current sign-in link. Firmware may expire a setup after 15 minutes; a longer CLI timeout does not extend that window. Start a fresh setup after expiry. Never reuse a previous callback for another account. Firmware exchanges and stores native OAuth credentials. Custom Python apps should use the matching runtime credential helpers; connection-scoped tokens must not be copied into app-global variables or source files. Native connectors handle credential use and refresh through firmware.

Account lifecycle and permissions

Friendly app names, bundle IDs, and app UUIDs work. An omitted account opens a picker. In the rename example, choose an account and name it Work. To avoid prompting, supply an exact account alias or connection UUID; deletion also requires --yes in scripts. New accounts use device policy unless an account override exists. Set an override with read-only, read-write, or auto-review; default removes the override and inherits device policy. The CLI does not change device-wide defaults or offer an atomic install-and-set-permissions option. Tool annotations such as readOnlyHint describe tool behavior; they are not permission grants. An uploaded --update preserves installed accounts and their settings. --replace intentionally resets accounts after confirmation. Test both accounts after an update and verify that reconnecting one leaves the other intact. See deployment and account commands for cancellation, noninteractive forms, packaging rules, and firmware requirements.