Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Start with the row that describes how your upstream MCP authenticates.
| Another exact-pinned npm or container STDIO server | Use `generic-npx` or `generic-docker`, then add profiles around it. |
| A local executable and argument array you personally reviewed | Use `local-stdio`; it saves literal command and argument-array metadata without a shell. |
| Remote HTTPS Streamable HTTP with a token or API key | In guided setup, choose `remote`; Miftah creates the strict `streamable-http` configuration with an optional secret-backed header. |
| Remote HTTPS Streamable HTTP with standards-compatible OAuth | Start with `miftah setup --native-oauth` or `miftah dashboard`. Miftah discovers supported OAuth from the endpoint; use manual `connection add` only when a provider gives you registration details. |
| Remote HTTPS Streamable HTTP with standards-compatible OAuth | In bare guided setup, choose `remote sign-in`; for scripts, use `miftah setup --native-oauth`. Miftah discovers supported OAuth from the endpoint; use manual `connection add` only when a provider gives you registration details. |
| Local or provider-specific MCP that opens its own OAuth flow | Use Upstream-owned OAuth. Miftah wraps the process but does not take over its token cache. |
| Google Search Console | Use the reviewed `google-search-console` adapter preset. OAuth remains upstream-owned. |
| One account and no need for profile, policy, routing, or audit controls | Keep the direct MCP entry; Miftah may not add value for this case. |
Expand All @@ -63,7 +63,7 @@ Shell examples below use POSIX syntax, including `~`, `$HOME`, and `\` line cont
miftah setup
```

On a bare interactive `miftah setup`, first choose `new` or `import` at `Start from (new, import) [new]`. Choose `new` and this starts by asking what you have: enter `remote` for a remote HTTPS endpoint, `local` for a reviewed executable and argument array, or a connector name such as `google-search-console`. It then collects the safe metadata that path needs and an output location, and can print an optional client JSON snippet for manual review. Choose `import` when you already have one MCP entry in a client JSON file: Miftah asks for that explicit absolute file, lists only entry names, and asks you to select one. It never prints the source entry's command, arguments, headers, environment values, or credentials, and it does not scan or modify the source client file. It never asks for a token, password, or browser cookie. For a remote OAuth server, run `miftah setup --native-oauth`: it asks for the exact HTTPS endpoint and account name, then discovers whether Miftah can safely own the OAuth flow. The plain `remote` path does not discover OAuth or call the upstream, so use `--native-oauth` when the server advertises standards-based OAuth. For `local`, it collects one literal argument at a time, shows a no-secret review summary, and requires an explicit acknowledgement. Miftah does not run the local executable during setup. Miftah validates the complete configuration before it writes an owner-restricted file, never overwrites an existing one, and never edits a Claude, Cursor, VS Code, or other MCP client file. Recognized adapters can then offer one explicit, provider-declared read-only readiness check; Miftah never guesses a tool or auto-approves a policy prompt. Use `miftah init` when you want the same catalog in a scripted command.
On a bare interactive `miftah setup`, first choose `new`, `remote sign-in`, or `import` at `Start from (new, remote sign-in, import) [new]`. Choose `new` and this starts by asking what you have: enter `remote` for a remote HTTPS endpoint, `local` for a reviewed executable and argument array, or a connector name such as `google-search-console`. It then collects the safe metadata that path needs and an output location, and can print an optional client JSON snippet for manual review. Choose `remote sign-in` when the remote MCP opens a browser to authenticate you. Miftah asks for the configuration name, account profile, and exact HTTPS endpoint, then discovers whether it can safely own a supported standards-based OAuth flow. It writes nothing if the endpoint does not prove one supported authorization server with dynamic registration. Keep `miftah setup --native-oauth` for scripted or repeatable setup. The plain `remote` path does not discover OAuth or call the upstream; use it for no-auth or documented API-key/header setup. Choose `import` when you already have one MCP entry in a client JSON file: Miftah asks for that explicit absolute file, lists only entry names, and asks you to select one. It never prints the source entry's command, arguments, headers, environment values, or credentials, and it does not scan or modify the source client file. It never asks for a token, password, or browser cookie. For `local`, it collects one literal argument at a time, shows a no-secret review summary, and requires an explicit acknowledgement. Miftah does not run the local executable during setup. Miftah validates the complete configuration before it writes an owner-restricted file, never overwrites an existing one, and never edits a Claude, Cursor, VS Code, or other MCP client file. Recognized adapters can then offer one explicit, provider-declared read-only readiness check; Miftah never guesses a tool or auto-approves a policy prompt. Use `miftah init` when you want the same catalog in a scripted command.

