Summary
Add a first-class deterministic workflow step that calls one tool on a configured MCP server directly, without asking an LLM agent to choose or invoke the tool.
This would let workflows use the existing MCP integration ecosystem for operations such as querying issue trackers, updating repository metadata, reading databases, or calling internal services while keeping the operation visible and reviewable in YAML.
This is an idea for discussion, not a commitment to a particular final syntax.
Motivation
Conductor is strongest when probabilistic work and deterministic orchestration are kept separate. Today, deterministic integration work generally falls back to type: script:
- name: update_tracker
type: script
command: uv
args: [run, python, -m, scripts.update_tracker]
That works, but the actual operation moves out of the workflow into Bash, Python, or TypeScript code. A reviewer can no longer understand the full workflow from the YAML alone, and each repository must own and maintain its integration glue.
Platforms such as n8n address this with native integrations, but building and maintaining a comparable catalog specifically for Conductor would be expensive. MCP already provides a standardized tool discovery and invocation surface across a large ecosystem of servers. A direct MCP step could therefore serve as a broad integration escape hatch while native steps remain available for common operations where Conductor can provide a better contract and user experience.
Proposed MVP shape
workflow:
runtime:
mcp_servers:
tracker:
type: stdio
command: tracker-mcp
env:
TRACKER_TOKEN: ${TRACKER_TOKEN}
agents:
- name: create_issue
type: mcp
server: tracker
tool: create_issue
arguments:
project_id: 105
subject: "{{ triage.output.title }}"
description: "{{ triage.output.details }}"
routes:
- to: notify
The step should:
- invoke exactly one named tool without an LLM call;
- recursively render
arguments against workflow context while preserving JSON-native values rather than coercing every value to a string;
- verify that
server exists in runtime.mcp_servers;
- verify that the tool is permitted by the servers configured
tools allowlist;
- expose the MCP result in workflow context for downstream templates and routes;
- support the normal step lifecycle in the main loop, parallel groups, and
for_each where safe;
- emit dedicated started/completed/failed events for CLI, JSONL, dashboard, and replay visibility;
- apply the existing per-result MCP output limit where applicable.
A possible output envelope is:
create_issue.output:
content: [...] # MCP content blocks, represented as JSON-safe data
structured: {...} # structuredContent when supplied by the server
is_error: false # MCP tool execution error flag
The exact output shape needs discussion. It should preserve structured MCP data rather than flattening everything to text. Optional output: validation could apply to a documented portion of that envelope.
Why this appears feasible in the current architecture
Conductor already has most of the required building blocks:
runtime.mcp_servers and MCPServerDef describe stdio, HTTP, and SSE servers.
src/conductor/mcp/manager.py::MCPManager is a host-side MCP client that can connect, discover tools, call tools, manage process lifetime, and apply tool-output limits.
WorkflowEngine already dispatches non-LLM step types such as script, set, and wait and stores their typed outputs in WorkflowContext.
- Existing routing, limits, checkpointing, event emission, and output validation can be reused around a new executor.
The direct step should be owned by the workflow engine/executor layer, not by an LLM provider. Current providers expose MCP through different mechanisms: Claude uses the host-side MCPManager, while Copilot and Claude Agent SDK delegate MCP configuration to their SDK/CLI. A deterministic step must have one provider-independent execution path and must also work in workflows whose LLM provider does not support MCP tools.
Important corrections and constraints
No transactional guarantee
MCP standardizes tool invocation, not the semantics or atomicity of the external operation. Closing an MCP process or connection does not imply rollback. A successful tool may have committed an external side effect before Conductor receives, stores, or checkpoints the result.
Likewise, if a workflow is cancelled after a mutating tool call but before the step is committed to context, resume may invoke it again. The step therefore has at-least-once risk around interruption and resume unless the tool itself supports idempotency keys or reconciliation.
No implicit retries in the MVP
Conductor cannot infer whether an arbitrary MCP tool is safe to repeat. Automatic retries could duplicate issues, comments, payments, commits, or other mutations. The first version should not retry tool calls implicitly. Any future retry support should be explicit and documented around idempotency.
Start with the transport the shared client actually supports
Although MCPServerDef accepts stdio, HTTP, and SSE, the shared host-side MCPManager currently implements stdio only. A coherent first implementation could therefore support stdio and return a clear validation error for other transports. HTTP/SSE can follow by extending the shared manager while preserving the step syntax.
Secrets and observability need an explicit policy
MCP does not redact secrets for Conductor. Tool arguments may contain credentials or sensitive payloads, and the current tool event formatting includes arguments. A direct step should not log full arguments or results by default. Events can safely include server/tool names, argument keys, elapsed time, result size, and truncation metadata; displaying values should require an explicit, redaction-aware policy.
Concurrency needs a defined ownership model
A workflow-owned connection pool could keep one stdio server alive and reuse it across direct calls. Calls sharing the same server/session should initially be serialized unless client/server concurrency is known to be safe; different servers or working directories can proceed independently. Cleanup must be bounded by workflow lifecycle and robust under cancellation.
Why sequence is not part of the MVP
A special multi-call sequence syntax is tempting for stateful servers, but it would immediately add nested scoping, conditional execution, partial failure policy, cleanup hooks, and misleading expectations of transactionality.
Ordinary workflow steps already express ordered calls and routing. Connection reuse can be an executor lifecycle detail, so consecutive type: mcp steps may share the same live stdio server without introducing another programming language inside YAML.
If real servers demonstrate a need for an indivisible connection-scoped block, sequence syntax can be discussed later as non-transactional convenience sugar.
Relationship to #222 (type: http)
This proposal complements rather than replaces #222.
A native HTTP step remains preferable for direct REST calls because Conductor can provide HTTP-specific behavior that a generic MCP tool cannot guarantee consistently:
- typed request and response bodies;
- status codes, headers, and non-2xx payloads as a stable output contract;
- method-aware retry defaults;
- standard authorization-header redaction;
- lower startup and serialization overhead;
- no dependency on the quality, lifecycle, or security behavior of a third-party MCP HTTP client server.
The MCP step is the broad integration escape hatch for existing MCP tools. The HTTP step is the purpose-built primitive for a very common protocol.
Suggested phased scope
Phase 1
- one
type: mcp step equals one tool call;
- stdio servers through an engine-owned executor/client lifecycle;
- typed recursive argument rendering;
- structured result preservation and optional output validation;
- no implicit retry and no sequence syntax;
- safe events, cancellation cleanup, routing, parallel/for-each support with per-session serialization;
- validation that the server exists and the tool is allowed.
Phase 2
- host-side Streamable HTTP/SSE transport support;
- transport-specific authentication and lifecycle handling;
- richer static validation using discovered tool schemas where practical.
Later, only if justified by real workflows
- explicit idempotency-aware retry policy;
- connection-scoped sequence convenience syntax;
- declarative redaction annotations for selected argument/result paths.
Open questions
- Should the step type be named
mcp, mcp_call, or mcp_tool?
- What stable JSON-safe representation should be used for MCP content blocks and
structuredContent?
- Should
isError: true become a failed workflow step by default, or remain routable output with an opt-in fail mode?
- Should a direct step reuse a long-lived workflow-level server process, or should lifecycle be scoped more narrowly by server and working directory?
- How should direct steps and provider-managed MCP servers avoid duplicate stdio processes when both use the same configuration?
- Is the Python MCP SDK promoted to a required dependency, or should this step live behind an installation extra?
- Which event fields can be shown safely without exposing tool arguments or results?
Acceptance criteria for an eventual MVP
Related: #222.
Summary
Add a first-class deterministic workflow step that calls one tool on a configured MCP server directly, without asking an LLM agent to choose or invoke the tool.
This would let workflows use the existing MCP integration ecosystem for operations such as querying issue trackers, updating repository metadata, reading databases, or calling internal services while keeping the operation visible and reviewable in YAML.
This is an idea for discussion, not a commitment to a particular final syntax.
Motivation
Conductor is strongest when probabilistic work and deterministic orchestration are kept separate. Today, deterministic integration work generally falls back to
type: script:That works, but the actual operation moves out of the workflow into Bash, Python, or TypeScript code. A reviewer can no longer understand the full workflow from the YAML alone, and each repository must own and maintain its integration glue.
Platforms such as n8n address this with native integrations, but building and maintaining a comparable catalog specifically for Conductor would be expensive. MCP already provides a standardized tool discovery and invocation surface across a large ecosystem of servers. A direct MCP step could therefore serve as a broad integration escape hatch while native steps remain available for common operations where Conductor can provide a better contract and user experience.
Proposed MVP shape
The step should:
argumentsagainst workflow context while preserving JSON-native values rather than coercing every value to a string;serverexists inruntime.mcp_servers;toolsallowlist;for_eachwhere safe;A possible output envelope is:
The exact output shape needs discussion. It should preserve structured MCP data rather than flattening everything to text. Optional
output:validation could apply to a documented portion of that envelope.Why this appears feasible in the current architecture
Conductor already has most of the required building blocks:
runtime.mcp_serversandMCPServerDefdescribe stdio, HTTP, and SSE servers.src/conductor/mcp/manager.py::MCPManageris a host-side MCP client that can connect, discover tools, call tools, manage process lifetime, and apply tool-output limits.WorkflowEnginealready dispatches non-LLM step types such asscript,set, andwaitand stores their typed outputs inWorkflowContext.The direct step should be owned by the workflow engine/executor layer, not by an LLM provider. Current providers expose MCP through different mechanisms: Claude uses the host-side
MCPManager, while Copilot and Claude Agent SDK delegate MCP configuration to their SDK/CLI. A deterministic step must have one provider-independent execution path and must also work in workflows whose LLM provider does not support MCP tools.Important corrections and constraints
No transactional guarantee
MCP standardizes tool invocation, not the semantics or atomicity of the external operation. Closing an MCP process or connection does not imply rollback. A successful tool may have committed an external side effect before Conductor receives, stores, or checkpoints the result.
Likewise, if a workflow is cancelled after a mutating tool call but before the step is committed to context, resume may invoke it again. The step therefore has at-least-once risk around interruption and resume unless the tool itself supports idempotency keys or reconciliation.
No implicit retries in the MVP
Conductor cannot infer whether an arbitrary MCP tool is safe to repeat. Automatic retries could duplicate issues, comments, payments, commits, or other mutations. The first version should not retry tool calls implicitly. Any future retry support should be explicit and documented around idempotency.
Start with the transport the shared client actually supports
Although
MCPServerDefaccepts stdio, HTTP, and SSE, the shared host-sideMCPManagercurrently implements stdio only. A coherent first implementation could therefore support stdio and return a clear validation error for other transports. HTTP/SSE can follow by extending the shared manager while preserving the step syntax.Secrets and observability need an explicit policy
MCP does not redact secrets for Conductor. Tool arguments may contain credentials or sensitive payloads, and the current tool event formatting includes arguments. A direct step should not log full arguments or results by default. Events can safely include server/tool names, argument keys, elapsed time, result size, and truncation metadata; displaying values should require an explicit, redaction-aware policy.
Concurrency needs a defined ownership model
A workflow-owned connection pool could keep one stdio server alive and reuse it across direct calls. Calls sharing the same server/session should initially be serialized unless client/server concurrency is known to be safe; different servers or working directories can proceed independently. Cleanup must be bounded by workflow lifecycle and robust under cancellation.
Why
sequenceis not part of the MVPA special multi-call sequence syntax is tempting for stateful servers, but it would immediately add nested scoping, conditional execution, partial failure policy, cleanup hooks, and misleading expectations of transactionality.
Ordinary workflow steps already express ordered calls and routing. Connection reuse can be an executor lifecycle detail, so consecutive
type: mcpsteps may share the same live stdio server without introducing another programming language inside YAML.If real servers demonstrate a need for an indivisible connection-scoped block, sequence syntax can be discussed later as non-transactional convenience sugar.
Relationship to #222 (
type: http)This proposal complements rather than replaces #222.
A native HTTP step remains preferable for direct REST calls because Conductor can provide HTTP-specific behavior that a generic MCP tool cannot guarantee consistently:
The MCP step is the broad integration escape hatch for existing MCP tools. The HTTP step is the purpose-built primitive for a very common protocol.
Suggested phased scope
Phase 1
type: mcpstep equals one tool call;Phase 2
Later, only if justified by real workflows
Open questions
mcp,mcp_call, ormcp_tool?structuredContent?isError: truebecome a failed workflow step by default, or remain routable output with an opt-in fail mode?Acceptance criteria for an eventual MVP
Related: #222.