Skip to content

Commit 5ed43e6

Browse files
committed
MCP for Workflow
1 parent 4d9e3c5 commit 5ed43e6

7 files changed

Lines changed: 261 additions & 1 deletion

File tree

dependencies.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ ext {
8989
revConductorClient = '4.0.10'
9090
revReactor = '1.3.1'
9191
revSpringAI = '1.1.2'
92-
revJSonSchemaValidator = '1.0.73'
92+
revJSonSchemaValidator = '2.0.0'
9393
mongodb = '4.11.0'
9494
pgVector = '0.1.4'
9595
revMCP = '0.13.0'

docs/documentation/mcp/workflow.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Managing Workflows via MCP
2+
3+
Conductor implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), exposing its workflow engine
4+
as an MCP server. Once enabled, any MCP-compatible client can start, inspect, pause, retry, and search workflows without
5+
touching the REST API.
6+
7+
## Enabling
8+
9+
Add to your `application.properties`:
10+
11+
```properties
12+
conductor.mcp-server.enabled=true
13+
```
14+
15+
## Connecting a Client
16+
17+
The MCP server is available at `http://localhost:8080/sse` using SSE transport.
18+
19+
```json
20+
{
21+
"mcpServers": {
22+
"conductor": {
23+
"type": "sse",
24+
"url": "http://localhost:8080/sse"
25+
}
26+
}
27+
}
28+
```
29+
30+
This works with Claude Code, Cursor, and any MCP-compatible client.
31+
32+
## Available Tools
33+
34+
| Tool | Description | Parameters |
35+
|-------------------------------|----------------------------------|------------------------------------------------------------------------------------------------------------------|
36+
| `startWorkflow` | Start a new workflow execution | `name` (required), `version`, `correlationId`, `priority` (0-99), `input` |
37+
| `getWorkflow` | Get workflow execution status | `workflowId` (required), `includeTasks` (default: true) |
38+
| `pauseWorkflow` | Pause a running workflow | `workflowId` (required) |
39+
| `resumeWorkflow` | Resume a paused workflow | `workflowId` (required) |
40+
| `terminateWorkflow` | Terminate a running workflow | `workflowId` (required), `reason` |
41+
| `restartWorkflow` | Restart a completed workflow | `workflowId` (required), `useLatestDefinitions` (default: false) |
42+
| `retryWorkflow` | Retry the last failed task | `workflowId` (required), `resumeSubworkflowTasks` (default: false) |
43+
| `deleteWorkflow` | Remove a workflow | `workflowId` (required), `archiveWorkflow` (default: true) |
44+
| `getRunningWorkflows` | Get running workflow IDs by name | `workflowName` (required), `version` (default: 1) |
45+
| `searchWorkflows` | Search workflows | `query`, `freeText`, `start` (default: 0), `size` (default: 100) |
46+
| `getWorkflowsByCorrelationId` | Get workflows by correlation ID | `name` (required), `correlationId` (required), `includeClosed` (default: false), `includeTasks` (default: false) |

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ nav:
7575
- documentation/api/bulk.md
7676
- documentation/api/eventhandlers.md
7777
- documentation/api/taskdomains.md
78+
- documentation/mcp/workflow.md
7879
- Workflow Definition:
7980
- documentation/configuration/workflowdef/index.md
8081
- System Tasks:

rest/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ dependencies {
44
implementation project(':conductor-core')
55

66
implementation 'org.springframework.boot:spring-boot-starter-web'
7+
implementation "org.springframework.ai:spring-ai-starter-mcp-server-webmvc:${revSpringAI}"
78
implementation "com.netflix.runtime:health-api:${revHealth}"
89

910
implementation "io.projectreactor.netty:reactor-netty-http:${revReactor}"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2025 Conductor Authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.conductoross.conductor;
14+
15+
import org.conductoross.conductor.mcp.controller.WorkflowMcpTools;
16+
import org.springframework.ai.tool.ToolCallbackProvider;
17+
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
18+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.ComponentScan;
21+
import org.springframework.context.annotation.Configuration;
22+
23+
@Configuration
24+
@ConditionalOnProperty(name = "conductor.mcp-server.enabled", havingValue = "true")
25+
@ComponentScan(basePackageClasses = McpServerConfiguration.class)
26+
public class McpServerConfiguration {
27+
@Bean
28+
ToolCallbackProvider workflowMcpToolsCallbackProvider(WorkflowMcpTools workflowMcpTools) {
29+
return MethodToolCallbackProvider.builder().toolObjects(workflowMcpTools).build();
30+
}
31+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2025 Conductor Authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.conductoross.conductor.mcp.controller;
14+
15+
import java.util.List;
16+
import java.util.Map;
17+
18+
import org.springframework.ai.tool.annotation.Tool;
19+
import org.springframework.ai.tool.annotation.ToolParam;
20+
import org.springframework.stereotype.Component;
21+
22+
import com.netflix.conductor.common.run.SearchResult;
23+
import com.netflix.conductor.common.run.Workflow;
24+
import com.netflix.conductor.common.run.WorkflowSummary;
25+
import com.netflix.conductor.service.WorkflowService;
26+
27+
@Component
28+
public class WorkflowMcpTools {
29+
30+
private final WorkflowService workflowService;
31+
32+
public WorkflowMcpTools(WorkflowService workflowService) {
33+
this.workflowService = workflowService;
34+
}
35+
36+
@Tool(description = "Start a new workflow execution. Returns the workflow instance ID.")
37+
public String startWorkflow(
38+
@ToolParam(description = "Name of the workflow definition") String name,
39+
@ToolParam(description = "Version of the workflow definition", required = false)
40+
Integer version,
41+
@ToolParam(
42+
description = "Correlation ID for tracking related workflows",
43+
required = false)
44+
String correlationId,
45+
@ToolParam(description = "Priority of the workflow (0-99)", required = false)
46+
Integer priority,
47+
@ToolParam(
48+
description = "Input parameters for the workflow as key-value pairs",
49+
required = false)
50+
Map<String, Object> input) {
51+
return workflowService.startWorkflow(
52+
name, version, correlationId, priority != null ? priority : 0, input);
53+
}
54+
55+
@Tool(
56+
description =
57+
"Get workflow execution status by workflow ID. Returns full workflow details including tasks.")
58+
public Workflow getWorkflow(
59+
@ToolParam(description = "The workflow instance ID") String workflowId,
60+
@ToolParam(
61+
description = "Whether to include task details in the response",
62+
required = false)
63+
Boolean includeTasks) {
64+
return workflowService.getExecutionStatus(
65+
workflowId, includeTasks != null ? includeTasks : true);
66+
}
67+
68+
@Tool(description = "Pause a running workflow.")
69+
public String pauseWorkflow(
70+
@ToolParam(description = "The workflow instance ID to pause") String workflowId) {
71+
workflowService.pauseWorkflow(workflowId);
72+
return "Workflow " + workflowId + " paused";
73+
}
74+
75+
@Tool(description = "Resume a paused workflow.")
76+
public String resumeWorkflow(
77+
@ToolParam(description = "The workflow instance ID to resume") String workflowId) {
78+
workflowService.resumeWorkflow(workflowId);
79+
return "Workflow " + workflowId + " resumed";
80+
}
81+
82+
@Tool(description = "Terminate a running workflow.")
83+
public String terminateWorkflow(
84+
@ToolParam(description = "The workflow instance ID to terminate") String workflowId,
85+
@ToolParam(description = "Reason for terminating the workflow", required = false)
86+
String reason) {
87+
workflowService.terminateWorkflow(workflowId, reason);
88+
return "Workflow " + workflowId + " terminated";
89+
}
90+
91+
@Tool(description = "Restart a completed workflow from the beginning.")
92+
public String restartWorkflow(
93+
@ToolParam(description = "The workflow instance ID to restart") String workflowId,
94+
@ToolParam(
95+
description = "Whether to use the latest workflow definition",
96+
required = false)
97+
Boolean useLatestDefinitions) {
98+
workflowService.restartWorkflow(
99+
workflowId, useLatestDefinitions != null ? useLatestDefinitions : false);
100+
return "Workflow " + workflowId + " restarted";
101+
}
102+
103+
@Tool(description = "Retry the last failed task in a workflow.")
104+
public String retryWorkflow(
105+
@ToolParam(description = "The workflow instance ID to retry") String workflowId,
106+
@ToolParam(description = "Whether to resume sub-workflow tasks", required = false)
107+
Boolean resumeSubworkflowTasks) {
108+
workflowService.retryWorkflow(
109+
workflowId, resumeSubworkflowTasks != null ? resumeSubworkflowTasks : false);
110+
return "Workflow " + workflowId + " retried";
111+
}
112+
113+
@Tool(description = "Remove a workflow from the system.")
114+
public String deleteWorkflow(
115+
@ToolParam(description = "The workflow instance ID to remove") String workflowId,
116+
@ToolParam(
117+
description = "Whether to archive instead of permanently deleting",
118+
required = false)
119+
Boolean archiveWorkflow) {
120+
workflowService.deleteWorkflow(
121+
workflowId, archiveWorkflow != null ? archiveWorkflow : true);
122+
return "Workflow " + workflowId + " removed";
123+
}
124+
125+
@Tool(description = "Get all running workflow IDs for a given workflow name.")
126+
public List<String> getRunningWorkflows(
127+
@ToolParam(description = "The workflow definition name") String workflowName,
128+
@ToolParam(description = "The workflow definition version", required = false)
129+
Integer version) {
130+
return workflowService.getRunningWorkflows(
131+
workflowName, version != null ? version : 1, null, null);
132+
}
133+
134+
@Tool(
135+
description =
136+
"Search for workflows. Use query syntax like 'status=RUNNING' or 'workflowType=myWorkflow'. Returns workflow summaries with pagination.")
137+
public SearchResult<WorkflowSummary> searchWorkflows(
138+
@ToolParam(
139+
description =
140+
"Search query (e.g. 'status=RUNNING AND workflowType=myWorkflow')",
141+
required = false)
142+
String query,
143+
@ToolParam(description = "Free text search", required = false) String freeText,
144+
@ToolParam(description = "Start index for pagination (default 0)", required = false)
145+
Integer start,
146+
@ToolParam(
147+
description = "Number of results to return (default 100, max 5000)",
148+
required = false)
149+
Integer size) {
150+
return workflowService.searchWorkflows(
151+
start != null ? start : 0,
152+
size != null ? size : 100,
153+
"updateTime:DESC",
154+
freeText != null ? freeText : "*",
155+
query != null ? query : "");
156+
}
157+
158+
@Tool(description = "Get workflows by correlation ID.")
159+
public List<Workflow> getWorkflowsByCorrelationId(
160+
@ToolParam(description = "The workflow definition name") String name,
161+
@ToolParam(description = "The correlation ID to search for") String correlationId,
162+
@ToolParam(description = "Include completed/failed workflows", required = false)
163+
Boolean includeClosed,
164+
@ToolParam(description = "Include task details", required = false)
165+
Boolean includeTasks) {
166+
return workflowService.getWorkflows(
167+
name,
168+
correlationId,
169+
includeClosed != null ? includeClosed : false,
170+
includeTasks != null ? includeTasks : false);
171+
}
172+
}

server/src/main/resources/application.properties

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ management.jmx.metrics.export.enabled=false
185185

186186
# When enabled logs metrics as info level logs
187187
conductor.metrics-logger.enabled=false
188+
189+
# MCP Server (Model Context Protocol)
190+
conductor.mcp-server.enabled=true
191+
spring.ai.mcp.server.type=SYNC
192+
spring.ai.mcp.server.protocol=SSE
193+
spring.ai.mcp.server.annotation-scanner.enabled=true
194+
spring.ai.mcp.server.name=conductor
195+
spring.ai.mcp.server.version=1.0.0
196+
188197
# Start AI Workers
189198
conductor.integrations.ai.enabled=true
190199

0 commit comments

Comments
 (0)