## First setup: GitHub with Claude Desktop

Expand Down Expand Up @@ -298,7 +298,7 @@ The optional dashboard:

1. starts a foreground-only service on literal `127.0.0.1`;
2. opens the system browser and asks for the one-time bootstrap code printed in the terminal;
3. creates a first validated known-preset configuration, Native remote OAuth profile and connection, or explicitly pasted local stdio or credential-free HTTPS remote entry when the selected config path does not exist; native OAuth discovery completes before the configuration is written;
3. creates a first validated known-preset configuration, Native remote OAuth profile and connection, or explicitly pasted local stdio or credential-free HTTPS remote entry when the selected config path does not exist; choose **Remote MCP with browser sign-in** for the native flow, whose discovery completes before the configuration is written;
4. offers a separate **Connect** action that starts the reviewed system-browser authorization;
5. shows redacted connection and audit state; and
6. generates client JSON for you to review and copy.
Expand Down
2 changes: 1 addition & 1 deletion docs/presets-and-clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Without `--interactive`, `init` creates only a configuration unless `--client` i

`--interactive` is available only when both input and output are real TTYs. EOF or Ctrl-C cancels before the configuration write. The wizard asks only for a name, an outcome-first source answer (`remote` or `local`) or catalog connector name, safe preset metadata (variable names, URLs, header metadata, pins), output location, and client selection. For `local-stdio`, it asks for one literal argv item at a time and gives a count-only review summary before explicit acknowledgement. It never asks for or echoes a secret value.

`miftah setup [name]` is the dedicated guided command. It uses that same TTY-only wizard and safe preset options, with interactivity required by the command instead of supplied as `--interactive`. At its source question, `remote` selects the strict `streamable-http` path and `local` selects `local-stdio`; catalog names remain accepted. The plain remote path does not discover OAuth or call the upstream, so use `miftah setup --native-oauth` only for a remote server that advertises standards-based OAuth.
`miftah setup [name]` is the dedicated guided command. It uses that same TTY-only wizard and safe preset options, with interactivity required by the command instead of supplied as `--interactive`. At its first question, choose `remote sign-in` when the selected remote MCP opens a browser to authenticate you; Miftah then uses the existing endpoint-first standards-based OAuth discovery flow before it writes anything. Choose `new` for the normal catalog question, where `remote` selects the strict `streamable-http` path and `local` selects `local-stdio`; catalog names remain accepted. The plain remote path does not discover OAuth or call the upstream. Keep `miftah setup --native-oauth` for a scripted or repeatable standards-based OAuth setup.

For one-account noninteractive `google-search-console` setup, `--oauth-client-secrets-file` is required and is written only to the generated profile environment. Guided `miftah setup` asks for one or more named accounts, their client-secrets paths, and an explicit default; it generates a separate upstream-owned `GSC_CONFIG_DIR` for each generated configuration file and profile. To add another account later, use `miftah setup --add-profile --config <file>` with a new `--profile` and `--oauth-client-secrets-file`; the selected configuration must remain wholly within the reviewed adapter boundary and every existing state directory must already be distinct. The safe adapter summary printed by `init` does not echo a configured client-secrets path. The printed client snippets use absolute paths to the Node executable and compiled Miftah CLI so GUI clients do not depend on `PATH`. Regenerate them after moving or upgrading Miftah, or after changing the configuration path. Copy generated JSON as JSON; do not hand-edit the command into a shell string.

