Skip to main content
Glama
MatinMHF

github-mcp-lite

by MatinMHF
README.md
<p align="center">
  <b>English</b> | <a href="README.fa.md">فارسی</a>
</p>

# github-mcp-lite

A minimal [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server
for creating GitHub repositories, committing files, and publishing releases
using a plain Personal Access Token (PAT).

No Docker, no local Git installation, and no GitHub Copilot subscription
required — unlike the [hosted `api.githubcopilot.com/mcp/`
server](https://github.com/github/github-mcp-server), which gates access
behind a Copilot entitlement. This server talks directly to the regular
GitHub REST API (`api.github.com`) over plain `fetch`, so it's a single
dependency-light file you can read top to bottom in a few minutes.

## Why this exists

Most MCP GitHub integrations either require a full local git toolchain, a
Docker container, or a Copilot-gated hosted endpoint. `github-mcp-lite` is
for cases where you just want an AI assistant to be able to:

- spin up a new repo,
- push a batch of files to it in one commit, and
- tag a release,

using nothing but a token you already have.

## Tools

| Tool | Description | Destructive? |
| --- | --- | --- |
| `get_authenticated_user` | Return the login/profile for the account owning the configured token. | No |
| `list_repositories` | List repositories the token can see, newest-updated first. | No |
| `create_repository` | Create a repo owned by the authenticated user. Auto-initialized with a README so a default branch exists immediately. | No |
| `commit_files` | Create or update one or more text files in a single commit. | Yes (overwrites file contents) |
| `commit_directory` | Upload an entire local directory in one commit. Handles binary files and respects `.gitignore` inside a git work tree. | Yes (overwrites file contents) |
| `create_release` | Publish a tagged GitHub release. | No |
| `delete_repository` | Permanently delete a repository. Requires the `delete_repo` token scope. | **Yes, irreversible** |
| `github_request` | Authenticated passthrough to any `api.github.com` path, any verb. | **Yes — full token authority** |

Each tool is annotated with `readOnlyHint` / `destructiveHint` /
`openWorldHint` metadata so MCP clients can surface appropriate confirmation
prompts.

### `get_authenticated_user`

No input. Returns `{ login, html_url, name }` for the token's owner.

### `list_repositories`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `affiliation` | string | no | Comma-separated: `owner`, `collaborator`, `organization_member`. Defaults to all three |
| `visibility` | `all` \| `public` \| `private` | no | Defaults to `all` |
| `per_page` | number | no | 1–100, defaults to `100` |
| `page` | number | no | Defaults to `1` |

Wraps `GET /user/repos`, sorted by most recently updated. Returns an array of
`{ full_name, private, language, updated_at, html_url }`. Results are paginated
— a full page means you should ask for the next one.

### `create_repository`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `name` | string | yes | Repository name |
| `description` | string | no | Repository description |
| `private` | boolean | no | Defaults to `false` (public) |

Returns `{ full_name, html_url, clone_url, default_branch }`.

### `commit_files`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `owner` | string | yes | Repository owner |
| `repo` | string | yes | Repository name |
| `branch` | string | no | Defaults to the repo's default branch |
| `message` | string | yes | Commit message |
| `files` | array of `{ path, content }` | yes | UTF-8 text content only; at least one file |

Internally this creates blobs, a tree, and a commit via the Git Data API,
then fast-forwards the branch ref — so it's a real single commit, not one
commit per file. Returns `{ commit_sha, html_url, files_changed }`.

### `commit_directory`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `owner` | string | yes | Repository owner |
| `repo` | string | yes | Repository name |
| `directory` | string | yes | Absolute path to the local directory to upload |
| `branch` | string | no | Defaults to the repo's default branch |
| `message` | string | yes | Commit message |
| `exclude` | array of string | no | Extra path substrings to skip, e.g. `['.env', 'secrets/']` |

Use this instead of `commit_files` when pushing a whole project. Inside a git
work tree it defers to `git ls-files` so `.gitignore` is honoured; otherwise it
walks the directory and prunes the usual junk (`node_modules`, `.git`, `dist`,
`build`, `.next`, `coverage`). Binary files are detected and uploaded as blobs;
text files are inlined into the tree, and the tree is built in chunks so a
large project stays within a handful of round trips. Returns
`{ commit_sha, html_url, files_uploaded, skipped }`.

> **Note:** this merges onto the existing tree — it adds and updates files but
> never deletes. A file removed locally will still be present on GitHub.

### `create_release`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `owner` | string | yes | Repository owner |
| `repo` | string | yes | Repository name |
| `tag_name` | string | yes | e.g. `v1.0.0` |
| `name` | string | no | Release title |
| `body` | string | no | Release notes (Markdown) |
| `target_commitish` | string | no | Branch or commit SHA to tag; defaults to the default branch |
| `draft` | boolean | no | Defaults to `false` |
| `prerelease` | boolean | no | Defaults to `false` |

Returns `{ html_url, id, tag_name }`.

### `delete_repository`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `owner` | string | yes | Repository owner |
| `repo` | string | yes | Repository name |

Permanently deletes the repository. Returns `{ deleted: true, full_name }`.
Requires a classic PAT with the `delete_repo` scope — fine-grained tokens
generally cannot delete repositories.

### `github_request`

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `path` | string | yes | API path beginning with `/`, query string included |
| `method` | `GET` \| `POST` \| `PATCH` \| `PUT` \| `DELETE` | no | Defaults to `GET` |
| `body` | object | no | JSON body for `POST` / `PATCH` / `PUT` |

An escape hatch for anything the dedicated tools don't cover: code scanning and
secret scanning alerts, deployments, commit statuses, repository invitations,
issues, pull requests, orgs, gists. `path` is everything after
`https://api.github.com`, e.g. `/repos/OWNER/REPO/code-scanning/alerts`.
Returns the parsed JSON response.

One generic tool rather than a wrapper per endpoint means the server can't fall
behind your token — a scope you add later is reachable with no code change.

> **Warning:** this carries the token's full authority, including `DELETE` on
> any resource the token can reach. It is annotated `destructiveHint: true`.
> If you want the reach without the risk, narrow the `method` enum in
> `index.js` to `GET` and it becomes a read-only window over every scope.

Paths are validated to begin with a single `/`, so absolute URLs
(`https://…`) and protocol-relative paths (`//…`) are rejected — without that,
a crafted path would send your bearer token to another host. `test.js` covers
this; run it with `node test.js`.

## Installation

### Prerequisites

- [Node.js](https://nodejs.org/) 18+ (for native `fetch` support)
- A GitHub [Personal Access Token](https://github.com/settings/tokens) with
  the `repo` scope (add `delete_repo` too if you want to use
  `delete_repository`)

> **Note:** Repository *creation* isn't reliably supported by fine-grained
> PATs, so a **classic** token is recommended.

### Clone and install dependencies

```bash
git clone https://github.com/MatinMHF/github-mcp-lite.git
cd github-mcp-lite
npm install
```

### Configure your token

The server reads the token from either `GITHUB_PERSONAL_ACCESS_TOKEN` or
`GITHUB_TOKEN` in its environment. Set one of these before it starts — the
process exits immediately with an error if neither is present.

## Usage

### Run directly

```bash
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx node index.js
```

The server communicates over stdio, so it's meant to be launched by an MCP
client (Claude Code, Claude Desktop, etc.), not run interactively on its own.

### Register with Claude Code

```bash
claude mcp add github-lite -s user -e GITHUB_PERSONAL_ACCESS_TOKEN=<your_token> -- node /path/to/index.js
```

On Windows PowerShell, quote the separator as `"--"` — PowerShell silently
strips a bare `--` before it reaches the script's arguments, which breaks
argument parsing:

```powershell
claude mcp add github-lite -s user -e GITHUB_PERSONAL_ACCESS_TOKEN=<your_token> "--" node "C:/path/to/index.js"
```

### Register with Claude Desktop (or any MCP-compatible client)

Add an entry to your client's MCP server config (e.g.
`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "github-lite": {
      "command": "node",
      "args": ["C:/path/to/github-mcp-lite/index.js"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxx"
      }
    }
  }
}
```

### Example workflow

A typical session once the server is registered with your MCP client:

1. **"Who am I authenticated as?"** → calls `get_authenticated_user`
2. **"Create a public repo called `my-project`"** → calls `create_repository`
   (auto-initializes with a README so a default branch exists)
3. **"Commit these files to it"** → calls `commit_files` with an array of
   `{ path, content }` entries, written in a single commit
4. **"Publish a v1.0.0 release"** → calls `create_release`

## Security notes

- The token is read once from the environment at startup and never logged.
- `commit_files`, `commit_directory`, `delete_repository` and `github_request`
  are marked `destructiveHint: true` in their tool annotations — well-behaved
  MCP clients should prompt for confirmation before invoking them.
- `github_request` is the widest of these: it can reach any endpoint the token
  is allowed to reach, with any verb. On a classic PAT the `repo` scope also
  grants its children — `repo:status`, `repo_deployment`, `public_repo`,
  `repo:invite` and `security_events` — which cannot be unticked individually.
  That last one means read *and write* access to code scanning and secret
  scanning alerts across every repo you own. A fine-grained token scoped to
  Contents, Administration and Metadata avoids it, at the cost of repository
  creation being unreliable.
- Because this server has `openWorldHint: true` on every tool (it makes
  real network calls to `api.github.com`), only run it with a token scoped
  to what you actually need.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. This project follows a
[Code of Conduct](CODE_OF_CONDUCT.md). To report a security issue, see
[SECURITY.md](SECURITY.md).

## License

MIT