178 lines
9.1 KiB
Markdown
178 lines
9.1 KiB
Markdown
# wf_mcp Architecture Boundaries
|
|
|
|
`wf_mcp` is one distribution for now, but it is organized as separable concerns.
|
|
The goal is to keep future package extraction cheap without adding packaging
|
|
overhead before the APIs settle.
|
|
|
|
For the short operator-facing map of the current nouns and tool families, start
|
|
with [`wf_mcp_operator_manual.md`](wf_mcp_operator_manual.md).
|
|
|
|
## Packages
|
|
|
|
| Package | Responsibility |
|
|
| --- | --- |
|
|
| `wf_mcp.proxy` | Expose configured upstream MCP servers through mounted FastMCP proxy providers. Owns proxy runtime, admin tools, and proxy tool listing helpers. |
|
|
| `wf_mcp.broker` | Coordinate remembered connections, catalog snapshots, discovery, events, and workflow execution through broker services. |
|
|
| `wf_mcp.workflow` | Convert discovered MCP tools into `wf_authoring` / `wf_core` node specs. |
|
|
| `wf_mcp.sdk` | Speak to upstream MCP servers through the MCP Python SDK. Owns adapter protocols, SDK transport/session calls, and SDK object converters. |
|
|
| `wf_mcp.control` | Parse and mutate file-backed proxy/broker configuration. |
|
|
| `wf_mcp.storage` | Persist auth records and catalog snapshots. |
|
|
| `wf_mcp.shared` | Pure helpers used across concerns, such as names, pagination, and error payloads. |
|
|
|
|
The root `wf_mcp` package is a small public facade for common user entrypoints,
|
|
not a dump of every internal helper. If a caller needs broker internals, SDK
|
|
adapter protocols, proxy admin pieces, or shared name parsing, import the
|
|
relevant concern package directly.
|
|
|
|
## Dependency Rules
|
|
|
|
- `wf_api` defines the workflow application surface and process-local
|
|
implementation. `wf_mcp` may import and adapt it; `wf_api` must not import
|
|
`wf_mcp`.
|
|
- `wf_mcp.sdk` should not import `wf_core` or `wf_authoring`.
|
|
- `wf_mcp.proxy` should not import `wf_mcp.workflow`.
|
|
- `wf_mcp.workflow` is the only layer that converts MCP capabilities into node specs.
|
|
- `wf_mcp.broker` may coordinate `sdk`, `storage`, `control`, and `workflow`.
|
|
- `wf_mcp.control` should not know about live MCP clients or workflow execution.
|
|
- `wf_mcp.shared` should stay pure and should not import other `wf_mcp` concern packages.
|
|
- Root compatibility shims should stay thin: import and re-export only.
|
|
|
|
## Workflow API Boundary
|
|
|
|
Workflow lifecycle operations now have a protocol-neutral front door:
|
|
|
|
```text
|
|
wf_mcp.workflow_surface.tools
|
|
-> wf_api.WorkflowApiSurface implementation
|
|
-> wf_api.WorkflowApi
|
|
-> wf_api domain services
|
|
-> WorkflowOperationContext
|
|
-> WfMcpService adapters/stores/runtime
|
|
```
|
|
|
|
`WorkflowSurfaceHandlers` is a compatibility shim only. New entrypoints should
|
|
construct `WorkflowApi(context_from_service(service))` directly.
|
|
|
|
The old backend-adapter/protocol layer has been removed. `WorkflowApiSurface`
|
|
is now the structural workflow operation contract. The local `WorkflowApi`
|
|
implementation composes domain services (`WorkflowCapabilityApi`,
|
|
`WorkflowDraftApi`, `WorkflowArtifactApi`, `WorkflowDeploymentApi`,
|
|
`WorkflowRunApi`) from a `WorkflowOperationContext`.
|
|
|
|
New code should type consumers against `wf_api.WorkflowApiSurface` when they can
|
|
work with either local or remote implementations. Use concrete `WorkflowApi`
|
|
only when same-process stores/runtime are required. Do not add new callers that
|
|
import `WorkflowSurfaceHandlers` directly unless they are compatibility tests.
|
|
|
|
The broader application-service boundary is documented in
|
|
[`wf_api_architecture.md`](wf_api_architecture.md).
|
|
|
|
This is a dependency-direction cleanup, not a full domain split. Most API
|
|
methods still mirror the old workflow-surface payloads and return
|
|
`dict[str, Any]`.
|
|
|
|
Protocol-neutral workflow helpers have moved to `wf_api`: constants, capability
|
|
refs, wrapper hints, next actions, raw workflow plans, runtime dependency
|
|
resolution, saved subgraph preparation, and durable run lifecycle helpers. The
|
|
old `wf_mcp.workflow_surface.*` helper modules are compatibility shims. MCP
|
|
tool schemas and registration still live in `wf_mcp.workflow_surface.models` and
|
|
`wf_mcp.workflow_surface.tools`.
|
|
|
|
## Broker Catalogs
|
|
|
|
The broker keeps two related catalog views:
|
|
|
|
- `get_catalog()` is the backend MCP catalog. It only includes enabled upstream
|
|
connection snapshots loaded from storage.
|
|
- `get_planner_catalog()` is the workflow-planning catalog. It includes backend
|
|
connection snapshots plus broker-local system sources such as `wf.std`.
|
|
|
|
Broker-local sources are not fake MCP backend connections. They are registered
|
|
as service spec sources so raw workflow plans can address nodes like
|
|
`wf.std.runtime_error` without polluting connection status, auth, adapter lookup,
|
|
or persisted backend catalog snapshots.
|
|
|
|
The longer-term source model is described in
|
|
[`wf_mcp_capability_sources.md`](wf_mcp_capability_sources.md). In that model,
|
|
sources own tools, workflow node specs, prompts, and resources, while broker,
|
|
proxy, planner, and admin UI surfaces project different capability kinds.
|
|
|
|
## Hot Reload
|
|
|
|
Proxy reload is intentionally isolated in
|
|
`wf_mcp.proxy.runtime`. FastMCP does not currently expose a complete
|
|
provider/proxy unmount lifecycle that we can rely on for safe per-connection
|
|
teardown. Until that exists, reload should be treated as best-effort remounting,
|
|
not a fully safe session/subscription lifecycle.
|
|
|
|
Unified mode currently reuses `ProxyRuntime` as its proxy mounting engine. The
|
|
old `TransparentProxyRuntime` compatibility alias has been removed; use
|
|
`ProxyRuntime` directly.
|
|
|
|
`ProxyRuntime` now owns a small `ProxyMountRegistry`. Proxy/admin tools are
|
|
registered once on the top-level local provider; reload only clears the visible
|
|
mounted upstream provider list and rebuilds it from current config. Unchanged
|
|
enabled connections reuse their cached proxy mount instead of recreating a new
|
|
client/proxy pair every time. Disabled or removed connections are no longer
|
|
mounted after reload, but their cached mounts are only *retired* internally for
|
|
now; they are not safely closed or unmounted because FastMCP does not yet expose
|
|
the lifecycle hook we need.
|
|
|
|
The reused FastMCP proxies are not holding one forever-open upstream connection.
|
|
`create_proxy_mount()` gives `create_proxy(...)` a disconnected client, and
|
|
FastMCP creates fresh request clients from it. What persists across reload is the
|
|
proxy/provider object and its component-list caches. FastMCP refreshes those
|
|
caches on explicit `list_*` calls and otherwise expires them after its TTL.
|
|
|
|
After a successful reload, the runtime publishes local `tools_changed`,
|
|
`resources_changed`, `prompts_changed`, and `catalog_changed` events when an
|
|
event bus is supplied. The admin MCP tool projects the same event kinds into
|
|
MCP list-changed notifications for the current client session. Config mutation
|
|
tools still only stage changes and return `requires_reload`; they do not emit
|
|
list-changed notifications until reload remounts the visible capability set.
|
|
Internally, reload metadata uses `ProxyReloadResult`; MCP tools serialize that
|
|
typed result to a plain payload at the boundary.
|
|
Proxy tool listing similarly uses `ProxyToolPayload` / `ProxyToolsPage`
|
|
internally and serializes to admin MCP payloads at the boundary.
|
|
|
|
Do not add more lifecycle behavior outside `ProxyMountRegistry`. Cached clients
|
|
still need clear close/reconnect/error semantics, and the registry is the single
|
|
place where that future behavior should land. Prefer FastMCP's official
|
|
unmount/provider lifecycle when it becomes available.
|
|
|
|
Do not add notification proxying or long-lived subscription handling across
|
|
reloads without first introducing an explicit mount lifecycle boundary.
|
|
|
|
The current practical proxy roadmap, including which FastMCP gaps are worth
|
|
working around locally and which should stay upstream-dependent for now, lives
|
|
in [`wf_mcp_proxy_reality_and_roadmap.md`](wf_mcp_proxy_reality_and_roadmap.md).
|
|
|
|
The public MCP mode split has been retired. The execution plan is
|
|
[`historical/superpowers/plans/2026-05-16-retire-legacy-mcp-modes.md`](historical/superpowers/plans/2026-05-16-retire-legacy-mcp-modes.md):
|
|
`broker` and `proxy` were legacy public launch surfaces, while the ordinary
|
|
server now exposes both local capabilities and proxied upstream capabilities.
|
|
Internal concern packages remain useful even though the public mode choices are
|
|
gone.
|
|
|
|
## Future Extraction
|
|
|
|
If this becomes multiple distributions, likely split points are:
|
|
|
|
- `wf-transport-mcp`: MCP-facing transport that exposes neutral
|
|
`WorkflowServer` / `wf_api` workflow, source-admin, and platform-admin
|
|
surfaces to MCP clients.
|
|
- `wf-sources-mcp`: upstream MCP source provider that owns external MCP server
|
|
discovery, sessions, tool invocation, resources/prompts, FastMCP integration,
|
|
and conversion of discovered MCP tools into workflow node specs.
|
|
- `wf-mcp-proxy`: compatibility/proxy runtime for mounting upstream MCP servers
|
|
through FastMCP, if that remains useful after `wf-sources-mcp` exists.
|
|
|
|
The key boundary is direction, not naming: "MCP as a client transport" and
|
|
"MCP as an upstream workflow source" are separate roles. The current `wf_mcp`
|
|
package still contains both roles plus compatibility entrypoints. New
|
|
server/transport code should depend on `wf_server` / `wf_api` surfaces and keep
|
|
MCP-specific upstream behavior behind a source-provider adapter.
|
|
|
|
For now, keep one distribution and use import discipline to preserve these
|
|
future split points.
|