Expand Down
17 changes: 14 additions & 3 deletions src/cli/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface SetupCommandResult {

type ReadinessDecision = "verify" | "skip" | "cancelled";
type AccountAdditionKind = "provider" | "environment";
type GuidedSetupStartingPoint = "new" | "import";
type GuidedSetupStartingPoint = "new" | "remote-sign-in" | "import";

interface InteractivePromptSession {
prompt(label: string, defaultValue?: string): Promise<string | undefined>;
Expand Down Expand Up @@ -106,10 +106,15 @@ function hasExplicitNewConfigurationInput(options: SetupCommandOptions): boolean
async function chooseGuidedSetupStartingPoint(context: InitCommandContext): Promise<GuidedSetupStartingPoint> {
const prompts = createInteractivePromptSession(context, "Guided setup was cancelled.");
try {
const answer = (await prompts.prompt("Start from (new, import)", "new"))?.toLowerCase();
const answer = (await prompts.prompt("Start from (new, remote sign-in, import)", "new"))?.toLowerCase();
if (answer === "new" || answer === "n") return "new";
if (answer === "remote-sign-in" || answer === "remote sign-in" || answer === "sign-in" || answer === "browser") {
return "remote-sign-in";
}
if (answer === "import" || answer === "i") return "import";
throw new CliUsageError("Choose 'new' to configure an MCP or 'import' to select an existing client entry.");
throw new CliUsageError(
"Choose 'new' to configure an MCP, 'remote-sign-in' when the MCP signs you in in a browser, or 'import' to select an existing client entry."
);
} finally {
prompts.close();
}
Expand Down Expand Up @@ -307,6 +312,12 @@ export async function runSetupCommand(options: SetupCommandOptions, context: Ini
await runGuidedClientEntryImport(context);
return { verification: "not-applicable", exitCode: 0, reports: [] };
}
if (startingPoint === "remote-sign-in") {
await runNativeOAuthSetup(options, context, {
...(context.nativeOAuthFetch === undefined ? {} : { fetch: context.nativeOAuthFetch })
});
return { verification: "not-applicable", exitCode: 0, reports: [] };
}
}
const created = await runInitCommand({ ...options, interactive: true }, context);
if (
Expand Down
21 changes: 17 additions & 4 deletions src/console/console-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const page = `<!doctype html>
<p class="step">02 / First connection</p>
<h2 id="preset-onboarding-title">Set up an MCP</h2>
</div>
<p>Choose a known connector, a reviewed local executable, or a remote HTTPS endpoint. Miftah writes validated configuration references; it never asks for a password or token here.</p>
<p>Choose a known connector, a reviewed local executable, or a remote HTTPS endpoint. Miftah writes validated configuration references; it never asks for a password or token here. If the remote MCP signs you in in a browser, use the browser sign-in flow below.</p>
</div>
<form id="preset-onboarding-form" class="form-grid">
<label>Configuration name<input name="name" required maxlength="256" placeholder="support-tools"></label>
Expand Down Expand Up @@ -119,6 +119,7 @@ const page = `<!doctype html>
<label data-preset-field="streamable-http" hidden>Header prefix (optional)<input name="headerPrefix" maxlength="256" placeholder="Bearer "></label>
<p class="field-note wide">For provider-owned login such as Google Search Console, Miftah saves the client-secrets path only. The upstream owns its browser login and private token cache. For a local executable, use the environment-variable field for a secret reference; never put a token in an argument.</p>
<div class="wide form-action"><button type="submit">Create configuration</button></div>
<div class="wide form-action"><button id="native-oauth-setup-link" type="button" class="secondary">Remote MCP with browser sign-in</button></div>
</form>
</section>

Expand All @@ -143,17 +144,17 @@ const page = `<!doctype html>
<div class="section-heading">
<div>
<p class="step">02 / First connection</p>
<h2 id="onboarding-title">Create a native OAuth profile</h2>
<h2 id="onboarding-title">Set up remote MCP with browser sign-in</h2>
</div>
<p>Miftah checks this exact HTTPS endpoint for standards-based OAuth before it creates the configuration. It uses dynamic registration only when the server advertises it. No token, client secret, or browser authorization starts at this step.</p>
<p>Miftah checks this exact HTTPS endpoint for supported browser sign-in before it creates the configuration. It uses standards-based OAuth with dynamic registration only when the server advertises it. No token, client secret, or browser authorization starts at this step.</p>
</div>
<form id="onboarding-form" class="form-grid">
<label>Configuration name<input name="name" required maxlength="256" placeholder="posthog-work"></label>
<label>Profile name<input name="profile" required maxlength="256" placeholder="production"></label>
<label class="wide">Profile description<input name="description" maxlength="1024" placeholder="Production analytics account"></label>
<label class="wide">Remote MCP resource URL<input name="resource" type="url" required maxlength="2048" placeholder="https://mcp.example.com/mcp"></label>
<p class="field-note wide">Miftah will stop without writing anything if this endpoint does not publish one supported OAuth authorization server with dynamic client registration. Advanced manual OAuth remains available for provider-specific registrations.</p>
<div class="wide form-action"><button type="submit">Discover OAuth and create profile</button></div>
<div class="wide form-action"><button type="submit">Check sign-in and create profile</button></div>
</form>
</section>

Expand Down Expand Up @@ -1198,6 +1199,18 @@ const script = `(() => {
});
}

const nativeOAuthSetupLink = byId("native-oauth-setup-link");
if (nativeOAuthSetupLink instanceof HTMLButtonElement) {
nativeOAuthSetupLink.addEventListener("click", () => {
if (onboardingView instanceof HTMLElement) onboardingView.scrollIntoView({ block: "start" });
const name = onboardingForm instanceof HTMLFormElement
? onboardingForm.querySelector("input[name='name']")
: undefined;
if (name instanceof HTMLInputElement) name.focus();
message("Enter the remote MCP details below. Miftah checks supported browser sign-in before it writes a configuration.");
});
}

const presetOnboardingForm = byId("preset-onboarding-form");
if (presetOnboardingForm instanceof HTMLFormElement) {
const presetSelection = byId("preset-selection");
Expand Down
7 changes: 5 additions & 2 deletions tests/console-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,8 +791,10 @@ describe("local Console control server", () => {
expect(html).toContain("Set up an MCP");
expect(html).toContain("Local executable + argument array");
expect(html).toContain("Remote HTTPS MCP endpoint");
expect(html).toContain("Discover OAuth and create profile");
expect(html).toContain("Miftah checks this exact HTTPS endpoint for standards-based OAuth before it creates the configuration.");
expect(html).toContain('id="native-oauth-setup-link"');
expect(html).toContain("Remote MCP with browser sign-in");
expect(html).toContain("Check sign-in and create profile");
expect(html).toContain("Miftah checks this exact HTTPS endpoint for supported browser sign-in before it creates the configuration.");
expect(html).toContain("Discover OAuth from configured upstream");
expect(html).toContain("Add another native OAuth account");
expect(html).toContain('id="provider-account-editor"');
Expand Down Expand Up @@ -848,6 +850,7 @@ describe("local Console control server", () => {
const javascript = await script.text();
expect(javascript).toContain("/api/v1/sessions");
expect(javascript).toContain("/api/v1/onboarding/native-oauth/discover");
expect(javascript).toContain("native-oauth-setup-link");
expect(javascript).toContain("/api/v1/connections/discover");
expect(javascript).toContain("/api/v1/profiles/native-oauth/discover");
expect(javascript).toContain("/api/v1/profiles/provider-account");
Expand Down
5 changes: 4 additions & 1 deletion tests/readme-product-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ describe("product README", () => {
it("gives a first-time user complete setup and profile-selection journeys", () => {
expect(readme).toContain("## Choose your setup path");
expect(readme).toContain("miftah setup");
expect(readme).toContain("choose `new`, `remote sign-in`, or `import`");
expect(readme).toContain("enter `remote` for a remote HTTPS endpoint");
expect(readme).toContain("`local` for a reviewed executable and argument array");
expect(readme).toContain("can print an optional client JSON snippet for manual review");
expect(readme).toContain("The plain `remote` path does not discover OAuth or call the upstream");
expect(readme).toContain("use `--native-oauth` when the server advertises standards-based OAuth");
expect(readme).toContain("Choose `remote sign-in` when the remote MCP opens a browser to authenticate you");
expect(readme).toContain("Keep `miftah setup --native-oauth` for scripted or repeatable setup");
expect(readme).toContain("Remote MCP with browser sign-in");
expect(readme).toContain("never asks for a token, password, or browser cookie");
expect(readme).toContain("## First setup: GitHub with Claude Desktop");
expect(readme).toContain("Developer → Edit Config");
Expand Down
Loading