Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ Use `"streamable-http"` for a remote MCP server:

Header names are case-insensitive, and a profile header always replaces the upstream header of the same name. Miftah requires HTTPS for non-loopback remote URLs; HTTP is accepted only for local development endpoints on `localhost`, `127.0.0.0/8`, or `::1`. The `"http"` transport is a version-1-only compatibility alias for `"streamable-http"`; version 2 requires `"streamable-http"`. `"sse"` remains available for legacy MCP servers but is deprecated; prefer Streamable HTTP for new deployments.

Remote authentication currently means explicitly configured static headers; Miftah does not yet run an upstream OAuth browser, callback, refresh, or revoke flow. Read [OAuth support](docs/oauth-support.md) before assuming a provider's remote or local OAuth mechanism is compatible.

An intentional Streamable HTTP restart or wrapper shutdown sends the MCP session DELETE request before closing the local client transport. A server may decline DELETE with HTTP 405, in which case Miftah still closes its local session but the remote server controls any remaining server-side state. If DELETE does not settle before the configured shutdown deadline, Miftah aborts the local transport rather than leaving a live credential session behind. Requested cancellation and requested progress notifications are forwarded across both local STDIO and Streamable HTTP upstreams. HTTP status failures are returned as `UPSTREAM_HTTP_ERROR` and MCP JSON-RPC failures as `UPSTREAM_PROTOCOL_ERROR`; response bodies and remote error messages are not exposed to callers.

Miftah uses the SDK's existing 60-second MCP request timeout and does not add a second request-timeout setting. Startup and shutdown retain the configured 30-second and 5-second defaults. The SDK performs its bounded Streamable HTTP SSE reconnection behavior; if the transport ultimately closes, Miftah's opt-in `process.restartOnCrash` policy and bounded `maxRestarts` budget control profile recovery.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ MCP client
-> upstream STDIO server
```

The STDIO public server is built with the official `@modelcontextprotocol/sdk` `Server` and `StdioServerTransport`. Each profile/upstream pair gets an SDK `Client` and its configured transport on first use: local processes use `StdioClientTransport`; remote upstreams use Streamable HTTP by default (the historical `"http"` alias is accepted only by version 1) or the deprecated legacy SSE client. Resolved profile headers override upstream headers case-insensitively before either remote transport is constructed. Streamable HTTP session shutdown sends DELETE while its session ID is still available, then closes the local client transport; legacy SSE has no corresponding server-session teardown request. Request cancellation and requested progress notifications retain their MCP request context across both STDIO and Streamable HTTP upstreams. Startup HTTP failures and operation HTTP/MCP protocol failures are normalized before crossing the proxy boundary, retaining only stable category/code metadata rather than server response text.
The STDIO public server is built with the official `@modelcontextprotocol/sdk` `Server` and `StdioServerTransport`. Each profile/upstream pair gets an SDK `Client` and its configured transport on first use: local processes use `StdioClientTransport`; remote upstreams use Streamable HTTP by default (the historical `"http"` alias is accepted only by version 1) or the deprecated legacy SSE client. Resolved profile headers override upstream headers case-insensitively before either remote transport is constructed. Remote SDK transports receive those static headers only; they do not receive an OAuth client provider. Streamable HTTP session shutdown sends DELETE while its session ID is still available, then closes the local client transport; legacy SSE has no corresponding server-session teardown request. Request cancellation and requested progress notifications retain their MCP request context across both STDIO and Streamable HTTP upstreams. Startup HTTP failures and operation HTTP/MCP protocol failures are normalized before crossing the proxy boundary, retaining only stable category/code metadata rather than server response text. See [OAuth support](oauth-support.md) for the deliberately unimplemented OAuth lifecycle and its future boundary.

The optional HTTP host owns the listener and no shared Miftah session state. It validates the exact `/mcp` target, Host, Origin, bearer authentication, a bounded JSON initialize request, and capacity before creating a `StreamableHTTPServerTransport`. That transport and a new `MiftahServer`, `ProfileManager`, and upstream manager belong to one HTTP session only. The host forces the session runtime to in-memory `session` profile state, even if normal STDIO configuration uses workspace/global persistence. Its session registry owns only opaque MCP session IDs, idle timers, and cleanup tasks; DELETE, expiry, connection shutdown, and wrapper shutdown detach the record before asynchronous runtime cleanup so no client can reuse a closing runtime, while retaining that capacity reservation until cleanup succeeds. A rejected cleanup is recorded, retains capacity, and makes graceful shutdown fail rather than silently admitting unlimited replacement runtimes. A reconnect is routed to the existing transport by its session ID, while the SDK permits only one active standalone SSE stream for that session.

Expand Down
2 changes: 2 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ Use `transport: "streamable-http"` for new remote MCP servers. The historical `t

