Skip to content

Commit bb15d92

Browse files
authored
Fix dispatch_workflow MCP handler to wrap inputs per schema (#13259)
1 parent 3ce227d commit bb15d92

3 files changed

Lines changed: 87 additions & 17 deletions

File tree

actions/setup/js/dispatch_workflow.test.cjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,33 @@ describe("dispatch_workflow handler factory", () => {
195195
);
196196
});
197197

198+
it("should handle workflows with no inputs", async () => {
199+
const config = {
200+
workflows: ["no-inputs-workflow"],
201+
workflow_files: {
202+
"no-inputs-workflow": ".lock.yml",
203+
},
204+
};
205+
const handler = await main(config);
206+
207+
// Test with inputs property missing entirely
208+
const message = {
209+
type: "dispatch_workflow",
210+
workflow_name: "no-inputs-workflow",
211+
};
212+
213+
const result = await handler(message, {});
214+
215+
expect(result.success).toBe(true);
216+
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({
217+
owner: "test-owner",
218+
repo: "test-repo",
219+
workflow_id: "no-inputs-workflow.lock.yml",
220+
ref: expect.any(String),
221+
inputs: {}, // Should pass empty object even when inputs property is missing
222+
});
223+
});
224+
198225
it("should delay 5 seconds between dispatches", async () => {
199226
const config = {
200227
workflows: ["workflow1", "workflow2"],

actions/setup/js/safe_outputs_tools_loader.cjs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,14 @@ function attachHandlers(tools, handlers) {
6767

6868
// Check if this is a dispatch_workflow tool (dynamic tool with workflow metadata)
6969
if (tool._workflow_name) {
70-
// Create a custom handler that adds workflow_name and uses dispatch_workflow type
70+
// Create a custom handler that wraps args in inputs and adds workflow_name
7171
const workflowName = tool._workflow_name;
7272
tool.handler = args => {
73-
// Add workflow_name to the args and call default handler with dispatch_workflow type
74-
const entry = {
75-
...args,
73+
// Wrap args in inputs property to match dispatch_workflow schema
74+
return handlers.defaultHandler("dispatch_workflow")({
75+
inputs: args,
7676
workflow_name: workflowName,
77-
type: "dispatch_workflow",
78-
};
79-
80-
// Use the default handler logic but with dispatch_workflow type
81-
return handlers.defaultHandler("dispatch_workflow")({ ...args, workflow_name: workflowName });
77+
});
8278
};
8379
}
8480
});

actions/setup/js/safe_outputs_tools_loader.test.cjs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ describe("safe_outputs_tools_loader", () => {
190190
expect(defaultHandler).toHaveBeenCalledWith("dispatch_workflow");
191191
});
192192

193-
it("should include workflow_name in dispatch_workflow handler args", () => {
193+
it("should wrap args in inputs property for dispatch_workflow handler", () => {
194194
const tools = [{ name: "ci_workflow", description: "CI workflow", _workflow_name: "ci" }];
195195
const mockHandlerFunction = vi.fn();
196196
const defaultHandler = vi.fn(() => mockHandlerFunction);
@@ -204,16 +204,63 @@ describe("safe_outputs_tools_loader", () => {
204204
const result = attachHandlers(tools, handlers);
205205

206206
// Call the handler
207-
const mockArgs = { input1: "value1" };
207+
const mockArgs = { input1: "value1", input2: "value2" };
208208
result[0].handler(mockArgs);
209209

210-
// Verify the handler function was called with workflow_name
211-
expect(mockHandlerFunction).toHaveBeenCalledWith(
212-
expect.objectContaining({
213-
workflow_name: "ci",
210+
// Verify the handler function was called with workflow_name and inputs wrapped
211+
expect(mockHandlerFunction).toHaveBeenCalledWith({
212+
workflow_name: "ci",
213+
inputs: {
214214
input1: "value1",
215-
})
216-
);
215+
input2: "value2",
216+
},
217+
});
218+
});
219+
220+
it("should handle dispatch_workflow with no inputs (empty object)", () => {
221+
const tools = [{ name: "no_inputs_workflow", description: "No inputs workflow", _workflow_name: "no-inputs" }];
222+
const mockHandlerFunction = vi.fn();
223+
const defaultHandler = vi.fn(() => mockHandlerFunction);
224+
const handlers = {
225+
createPullRequestHandler: vi.fn(),
226+
pushToPullRequestBranchHandler: vi.fn(),
227+
uploadAssetHandler: vi.fn(),
228+
defaultHandler: defaultHandler,
229+
};
230+
231+
const result = attachHandlers(tools, handlers);
232+
233+
// Call the handler with empty object (typical for MCP tools with no inputs)
234+
result[0].handler({});
235+
236+
// Verify inputs is still included as empty object
237+
expect(mockHandlerFunction).toHaveBeenCalledWith({
238+
workflow_name: "no-inputs",
239+
inputs: {},
240+
});
241+
});
242+
243+
it("should handle dispatch_workflow with undefined args", () => {
244+
const tools = [{ name: "undefined_workflow", description: "Undefined workflow", _workflow_name: "undefined-test" }];
245+
const mockHandlerFunction = vi.fn();
246+
const defaultHandler = vi.fn(() => mockHandlerFunction);
247+
const handlers = {
248+
createPullRequestHandler: vi.fn(),
249+
pushToPullRequestBranchHandler: vi.fn(),
250+
uploadAssetHandler: vi.fn(),
251+
defaultHandler: defaultHandler,
252+
};
253+
254+
const result = attachHandlers(tools, handlers);
255+
256+
// Call the handler with undefined (edge case)
257+
result[0].handler(undefined);
258+
259+
// When args is undefined, inputs should not be included
260+
// The dispatch_workflow handler will handle missing inputs property
261+
expect(mockHandlerFunction).toHaveBeenCalledWith({
262+
workflow_name: "undefined-test",
263+
});
217264
});
218265
});
219266

0 commit comments

Comments
 (0)