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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ Profiles are named credential environments. Keep secret values outside JSON when

The GitHub preset pins `ghcr.io/github/github-mcp-server:v1.5.0`. To upgrade safely, read the release notes first, update the tag in your config, run `miftah validate`, then smoke-test both profiles before rollout.

Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, and `miftah_route_preview`. Upstream tools are exposed unchanged unless they collide with a reserved management name. After a profile change or active-profile restart, MCP clients receive `tools/list_changed` and should re-list tools before calling a changed schema.
Claude can call `miftah_list_profiles`, `miftah_current_profile`, `miftah_use_profile`, `miftah_profile_info`, `miftah_health`, `miftah_validate_config`, `miftah_list_upstream_tools`, `miftah_restart_profile`, and `miftah_route_preview`. Upstream tools are exposed unchanged unless they collide with a reserved management name. After a profile change or active-profile restart, MCP clients receive list-change notifications and should re-list tools, resources, and prompts before relying on cached capabilities.

For account bundles, define `upstreams` instead of `upstream`. Tools are exposed as `<upstream>__<tool>` (for example `github__search_issues`) and each profile can provide per-upstream environment or header overrides. Resources and prompts are available only when the bundle has exactly one upstream; multi-entry bundles intentionally omit them until namespaced aggregation exists. `miftah_health` reports this availability. See `examples/multi-upstream.miftah.json`.
For account bundles, define `upstreams` instead of `upstream`. Tools, resource names, and prompt names are exposed as `<upstream>__<name>` (for example `github__search_issues` and `github__account_prompt`), and each profile can provide per-upstream environment or header overrides. A multi-upstream resource URI is a Miftah-owned route such as `miftah://resource/github?uri=account%3A%2F%2Fcurrent`; its encoded value is redacted before exposure, while Miftah retains the exact original URI only in its per-profile route map. Before exposing multi-upstream resource or prompt URI metadata, Miftah strips URI userinfo and fragments and redacts every query value. Reads and prompt gets are routed only through the exact route map, never by guessing from a client identifier.

Multi-upstream resource and prompt lists use opaque, bounded in-memory Miftah cursors so individual upstream cursors are not exposed. Re-list after a profile change or restart instead of retaining an old cursor. A standard `upstream` or a one-entry `upstreams` map preserves raw resource URIs, prompt names, and native upstream cursors for compatibility. A zero-entry bundle still omits resource and prompt capabilities. Aggregate discovery currently fails closed when any configured upstream cannot list its capability; partial healthy-upstream discovery is deferred until the resilience work is complete. See `examples/multi-upstream.miftah.json`.

## Routing and safety

Expand Down Expand Up @@ -163,7 +165,7 @@ Use `miftah doctor` to inspect config and upstream readiness without printing pr

## Current boundaries

The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, hybrid routing rules, policies, namespaced tools for account bundles, resources/prompts proxying for a standard or sole named upstream, in-memory process/session caching, redacted JSONL audit logging, and a packageable CLI. Resources and prompts are intentionally unavailable for zero- or multi-entry `upstreams` bundles. Unsupported lifecycle tuning, persisted state, UI, routing plugins, profile matchers, and configurable tool namespaces are rejected with `UNSUPPORTED_CONFIG_OPTION` rather than silently ignored.
The current experimental code implements local STDIO and remote HTTP/SSE upstream clients, profile switching, hybrid routing rules, policies, namespaced tools/resources/prompts for account bundles, in-memory process/session caching, redacted JSONL audit logging, and a packageable CLI. Multi-upstream resource and prompt discovery currently fails closed rather than returning a partial aggregate. Unsupported lifecycle tuning, persisted state, UI, routing plugins, profile matchers, and configurable tool namespaces are rejected with `UNSUPPORTED_CONFIG_OPTION` rather than silently ignored.

## License

Expand Down
6 changes: 5 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ MCP client

The public server is built with the official `@modelcontextprotocol/sdk` `Server` and `StdioServerTransport`. Each profile gets an SDK `Client` and `StdioClientTransport` on first use. A single-flight start map prevents concurrent calls from launching duplicate processes. Tool discovery is held in immutable, per-profile capability snapshots; concurrent callers share one discovery operation, and a snapshot is published only after all upstream discovery and collision checks complete. A call captures its active profile before discovery, so a later profile switch cannot change that call's active-profile fallback.

The server advertises management tools plus tools discovered from the active profile. It advertises `tools.listChanged` and emits `notifications/tools/list_changed` after profile changes or an active-profile restart when a client must re-list. Unknown names are rejected from the capability snapshot rather than guessed or forwarded. If a routing rule targets another profile, Miftah forwards only when that profile exposes the same name with an identical client-visible schema; otherwise it returns `TOOL_SCHEMA_MISMATCH`. A standard `upstream` and a named `upstreams` map with exactly one entry proxy resources and prompts through that sole upstream. A zero-entry map and a map with two or more entries omit the MCP `resources` and `prompts` capabilities and do not register their handlers; direct resource or prompt requests then receive the standard `-32601` method-not-found response. This prevents Miftah from accidentally selecting the first upstream when aggregation and namespacing are unavailable. `miftah_health` reports `resourcePromptProxy` availability and its reason, which is also included in the server instructions when disabled. Reserved management tool names are protected; the default collision strategy prefixes a conflicting upstream name with `upstream_`.
The server advertises management tools plus tools discovered from the active profile. It advertises `tools.listChanged` and emits `notifications/tools/list_changed` after profile changes or an active-profile restart when a client must re-list. Unknown names are rejected from the capability snapshot rather than guessed or forwarded. If a routing rule targets another profile, Miftah forwards only when that profile exposes the same name with an identical client-visible schema; otherwise it returns `TOOL_SCHEMA_MISMATCH`.