Remote URLs must use `https`. Miftah accepts `http` only for loopback development URLs on `localhost`, `127.0.0.0/8`, or `::1`; other cleartext URLs and non-HTTP URL schemes fail config validation at the exact `upstream.url` or `upstreams.<name>.url` path. Profile headers override upstream headers case-insensitively, so `Authorization` and `authorization` are one credential slot rather than two combined values.

Remote authentication is static: configure only explicit `headers`/secret references that the upstream documents. There is no OAuth configuration object, callback URL, client-registration setting, browser flow, token refresh, or token revoke command in the current strict schema. Unknown OAuth keys are rejected instead of being silently accepted. See [OAuth support](oauth-support.md) for the exact provider, local-stdio, and future remote-HTTP boundary.

Miftah uses Node's normal TLS validation and does not disable certificate verification. A self-signed remote certificate therefore fails closed unless the operator establishes a trusted local CA through the normal Node trust configuration. Do not use cleartext remote HTTP to transport profile credentials.

For Streamable HTTP, an intentional restart, idle close, or wrapper shutdown sends DELETE for a negotiated MCP session before closing the local client transport. A remote server can return HTTP 405 to decline session termination; Miftah then completes local cleanup and the server retains control of its own session lifetime. If DELETE hangs, Miftah aborts the local transport at `shutdownTimeoutMs` rather than retrying an unbounded remote cleanup. Legacy SSE has no equivalent remote-session DELETE.
Expand Down
2 changes: 2 additions & 0 deletions docs/library-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ The configuration contract exposes `ActiveProfileStateScope`, `AuditConfig`, `Au

`UpstreamConfig.trustToolAnnotations` is opt-in and defaults to false. `ToolingConfig.unknownToolRisk` uses the exported `UnknownToolRisk` union (`"write" | "destructive"`) and defaults to `"destructive"`; callers can use exact `toolRiskOverrides` for known read tools.

There is no public OAuth configuration, connection, adapter, client-provider, or token-lifecycle export in this release. `UpstreamConfig.headers` remains the explicit remote credential surface; configuration additions must not imply a supported OAuth workflow until their runtime, secure-store, CLI, and migration contracts exist. See [OAuth support](oauth-support.md).

`ProfileRoutingConfig` describes opt-in identifiers for Miftah's fixed in-tree provider matchers. It is declarative configuration only: it does not load third-party code, resolve secrets, or grant a matcher access to process or network APIs.

## Plugin API subpath
Expand Down
59 changes: 59 additions & 0 deletions docs/oauth-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# OAuth support and compatibility

Miftah is an MCP wrapper and credential-profile boundary. It can carry explicitly configured credentials to an upstream, but it is not an OAuth broker today.

**Miftah does not currently perform OAuth discovery, client registration, browser authorization, callbacks, token refresh, or revocation.** `streamable-http` is a remote transport choice, not evidence that an upstream's OAuth flow is supported. **Miftah does not support OAuth for every MCP server or provider.**

This page is the public compatibility contract for OAuth-shaped upstream authentication. It distinguishes what operators can use now from the deliberately unimplemented work proposed in the OAuth roadmap. It does not authorize the runtime to begin managing provider credentials.

## Support matrix

| Support class | Transport and current ownership | Operator fallback |
| --- | --- | --- |
| Standards-compatible remote HTTP MCP OAuth | Future target for an HTTPS Streamable HTTP MCP server that follows protected-resource and authorization-server metadata conventions. Miftah has no enabled native implementation for discovery, client registration, authorization, callback, refresh, reauth, revoke, or identity evidence yet. | Use the upstream's documented manual/pre-registered credential path and configured static headers, or wait for the versioned native contract. |
| Provider-adapter-backed local or non-standard OAuth | No provider-adapter API exists today. Miftah does not automate a provider browser flow, private callback convention, or token-store format. | Use the upstream's documented login and configuration; Miftah can launch, redact, and diagnose the configured upstream only. |
| Upstream-owned or manual credentials | `stdio`, legacy `sse`, and remote Streamable HTTP headers can be wrapped now. The upstream/provider owns login, callback, token cache, refresh, reauth, and revoke; Miftah only resolves explicit environment/header secret references and starts the upstream. | Complete the provider-owned login, supply its documented credential path, environment value, or static secret reference, then run `miftah validate` and `miftah doctor`. |
| Unsupported authentication patterns | Provider passwords, browser cookies, and arbitrary third-party token caches are not a supported Miftah-managed OAuth mechanism. Miftah does not own, parse, scrape, import, replay, or lifecycle-manage provider passwords, browser cookies, or arbitrary third-party token caches as OAuth artifacts. An operator can still pass an explicit value or path through ordinary `env`/`headers`/process configuration; that does not make it a supported OAuth flow. | Use a provider-supported mechanism, or leave that upstream unconfigured when its only path depends on opaque private state. |

`miftah doctor` can validate reachable configured upstreams and safe secret-provider availability. It does not prove that an OAuth browser flow, provider consent, refresh token, scope, or account binding is valid.

## Future standards-compatible remote HTTP contract

The intended native support class is deliberately narrow: a remote **HTTPS Streamable HTTP** MCP upstream that advertises the relevant standards. Local loopback HTTP remains a development exception for transport validation; it is not a claim that cleartext remote OAuth is safe. Native support is not enabled until the profile-bound connection model and authorization engine are delivered.

When it is implemented, the contract must be explicit and profile-bound:

- **Discovery:** obtain protected-resource metadata and authorization-server metadata from the upstream's advertised endpoints; do not guess provider-specific endpoints.
- **Client registration:** prefer an operator pre-registered client, then a safely hosted Client ID metadata document when available; use dynamic client registration only when the authorization server advertises it and the security review permits it.
- **Authorization and callback:** send the OAuth `resource` indicator, use PKCE `S256` and a state value, and receive a loopback callback only in a bounded local flow. Authorization URLs, callback parameters, codes, and tokens must not enter audit records, diagnostics, or configuration files.
- **Refresh, reauth, and revoke:** store profile-bound credentials only in an approved secure store; refresh before use when permitted, require explicit reauth when refresh fails, and call revoke only when the authorization server advertises a safe revoke endpoint.
- **Identity evidence:** treat an OAuth token as permission to call a service, not proof of the selected account. A configured identity probe remains the evidence for an account fingerprint.

The repository includes a deterministic, loopback-only compatibility probe in `tests/remote-oauth-compatibility.test.ts`. It exercises protected-resource discovery, authorization-server discovery, advertised dynamic registration, PKCE, authorization-code exchange, and a bearer-authenticated MCP retry using fixture-only opaque values. It proves that the installed MCP SDK can perform that standards-shaped sequence; it does **not** mean that Miftah currently owns or exposes the flow in production.

## Local and provider-owned OAuth

For a **Google Search Console-style local OAuth** upstream, such as a local `uvx mcp-search-console` process configured with a provider client-secrets file, complete the provider's login first. Configure only the documented client-secret file path, environment value, or other upstream input; then let that upstream create and maintain its own local credential cache. **Miftah must not scrape, copy, or manage that upstream token cache.**

The same rule applies to providers such as Sentry when their upstream MCP server owns consent, redirects, or cached tokens. Miftah may pass an explicit configured path or secret reference to the child, isolate explicitly configured files for a local process when the existing isolation contract allows it, and redact resolved values. It must not infer an account, open a browser on behalf of the upstream, or reinterpret an opaque provider cache.

## Identity boundary

Miftah's optional identity verifier is independent of OAuth. Its meaningful lifecycle states include `not-verified`, `verified`, `expired`, and `unsupported`; the state is based on an explicitly configured bounded upstream-tool probe, not an OAuth callback. **OAuth success, token validity, and granted scopes are not account authorization.** A provider can issue a valid token for the wrong human, organization, or profile, so write/destructive account checks still need configured identity evidence and policy controls.

## Configuration and public API boundary

Current `UpstreamConfig` supports `stdio`, `streamable-http`, and legacy `sse` transports plus explicit `headers`; version 1 also retains the documented `http` alias. **There is no `oauth` configuration object, OAuth public type/export, or adapter API in this release.** Strict configuration rejects unknown OAuth/callback/client keys rather than accepting a no-op security setting.

**Remote transports use configured static `headers` only; they do not pass an OAuth client provider to the MCP SDK.** The public library API likewise exports no OAuth client type or credential-lifecycle API. Existing environment, dotenv, keychain, 1Password, and explicit local secret-provider plugins resolve configured values; they do not become a generic OAuth cache.

**Any future OAuth surface must be additive, versioned, and paired with an explicit migration and release note.** It must define profile-bound connection records, secure storage, static-`Authorization` conflict behavior, CLI/doctor diagnostics, lifecycle recovery, and backward compatibility before schema or runtime support is added.

## Operator decision guide

1. If the upstream accepts a stable API key or bearer value, configure an explicit secret reference in `headers` or `env`, then validate and doctor the exact configuration.
2. If the upstream owns a local OAuth login, complete that login under the upstream's instructions and configure only its documented input. Treat the upstream's token cache as its private state.
3. If the upstream advertises standards-compatible remote OAuth, do not assume Miftah can currently drive it. Use the upstream's supported manual/pre-registered alternative until Miftah ships a reviewed native connection contract.
4. If the provider requires passwords, browser cookies, or an undocumented token cache, do not treat it as a Miftah-managed OAuth flow. Use a provider-supported mechanism or leave the upstream unconfigured; an explicit operator-provided value does not make Miftah responsible for that credential lifecycle.

See [configuration](config.md#remote-upstream-transports), [security](security.md), and [architecture](architecture.md) for the corresponding current transport, redaction, and public-API boundaries.
2 changes: 2 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ The STDIO transport is the default because it avoids a network listener. Remote

For remote HTTP diagnostics, Miftah retains only a stable HTTP status or MCP protocol code. It deliberately omits server response bodies and remote protocol messages, because they can contain credentials or sensitive provider context. Streamable HTTP is preferred and sends DELETE on intentional local session cleanup; legacy SSE is deprecated and has no equivalent remote-session deletion. The local HTTP server follows the same lifecycle rule for each isolated session and adds loopback-first binding, explicit nonloopback authentication, Host validation, Origin denial by default, and bounded cleanup.

Miftah does not currently broker OAuth. It can pass explicit secret-backed headers or launch an upstream that manages its own authentication under the ordinary configuration contract, but it does not own, parse, scrape, import, replay, or lifecycle-manage provider passwords, browser cookies, or arbitrary third-party token caches as OAuth artifacts. OAuth authorization codes, callback parameters, and tokens must never enter configuration, audit records, or diagnostics. See [OAuth support](oauth-support.md) for the supported classes and the planned standards-compatible remote boundary.

## Identity verification boundary

Identity verification is an optional, local account-fingerprint comparison. It is not credential validity, provider authentication, account authorization, or scope validation. Miftah does not ship provider SDKs or plugins for it.
Expand Down
Loading