@ldraney/github-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -0
- package/dist/auth/local-server.d.ts +16 -0
- package/dist/auth/local-server.js +146 -0
- package/dist/auth/oauth-device-flow.d.ts +20 -0
- package/dist/auth/oauth-device-flow.d.ts.map +1 -0
- package/dist/auth/oauth-device-flow.js +141 -0
- package/dist/auth/oauth-device-flow.js.map +1 -0
- package/dist/auth/oauth-flow.d.ts +20 -0
- package/dist/auth/oauth-flow.js +95 -0
- package/dist/auth/token-storage.d.ts +16 -0
- package/dist/auth/token-storage.d.ts.map +1 -0
- package/dist/auth/token-storage.js +33 -0
- package/dist/auth/token-storage.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/webhooks.d.ts +15 -0
- package/dist/resources/webhooks.d.ts.map +1 -0
- package/dist/resources/webhooks.js +194 -0
- package/dist/resources/webhooks.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +247 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/categories/actions.d.ts +5 -0
- package/dist/tools/categories/actions.d.ts.map +1 -0
- package/dist/tools/categories/actions.js +395 -0
- package/dist/tools/categories/actions.js.map +1 -0
- package/dist/tools/categories/gists.d.ts +5 -0
- package/dist/tools/categories/gists.d.ts.map +1 -0
- package/dist/tools/categories/gists.js +315 -0
- package/dist/tools/categories/gists.js.map +1 -0
- package/dist/tools/categories/issues.d.ts +5 -0
- package/dist/tools/categories/issues.d.ts.map +1 -0
- package/dist/tools/categories/issues.js +425 -0
- package/dist/tools/categories/issues.js.map +1 -0
- package/dist/tools/categories/orgs.d.ts +5 -0
- package/dist/tools/categories/orgs.d.ts.map +1 -0
- package/dist/tools/categories/orgs.js +452 -0
- package/dist/tools/categories/orgs.js.map +1 -0
- package/dist/tools/categories/pulls.d.ts +5 -0
- package/dist/tools/categories/pulls.d.ts.map +1 -0
- package/dist/tools/categories/pulls.js +370 -0
- package/dist/tools/categories/pulls.js.map +1 -0
- package/dist/tools/categories/repos.d.ts +5 -0
- package/dist/tools/categories/repos.d.ts.map +1 -0
- package/dist/tools/categories/repos.js +392 -0
- package/dist/tools/categories/repos.js.map +1 -0
- package/dist/tools/categories/search.d.ts +5 -0
- package/dist/tools/categories/search.d.ts.map +1 -0
- package/dist/tools/categories/search.js +217 -0
- package/dist/tools/categories/search.js.map +1 -0
- package/dist/tools/categories/users.d.ts +5 -0
- package/dist/tools/categories/users.d.ts.map +1 -0
- package/dist/tools/categories/users.js +247 -0
- package/dist/tools/categories/users.js.map +1 -0
- package/dist/tools/generator.d.ts +19 -0
- package/dist/tools/generator.d.ts.map +1 -0
- package/dist/tools/generator.js +69 -0
- package/dist/tools/generator.js.map +1 -0
- package/dist/webhooks/event-queue.d.ts +54 -0
- package/dist/webhooks/event-queue.d.ts.map +1 -0
- package/dist/webhooks/event-queue.js +117 -0
- package/dist/webhooks/event-queue.js.map +1 -0
- package/dist/webhooks/smee-client.d.ts +27 -0
- package/dist/webhooks/smee-client.d.ts.map +1 -0
- package/dist/webhooks/smee-client.js +153 -0
- package/dist/webhooks/smee-client.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# github-mcp
|
|
2
|
+
|
|
3
|
+
A comprehensive GitHub MCP server with OAuth Device Flow authentication and 800+ endpoint coverage.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **800+ GitHub API endpoints** as MCP tools (vs ~50 in official server)
|
|
8
|
+
- **OAuth Device Flow** - No PAT management, just authenticate in browser
|
|
9
|
+
- **OS Keychain storage** - Secure token storage via keytar
|
|
10
|
+
- **Real-time webhooks** - GitHub events as MCP resources via smee.io
|
|
11
|
+
- **npx installable** - No Docker required
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx github-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
On first run:
|
|
20
|
+
1. Opens browser to github.com/login/device
|
|
21
|
+
2. Enter the displayed code
|
|
22
|
+
3. Token stored securely in OS keychain
|
|
23
|
+
4. MCP server starts with all tools available
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g github-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or run directly:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx github-mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### As MCP Server
|
|
40
|
+
|
|
41
|
+
Add to your Claude Desktop config:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"github": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["github-mcp"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### CLI Commands
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Start server (default)
|
|
58
|
+
github-mcp
|
|
59
|
+
|
|
60
|
+
# Auth commands
|
|
61
|
+
github-mcp auth login # Trigger OAuth flow
|
|
62
|
+
github-mcp auth logout # Remove stored token
|
|
63
|
+
github-mcp auth status # Check auth status
|
|
64
|
+
|
|
65
|
+
# With environment token (skips OAuth)
|
|
66
|
+
GITHUB_TOKEN=ghp_xxx github-mcp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Tools
|
|
70
|
+
|
|
71
|
+
Tools are organized by GitHub API category:
|
|
72
|
+
|
|
73
|
+
| Category | Examples |
|
|
74
|
+
|----------|----------|
|
|
75
|
+
| `github_repos_*` | list, get, create, delete |
|
|
76
|
+
| `github_issues_*` | list, get, create, update, comment |
|
|
77
|
+
| `github_pulls_*` | list, get, create, merge, review |
|
|
78
|
+
| `github_users_*` | get, list, followers |
|
|
79
|
+
| `github_actions_*` | workflows, runs, jobs |
|
|
80
|
+
| `github_gists_*` | list, get, create |
|
|
81
|
+
| ... | 38 categories total |
|
|
82
|
+
|
|
83
|
+
## Webhook Events
|
|
84
|
+
|
|
85
|
+
When webhooks are configured, events appear as MCP resources:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
github://webhooks/push
|
|
89
|
+
github://webhooks/pull_request
|
|
90
|
+
github://webhooks/issues
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Setting Up Webhooks
|
|
94
|
+
|
|
95
|
+
1. Server generates a smee.io channel on first run
|
|
96
|
+
2. Add the channel URL as a webhook in your repo settings
|
|
97
|
+
3. Events stream to the MCP server in real-time
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
Environment variables:
|
|
102
|
+
|
|
103
|
+
| Variable | Description | Required |
|
|
104
|
+
|----------|-------------|----------|
|
|
105
|
+
| `GITHUB_TOKEN` | Skip OAuth, use this token | No |
|
|
106
|
+
| `GITHUB_CLIENT_ID` | OAuth App client ID | For OAuth |
|
|
107
|
+
| `SMEE_URL` | Custom smee.io channel | No |
|
|
108
|
+
|
|
109
|
+
## Comparison with Official GitHub MCP
|
|
110
|
+
|
|
111
|
+
| Feature | Official (Go) | github-mcp |
|
|
112
|
+
|---------|---------------|------------|
|
|
113
|
+
| Endpoints | ~50 | 800+ |
|
|
114
|
+
| Auth | PAT only | OAuth Device Flow |
|
|
115
|
+
| Webhooks | No | Yes (smee.io) |
|
|
116
|
+
| Install | Docker | npx |
|
|
117
|
+
| Token Storage | Manual | OS Keychain |
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/ldraney/github-mcp.git
|
|
123
|
+
cd github-mcp
|
|
124
|
+
npm install
|
|
125
|
+
npm run build
|
|
126
|
+
npm start
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface CallbackResult {
|
|
2
|
+
token: string;
|
|
3
|
+
scopes?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Start a temporary HTTP server to receive OAuth callback
|
|
7
|
+
*
|
|
8
|
+
* @param expectedNonce - The nonce to validate against
|
|
9
|
+
* @returns Promise that resolves with token when callback is received
|
|
10
|
+
*/
|
|
11
|
+
export declare function startLocalCallbackServer(expectedNonce: string): Promise<{
|
|
12
|
+
port: number;
|
|
13
|
+
waitForCallback: () => Promise<CallbackResult>;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
/**
|
|
4
|
+
* Start a temporary HTTP server to receive OAuth callback
|
|
5
|
+
*
|
|
6
|
+
* @param expectedNonce - The nonce to validate against
|
|
7
|
+
* @returns Promise that resolves with token when callback is received
|
|
8
|
+
*/
|
|
9
|
+
export function startLocalCallbackServer(expectedNonce) {
|
|
10
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
11
|
+
let callbackResolve;
|
|
12
|
+
let callbackReject;
|
|
13
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
14
|
+
callbackResolve = resolve;
|
|
15
|
+
callbackReject = reject;
|
|
16
|
+
});
|
|
17
|
+
const server = http.createServer((req, res) => {
|
|
18
|
+
if (!req.url) {
|
|
19
|
+
res.writeHead(400);
|
|
20
|
+
res.end('Bad request');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const url = new URL(req.url, `http://localhost`);
|
|
24
|
+
if (url.pathname !== '/callback') {
|
|
25
|
+
res.writeHead(404);
|
|
26
|
+
res.end('Not found');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const token = url.searchParams.get('token');
|
|
30
|
+
const nonce = url.searchParams.get('nonce');
|
|
31
|
+
const scopes = url.searchParams.get('scope');
|
|
32
|
+
const error = url.searchParams.get('error');
|
|
33
|
+
if (error) {
|
|
34
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
35
|
+
res.end(`
|
|
36
|
+
<!DOCTYPE html>
|
|
37
|
+
<html>
|
|
38
|
+
<head><title>Auth Error</title></head>
|
|
39
|
+
<body>
|
|
40
|
+
<h1>Authentication Failed</h1>
|
|
41
|
+
<p>${error}</p>
|
|
42
|
+
<p>You can close this window.</p>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
45
|
+
`);
|
|
46
|
+
callbackReject(new Error(error));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!token) {
|
|
50
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
51
|
+
res.end(`
|
|
52
|
+
<!DOCTYPE html>
|
|
53
|
+
<html>
|
|
54
|
+
<head><title>Auth Error</title></head>
|
|
55
|
+
<body>
|
|
56
|
+
<h1>Authentication Failed</h1>
|
|
57
|
+
<p>No token received.</p>
|
|
58
|
+
<p>You can close this window.</p>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
`);
|
|
62
|
+
callbackReject(new Error('No token received'));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (nonce !== expectedNonce) {
|
|
66
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
67
|
+
res.end(`
|
|
68
|
+
<!DOCTYPE html>
|
|
69
|
+
<html>
|
|
70
|
+
<head><title>Auth Error</title></head>
|
|
71
|
+
<body>
|
|
72
|
+
<h1>Authentication Failed</h1>
|
|
73
|
+
<p>Invalid nonce - possible CSRF attack.</p>
|
|
74
|
+
<p>You can close this window.</p>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
`);
|
|
78
|
+
callbackReject(new Error('Nonce mismatch - possible CSRF attack'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Success!
|
|
82
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
83
|
+
res.end(`
|
|
84
|
+
<!DOCTYPE html>
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<title>Authentication Successful</title>
|
|
88
|
+
<style>
|
|
89
|
+
body {
|
|
90
|
+
font-family: system-ui, sans-serif;
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
align-items: center;
|
|
94
|
+
height: 100vh;
|
|
95
|
+
margin: 0;
|
|
96
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
97
|
+
}
|
|
98
|
+
.card {
|
|
99
|
+
background: white;
|
|
100
|
+
padding: 40px;
|
|
101
|
+
border-radius: 16px;
|
|
102
|
+
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
|
103
|
+
text-align: center;
|
|
104
|
+
}
|
|
105
|
+
.checkmark {
|
|
106
|
+
font-size: 64px;
|
|
107
|
+
margin-bottom: 20px;
|
|
108
|
+
}
|
|
109
|
+
h1 { color: #333; margin: 0 0 10px 0; }
|
|
110
|
+
p { color: #666; margin: 0; }
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<div class="card">
|
|
115
|
+
<div class="checkmark">✓</div>
|
|
116
|
+
<h1>Authenticated!</h1>
|
|
117
|
+
<p>You can close this window and return to your terminal.</p>
|
|
118
|
+
</div>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
121
|
+
`);
|
|
122
|
+
callbackResolve({ token, scopes: scopes || undefined });
|
|
123
|
+
});
|
|
124
|
+
// Listen on random available port
|
|
125
|
+
server.listen(0, '127.0.0.1', () => {
|
|
126
|
+
const address = server.address();
|
|
127
|
+
if (!address || typeof address === 'string') {
|
|
128
|
+
rejectServer(new Error('Failed to get server port'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
resolveServer({
|
|
132
|
+
port: address.port,
|
|
133
|
+
waitForCallback: () => callbackPromise,
|
|
134
|
+
close: () => server.close(),
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
server.on('error', (err) => {
|
|
138
|
+
rejectServer(err);
|
|
139
|
+
});
|
|
140
|
+
// Timeout after 5 minutes
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
callbackReject(new Error('Authentication timed out'));
|
|
143
|
+
server.close();
|
|
144
|
+
}, 5 * 60 * 1000);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DeviceFlowVerification {
|
|
2
|
+
device_code: string;
|
|
3
|
+
user_code: string;
|
|
4
|
+
verification_uri: string;
|
|
5
|
+
expires_in: number;
|
|
6
|
+
interval: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Perform OAuth Device Flow authentication
|
|
10
|
+
*/
|
|
11
|
+
export declare function login(): Promise<string | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Remove stored credentials
|
|
14
|
+
*/
|
|
15
|
+
export declare function logout(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Check and display authentication status
|
|
18
|
+
*/
|
|
19
|
+
export declare function getAuthStatus(): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=oauth-device-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-device-flow.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-device-flow.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4DpD;AAED;;GAEG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ5C;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BnD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createOAuthDeviceAuth } from '@octokit/auth-oauth-device';
|
|
2
|
+
import { Octokit } from '@octokit/rest';
|
|
3
|
+
import { setToken, getToken, deleteToken } from './token-storage.js';
|
|
4
|
+
// Default GitHub OAuth App client ID
|
|
5
|
+
// Users should register their own OAuth App for production use
|
|
6
|
+
const DEFAULT_CLIENT_ID = process.env.GITHUB_CLIENT_ID || 'Ov23liXXXXXXXXXXXXXX';
|
|
7
|
+
// Scopes needed for full GitHub API access
|
|
8
|
+
const SCOPES = [
|
|
9
|
+
'repo',
|
|
10
|
+
'read:org',
|
|
11
|
+
'read:user',
|
|
12
|
+
'user:email',
|
|
13
|
+
'read:project',
|
|
14
|
+
'write:discussion',
|
|
15
|
+
'gist',
|
|
16
|
+
'notifications',
|
|
17
|
+
'workflow',
|
|
18
|
+
'read:packages',
|
|
19
|
+
'admin:repo_hook',
|
|
20
|
+
'admin:org_hook',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Perform OAuth Device Flow authentication
|
|
24
|
+
*/
|
|
25
|
+
export async function login() {
|
|
26
|
+
const clientId = process.env.GITHUB_CLIENT_ID || DEFAULT_CLIENT_ID;
|
|
27
|
+
if (clientId === DEFAULT_CLIENT_ID || clientId.startsWith('Ov23liXXXX')) {
|
|
28
|
+
console.error('\n⚠️ No GITHUB_CLIENT_ID set. Please register an OAuth App:');
|
|
29
|
+
console.error(' 1. Go to https://github.com/settings/developers');
|
|
30
|
+
console.error(' 2. Click "New OAuth App"');
|
|
31
|
+
console.error(' 3. Set callback URL to: http://localhost/callback');
|
|
32
|
+
console.error(' 4. Enable "Device Flow" in the app settings');
|
|
33
|
+
console.error(' 5. Set GITHUB_CLIENT_ID environment variable\n');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
console.error('\n🔐 Starting GitHub OAuth Device Flow...\n');
|
|
37
|
+
try {
|
|
38
|
+
const auth = createOAuthDeviceAuth({
|
|
39
|
+
clientType: 'oauth-app',
|
|
40
|
+
clientId,
|
|
41
|
+
scopes: SCOPES,
|
|
42
|
+
onVerification: (verification) => {
|
|
43
|
+
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
44
|
+
console.error('');
|
|
45
|
+
console.error(' 📋 Your verification code:');
|
|
46
|
+
console.error('');
|
|
47
|
+
console.error(` ${verification.user_code}`);
|
|
48
|
+
console.error('');
|
|
49
|
+
console.error(' 🌐 Open this URL in your browser:');
|
|
50
|
+
console.error('');
|
|
51
|
+
console.error(` ${verification.verification_uri}`);
|
|
52
|
+
console.error('');
|
|
53
|
+
console.error(' ⏳ Waiting for authorization...');
|
|
54
|
+
console.error('');
|
|
55
|
+
console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
56
|
+
// Try to open browser automatically
|
|
57
|
+
openBrowser(verification.verification_uri);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const { token } = await auth({ type: 'oauth' });
|
|
61
|
+
// Store token securely
|
|
62
|
+
await setToken(token);
|
|
63
|
+
// Verify token works
|
|
64
|
+
const octokit = new Octokit({ auth: token });
|
|
65
|
+
const { data: user } = await octokit.users.getAuthenticated();
|
|
66
|
+
console.error('');
|
|
67
|
+
console.error(`✅ Successfully authenticated as: ${user.login}`);
|
|
68
|
+
console.error(' Token stored securely in OS keychain');
|
|
69
|
+
console.error('');
|
|
70
|
+
return token;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
74
|
+
console.error(`\n❌ Authentication failed: ${message}\n`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove stored credentials
|
|
80
|
+
*/
|
|
81
|
+
export async function logout() {
|
|
82
|
+
const deleted = await deleteToken();
|
|
83
|
+
if (deleted) {
|
|
84
|
+
console.error('✅ Logged out. Token removed from keychain.');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.error('ℹ️ No stored credentials found.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check and display authentication status
|
|
92
|
+
*/
|
|
93
|
+
export async function getAuthStatus() {
|
|
94
|
+
const token = process.env.GITHUB_TOKEN || await getToken();
|
|
95
|
+
if (!token) {
|
|
96
|
+
console.error('❌ Not authenticated');
|
|
97
|
+
console.error(' Run: github-mcp auth login');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const octokit = new Octokit({ auth: token });
|
|
102
|
+
const { data: user } = await octokit.users.getAuthenticated();
|
|
103
|
+
console.error('✅ Authenticated');
|
|
104
|
+
console.error(` User: ${user.login}`);
|
|
105
|
+
console.error(` Name: ${user.name || 'Not set'}`);
|
|
106
|
+
console.error(` Email: ${user.email || 'Not public'}`);
|
|
107
|
+
if (process.env.GITHUB_TOKEN) {
|
|
108
|
+
console.error(' Source: GITHUB_TOKEN environment variable');
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.error(' Source: OS keychain');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('❌ Token is invalid or expired');
|
|
116
|
+
console.error(' Run: github-mcp auth login');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Open URL in default browser (cross-platform)
|
|
121
|
+
*/
|
|
122
|
+
function openBrowser(url) {
|
|
123
|
+
const { exec } = require('child_process');
|
|
124
|
+
const platform = process.platform;
|
|
125
|
+
let command;
|
|
126
|
+
if (platform === 'darwin') {
|
|
127
|
+
command = `open "${url}"`;
|
|
128
|
+
}
|
|
129
|
+
else if (platform === 'win32') {
|
|
130
|
+
command = `start "${url}"`;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
command = `xdg-open "${url}"`;
|
|
134
|
+
}
|
|
135
|
+
exec(command, (error) => {
|
|
136
|
+
if (error) {
|
|
137
|
+
// Silent fail - user can manually open the URL
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=oauth-device-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-device-flow.js","sourceRoot":"","sources":["../../src/auth/oauth-device-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get existing token (from env or keychain)
|
|
3
|
+
*/
|
|
4
|
+
export declare function getToken(): Promise<string | null>;
|
|
5
|
+
/**
|
|
6
|
+
* Start OAuth login flow
|
|
7
|
+
*/
|
|
8
|
+
export declare function login(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Logout - remove stored token
|
|
11
|
+
*/
|
|
12
|
+
export declare function logout(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Get authentication status
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAuthStatus(): Promise<{
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
username?: string;
|
|
19
|
+
scopes?: string[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { Octokit } from '@octokit/rest';
|
|
4
|
+
import { startLocalCallbackServer } from './local-server.js';
|
|
5
|
+
import { storeToken, retrieveToken, retrieveScopes, deleteToken } from './token-storage.js';
|
|
6
|
+
// OAuth configuration
|
|
7
|
+
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || 'Ov23liEtc0pnj6t2b6Jr';
|
|
8
|
+
const AUTH_BACKEND_URL = process.env.AUTH_BACKEND_URL || 'https://gh-mcp-auth.fly.dev';
|
|
9
|
+
const OAUTH_SCOPES = 'repo,read:org,read:user,gist,notifications,workflow';
|
|
10
|
+
/**
|
|
11
|
+
* Generate a cryptographically secure random nonce
|
|
12
|
+
*/
|
|
13
|
+
function generateNonce() {
|
|
14
|
+
return crypto.randomBytes(24).toString('base64url');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get existing token (from env or keychain)
|
|
18
|
+
*/
|
|
19
|
+
export async function getToken() {
|
|
20
|
+
// Environment variable takes precedence
|
|
21
|
+
if (process.env.GITHUB_TOKEN) {
|
|
22
|
+
return process.env.GITHUB_TOKEN;
|
|
23
|
+
}
|
|
24
|
+
// Try keychain
|
|
25
|
+
return retrieveToken();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start OAuth login flow
|
|
29
|
+
*/
|
|
30
|
+
export async function login() {
|
|
31
|
+
// Check environment first
|
|
32
|
+
if (process.env.GITHUB_TOKEN) {
|
|
33
|
+
console.log('Using GITHUB_TOKEN from environment');
|
|
34
|
+
return process.env.GITHUB_TOKEN;
|
|
35
|
+
}
|
|
36
|
+
const nonce = generateNonce();
|
|
37
|
+
// Start local callback server
|
|
38
|
+
const { port, waitForCallback, close } = await startLocalCallbackServer(nonce);
|
|
39
|
+
// Build OAuth URL
|
|
40
|
+
const state = `${port}:${nonce}`;
|
|
41
|
+
const authUrl = new URL('https://github.com/login/oauth/authorize');
|
|
42
|
+
authUrl.searchParams.set('client_id', GITHUB_CLIENT_ID);
|
|
43
|
+
authUrl.searchParams.set('redirect_uri', `${AUTH_BACKEND_URL}/callback`);
|
|
44
|
+
authUrl.searchParams.set('scope', OAUTH_SCOPES);
|
|
45
|
+
authUrl.searchParams.set('state', state);
|
|
46
|
+
console.log('Opening browser for GitHub authentication...');
|
|
47
|
+
console.log(`If browser doesn't open, visit: ${authUrl.toString()}`);
|
|
48
|
+
// Open browser
|
|
49
|
+
await open(authUrl.toString());
|
|
50
|
+
try {
|
|
51
|
+
// Wait for callback
|
|
52
|
+
console.log('Waiting for authentication...');
|
|
53
|
+
const { token, scopes } = await waitForCallback();
|
|
54
|
+
// Store in keychain
|
|
55
|
+
await storeToken(token, scopes);
|
|
56
|
+
console.log('Token stored securely in keychain.');
|
|
57
|
+
return token;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error('Authentication failed:', error instanceof Error ? error.message : error);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Logout - remove stored token
|
|
69
|
+
*/
|
|
70
|
+
export async function logout() {
|
|
71
|
+
await deleteToken();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get authentication status
|
|
75
|
+
*/
|
|
76
|
+
export async function getAuthStatus() {
|
|
77
|
+
const token = await getToken();
|
|
78
|
+
if (!token) {
|
|
79
|
+
return { authenticated: false };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const octokit = new Octokit({ auth: token });
|
|
83
|
+
const { data: user } = await octokit.users.getAuthenticated();
|
|
84
|
+
const scopes = await retrieveScopes();
|
|
85
|
+
return {
|
|
86
|
+
authenticated: true,
|
|
87
|
+
username: user.login,
|
|
88
|
+
scopes: scopes?.split(',') || undefined,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Token might be invalid
|
|
93
|
+
return { authenticated: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store token securely in OS keychain
|
|
3
|
+
*/
|
|
4
|
+
export declare function storeToken(token: string, scopes?: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Retrieve token from OS keychain
|
|
7
|
+
*/
|
|
8
|
+
export declare function retrieveToken(): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Retrieve stored scopes
|
|
11
|
+
*/
|
|
12
|
+
export declare function retrieveScopes(): Promise<string | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Delete token from OS keychain
|
|
15
|
+
*/
|
|
16
|
+
export declare function deleteToken(): Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../src/auth/token-storage.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3D;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEvD;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAEpD;AAED;;GAEG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,CAGjD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import keytar from 'keytar';
|
|
2
|
+
const SERVICE_NAME = 'github-mcp';
|
|
3
|
+
const ACCOUNT_NAME = 'oauth-token';
|
|
4
|
+
const SCOPE_ACCOUNT = 'oauth-scopes';
|
|
5
|
+
/**
|
|
6
|
+
* Store token securely in OS keychain
|
|
7
|
+
*/
|
|
8
|
+
export async function storeToken(token, scopes) {
|
|
9
|
+
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, token);
|
|
10
|
+
if (scopes) {
|
|
11
|
+
await keytar.setPassword(SERVICE_NAME, SCOPE_ACCOUNT, scopes);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Retrieve token from OS keychain
|
|
16
|
+
*/
|
|
17
|
+
export async function retrieveToken() {
|
|
18
|
+
return keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve stored scopes
|
|
22
|
+
*/
|
|
23
|
+
export async function retrieveScopes() {
|
|
24
|
+
return keytar.getPassword(SERVICE_NAME, SCOPE_ACCOUNT);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Delete token from OS keychain
|
|
28
|
+
*/
|
|
29
|
+
export async function deleteToken() {
|
|
30
|
+
const tokenDeleted = await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
31
|
+
await keytar.deletePassword(SERVICE_NAME, SCOPE_ACCOUNT);
|
|
32
|
+
return tokenDeleted;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.js","sourceRoot":"","sources":["../../src/auth/token-storage.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,YAAY,GAAG,YAAY,CAAC;AAClC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAE1C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAa;IAC1C,MAAM,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,OAAO,MAAM,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,OAAO,MAAM,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC/B,OAAO,KAAK,KAAK,IAAI,CAAC;AACxB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|