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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

### Changed

- [#96](https://github.com/mohanagy/miftah/issues/96) Confirmation-required MCP calls now default to human form elicitation and fail closed when the client cannot present that form. The former self-approval bearer path is available only through explicit `security.approvalMode: "delegated-agent"`, is hidden from normal tool discovery, and is audited as delegated authorization rather than human proof; approval records are bound to that form or delegated mechanism.
- [#97](https://github.com/mohanagy/miftah/issues/97) The generated multi-profile GitHub preset now requires exact profile-switch confirmation and explicit current-session selection before destructive work, preventing a silent profile change or implicit selection from satisfying that boundary.
- [#98](https://github.com/mohanagy/miftah/issues/98) Management tools now publish reviewed MCP behavioral annotations from one contract table. `miftah init --client claude-code` prints exact, manually merged Claude Code permission guidance for visible privileged management tools without modifying client settings.

## [0.2.1] - 2026-07-17

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The default endpoint is `http://127.0.0.1:3000/mcp`; see [HTTP server transport]

Profiles are named credential environments. Keep secret values outside JSON and use the exact generated references in the checked-in [GitHub](examples/github.miftah.json), [Sentry](examples/sentry.miftah.json), or [generic reference](examples/generic.miftah.json) example. The strict catalog pins GitHub to `ghcr.io/github/github-mcp-server:v1.5.0` with its documented read-only tool configuration; it does not claim a digest. The [compatibility matrix](docs/presets-and-clients.md) describes safe promotion and deployment recording for that tag.

Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_reset_profile`, `miftah_lock_profile`, `miftah_unlock_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, `miftah_verify_identity`, `miftah_route_preview`, `miftah_list_approvals`, `miftah_approve`, and `miftah_deny`. In a multi-upstream bundle, upstream tools are exposed as `<upstream>__<tool>`. For a single upstream whose exact tool name collides with a reserved management name, the default is `upstream_<name>`; `tooling.collisionStrategy: "fail"` instead rejects it. Other upstream names that merely start with `miftah_` are not reserved. After a profile change, restart, upstream failure, recovery, or upstream list-change notification that changes a public capability surface, MCP clients receive list-change notifications and should re-list the affected tools, resources, resource templates, or prompts before relying on cached capabilities.
Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_reset_profile`, `miftah_lock_profile`, `miftah_unlock_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, `miftah_verify_identity`, `miftah_route_preview`, and `miftah_list_approvals`. `miftah_approve` and `miftah_deny` are intentionally advertised only when an operator explicitly sets `security.approvalMode` to `"delegated-agent"`; their names remain reserved in every mode. In a multi-upstream bundle, upstream tools are exposed as `<upstream>__<tool>`. For a single upstream whose exact tool name collides with a reserved management name, the default is `upstream_<name>`; `tooling.collisionStrategy: "fail"` instead rejects it. Other upstream names that merely start with `miftah_` are not reserved. After a profile change, restart, upstream failure, recovery, or upstream list-change notification that changes a public capability surface, MCP clients receive list-change notifications and should re-list the affected tools, resources, resource templates, or prompts before relying on cached capabilities.

Active profile state is in-memory by default. `state.scope: "session"` resets on a new MCP transport; opt-in `workspace` or config-identity-namespaced `global` scope persists only safe selection metadata (the profile and timestamp) using atomic owner-restricted storage. Clients cannot choose a scope or state path. Optional runtime locks and risk leases are connection-bound and never enter that durable state. See [active profile state](docs/config.md#active-profile-state) for lock precedence, fallback diagnostics, and platform paths.

Expand Down Expand Up @@ -117,7 +117,7 @@ Routing can use the active profile or rules matching tool arguments:
}
```

When several profiles match, Miftah refuses to guess. Use explicit profile switching for write and destructive actions. The same routing, policy, redaction, and audit pipeline applies to upstream tool calls, resource reads and subscriptions, and prompt retrieval. Policy patterns use each upstream tool's original name for tools, `resources/read` for reads and subscriptions, and `prompts/get` for prompt retrieval. A deny, blocked, or ambiguous decision is returned before Miftah forwards the request. A confirmation-required operation pauses for a connection-bound, one-time approval: form-capable MCP clients receive a generic boolean elicitation, while other clients receive a short-lived fallback bearer for `miftah_approve` or `miftah_deny`. Provider token scopes still matter: local policy cannot make a write-capable provider token read-only. Profiles that set a policy name must reference an existing entry in `policies`, while profiles with no `policy` field keep the default allow behavior.
When several profiles match, Miftah refuses to guess. Use explicit profile switching for write and destructive actions. The same routing, policy, redaction, and audit pipeline applies to upstream tool calls, resource reads and subscriptions, and prompt retrieval. Policy patterns use each upstream tool's original name for tools, `resources/read` for reads and subscriptions, and `prompts/get` for prompt retrieval. A deny, blocked, or ambiguous decision is returned before Miftah forwards the request. A confirmation-required operation pauses for a connection-bound, one-time approval. The default `security.approvalMode: "human"` uses a generic form only with clients that support MCP form elicitation; a client without that capability fails closed and receives no bearer. An operator may explicitly choose `"delegated-agent"` for automation, which exposes a short-lived bearer and `miftah_approve`/`miftah_deny`; that is delegated agent authorization, never proof of a human decision. Provider token scopes still matter: local policy cannot make a write-capable provider token read-only. Profiles that set a policy name must reference an existing entry in `policies`, while profiles with no `policy` field keep the default allow behavior.

Miftah can also match bounded workspace metadata without treating a project file as configuration. It resolves a valid environment hint, then the nearest valid project-marker hint, then matching rules over tool arguments and collected context, then the configured fallback. Rules that select different profiles return `ROUTING_AMBIGUOUS`, and a context hint never authorizes a destructive operation that requires an explicit rule. `miftah_route_preview` and eligible audit records expose only sanitized routing evidence plus risk-classification source/confidence, not raw project environment values, upstream metadata, or project file contents. See [routing context](docs/config.md#routing-context) for the marker schema, root behavior, and evidence boundary.

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Before a configured local STDIO target starts, `ProfileRuntimeIsolation` derives

The metadata-only routing context collector is the sole source of workspace context, profile hints, preview evidence, and proxied-operation audit evidence. It reads only allowlisted bounded metadata: file roots, cwd, two environment selectors, strict project markers, package/workspace fields, and a local Git origin. It separately derives canonical GitHub repository identifiers from safe package/workspace metadata and supported local remote forms; it never passes a raw context object to a matcher. The runtime configuration file remains separate from that input. Once initialized, `MiftahServer` capability-gates `roots/list`, caches only root URIs per client connection, and refreshes only on an advertised roots-list-changed notification. Every proxied operation and route preview receives one immutable snapshot; it uses that snapshot for rule matching, hint selection, fixed and plugin matcher projection, and redacted evidence so routing cannot change between those steps.

Every supported MCP request enters one outer audit scope. The scope records one terminal operation event on success or safe failure and also covers discovery/list failures, unknown names, and management tools that do not enter the proxy pipeline. `OperationPipeline` enriches proxied tool calls, resource reads, resource subscriptions, and prompt retrieval with captured source/target profile, upstream, routing, policy, risk, risk source/confidence, sanitized `routingEvidence`, and bounded `routingMatcherEvidence`; it does not emit its own record. It captures the source tool snapshot before awaiting policy work, normalizes only the four MCP behavioral booleans, and accepts them for classification only when the matching configured base upstream explicitly trusts tool annotations. The snapshot fingerprint includes client-visible annotations, so a routed profile with different metadata fails the same schema-compatibility guard as any other differing tool contract. It then captures the source profile state, resolves explicit hints and rules before the matcher band, runs the fixed in-tree registry and explicitly allowlisted routing plugins only through contained child hosts, uses `matcher:<provider>` or `matcher:plugin:<id>` only before fallback, evaluates the selected profile policy, and checks a captured lease and explicit-selection boundary before resolving the exact target upstream route. Matcher ambiguity carries only canonical bounded evidence and never resolves an upstream; a matcher reason cannot satisfy an explicit destructive-operation rule. It rechecks that captured lease immediately before execution, so a later renewal cannot authorize an old request and a different routed profile cannot borrow it. Route preview remains side-effect-free: it examines only cached compatible snapshots and falls back conservatively when none exists. Tools retain their original upstream names for routing and policy compatibility while the matcher receives the client-visible name solely for exact provider-token recognition; resource reads and subscriptions use the stable policy name `resources/read`, while prompt retrieval uses `prompts/get`. Denied, blocked, and ambiguous operations never resolve or execute an upstream read/get route. Confirmation-required operations resolve only enough to bind the exact target, then enter a separate approval lifecycle before upstream session execution: state is connection-bound, the form-elicitation path carries only a generic boolean, and a fallback bearer can approve only the exact bound operation once. Approval audit records store lifecycle action and safe context, never the raw approval bearer or operation arguments. Upstream managers publish typed lifecycle transitions, which the server records as separate audit events without letting audit I/O interrupt cleanup or recovery.
Every supported MCP request enters one outer audit scope. The scope records one terminal operation event on success or safe failure and also covers discovery/list failures, unknown names, and management tools that do not enter the proxy pipeline. `OperationPipeline` enriches proxied tool calls, resource reads, resource subscriptions, and prompt retrieval with captured source/target profile, upstream, routing, policy, risk, risk source/confidence, sanitized `routingEvidence`, and bounded `routingMatcherEvidence`; it does not emit its own record. It captures the source tool snapshot before awaiting policy work, normalizes only the four MCP behavioral booleans, and accepts them for classification only when the matching configured base upstream explicitly trusts tool annotations. The snapshot fingerprint includes client-visible annotations, so a routed profile with different metadata fails the same schema-compatibility guard as any other differing tool contract. It then captures the source profile state, resolves explicit hints and rules before the matcher band, runs the fixed in-tree registry and explicitly allowlisted routing plugins only through contained child hosts, uses `matcher:<provider>` or `matcher:plugin:<id>` only before fallback, evaluates the selected profile policy, and checks a captured lease and explicit-selection boundary before resolving the exact target upstream route. Matcher ambiguity carries only canonical bounded evidence and never resolves an upstream; a matcher reason cannot satisfy an explicit destructive-operation rule. It rechecks that captured lease immediately before execution, so a later renewal cannot authorize an old request and a different routed profile cannot borrow it. Route preview never forwards the hypothetical upstream operation, but it can invoke configured local routing plugins and therefore is not a permission-free operation. Tools retain their original upstream names for routing and policy compatibility while the matcher receives the client-visible name solely for exact provider-token recognition; resource reads and subscriptions use the stable policy name `resources/read`, while prompt retrieval uses the stable policy name `prompts/get`. Denied, blocked, and ambiguous operations never resolve or execute an upstream read/get route. Confirmation-required operations resolve only enough to bind the exact target, then enter a separate approval lifecycle before upstream session execution: state is connection-bound, the form-elicitation path carries only a generic boolean, and the default human mode fails closed when that form boundary is unavailable. An operator may explicitly select delegated-agent mode, which permits a bearer to approve only the exact bound operation once and is audited as delegated authorization rather than human proof. Approval audit records store lifecycle action, mechanism, and safe context, never the raw approval bearer or operation arguments. The management-tool contract supplies MCP behavioral annotations and exact client permission guidance, but those client hints do not replace server authorization. Upstream managers publish typed lifecycle transitions, which the server records as separate audit events without letting audit I/O interrupt cleanup or recovery.

When rotation or integrity is configured, the audit journal layer serializes same-host append, rotation, retention, and reader snapshots with a kernel-released local lock, so an abruptly terminated writer cannot leave a permanent lock artifact. A managed journal is local to one host and must not be shared for concurrent writes across machines. It rotates only between complete JSONL batches, creates a new restrictive active file before accepting the next batch, and limits retention to validated managed regular archives. Integrity transitions persist a restrictive pending intent, validate the next physical state, then persist a committed decision before cleanup; recovery can discard only a pending unacknowledged suffix, while a committed transition must verify or fail closed. Readers and exports copy a coherent retained-segment set into private staging before parsing, so output work does not hold the writer lock. The optional integrity mode calculates a continuing SHA-256 chain over already-redacted records and records segment/retention continuation metadata; verification replays that state and returns the first safe broken segment/record/reason rather than audit contents. This local state is deliberately not a public library API or a substitute for an independently protected archive.

Expand Down
4 changes: 2 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ miftah validate --config "$HOME/Miftah configs/work wrapper.json"

`generic-npx` requires `--npm-package` with exact package SemVer; `generic-docker` requires a canonical digest in `--docker-image`; and `streamable-http` requires `--url` plus optional credential environment/header metadata. `--credential-env` is optional where supported. See [preset and client compatibility](presets-and-clients.md) for exact inputs, pins, provenance, and client snippets.

`--interactive` uses a wizard only when both input and output are TTYs. EOF or Ctrl-C cancels without writing a config. It asks for variable names and safe metadata, never secret values. In noninteractive use, `init` creates only the config unless `--client` is supplied. `--client` prints JSON with absolute Node and compiled Miftah paths; it does not write a host config. Regenerate the snippet after moving or upgrading Miftah or changing the config path.
`--interactive` uses a wizard only when both input and output are TTYs. EOF or Ctrl-C cancels without writing a config. It asks for variable names and safe metadata, never secret values. In noninteractive use, `init` creates only the config unless `--client` is supplied. `--client` prints JSON with absolute Node and compiled Miftah paths; it does not write a host config. For `claude-code` or `all`, it also prints a separate, exact management-tool `permissions.ask` fragment for manual merge into Claude Code settings; it never writes or overwrites those settings. Regenerate the snippets after moving or upgrading Miftah or changing the config path.

### `migrate-config`

Expand Down Expand Up @@ -81,7 +81,7 @@ When identity verification is unconfigured, doctor records `DOCTOR_IDENTITY` as

### MCP profile management

`miftah_current_profile` returns the active/default profile plus safe selection metadata: `selectionSource`, `selectedAt`, and `scope`, plus `confirmation`, `lease`, and `lock`. When stored active-profile state is corrupt, stale, or unavailable, it additionally returns a stable `stateDiagnostic`; it never returns the state-file path or raw state contents. `miftah_use_profile` changes the active profile according to the configured scope. `miftah_reset_profile` returns to the configured default and writes that default when the scope is durable. When `security.requireProfileSwitchConfirmation` is enabled, a form-capable client confirms the exact switch with a generic boolean form; a fallback client must use the connection-bound bearer from the failed result with `miftah_approve` and retry the same request.
`miftah_current_profile` returns the active/default profile plus safe selection metadata: `selectionSource`, `selectedAt`, and `scope`, plus `confirmation`, `lease`, and `lock`. When stored active-profile state is corrupt, stale, or unavailable, it additionally returns a stable `stateDiagnostic`; it never returns the state-file path or raw state contents. `miftah_use_profile` changes the active profile according to the configured scope. `miftah_reset_profile` returns to the configured default and writes that default when the scope is durable. When `security.requireProfileSwitchConfirmation` is enabled, the default human mode requires a generic form from a form-capable client and otherwise fails closed. Only the explicit `security.approvalMode: "delegated-agent"` mode offers a connection-bound bearer through `miftah_approve` for the exact retry; it is automation authorization, not a human confirmation.

`miftah_lock_profile` and `miftah_unlock_profile` are advertised for a stable MCP surface. Calls reject with `PROFILE_LOCKING_DISABLED` unless `security.allowProfileLockingFromMcp` is enabled. When enabled, they return JSON containing `profileState`, operate only for the current MCP connection, and never modify durable selection state. A configured `security.lockToProfile` cannot be changed with either tool.

Expand Down
Loading