17 KiB
wf_mcp Plan
This document describes the intended direction of wf_mcp.
The important change in scope is this:
wf_mcpis not just a tool wrapper layerwf_mcpis a namespaced MCP capability broker plus workflow build/run layer
That means it should be able to face:
- human users
- client LLMs
- workflow execution services
without forcing every MCP capability to immediately become a workflow node.
Goals
wf_mcp should:
- manage multiple named MCP backend connections
- persist auth/session state
- discover and cache MCP capabilities per connection
- expose namespaced catalogs to humans and LLMs
- wrap callable tool capabilities into workflow-executable
NodeSpecs - compile and run workflows against those capabilities
- preserve traceability between client actions, MCP backend calls, and workflow runs
Non-goals for the first phase
- full persistence of workflow runs/jobs
- parallel workflow execution semantics beyond what
wf_corealready supports - turning every MCP capability type into a workflow node immediately
- hiding protocol complexity by inventing vague magic abstractions
Layering
wf_core
Owns:
- workflow model
- validation
- runtime semantics
- frames
- trace
- interrupts
- foreach
Does not own:
- MCP connections
- auth/session persistence
- discovery caching
- capability brokerage
wf_authoring
Owns:
@nodeNodeSpecWorkflowBuilder- condition DSL
- subgraph wrapping
Does not own:
- MCP transport/client logic
wf_mcp
Owns:
- connection registry
- auth store
- capability discovery/cache
- namespaced capability catalog
- MCP backend adapter layer
- tool-to-
NodeSpecwrapping - raw plan compilation and workflow execution entrypoints
- future user-facing service/API surface
Mental model
Think of wf_mcp as having two planes.
1. Capability proxy plane
This plane exposes what a backend MCP connection offers.
Capabilities include:
- tools
- resources
- prompts
- notifications/events
- auth metadata
- tasks
- elicitations
- app/server metadata
This plane is about discovery, namespacing, caching, and proxying.
2. Workflow execution plane
This plane decides which capabilities can be used inside workflows and how.
In the first phase:
- tools become executable workflow nodes
- resources and prompts are exposed in catalog/proxy APIs first
- tasks, notifications, and elicitations are modeled and surfaced first, then integrated into workflows later
Connection identity
Connections must be first-class and stable.
Expected shape:
<server>.<account>
Examples:
google.personalgoogle.workgithub.maineverything.default
The same backend/server type may have multiple configured connections with different auth, tool availability, or environment.
Namespacing
All exposed capabilities should be explicitly namespaced.
Examples:
google.personal.tool.list_filesgoogle.personal.prompt.summarize_foldergoogle.personal.resource.drive://folder/abceverything.default.tool.echo_tool
The exact string shape can evolve, but these properties should hold:
- globally unique
- reversible back to connection id + local capability id
- understandable by both humans and LLMs
Current implemented slice
Already present:
- connection config and registry
- pluggable file-backed auth/catalog store
- tool catalog snapshots
- raw workflow plan compilation
- workflow execution via async runtime
- lightweight adapter protocol
- fake adapter tests
- real MCP SDK adapter for
stdioandstreamable_http
This is a good first vertical slice for tool execution.
What "good proxy" means
For wf_mcp, "proxy" should not only mean "call tools on behalf of a client."
It should mean:
- discover backend MCP capabilities and expose them with stable namespacing
- preserve enough metadata that a client can understand what exists and how to use it
- forward or mirror backend-facing events and responses in a traceable way
- keep auth/session boundaries explicit per connection
- separate "what the backend offers" from "what becomes a workflow node"
So the proxy layer should eventually broker:
- tools
- resources
- prompts
- notifications/events
- auth-related state markers
- tasks / long-running operations
- elicitations / human-input capabilities
- app/server metadata
Not all of these need workflow semantics immediately, but they should still have a place in the package model.
Transparent MCP proxy mode
The broker also needs a more protocol-native mode than the current generic broker tools.
The current broker tools are useful for catalog and control-plane inspection:
get_catalogread_broker_resourcerender_broker_promptinvoke_broker_method
But these do not make upstream capabilities appear as first-class MCP capabilities to a client like Inspector.
For a real proxy experience, the broker should eventually mirror upstream capabilities into the broker's own MCP surfaces:
- upstream
tools/listentries appear in brokertools/list - upstream
tools/callrequests route back to the owning connection - upstream
resources/listentries appear in brokerresources/list - upstream
resources/readrequests route back to the owning connection - upstream
prompts/listentries appear in brokerprompts/list - upstream
prompts/getrequests route back to the owning connection
Example desired tool entry:
{
"name": "everything.default.echo",
"title": "Everything Reference Server: Echo Tool",
"description": "Echoes back the input string",
"inputSchema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Message to echo"
}
},
"required": ["message"],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
},
"execution": {
"taskSupport": "forbidden"
}
}
Mirrored tools are actual MCP tools exposed by the broker/proxy surface. Do not
reintroduce a generic call_tool wrapper for upstream tools; it duplicates the
proxy plane and is the wrong abstraction for stateful MCP servers.
Official protocol boundary
The proxy layer should prefer official MCP boundary types over local mirror models.
Use MCP SDK types at the proxy boundary:
mcp.types.Toolmcp.types.Resourcemcp.types.Promptmcp.types.CallToolResultmcp.types.GetPromptResultmcp.types.ReadResourceResult
Local models such as DiscoveredTool, CatalogNodeEntry, and NodeSpec remain useful, but they are not the wire contract a standard MCP client understands. They are internal broker/workflow models.
Practical rule:
- MCP proxy plane speaks official MCP types
- workflow plane compiles selected tools into
wf_authoring.NodeSpec - catalog/UI plane can use normalized local models as a persisted view
This keeps protocol compatibility separate from workflow-specific concepts like outcome.
Name mapping
Mirrored capability names must be globally unique and reversible.
Recommended tool name shape:
<connection_id>.<local_tool_name>
Example:
everything.default.echogithub.work.create_issue
This matches the MCP tool-name guidance: ASCII letters, digits, _, -, and . are valid.
The proxy should keep a mapping table:
- broker name -> connection id
- broker name -> upstream kind
- broker name -> upstream local name or URI
- broker name -> display title
- broker name -> enabled/disabled state
This mapping should eventually support UI/admin controls:
- enable/disable a connection
- enable/disable specific tools/resources/prompts
- logout / clear auth for a connection
- refresh a connection
- inspect cached vs live capability state
The UI can come later, but the data model should make room for these states.
FastMCP middleware path
FastMCP's documented tool injection middleware is promising for the first transparent tool mirror.
The relevant hooks are:
ToolInjectionMiddleware.on_list_toolsToolInjectionMiddleware.on_call_tool
These are exactly the two operations needed for first-class mirrored tools:
- inject upstream tools into broker
tools/list - intercept calls to injected tool names and route them upstream
Implementation should verify whether the installed MCP/FastMCP dependency exposes the documented middleware API. If it does, use it. If it does not, treat this as a dependency/version problem and either:
- add the compatible
fastmcpdependency explicitly - or implement the proxy at a lower MCP server/provider layer
Do not encode the proxy as a pile of static decorators. The proxy surface is connection/catalog driven and must be able to change after refresh.
Sampling and elicitation
Sampling and elicitation make protocol-transparent proxying much harder than simple tool forwarding.
Simple request flow:
- client -> broker -> upstream -> broker -> client
Sampling/elicitation flow:
- upstream server may ask the client for a model sample or user input
- broker must forward that request to the original client
- broker must route the client's answer back to the upstream server
For a client like Inspector to show an elicitation form from an upstream server, the proxy must preserve this bidirectional protocol behavior.
Near-term stance:
- mirror normal tools/resources/prompts first
- keep raw method invocation only for explicit protocol/admin diagnostics
- test the Everything Reference Server elicitation tool as the first serious proxy pressure test
- only claim elicitation/sampling support once a live Inspector flow works end to end
Relationship to workflow execution
Transparent proxying and workflow execution are separate concerns.
Proxy path:
- expose upstream tools/resources/prompts to MCP clients
- preserve official MCP semantics
- forward protocol interactions
Workflow path:
- turn selected tools into
NodeSpecs - expose them to
wf_core - add workflow-specific state mapping, outcomes, tracing, retries, interrupts
The proxy must not leak workflow concepts like outcome into MCP tools/list. outcome belongs to workflow execution, not the MCP wire contract.
Next capability expansion
The next architectural move should be broadening the capability model beyond tools.
Add models for
DiscoveredResourceDiscoveredPromptDiscoveredNotificationDiscoveredTaskCapabilityDiscoveredElicitationCapability- maybe
DiscoveredAppMetadata
These do not all need execution semantics immediately.
They do need:
- namespacing
- storage/caching
- catalog exposure
Catalog direction
We should move from a tool-only catalog to a unified capability catalog.
The catalog should be able to expose, per connection:
- tools
- resources
- prompts
- capability metadata
- auth requirements or state markers
- freshness metadata
The client-facing catalog payload should be usable by:
- a human inspector UI
- an LLM that builds workflows
- internal service code
Near-term implementation order
This is the intended order of work from here.
Milestone 1: Broaden discovery models
Add normalized discovery models for:
- resources
- prompts
- notifications
- tasks
- elicitations
- app/server metadata
Outcome:
wf_mcpcan describe more of an MCP backend than just tools- namespacing rules apply uniformly across capability types
Milestone 2: Unified capability snapshots
Move from tool-only snapshots to connection-scoped capability snapshots that can store:
- tools
- resources
- prompts
- capability metadata
- freshness timestamps
- auth-state hints
Outcome:
- the catalog becomes useful to a real client or inspector
- capability caching is no longer tool-specific
Milestone 3: Service APIs for non-tool capabilities
Expose service methods that let a client:
- inspect resources
- inspect prompts
- read capability metadata
- refresh one connection or all connections
Outcome:
wf_mcpbecomes a usable broker, not only a workflow launcher
Milestone 4: Trace/event correlation
Add a light event model that can connect:
- client action
- backend MCP request/response
- workflow run/step trace
Outcome:
- better observability
- cleaner debugging
- future notification/task support has a home
Milestone 5: Selective workflow mapping
Only after the above is stable:
- keep tools as workflow nodes
- evaluate whether prompts/resources should become helper nodes or stay catalog-only
- map elicitation to workflow interrupts carefully
Outcome:
- workflow integration grows from understood protocol behavior, not guesses
Workflow integration policy
Not every capability becomes a workflow node right away.
Immediate workflow nodes
- tools
Catalog/proxy first, workflow later
- resources
- prompts
- notifications
- tasks
- elicitations
Why:
- tools already match the node call model well
- the others need more careful semantic mapping
Elicitations and interrupts
Elicitations are especially interesting because they align with the workflow interrupt model.
Planned stance:
- expose MCP elicitation capability in catalogs first
- understand the backend semantics first
- later map appropriate elicitation flows into workflow
InterruptNodebehavior
This should be done deliberately, because workflow interrupt semantics are stronger and more structured than generic protocol-level elicitation.
Tasks and long-running work
Tasks likely align with future run/job persistence, but they should not force that design immediately.
Near-term stance:
- surface task capability in capability catalogs
- understand the task API shape
- leave run persistence mostly out of scope for now
Future direction:
- scheduled jobs
- persisted runs
- polling or event-driven task monitoring
Notifications and traceability
Traceability is a major requirement.
We should eventually distinguish:
- workflow execution trace
- backend MCP call trace
- client-facing event stream
The important design rule is that these should be related, but not collapsed into one giant anonymous log.
Concrete next files
If we follow the plan above, the next likely modules are:
wf_mcp/discovery.py- orchestrate refresh and cache policy for all capability types
wf_mcp/capabilities.py- broader normalized capability models if
models.pystarts getting crowded
- broader normalized capability models if
wf_mcp/events.py- event / trace correlation shapes
wf_mcp/resources.py- resource-facing service helpers
wf_mcp/prompts.py- prompt-facing service helpers
Whether these stay as separate files or fold back into models.py / service.py should be driven by clarity, not purity.
Auth and storage
Auth should remain behind a pluggable store interface.
First implementation:
- file-backed store
Expected future replacements:
- database-backed store
- encrypted local store
- secret-manager-backed store
The store should persist:
- auth records
- cached capability snapshots
It may later persist:
- saved plans/workflows
- job specs
- run metadata
Execution model
Near-term execution stance:
- async-first at the MCP layer
- use existing async workflow runtime
- keep workflow runs mostly in memory
- leave room for future scheduled/offline execution
The important offline use case is:
- build workflow once
- execute it later without the LLM in the loop
This is closer to scheduled automation than to interactive planning.
Public API direction
wf_mcp should expose two explicit entrypoints.
1. Convenient/build-style API
For human or higher-level service use.
Examples:
- build workflow from selected catalog items
- helper methods around namespaced tools/resources/prompts
2. Raw plan API
For client LLM use.
This should accept plans that:
- reference namespaced capabilities directly
- avoid raw
NodeDefauthoring - still compile down to
wf_core.Workflow
Test organization direction
The current test suite is still small enough to live in two top-level files, but it will get noisy if wf_mcp grows beyond tools.
Recommended direction once the next capability work starts:
tests/wf_core/tests/wf_authoring/tests/wf_mcp/tests/fixtures/
For wf_mcp, likely split by concern:
tests/wf_mcp/test_store.pytests/wf_mcp/test_catalog.pytests/wf_mcp/test_service.pytests/wf_mcp/test_adapters.pytests/wf_mcp/test_sdk_adapter.py
This does not need to happen immediately, but it should happen before one test_wf_mcp.py turns into a junk drawer.
Working rule for the next phase
Before making a new MCP capability executable inside workflows, first answer:
- is this primarily catalog/proxy surface, or workflow surface?
- what is the stable namespaced identifier?
- what is the trace/event story?
- does it need auth/session context?
- does it map cleanly to
wf_core, or does it stay above it?
That rule should keep us from forcing everything through the node model too early.