Miftah includes an optional, local-only browser Console over its control API. It is a separate foreground process and listener from the MCP /mcp transport. Start the dashboard with:
miftah dashboardWithout --config, the dashboard discovers direct, validated Miftah JSON files in ~/.config/miftah and asks the operator to select one. It does not scan Claude, Cursor, VS Code, process arguments, or arbitrary home directories. Candidate paths must be canonical regular files in that bounded directory. The catalog shows aggregate found, ready, and need-attention counts. Rejected candidates are grouped only as file permissions, invalid configuration, unsafe path or replacement, duplicate, or unreadable/changing file. The Console never returns a rejected candidate's name, path, configuration value, or parser error.
On macOS and Linux, expected configuration files must be owned by the current user, must not be symlinks, and must not grant group or other read/write access; 0600 is the normal generated mode. The standard directory must not be group- or other-writable; 0700 is the normal generated mode. On Windows, discovery instead verifies current-user ownership and a restrictive DACL. If the directory boundary cannot be proved, automatic discovery fails closed. Correct the expected file or directory access outside the browser, run miftah validate --config /absolute/path/to/config.json, and refresh. Do not relax permissions merely to make a candidate appear.
A selection is bound to the verified file content: if the file changes, select it again rather than applying controls to a replacement. If no safe configuration exists and no candidates need attention, first-run onboarding creates ~/.config/miftah/miftah.json only after explicit submission.
miftah dashboard --config <file> is different: it opens exactly that one configuration and does not show or scan a catalog. Use --port <number> for a fixed loopback port, or --no-open to print the URL without launching a browser. The API-only compatibility command remains:
miftah console --config ~/.config/miftah/service.jsonBoth commands bind literal 127.0.0.1 on an ephemeral port by default. There is no host option, LAN mode, background daemon, or automatic startup. Closing the foreground command stops the dashboard.
Startup prints the loopback URL and one CSPRNG-backed bootstrap code to the launching terminal. The code:
- is accepted once at
POST /api/v1/sessionsasAuthorization: Bootstrap <code>; - expires after five minutes and can be replaced by restarting the command or rotating the running host;
- is never placed in a URL, cookie, browser storage, browser response, audit record, or diagnostic; and
- is unrelated to MCP HTTP authentication and OAuth access or refresh tokens.
The Console page asks the operator to type this terminal code. A successful same-origin exchange returns an in-memory CSRF proof and sets an opaque HttpOnly; SameSite=Strict session cookie scoped to /api/v1. The cookie is a session handle, not the bootstrap credential. The CSRF proof remains in page memory and accompanies every later mutation as X-Miftah-CSRF; the UI does not persist it in localStorage or sessionStorage. A bootstrap cannot be replayed.
A normal page reload resumes a still-valid session through GET /api/v1/session: the HttpOnly cookie authenticates the read and the response restores the CSRF proof only to page memory. Expired, reused, malformed, superseded, and wrong-process codes receive distinct redacted states. A missing, expired, or earlier-process session gives one contextual recovery action; when a new foreground process is required, run miftah dashboard in the terminal and use its new URL and code. Recovery does not use localStorage, sessionStorage, a URL, a log, configuration, or an audit argument for the bootstrap code, session handle, or CSRF proof.
Browser sessions have a 15-minute idle limit and a one-hour absolute limit. Restarting, stopping, or rotating the control host invalidates them. Loopback HTTP cannot provide a meaningful Secure cookie flag, so exact Host and Origin validation, SameSite, HttpOnly, one-use bootstrap, CSRF, and short lifetime are all mandatory controls. A hostile process running as the same OS user remains outside this boundary.
Every request must use the exact listener Host. Browser mutations, including bootstrap, must also use the exact listener Origin. Normal same-origin navigation and authenticated GET and HEAD requests may omit Origin, because browsers do not consistently attach it to read requests; hostile or duplicate Origin values are still rejected, cross-site cookies remain blocked by SameSite=Strict, and every mutation still requires exact Origin plus CSRF. Except for the bootstrap exchange and static application assets, every API endpoint requires the session cookie. JSON request bodies are capped at 64 KiB and must use Content-Type: application/json, except the client-entry onboarding endpoint has a bounded envelope limit of up to 136 KiB so it can carry one separately byte-limited 64 KiB pasted document after JSON escaping. An explicitly configured request-size cap remains authoritative. Headers are capped at 16 KiB. The process admits at most 240 trusted requests per minute and only eight bootstrap attempts per minute; excess requests receive 429 plus Retry-After.
POST /api/v1/sessions, POST /api/v1/connections/:ref/connect, POST /api/v1/connections/:ref/test, POST /api/v1/connections/:ref/reauth, and DELETE /api/v1/connections/:ref/credential must send Content-Type: application/json with the JSON body {}. POST /api/v1/connections instead accepts a strict JSON object with required profile, issuer, clientRegistration, and scopes fields plus optional connectionRef and upstream fields; unknown fields are rejected. POST /api/v1/profile-readiness accepts only a selected profile plus an optional upstream; the Console always selects an exact configured upstream when there is more than one. It can invoke only a provider-declared safe read-only check after audit and policy preflight, binds that operation to the browser request so a disconnected client cancels it, and never accepts a tool name or arguments. POST /api/v1/profiles/default accepts only one existing profile name. It changes only the durable defaultProfile through the guarded replacement and audit transaction; it never starts an upstream, opens OAuth, accepts a credential value, or changes provider-owned token caches. POST /api/v1/profiles/rename accepts only an existing profile and a distinct bounded newProfile; unknown fields are rejected. It atomically renames the profile plus configuration-owned default, routing, routing-plugin, profile-lock, and native OAuth connection references. For native OAuth it moves the credential only between exact OS-vault keys and migrates non-secret connection metadata through one recoverable local transaction; credentials never enter configuration, HTTP responses, Console audit records, provider caches, profile state, or identity records. Existing MCP clients remain on their current session until restart. POST /api/v1/profiles/remove accepts only a selected profile and optional replacementProfile. It refuses the last profile and requires a different existing replacement whenever it must redirect a durable default, routing rule, routing-plugin binding, or configured profile lock. It does not resolve or delete secrets, provider caches, token caches, or OS-vault data. A profile with a configured native OAuth binding is rejected because Console will not split configuration removal from the required atomic OS-vault lifecycle. Native-OAuth first run accepts only non-secret configuration name, profile, description, exact resource/issuer, client-registration mode, and scopes. Reviewed-preset first run accepts only safe catalog metadata; for Google Search Console that can include strict named account objects containing a profile name, optional description, and client-secrets path, plus an explicit default when there is more than one account. The local-stdio preset additionally accepts one literal executable, a bounded literal argument array, optional native absolute working directory, optional credential-environment name, and a required acceptLocalCommand: true acknowledgement. It never uses a shell or starts that generic local executable during onboarding; shell/wrapper, URL, control, environment-reference, and credential-shaped values are rejected. On Windows, it accepts only a direct absolute .exe or .com binary, not a bare command or .cmd/.bat shim, so the direct argv path cannot fall back to a command processor. Client-entry first run accepts a bounded in-memory JSON document plus an explicit entry name; it accepts a selected local stdio entry that fits the finite static launch grammar or one credential-free HTTPS remote entry. A remote entry must be a url under mcpServers or servers, explicitly marked type: "http" or "streamable-http"; it must be HTTPS without userinfo, query, fragment, or opaque credential-shaped path segments. The Console never persists, returns, or audits the pasted document, never copies env or headers, and does not discover OAuth or call the remote endpoint during import. On Windows, a local import accepts only a direct absolute .exe or .com executable; bare runners such as npx or node, and .cmd/.bat shims, are rejected rather than being dispatched through a command processor. Token, password, cookie, raw secret contents, callback, environment wrappers, inline code, opaque values or assignments, unsupported remote transports, opaque credential-shaped URL path segments, and arbitrary extra fields are rejected.
POST /api/v1/profiles/environment-account accepts only a profile slug, optional description, an environment-variable name, and optional makeDefault: true. It is available only for the currently selected trusted configuration when every existing profile has one matching direct credential environment binding for a single local stdio upstream. It rejects remote HTTP, provider-adapter, OAuth, named-upstream, raw credential, duplicate source-variable, and arbitrary profile-override cases. The operation records intent and completion in the fail-closed Console journal, writes via the guarded configuration transaction, never starts the upstream, and returns no credential environment reference or configuration bytes.
The unconfigured first-run dashboard exposes Save connector choice, Continue saved connector choice, and Discard saved choice through the private setup-draft endpoint. GET /api/v1/setup-draft returns the current safe draft or null. PUT /api/v1/setup-draft accepts only the safe connector intent (source: "connector", bounded configuration name, catalog preset, and stage) plus an optional expectedRevision compare-and-swap value, then returns the saved draft with its revision and timestamp. DELETE /api/v1/setup-draft requires { revision: number } and returns 204 No Content. PUT and DELETE require the existing exact-Origin CSRF proof. The checkpoint is private, owner-restricted, versioned, and shared with miftah setup --resume; it is not stored in browser memory beyond the active page, localStorage, sessionStorage, configuration files, audit records, OAuth metadata, or client settings. It never accepts or returns a URL, local command, argument, working directory, credential reference or value, header, client-entry document, client-secret path, token/cache location, browser state, OAuth code, or OAuth token. It is available only while no configuration has been selected or created, and it is cleared after successful first-run publication or an explicit discard.
| Method and path | Purpose |
|---|---|
POST /api/v1/sessions |
Exchange the one-use bootstrap code for one browser session. |
GET /api/v1/session |
Resume a still-valid cookie-authenticated browser session and return its CSRF proof to page memory. |
GET /api/v1/setup-draft |
Return the current first-run safe connector checkpoint, or null. Requires a Console session and returns no connection details. |
PUT /api/v1/setup-draft |
Save one strict non-secret connector name/preset/stage checkpoint. Requires CSRF and optional exact expected revision. |
DELETE /api/v1/setup-draft |
Discard one exact-revision first-run checkpoint. Requires CSRF and never creates or changes a configuration. |
POST /api/v1/onboarding/preset |
Exclusively create the first validated configuration from a reviewed preset and safe connector metadata. Requires CSRF; GSC accepts only structured account names/descriptions/client-secrets paths and rejects raw credential values. local-stdio additionally requires an explicit acknowledgement and stores only a literal executable/argument array; onboarding does not launch it. |
POST /api/v1/onboarding/client-entry |
Exclusively create the first validated configuration from one explicitly selected local stdio or credential-free HTTPS remote client entry. Requires CSRF; the pasted document is in-memory only. Local imports use the shared static launch grammar; remote imports require a url plus explicit type: "http" or "streamable-http", HTTPS without userinfo/query/fragment or opaque credential-shaped path segments, and never trigger OAuth discovery or an upstream call. On Windows, a local import accepts only a direct absolute .exe or .com executable, not bare runners or .cmd/.bat shims. env, headers, shell settings, unsupported remote transports, environment wrappers, inline code, opaque values or assignments, unpinned package references, and credential-shaped arguments are rejected. |
POST /api/v1/onboarding/native-oauth |
Exclusively create the first validated v3 native-OAuth profile, upstream, and connection. Requires CSRF and refuses an existing file. |
GET /api/v1/health |
Return safe config identity, Console audit health, and restart-required guidance. |
GET /api/v1/config |
Return allowlisted configuration metadata only. |
GET /api/v1/configurations |
Return the no-config dashboard's bounded, metadata-only configuration catalog. Not available for an explicit --config Console. |
POST /api/v1/configurations/:id/select |
Select one opaque catalog entry for this Console process. Requires CSRF; it never changes MCP client files or live MCP sessions. |
POST /api/v1/profile-readiness |
Run one declared safe read-only check for the selected profile/upstream. Requires CSRF, never auto-runs, and returns only bounded status rather than provider output. |
POST /api/v1/profiles/default |
Make one existing profile the durable default for future Miftah sessions. Requires CSRF, accepts only { "profile": "…" }, and performs no OAuth, upstream, credential, or provider-cache operation. |
POST /api/v1/profiles/rename |
Rename one selected profile and configuration-owned default, routing, routing-plugin, and profile-lock references. Requires CSRF and accepts only { "profile": "…", "newProfile": "…" }. For native OAuth it moves the credential only between exact OS-vault keys and migrates non-secret connection metadata through one recoverable local transaction for OS-vault credential migration; it never exposes a credential or touches provider caches. An unfinished prior rename returns 409 oauth_profile_rename_recovery_required; a completed recovery returns 409 profile_selection_stale, so reload or reselect the configuration before retrying. |
POST /api/v1/profiles/remove |
Remove one selected profile through the guarded audited configuration transaction. Requires CSRF and accepts only { "profile": "…", "replacementProfile": "…"? }; a replacement is required for durable references. It never deletes secrets, provider caches, token caches, or OS-vault entries, and it rejects configured native OAuth bindings rather than splitting that lifecycle. |
POST /api/v1/profiles/environment-account |
Add one simple local environment-backed account. Requires CSRF and a selected trusted configuration. It accepts only a profile, optional description, environment-variable name, and optional durable default; never a credential value. |
GET /api/v1/profiles |
Return profile names, descriptions, tags, policy names, and named-upstream keys only. |
GET /api/v1/connections |
Return configured non-secret OAuth connection bindings without opening the vault. |
GET /api/v1/connections/:ref |
Return redacted credential and identity status for one exact connection. |
POST /api/v1/connections |
Atomically add one schema-valid OAuth connection binding. Requires CSRF. |
POST /api/v1/connections/:ref/connect |
Run the approved system-browser authorization for one exact connection. Requires CSRF. |
POST /api/v1/connections/:ref/test |
Test one exact configured connection without starting a new interactive authorization. Requires CSRF. |
POST /api/v1/connections/:ref/reauth |
Replace one exact credential without deleting the old credential before success. Requires CSRF. |
DELETE /api/v1/connections/:ref/credential |
Delete one exact local vault credential. Requires CSRF. Provider-side revocation is not claimed. |
GET /api/v1/audit?limit=1..200 |
Return allowlisted metadata from the owner-restricted Console mutation journal, never raw JSONL or arguments. |
GET /api/v1/client-snippets?client=<name> |
Generate review-and-copy JSON for Claude Desktop, Claude Code, Cursor, VS Code, or all; never edit client files. |
Success responses use { "data": ... }. Errors use { "error": { "code": "...", "message": "..." } } with semantic HTTP status codes. Responses are non-cacheable and carry restrictive content-type, framing, referrer, and content-security headers. Internal paths, raw configuration, secret references, environment maps, command arguments, headers, tokens, authorization URLs, raw provider errors, and raw audit bytes are not part of the browser contract. For a recognized provider adapter, configuration metadata states only the declared authentication ownership; the Console hides the native OAuth editor rather than implying it can take over the adapter's OAuth cache. It emits that provider-owned state only when every effective profile/upstream pair matches the same reviewed adapter. A manual-only local configuration receives the environment-account editor only when it meets the exact simple static-binding boundary; metadata exposes the destination environment key, never the source reference or its value. Other mixed or unreviewed local-process configurations keep any exact reviewed safe-check targets available, but do not claim native OAuth, provider ownership, or static account addition.
Connection creation reuses the same typed application service as the CLI: an existing candidate is schema-validated, applied from an exact source snapshot, backed up uniquely, and published through the guarded atomic replacement. First-run onboarding constructs and validates the complete v3 candidate before an exclusive, non-overwriting create. Console clears its selection after a configuration write, so choose the configuration again before another control operation; this prevents a concurrent replacement from being silently trusted. Console mutations use a separate owner-restricted, fail-closed journal under .miftah/audit/console.jsonl beside the configuration. If that journal cannot be prepared, the mutation is refused before its side effect.
The control API manages durable configuration and the local OAuth vault for future connections. It cannot inspect or take over another Miftah process, replace an active Claude Desktop STDIO session, or change that process's in-memory profile selection. Restart or reconnect the MCP client after a durable Console change. A future broker or IPC design would require a separate authenticated threat-model review.