From a2ba4f0c3cffc3cf573ece6e13e592eab60b834d Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:29:53 +0200 Subject: [PATCH 1/6] docs: add design spec for create_branch tool Specifies a new create_branch tool for the repos toolset that wraps octokit's git.createRef, resolving a branch name or commit SHA into the target SHA before creating the ref. --- .../2026-08-06-create-branch-tool-design.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-06-create-branch-tool-design.md diff --git a/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md b/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md new file mode 100644 index 0000000..ed5254c --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md @@ -0,0 +1,81 @@ +# Design: `create_branch` tool + +## Summary + +Add a new write tool, `create_branch`, to `/src/toolsets/repos.ts`. It creates a new branch in a GitHub repository from an existing branch name or commit SHA, wrapping octokit's `git.createRef` API. + +## Motivation + +The `repos` toolset currently exposes read tools for branches (`get_branch`, `list_branches`) and one write tool (`create_or_update_file`), but no way to create a new branch. `create_branch` fills that gap using the same conventions already established in the file. + +## Scope + +In scope: +- One new tool, `create_branch`, registered only in `read-write` permission mode. +- Resolving a source branch name or commit SHA into the SHA needed by `git.createRef`. +- Tests and README updates for the new tool. + +Out of scope: +- Creating tag refs or other non-branch ref types. +- Deleting or updating existing refs. +- Defaulting `from` to the repository's default branch when omitted (out of scope — `from` is required). + +## Design + +### Location + +Added to `/src/toolsets/repos.ts`, inside the existing `if (permission === 'read-write')` block, immediately after `create_or_update_file`. No changes needed to `/src/server.ts` — the `repos` toolset is already registered there. + +### Input schema + +```ts +inputSchema: z.object({ + ...ownerRepoSchema, // owner, repo + branch: z.string().describe('Name for the new branch (without refs/heads/ prefix)'), + from: z.string().describe('Source branch name or commit SHA to create the new branch from'), +}) +``` + +Both `branch` and `from` are required. No default-branch fallback if `from` is omitted — this is deliberately not supported to keep the tool's behavior explicit and avoid an extra implicit API call. + +### Behavior + +1. Attempt to resolve `from` as a branch name: `octokit.rest.repos.getBranch({ owner, repo, branch: from })`. On success, take `sha = response.data.commit.sha`. +2. If that call fails with HTTP status `404`, treat `from` as a literal commit SHA and use it directly as `sha`. +3. If that call fails with any other error (auth failure, rate limit, network error, etc.), propagate it immediately via `toToolError(error)` — do not fall through to treating `from` as a SHA. +4. Call `octokit.rest.git.createRef({ owner, repo, ref: \`refs/heads/${branch}\`, sha })`. +5. On success, return `toToolResult(response.data)`. On failure, return `toToolError(error)`. + +### Error handling + +Reuses the existing `toToolError()` helper from `common.ts`, consistent with every other tool in the file. Two realistic failure modes surface to the caller: + +- `from` does not resolve to a valid branch or commit (`createRef` returns 422, e.g. "Object does not exist"). +- `branch` already exists as a ref (`createRef` returns 422, e.g. "Reference already exists"). + +The 404-vs-other-error distinction in step 3 matters: octokit's `RequestError` includes a `.status` property. Checking `error.status === 404` specifically (rather than treating any thrown error as "not a branch, try as SHA") prevents a transient 500 or a 401 from being silently reinterpreted, which would otherwise surface a confusing downstream 422 from `createRef` instead of the real underlying error. + +### Testing + +Add to `/test/unit/toolsets/repos.test.ts`, following the existing `nock` + `vitest` pattern used for `create_or_update_file`: + +- Happy path, `from` resolves as a branch name: mock `GET /repos/:owner/:repo/branches/:from` → 200, then `POST /repos/:owner/:repo/git/refs` → 201. +- Happy path, `from` is a raw commit SHA: mock branch lookup → 404, then assert `createRef` is called directly with that SHA (no further lookup). +- Error path: `createRef` fails (e.g. ref already exists) → result has `isError: true`. +- Read-only mode: `create_branch` is not present in `listTools()` output, matching the existing `create_or_update_file` read-only test. + +### Documentation + +Update `/README.md`: +- Add a row to the `repos` toolset table (after `create_or_update_file`, line ~157): + ``` + | `create_branch` | W | Create a new branch from an existing branch or commit SHA. | + ``` +- Bump the total tool count on line 144 from **104 tools** to **105 tools**. + +## Alternatives considered + +- **Raw SHA-only input** (`ref`, `sha` passed straight through to `git.createRef`): simplest, matches the raw API exactly, but pushes branch-to-SHA resolution onto the caller. Rejected in favor of resolving `from` internally for caller convenience. +- **Full ref path input** (e.g. `refs/heads/foo`, `refs/tags/v1`): more general, supports any ref type. Rejected because the tool is scoped to branch creation only (mirrors `create_branch`, not a generic `create_ref`); tag/ref-type support can be added later as a separate tool if needed. +- **Regex-based SHA detection** instead of try-branch-then-404-fallback: avoids an extra API call when `from` is already a SHA, but risks misclassifying a branch name that happens to look like a hex string. Rejected for correctness in favor of the lookup-based approach. +- **Optional `from` defaulting to the repo's default branch**: convenient for the common "branch off main" case, but adds an implicit extra API call and hides behavior. Rejected — `from` is required. From eda61b7638ba148ef98c178bcf7644bb5dea4e80 Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:30:59 +0200 Subject: [PATCH 2/6] chore: stop tracking docs/ and add to .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Internal superpowers specs and plans are local working docs, not meant to ship in the repo — matches the precedent set for .husky/ (24076d4 previously deleted the docs/ folder entirely for the same reason). The spec file itself remains on disk locally, just untracked. --- .gitignore | 1 + .../2026-08-06-create-branch-tool-design.md | 81 ------------------- 2 files changed, 1 insertion(+), 81 deletions(-) delete mode 100644 docs/superpowers/specs/2026-08-06-create-branch-tool-design.md diff --git a/.gitignore b/.gitignore index ae62dee..1fafa04 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ dist/ dist/*.mcpb dist/*.zip .husky/ +docs/ diff --git a/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md b/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md deleted file mode 100644 index ed5254c..0000000 --- a/docs/superpowers/specs/2026-08-06-create-branch-tool-design.md +++ /dev/null @@ -1,81 +0,0 @@ -# Design: `create_branch` tool - -## Summary - -Add a new write tool, `create_branch`, to `/src/toolsets/repos.ts`. It creates a new branch in a GitHub repository from an existing branch name or commit SHA, wrapping octokit's `git.createRef` API. - -## Motivation - -The `repos` toolset currently exposes read tools for branches (`get_branch`, `list_branches`) and one write tool (`create_or_update_file`), but no way to create a new branch. `create_branch` fills that gap using the same conventions already established in the file. - -## Scope - -In scope: -- One new tool, `create_branch`, registered only in `read-write` permission mode. -- Resolving a source branch name or commit SHA into the SHA needed by `git.createRef`. -- Tests and README updates for the new tool. - -Out of scope: -- Creating tag refs or other non-branch ref types. -- Deleting or updating existing refs. -- Defaulting `from` to the repository's default branch when omitted (out of scope — `from` is required). - -## Design - -### Location - -Added to `/src/toolsets/repos.ts`, inside the existing `if (permission === 'read-write')` block, immediately after `create_or_update_file`. No changes needed to `/src/server.ts` — the `repos` toolset is already registered there. - -### Input schema - -```ts -inputSchema: z.object({ - ...ownerRepoSchema, // owner, repo - branch: z.string().describe('Name for the new branch (without refs/heads/ prefix)'), - from: z.string().describe('Source branch name or commit SHA to create the new branch from'), -}) -``` - -Both `branch` and `from` are required. No default-branch fallback if `from` is omitted — this is deliberately not supported to keep the tool's behavior explicit and avoid an extra implicit API call. - -### Behavior - -1. Attempt to resolve `from` as a branch name: `octokit.rest.repos.getBranch({ owner, repo, branch: from })`. On success, take `sha = response.data.commit.sha`. -2. If that call fails with HTTP status `404`, treat `from` as a literal commit SHA and use it directly as `sha`. -3. If that call fails with any other error (auth failure, rate limit, network error, etc.), propagate it immediately via `toToolError(error)` — do not fall through to treating `from` as a SHA. -4. Call `octokit.rest.git.createRef({ owner, repo, ref: \`refs/heads/${branch}\`, sha })`. -5. On success, return `toToolResult(response.data)`. On failure, return `toToolError(error)`. - -### Error handling - -Reuses the existing `toToolError()` helper from `common.ts`, consistent with every other tool in the file. Two realistic failure modes surface to the caller: - -- `from` does not resolve to a valid branch or commit (`createRef` returns 422, e.g. "Object does not exist"). -- `branch` already exists as a ref (`createRef` returns 422, e.g. "Reference already exists"). - -The 404-vs-other-error distinction in step 3 matters: octokit's `RequestError` includes a `.status` property. Checking `error.status === 404` specifically (rather than treating any thrown error as "not a branch, try as SHA") prevents a transient 500 or a 401 from being silently reinterpreted, which would otherwise surface a confusing downstream 422 from `createRef` instead of the real underlying error. - -### Testing - -Add to `/test/unit/toolsets/repos.test.ts`, following the existing `nock` + `vitest` pattern used for `create_or_update_file`: - -- Happy path, `from` resolves as a branch name: mock `GET /repos/:owner/:repo/branches/:from` → 200, then `POST /repos/:owner/:repo/git/refs` → 201. -- Happy path, `from` is a raw commit SHA: mock branch lookup → 404, then assert `createRef` is called directly with that SHA (no further lookup). -- Error path: `createRef` fails (e.g. ref already exists) → result has `isError: true`. -- Read-only mode: `create_branch` is not present in `listTools()` output, matching the existing `create_or_update_file` read-only test. - -### Documentation - -Update `/README.md`: -- Add a row to the `repos` toolset table (after `create_or_update_file`, line ~157): - ``` - | `create_branch` | W | Create a new branch from an existing branch or commit SHA. | - ``` -- Bump the total tool count on line 144 from **104 tools** to **105 tools**. - -## Alternatives considered - -- **Raw SHA-only input** (`ref`, `sha` passed straight through to `git.createRef`): simplest, matches the raw API exactly, but pushes branch-to-SHA resolution onto the caller. Rejected in favor of resolving `from` internally for caller convenience. -- **Full ref path input** (e.g. `refs/heads/foo`, `refs/tags/v1`): more general, supports any ref type. Rejected because the tool is scoped to branch creation only (mirrors `create_branch`, not a generic `create_ref`); tag/ref-type support can be added later as a separate tool if needed. -- **Regex-based SHA detection** instead of try-branch-then-404-fallback: avoids an extra API call when `from` is already a SHA, but risks misclassifying a branch name that happens to look like a hex string. Rejected for correctness in favor of the lookup-based approach. -- **Optional `from` defaulting to the repo's default branch**: convenient for the common "branch off main" case, but adds an implicit extra API call and hides behavior. Rejected — `from` is required. From 1222a94bccd127bc67e3720495f050c0f28b4d7d Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:34:22 +0200 Subject: [PATCH 3/6] feat: add create_branch tool to repos toolset --- src/toolsets/repos.ts | 39 ++++++++++++++ test/unit/toolsets/repos.test.ts | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/src/toolsets/repos.ts b/src/toolsets/repos.ts index 451e4af..d652f03 100644 --- a/src/toolsets/repos.ts +++ b/src/toolsets/repos.ts @@ -1,4 +1,5 @@ import type { McpServer } from '@modelcontextprotocol/server'; +import type { RequestError } from '@octokit/request-error'; import type { Octokit } from 'octokit'; import { z } from 'zod'; import { ownerRepoSchema, paginationSchema, toToolResult, toToolError } from './common.js'; @@ -184,5 +185,43 @@ export function registerReposTools( } }, ); + + server.registerTool( + 'create_branch', + { + description: + 'Create a new branch in a GitHub repository from an existing branch or commit SHA. Docs: https://docs.github.com/en/rest/git/refs#create-a-reference', + inputSchema: z.object({ + ...ownerRepoSchema, + branch: z.string().describe('Name for the new branch (without the refs/heads/ prefix)'), + from: z.string().describe('Source branch name or commit SHA to create the new branch from'), + }), + }, + async ({ owner, repo, branch, from }) => { + try { + let sha: string; + try { + const branchResponse = await octokit.rest.repos.getBranch({ owner, repo, branch: from }); + sha = branchResponse.data.commit.sha; + } catch (error) { + const reqError = error as RequestError; + if (reqError.status !== 404) { + throw error; + } + sha = from; + } + + const response = await octokit.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branch}`, + sha, + }); + return toToolResult(response.data); + } catch (error) { + return toToolError(error); + } + }, + ); } } diff --git a/test/unit/toolsets/repos.test.ts b/test/unit/toolsets/repos.test.ts index b2233ec..e2c9119 100644 --- a/test/unit/toolsets/repos.test.ts +++ b/test/unit/toolsets/repos.test.ts @@ -69,6 +69,97 @@ describe('registerReposTools', () => { expect(tools.map((tool) => tool.name)).toContain('create_or_update_file'); }); + it('creates a branch by resolving "from" as a branch name first', async () => { + nock('https://api.github.com') + .get('/repos/octocat/hello-world/branches/main') + .reply(200, { name: 'main', commit: { sha: 'abc123' } }); + nock('https://api.github.com') + .post('/repos/octocat/hello-world/git/refs', { + ref: 'refs/heads/feature-x', + sha: 'abc123', + }) + .reply(201, { ref: 'refs/heads/feature-x', object: { sha: 'abc123' } }); + + const client = await connectedClient(registerReposTools, 'read-write'); + const result = await client.callTool({ + name: 'create_branch', + arguments: { owner: 'octocat', repo: 'hello-world', branch: 'feature-x', from: 'main' }, + }); + + expect(result.isError).toBeFalsy(); + const text = (result.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + expect(JSON.parse(text)).toMatchObject({ ref: 'refs/heads/feature-x' }); + }); + + it('falls back to treating "from" as a raw commit SHA when the branch lookup 404s', async () => { + nock('https://api.github.com') + .get('/repos/octocat/hello-world/branches/deadbeef') + .reply(404, { message: 'Branch not found' }); + nock('https://api.github.com') + .post('/repos/octocat/hello-world/git/refs', { + ref: 'refs/heads/feature-x', + sha: 'deadbeef', + }) + .reply(201, { ref: 'refs/heads/feature-x', object: { sha: 'deadbeef' } }); + + const client = await connectedClient(registerReposTools, 'read-write'); + const result = await client.callTool({ + name: 'create_branch', + arguments: { owner: 'octocat', repo: 'hello-world', branch: 'feature-x', from: 'deadbeef' }, + }); + + expect(result.isError).toBeFalsy(); + const text = (result.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + expect(JSON.parse(text)).toMatchObject({ ref: 'refs/heads/feature-x' }); + }); + + it('propagates a non-404 error from the branch lookup without falling back to SHA treatment', async () => { + nock('https://api.github.com') + .get('/repos/octocat/hello-world/branches/main') + .reply(500, { message: 'Internal Server Error' }); + + const client = await connectedClient(registerReposTools, 'read-write'); + const result = await client.callTool({ + name: 'create_branch', + arguments: { owner: 'octocat', repo: 'hello-world', branch: 'feature-x', from: 'main' }, + }); + + expect(result.isError).toBe(true); + const text = (result.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + expect(text).toContain('Internal Server Error'); + }); + + it('returns a tool error when createRef fails, e.g. branch already exists', async () => { + nock('https://api.github.com') + .get('/repos/octocat/hello-world/branches/main') + .reply(200, { name: 'main', commit: { sha: 'abc123' } }); + nock('https://api.github.com') + .post('/repos/octocat/hello-world/git/refs') + .reply(422, { message: 'Reference already exists' }); + + const client = await connectedClient(registerReposTools, 'read-write'); + const result = await client.callTool({ + name: 'create_branch', + arguments: { owner: 'octocat', repo: 'hello-world', branch: 'feature-x', from: 'main' }, + }); + + expect(result.isError).toBe(true); + const text = (result.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + expect(text).toContain('Reference already exists'); + }); + + it('does not register create_branch in read-only mode', async () => { + const client = await connectedClient(registerReposTools, 'read-only'); + const { tools } = await client.listTools(); + expect(tools.map((tool) => tool.name)).not.toContain('create_branch'); + }); + + it('registers create_branch in read-write mode', async () => { + const client = await connectedClient(registerReposTools, 'read-write'); + const { tools } = await client.listTools(); + expect(tools.map((tool) => tool.name)).toContain('create_branch'); + }); + it('registers all 7 read-only tools regardless of permission', async () => { const client = await connectedClient(registerReposTools, 'read-only'); const { tools } = await client.listTools(); From cfebbc1f58a52fd37406cb405534215c60ca35a4 Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:36:39 +0200 Subject: [PATCH 4/6] docs: document create_branch tool and bump tool count to 105 --- README.md | 13 +++++++------ package.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3f2d3de..dafa973 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Github-MCP-Server-JS