For a multi-entry `upstreams` map, `ResourcePromptRegistry` discovers resources and prompts from every configured upstream and publishes namespaced public values only after collision checks succeed. It names resources and prompts `<upstream>__<name>`, and exposes resources as `miftah://resource/<encoded-upstream>?uri=<encoded-redacted-upstream-uri>`. The registry retains the original upstream URI privately with the exact profile and upstream route. Prompt resource links and read-result sub-resources are registered as exact Miftah routes to their originating upstream. Before resource/prompt URI metadata crosses the boundary, structural redaction strips userinfo/fragments and redacts query values, including URI metadata returned by reads and prompt content. A read or prompt get first resolves that route, then forwards to precisely that upstream; unknown identifiers are rejected rather than forwarded. Aggregate pagination stores the individual upstream cursors behind opaque, bounded in-memory LRU state scoped to the profile and capability kind.

The server advertises `resources.listChanged` and `prompts.listChanged` with `tools.listChanged`, and emits all applicable notifications after a profile change or active-profile restart. Clients must re-list because route maps and aggregate cursors are profile-local and invalidated on profile changes and restarts. A standard `upstream` and a named `upstreams` map with exactly one entry proxy resources and prompts through that sole upstream without changing their URIs, names, or cursors. A zero-entry map omits the MCP `resources` and `prompts` capabilities and does not register their handlers; direct resource or prompt requests then receive the standard `-32601` method-not-found response. Multi-upstream discovery is deliberately fail-closed: if any configured upstream cannot list a capability, Miftah returns no partial aggregate. `miftah_health` reports capability availability and its reason when disabled. Reserved management tool names are protected; the default collision strategy prefixes a conflicting upstream name with `upstream_`.

Configuration and runtime concerns are intentionally separate:

Expand Down
6 changes: 5 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ Miftah accepts config version `"1"` only and does not silently migrate config fi

The generated JSON Schema enforces static structure, including exactly one of `upstream` or `upstreams`. References to names declared in dynamic maps cannot be represented by JSON Schema alone; run `miftah validate` in addition to editor validation to verify profile, policy, routing, lock, and per-profile upstream references.

With `upstreams`, each profile may override `env`, `headers`, `args`, or `cwd` under a named upstream. Miftah namespaces discovered tools as `<upstream>__<tool>` so one wrapper can safely expose several providers. Tool discovery uses the active profile. Clients receive `notifications/tools/list_changed` after a profile change or active-profile restart and must re-run `tools/list` before relying on a changed tool set or schema. If a routing rule selects another profile, Miftah forwards only tools with an identical client-visible schema in both profiles; otherwise it returns `TOOL_SCHEMA_MISMATCH` instead of forwarding a call whose schema the client did not see. Resources and prompts are proxied only when `upstreams` contains exactly one entry; zero-entry and multi-entry bundles omit those capabilities and their handlers rather than choosing an upstream. Use tools or a wrapper with a single upstream for resources and prompts while namespaced aggregation is deferred.
With `upstreams`, each profile may override `env`, `headers`, `args`, or `cwd` under a named upstream. Miftah namespaces discovered tools as `<upstream>__<tool>` so one wrapper can safely expose several providers. Tool discovery uses the active profile. Clients receive `notifications/tools/list_changed` after a profile change or active-profile restart and must re-run `tools/list` before relying on a changed tool set or schema. If a routing rule selects another profile, Miftah forwards only tools with an identical client-visible schema in both profiles; otherwise it returns `TOOL_SCHEMA_MISMATCH` instead of forwarding a call whose schema the client did not see.

For a multi-entry `upstreams` map, Miftah aggregates resources and prompts. Resource names and prompt names use `<upstream>__<name>`. Resource URIs use `miftah://resource/<encoded-upstream>?uri=<encoded-redacted-upstream-uri>` and resolve through an exact, per-profile route map; callers cannot select an upstream by supplying a raw URI or an unlisted namespaced identifier. Prompt links and sub-resource URIs become exact Miftah routes to their originating upstream as well. Before publication, Miftah strips URI userinfo/fragments and redacts every query value in resource and prompt URI metadata. Multi-upstream list cursors are opaque, bounded Miftah cursors, scoped to the active profile and capability type, and cannot be reused after a profile change or restart. Clients receive `notifications/resources/list_changed` and `notifications/prompts/list_changed` with the tool notification and must re-list all affected capabilities.

A standard `upstream` and a one-entry `upstreams` map retain raw resource URIs, prompt names, and native upstream pagination. A zero-entry map omits resource and prompt capabilities. Multi-upstream discovery fails closed when any configured upstream fails to list resources or prompts; Miftah does not currently return a partial aggregate from healthy upstreams.

Before loading secret sources or starting an upstream, Miftah validates that `defaultProfile`, profile policy names, routing-rule profiles, `security.lockToProfile`, and per-profile upstream override names all exist. Validation errors are human-readable and expose `MiftahError.details.diagnostics` for programs; each diagnostic includes a stable `code`, dotted `path`, `severity`, `message`, and remediation.

Expand Down
Loading