An MCP (Model Context Protocol) server that lets AI assistants manage GitHub Issues across multiple projects — without ever leaving the chat.
The workflow is deliberate: the AI drafts a task as a local Markdown file first, you review it, then it publishes to GitHub on your command. No silent API calls.
AI assistant
│
├─ fetch_issue ────────► reads existing GitHub Issue for context
│
├─ create_task_draft ──► tasks/<project>/2026-02-26-fix-login-bug.md (local file, review it)
│
└─ publish_issue ──────► github.com/your-org/your-repo/issues/42 (only after you confirm)
- You describe a task to your AI assistant in plain language.
- The server saves a structured
.mddraft locally. - You review or edit the file.
- You tell the AI to publish — it creates the GitHub Issue via the API.
You can also ask the AI to fetch an existing issue by number or URL — it will retrieve the full context (title, body, labels, assignees, comments) so you can start working on it immediately.
- Node.js 18+ (or managed via nvm)
- Claude Code CLI —
npm install -g @anthropic-ai/claude-code - A GitHub account with a Personal Access Token (
reposcope)
git clone https://github.com/your-org/github-issues-server.git
cd github-issues-server
cp .env.example .env
# edit .env — add your GitHub token(s)
cp projects.yaml.example projects.yaml
# edit projects.yaml — add your projects
./mcp.sh setupmcp.sh setup installs dependencies and registers the MCP server with Claude Code in one step. That's it.
Create .env in the project root (it is gitignored):
GITHUB_TOKEN_MYPROJECT=ghp_xxxxxxxxxxxxxxxxxxxxEach project can use a separate token, which lets you work across personal and organization repositories with different permission scopes.
To generate a token: GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens (or classic tokens with repo scope).
projects:
myproject:
owner: your-org # GitHub organization or username
repo: your-repo # Repository name
tokenEnv: GITHUB_TOKEN_MYPROJECT # Which .env variable to use
tasksDir: ./tasks/myproject # Where draft .md files are storedMultiple projects:
projects:
api:
owner: acme-corp
repo: backend-api
tokenEnv: GITHUB_TOKEN_API
tasksDir: ./tasks/api
web:
owner: acme-corp
repo: frontend
tokenEnv: GITHUB_TOKEN_WEB
tasksDir: ./tasks/web
oss:
owner: your-username
repo: open-source-lib
tokenEnv: GITHUB_TOKEN_OSS
tasksDir: ./tasks/ossTask directories are created automatically when the first draft is saved.
./mcp.sh setupBy default registers the server as project-agent with user-level scope (visible in all your projects):
=== github-issues-server MCP Setup ===
Server name : project-agent
Scope : user
Dir : /path/to/github-issues-server
[1/3] Installing dependencies... Done.
[2/3] Registering MCP server with Claude CLI... Registered 'project-agent' (scope: user).
[3/3] Verifying...
project-agent: /path/to/mcp.sh - ✓ Connected
=== Setup complete! ===
Custom server name or scope:
./mcp.sh setup my-server # custom name, user scope
./mcp.sh setup my-server local # local scope (current project only)
./mcp.sh setup my-server project # project scope (.mcp.json)npm install
claude mcp add -s user -- project-agent /absolute/path/to/github-issues-server/mcp.shAfter running ./mcp.sh setup, the server is registered automatically. Verify:
claude mcp list
# project-agent: /path/to/mcp.sh - ✓ ConnectedIn PhpStorm, VS Code, or any IDE with a Claude Code plugin, type /mcp to see connected servers.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"project-agent": {
"command": "/absolute/path/to/github-issues-server/mcp.sh",
"env": {}
}
}
}Tokens are loaded from .env by mcp.sh — no need to duplicate them in the Desktop config.
Restart Claude Desktop after editing the config.
Lists all projects defined in projects.yaml.
No input required.
Example response:
## Available Projects
- **api**: acme-corp/backend-api (tasks: ./tasks/api)
- **web**: acme-corp/frontend (tasks: ./tasks/web)
Creates a structured Markdown draft locally. Does not touch GitHub.
| Parameter | Type | Required | Description |
|---|---|---|---|
project |
string |
yes | Project name from projects.yaml |
title |
string |
yes | Issue title |
context |
string |
yes | Description / background |
files |
string[] |
no | Affected file paths |
checklist |
string[] |
no | Checklist items |
assignee |
string |
no | GitHub username |
type |
string |
no | bug | feature | task (default: task) |
The draft is saved to tasks/<project>/YYYY-MM-DD-<slug>.md:
# Fix login redirect loop
**Type**: bug
**Assignee**: @johndoe
## Context
After OAuth callback, users are redirected back to /login instead of the dashboard.
## Affected Files
- `src/auth/callback.ts`
- `src/middleware/session.ts`
## Checklist
- [ ] Reproduce the issue locally
- [ ] Check session token expiry logic
- [ ] Add integration test for OAuth flowFetches an existing GitHub Issue by number or URL and returns its full context.
| Parameter | Type | Required | Description |
|---|---|---|---|
project |
string |
yes | Project name from projects.yaml |
issue |
string |
yes | Issue number (e.g. 123) or full GitHub issue URL |
include_comments |
boolean |
no | Whether to fetch comments as well (default: true) |
Example response:
## Issue #123: Fix login redirect loop
| Field | Value |
|---|---|
| Repository | acme-corp/backend-api |
| State | 🟢 open |
| Labels | `bug`, `urgent` |
| Assignees | @alice |
| Created | 2026-01-15 |
| Updated | 2026-02-20 |
| URL | https://github.com/acme-corp/backend-api/issues/123 |
### Description
After OAuth callback, users are redirected back to /login instead of the dashboard.
Reads a draft .md file and creates a GitHub Issue. Call this only after the user has reviewed the draft.
| Parameter | Type | Required | Description |
|---|---|---|---|
project |
string |
yes | Project name from projects.yaml |
draftFile |
string |
yes | Absolute path to the draft .md file |
assignee |
string |
no | GitHub username (defaults to owner from projects.yaml) |
type |
string |
no | Overrides the type from the draft (bug, feature, task) |
What happens at publish time:
- Title — prefixed with a type tag:
[BUG] Title,[FEATURE] Title,[TASK] Title - Assignee — set to
ownerfromprojects.yamlunless overridden - Label — applied automatically based on type:
bug→bugfeature→enhancementtask→task
- Body — the
# Titleheading is stripped (already in the issue title), and a generated-by badge is prepended:
> [!NOTE]
> The task was generated using the MCP server — prog-time/github-issues-server
Example response:
## Issue Published
**#42**: [BUG] Fix login redirect loop
**Repository**: acme-corp/backend-api
**URL**: https://github.com/acme-corp/backend-api/issues/42
You: In project "api", create a task: the login page has a redirect loop after OAuth.
Affected files: src/auth/callback.ts. Add a checklist for the fix.
AI: Draft saved to tasks/api/2026-02-26-fix-login-redirect-loop.md
[shows full markdown]
Review it and let me know if you'd like to publish.
You: Looks good, publish it.
AI: Issue #42 created: https://github.com/acme-corp/backend-api/issues/42
github-issues-server/
├── src/
│ ├── server.ts # MCP server entry point
│ ├── config.ts # Loads projects.yaml and resolves tokens
│ ├── router.ts # Registers all tools
│ ├── logger.ts # File + stderr logger
│ └── tools/
│ ├── listProjects.ts
│ ├── draft.ts
│ ├── publish.ts
│ └── fetchIssue.ts
├── tests/
│ └── src/
│ ├── config.test.ts
│ └── tools/
│ ├── draft.test.ts
│ ├── fetchIssue.test.ts
│ ├── listProjects.test.ts
│ └── publish.test.ts
├── tasks/ # Auto-created; stores draft .md files
│ └── <project>/
├── logs/
│ └── server.log # Server log (auto-created)
├── projects.yaml # Your project definitions
├── projects.yaml.example # Template
├── .env # GitHub tokens (gitignored)
├── .env.example # Template
├── mcp.sh # Server launcher + setup in one script
├── vitest.config.ts
├── package.json
└── tsconfig.json
The server writes logs to two places simultaneously:
| Destination | Purpose |
|---|---|
logs/server.log |
Persistent file log, human-readable |
stderr |
Captured by Claude Desktop into its mcp-*.log |
To tail the log:
tail -f logs/server.logClaude Desktop log location (macOS):
~/Library/Logs/Claude/mcp-server-project-agent.log
When you pull new changes:
git pull
./mcp.sh setup # re-installs deps and re-registers the server- Fork the repository
- Create a branch:
git checkout -b feature/my-feature - Make your changes
- Open a pull request
To run in dev mode:
npm run devTo run tests:
npm test # run once
npm run test:watch # watch modeTo check types:
npx tsc --noEmitTo lint:
npx eslint .To build:
npm run build
npm startCI runs ESLint, type-check, and the full test suite on every push and pull request against main.
MIT