- 🚀 A pure Node.js GitHub MCP server for Claude Desktop and any MCP-compatible client — ⚡ 104 REST tools across 16 toolsets, 🚫 no Docker, 🚫 no Go, 🚫 no python. ✨ + 🚀 A pure Node.js GitHub MCP server for Claude Desktop and any MCP-compatible client — ⚡ 105 REST tools across 16 toolsets, 🚫 no Docker, 🚫 no Go, 🚫 no python. ✨

@@ -21,7 +21,7 @@ Built exclusively on the two **first-party SDKs from the official providers** ## ✨ Highlights -- 🧰 **Complete surface** — 104 tools across 16 toolsets (issues, pull requests, actions, code security, Copilot admin, ProjectsV2, and more). +- 🧰 **Complete surface** — 105 tools across 16 toolsets (issues, pull requests, actions, code security, Copilot admin, ProjectsV2, and more). - 🔒 **Secure by default** — flip `GITHUB_PERMISSION=read-only` and every mutating tool is never even registered. - 📦 **Three install channels** — npm (`npx`), Claude Desktop Extension (`.mcpb`), or unpacked extension (`.zip`). - ✅ **Signed releases** — every version built by GitHub Actions with npm provenance and Sigstore attestation. @@ -40,7 +40,7 @@ Five gaps in the current GitHub-MCP landscape: - 🐳 **The newer official server needs Docker + Go.** [github/github-mcp-server](https://github.com/github/github-mcp-server) ships as a Docker-run Go binary — often blocked by enterprise policy. -- 🧰 **Broader tool coverage.** 104 tools across 16 toolsets — a superset of the archived `server-github` and typical `gh`-CLI wrappers. See [Toolsets](#toolsets) for the full list. +- 🧰 **Broader tool coverage.** 105 tools across 16 toolsets — a superset of the archived `server-github` and typical `gh`-CLI wrappers. See [Toolsets](#toolsets) for the full list. - 🔒 **Read-only mode is one env var.** `GITHUB_PERMISSION=read-only` registers only the 76 read tools; the 28 mutating operations (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never exposed to the model. Same binary, one variable, verified by tests. @@ -84,7 +84,7 @@ Only `GITHUB_TOKEN` is required — the other three are shown with their default - **Read-only mode:** `"GITHUB_PERMISSION": "read-only"` — the 28 mutating tools (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never registered. - **Verbose logs:** `"LOG_LEVEL": "debug"` prints every request/response summary to stderr; Claude Desktop surfaces stderr in its MCP log. Every line is already JSON — no separate format flag needed. -Restart Claude Desktop. All 104 tools become available in every new chat. +Restart Claude Desktop. All 105 tools become available in every new chat. The server can also run standalone from any terminal: @@ -101,7 +101,7 @@ No config-file editing required; the token is stored in the OS keychain. 2. Open **Claude Desktop → Settings → Extensions**. 3. **Drag the `.mcpb` file** into the Extensions pane. 4. Fill in your `GITHUB_TOKEN` (masked; stored in the macOS / Windows keychain, never in plaintext). The remaining fields carry sensible defaults. -5. Click **Install**. All 104 tools are immediately available. +5. Click **Install**. All 105 tools are immediately available. ### 🛠️ Path 3 — Claude Desktop unpacked extension (`.zip`, developer mode) @@ -141,7 +141,7 @@ Configuration is entirely via environment variables. Claude Desktop sets them fr ## 🧰 Toolsets -All 16 toolsets are shipped, exposing **104 tools** total. Write tools are only registered when `GITHUB_PERMISSION=read-write` (the default); `read-only` mode registers the read tools alone. The **Access** column indicates: **R** = registered in read-only mode; **W** = registered only in read-write mode. +All 16 toolsets are shipped, exposing **105 tools** total. Write tools are only registered when `GITHUB_PERMISSION=read-write` (the default); `read-only` mode registers the read tools alone. The **Access** column indicates: **R** = registered in read-only mode; **W** = registered only in read-write mode. ### 📁 `repos` — repositories, branches, commits, tags, file contents @@ -155,6 +155,7 @@ All 16 toolsets are shipped, exposing **104 tools** total. Write tools are only | `get_commit` | R | Get a single commit in a repository. | | `list_tags` | R | List tags in a repository. | | `create_or_update_file` | W | Create a new file or update an existing file in a repository. | +| `create_branch` | W | Create a new branch from an existing branch or commit SHA. | ### 🐛 `issues` — issue CRUD, comments, labels, conversation locking diff --git a/package.json b/package.json index 6cf4961..96589b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github-mcp-server-js", "version": "0.1.5", - "description": "A GitHub MCP server built on octokit.js and the MCP TypeScript SDK v2 — 104 tools across 16 toolsets, packaged as npm and .mcpb Claude Desktop Extension.", + "description": "A GitHub MCP server built on octokit.js and the MCP TypeScript SDK v2 — 105 tools across 16 toolsets, packaged as npm and .mcpb Claude Desktop Extension.", "type": "module", "license": "MIT", "author": "Qunfei Wu", From a3b94d4dddb4f34d5b6ab61d25e8693f42515ecb Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:41:21 +0200 Subject: [PATCH 5/6] docs: bump remaining 104 reference in GITHUB_PERMISSION table row --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dafa973..6a5a693 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Configuration is entirely via environment variables. Claude Desktop sets them fr |---|---|---|---| | `GITHUB_TOKEN` | Yes | — | Personal access token used for all GitHub API calls. | | `GITHUB_SERVER_URL` | No | `github.com` | GitHub host — bare hostname or full API base URL. Set this for GitHub Enterprise Server. | -| `GITHUB_PERMISSION` | No | `read-write` | `read-only` (registers 76 read tools) or `read-write` (all 104). | +| `GITHUB_PERMISSION` | No | `read-write` | `read-only` (registers 76 read tools) or `read-write` (all 105). | | `LOG_LEVEL` | No | `info` | `debug`, `info`, or `error`. Every tool call logs `tool_call` / `tool_ok` / `tool_error` as one JSON object per stderr line, plus an MCP `notifications/message` for the connected client. | ## 🧰 Toolsets From 9009a4c6aed84cf2590e2f9b2e24571090f67560 Mon Sep 17 00:00:00 2001 From: "wu.qunfei@gmail.com" Date: Thu, 6 Aug 2026 17:58:16 +0200 Subject: [PATCH 6/6] docs: fix remaining 104->105 and 28->29 count drift in manifest.json and README --- README.md | 4 ++-- manifest.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6a5a693..d330f2a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Five gaps in the current GitHub-MCP landscape: - 🧰 **Broader tool coverage.** 105 tools across 16 toolsets — a superset of the archived `server-github` and typical `gh`-CLI wrappers. See [Toolsets](#toolsets) for the full list. -- 🔒 **Read-only mode is one env var.** `GITHUB_PERMISSION=read-only` registers only the 76 read tools; the 28 mutating operations (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never exposed to the model. Same binary, one variable, verified by tests. +- 🔒 **Read-only mode is one env var.** `GITHUB_PERMISSION=read-only` registers only the 76 read tools; the 29 mutating operations (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never exposed to the model. Same binary, one variable, verified by tests. **`github-mcp-server-js` fills all five** — pure Node 24+ / TypeScript, single-file bundle, `npx`-installable, shipped as both an npm package and a Claude Desktop Extension. @@ -81,7 +81,7 @@ Add this entry (create the file with `{ "mcpServers": {} }` if it doesn't exist) Only `GITHUB_TOKEN` is required — the other three are shown with their defaults so you can see every knob at a glance. Common adjustments: - **GitHub Enterprise Server:** `"GITHUB_SERVER_URL": "github.mycompany.com"` (bare hostname is fine; the server appends `/api/v3` automatically). -- **Read-only mode:** `"GITHUB_PERMISSION": "read-only"` — the 28 mutating tools (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never registered. +- **Read-only mode:** `"GITHUB_PERMISSION": "read-only"` — the 29 mutating tools (`create_issue`, `merge_pull_request`, `star_repo`, `run_workflow`, …) are never registered. - **Verbose logs:** `"LOG_LEVEL": "debug"` prints every request/response summary to stderr; Claude Desktop surfaces stderr in its MCP log. Every line is already JSON — no separate format flag needed. Restart Claude Desktop. All 105 tools become available in every new chat. diff --git a/manifest.json b/manifest.json index 810b4c6..4f69fdf 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "github-mcp-server-js", "display_name": "GitHub MCP Server (JS)", "version": "0.1.5", - "description": "MCP server exposing 104 GitHub REST tools across 16 toolsets, built on octokit.js.", + "description": "MCP server exposing 105 GitHub REST tools across 16 toolsets, built on octokit.js.", "author": { "name": "Qunfei Wu" }, "homepage": "https://github.com/wuqunfei/github-mcp-server-js", "repository": { @@ -42,7 +42,7 @@ "github_permission": { "type": "string", "title": "Permission (read-only or read-write)", - "description": "read-only registers only read tools; read-write registers all 104 tools.", + "description": "read-only registers only read tools; read-write registers all 105 tools.", "default": "read-write" }, "log_level": {