-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworkflow-dispatch-tool.ts
More file actions
85 lines (74 loc) · 3.15 KB
/
workflow-dispatch-tool.ts
File metadata and controls
85 lines (74 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import type { FastMCP } from "fastmcp";
import { z } from "zod";
import { gateAuth } from "./github-auth.js";
import { classifyError, getOctokit } from "./github-client.js";
import { errorRespond, jsonRespond } from "./json.js";
import { RepoRefSchema } from "./schemas.js";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface WorkflowDispatchResult {
message: string;
dryRun?: boolean;
}
// ---------------------------------------------------------------------------
// Tool registration
// ---------------------------------------------------------------------------
export function registerWorkflowDispatchTool(server: FastMCP): void {
server.addTool({
name: "workflow_dispatch",
description:
"Trigger a GitHub Actions workflow via workflow_dispatch event. GitHub returns 204 with no body, so run ID must be polled separately if needed.",
annotations: { readOnlyHint: false },
parameters: RepoRefSchema.extend({
workflow: z.string().describe("Workflow file name (e.g. 'ci.yml') or workflow ID."),
ref: z.string().describe("Branch or tag to run the workflow on (e.g. 'main')."),
inputs: z
.record(z.string(), z.string())
.optional()
.describe("Optional workflow input parameters as key-value pairs."),
dryRun: z
.boolean()
.optional()
.default(false)
.describe(
"If true, return the resolved parameters that WOULD be dispatched WITHOUT triggering the workflow.",
),
}),
execute: async (args) => {
const auth = gateAuth();
if (!auth.ok) return errorRespond(auth.envelope);
const { owner, repo, workflow, ref, inputs, dryRun } = args;
// Only coerce to Number when the string is purely numeric (e.g. a workflow ID integer).
// The GitHub API accepts filename strings directly without coercion.
const workflowId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
if (dryRun) {
const dryRunResult: WorkflowDispatchResult = {
message: `[dry-run] Would dispatch workflow '${workflow}' on ${owner}/${repo}:${ref} with workflow_id=${JSON.stringify(workflowId)}${inputs ? ` and inputs ${JSON.stringify(inputs)}` : ""}.`,
dryRun: true,
};
return jsonRespond(dryRunResult);
}
try {
const octokit = getOctokit();
await octokit.actions.createWorkflowDispatch({
owner,
repo,
workflow_id: workflowId,
ref,
inputs: inputs ?? {},
});
const result: WorkflowDispatchResult = {
message: `Workflow '${workflow}' dispatched successfully on ${owner}/${repo}:${ref}. GitHub returns 204 (no run ID); poll workflow runs to find the dispatched run.`,
};
return jsonRespond(result);
} catch (err) {
console.error(
`[workflow_dispatch] Failed to dispatch workflow '${workflow}' for ${owner}/${repo}:`,
err instanceof Error ? err.message : String(err),
);
return errorRespond(classifyError(err));
}
},
});
}