Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ Several GitHub MCP servers exist, alongside GitHub's own `gh` CLI. This server's

The table below compares coverage by **feature area** against the two most common alternatives. It stays deliberately coarse-grained — the [tool table](#whats-included) is the source of truth for which tools exist right now, and `gh`'s coverage is documented in the [GitHub CLI manual](https://cli.github.com/manual/). Legend: ✅ first-class · ⚠️ only via a lower-level escape hatch (`gh api`, local `git`) · ❌ absent.

| Feature area | This server | Official `mcp__github__*` | `gh` CLI |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -------------------------------- |
| Repo metadata & discovery | ✅ | ✅ | ✅ |
| Issue read / triage / lifecycle (labels, assignees, comments) | ✅ | ✅ | ✅ |
| Code & repo search | ✅ | ✅ | ✅ |
| File content read + remote commit (single & multi-file) | ✅ | ✅ | ⚠️ `gh api` only |
| Branch list / create / delete | ✅ | ✅ | ⚠️ `gh api` / local `git` |
| PR open / diff / request reviewers | ✅ | ✅ | ✅ |
| PR read detail / merge / update lifecycle | ✅ | ✅ | ✅ |
| PR reviews & review comments (read + reply) | ⚠️ read only (`list_pr_reviews` + threads); reply not yet | ✅ | ⚠️ partial (`gh api` for inline) |
| **PR review thread resolve / unresolve** | ✅ | ❌ | ⚠️ `gh api graphql` only |
| Commit history (list / show / compare) | ✅ | ✅ | ⚠️ `gh api` only |
| Workflow / Actions (CI status, logs, rerun) | ⚠️ read + rerun (runs, jobs, workflows, job logs, artifacts, rerun all / failed jobs); cancel / dispatch planned ([#8](https://github.com/nemolize/remote-mcp-github/issues/8)) | ✅ | ✅ |
| Releases & tags | ❌ | ✅ | ✅ |
| Repo admin (create / fork / delete) | ❌ | ✅ | ✅ |
| Security scanning (secret / code / Dependabot) | ❌ | ✅ | ⚠️ `gh api` only |
| Local working-tree ops (clone, checkout, …) | ❌ — out of scope (remote-only) | ❌ | ✅ |
| Feature area | This server | Official `mcp__github__*` | `gh` CLI |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------- | -------------------------------- |
| Repo metadata & discovery | ✅ | ✅ | ✅ |
| Issue read / triage / lifecycle (labels, assignees, comments) | ✅ | ✅ | ✅ |
| Code & repo search | ✅ | ✅ | ✅ |
| File content read + remote commit (single & multi-file) | ✅ | ✅ | ⚠️ `gh api` only |
| Branch list / create / delete | ✅ | ✅ | ⚠️ `gh api` / local `git` |
| PR open / diff / request reviewers | ✅ | ✅ | ✅ |
| PR read detail / merge / update lifecycle | ✅ | ✅ | ✅ |
| PR reviews & review comments (read + reply) | ⚠️ read only (`list_pr_reviews` + threads); reply not yet | ✅ | ⚠️ partial (`gh api` for inline) |
| **PR review thread resolve / unresolve** | ✅ | ❌ | ⚠️ `gh api graphql` only |
| Commit history (list / show / compare) | ✅ | ✅ | ⚠️ `gh api` only |
| Workflow / Actions (CI status, logs, rerun) | read + write (runs, jobs, workflows, job logs, artifacts, rerun all / failed jobs, cancel, dispatch) | ✅ | ✅ |
| Releases & tags | ❌ | ✅ | ✅ |
| Repo admin (create / fork / delete) | ❌ | ✅ | ✅ |
| Security scanning (secret / code / Dependabot) | ❌ | ✅ | ⚠️ `gh api` only |
| Local working-tree ops (clone, checkout, …) | ❌ — out of scope (remote-only) | ❌ | ✅ |

Also outside this server's scope today: notifications, Copilot delegation, gists, and projects — reach for the official server or `gh` for those.

Expand Down Expand Up @@ -70,6 +70,8 @@ All tools respond in Markdown (not raw JSON) so the model can read them efficien
| `get_artifact` | read | Single artifact metadata + archive download URL (zip; metadata only, no download / unzip) |
| `rerun_workflow_run` | write | Re-run an entire workflow run (all jobs) — new attempt; poll `get_workflow_run` for status |
| `rerun_failed_jobs` | write | Re-run only the failed jobs of a run — new attempt; poll `get_workflow_run` for status |
| `cancel_workflow_run` | write | Cancel an in-progress run (async); poll `get_workflow_run` until conclusion is `cancelled` |
| `trigger_workflow_dispatch` | write | Manually dispatch a `workflow_dispatch` workflow on a ref with optional inputs |
| `list_branches` | read | List branches in a repo (name, head SHA, protected flag) |
| `create_branch` | write | Branch from a base (or the repo's default) |
| `delete_branch` | write | Delete a branch (default branch refused) |
Expand Down
7 changes: 5 additions & 2 deletions src/mcp/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ export const logRateLimit = (
// Fields a write tool reports about the mutation it performed. `tool` is always
// present; the rest are tool-specific (e.g. `owner` + `repo` + `branch` +
// `path` for file commits, `issue_number` for issue/PR edits, `run_id` for
// workflow-run mutations, `thread_id` for GraphQL review-thread mutations that
// have no owner/repo at the call boundary).
// workflow-run mutations, `workflow_id` for a workflow dispatch, `thread_id`
// for GraphQL review-thread mutations that have no owner/repo at the call
// boundary).
// `null`/undefined values are dropped so the emitted line only carries what the
// call touched.
export type WriteAuditFields = {
Expand All @@ -168,6 +169,8 @@ export type WriteAuditFields = {
issue_number?: number;
pull_number?: number;
run_id?: number;
workflow_id?: number | string; // trigger_workflow_dispatch
ref?: string; // trigger_workflow_dispatch
file_count?: number;
thread_id?: string;
};
Expand Down
65 changes: 65 additions & 0 deletions src/tools/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,4 +495,69 @@ export const registerActionTools = (server: McpServer, client: OctokitFactory):
);
}),
);

server.registerTool(
"cancel_workflow_run",
{
description:
"Cancel an in-progress GitHub Actions workflow run, stopping its remaining jobs. Use when the user asks to stop / abort a running build. Mutates CI state. A run that has already finished returns 409 Conflict. Cancellation is asynchronous; returns a confirmation, poll `get_workflow_run` until the run's conclusion becomes `cancelled`.",
inputSchema: {
...RepoTarget,
run_id: z.number().int().positive().describe("Workflow run ID to cancel."),
},
},
async ({ owner, repo, run_id }) =>
wrapTool(async () => {
// cancelWorkflowRun answers 202 Accepted with an empty body — the
// cancellation is async, so there is no final state to render. Report
// the request; the caller reads the eventual `cancelled` conclusion via
// get_workflow_run.
const { headers } = await client().rest.actions.cancelWorkflowRun(
stripUndefined({ owner, repo, run_id }),
);
logRateLimit(headers);
logWrite({ tool: "cancel_workflow_run", owner, repo, run_id });
return text(
`# Cancellation requested\n\n- run \`${run_id}\` in ${owner}/${repo} requested to cancel (asynchronous)\n- poll \`get_workflow_run\` (run_id ${run_id}) until its conclusion is \`cancelled\``,
);
}),
);

server.registerTool(
"trigger_workflow_dispatch",
{
description:
"Trigger a `workflow_dispatch`-enabled workflow manually on a given branch or tag, optionally with inputs. Use when the user asks to run / kick off a workflow by hand. Mutates CI state. The workflow's definition must declare a `workflow_dispatch` trigger, or GitHub returns 422. The dispatch endpoint returns no run reference; after triggering, find the new run with `list_workflow_runs` (filter by this `workflow_id` and `event: 'workflow_dispatch'`).",
inputSchema: {
...RepoTarget,
workflow_id: WorkflowId.describe(
"Workflow filename (e.g. 'ci.yml') or numeric ID to dispatch.",
),
ref: z.string().min(1).describe("Branch or tag the workflow runs against (e.g. 'main')."),
inputs: z
// GitHub accepts string / number / boolean input values (it coerces
// per the workflow's declared input types), so accept all three
// scalars rather than forcing every value to a string.
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
.optional()
.describe(
"`workflow_dispatch` inputs as a string-keyed map of scalar values. Must match the workflow's declared inputs.",
),
},
},
async ({ owner, repo, workflow_id, ref, inputs }) =>
wrapTool(async () => {
// createWorkflowDispatch answers 204 No Content — GitHub returns no run
// reference for the dispatched run, so there is nothing to render but the
// request. The caller locates the new run via list_workflow_runs.
const { headers } = await client().rest.actions.createWorkflowDispatch(
stripUndefined({ owner, repo, workflow_id, ref, inputs }),
);
logRateLimit(headers);
logWrite({ tool: "trigger_workflow_dispatch", owner, repo, workflow_id, ref });
return text(
`# Workflow dispatch requested\n\n- workflow \`${workflow_id}\` in ${owner}/${repo} dispatched on \`${ref}\`\n- the dispatch endpoint returns no run ID; find the new run with \`list_workflow_runs\` (workflow_id \`${workflow_id}\`, event \`workflow_dispatch\`)`,
);
}),
);
};
Loading