@gitstar-ai/mcp 0.2.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 +85 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.js +87 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +10 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @gitstar-ai/mcp
|
|
2
|
+
|
|
3
|
+
GitHub actions inside any AI tool, powered by [Gitstar](https://tinkling.vercel.app).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
Add to `claude_desktop_config.json`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"gitstar": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "@gitstar-ai/mcp"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Claude Code (CLI)
|
|
23
|
+
|
|
24
|
+
Add to `.claude/settings.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"gitstar": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@gitstar-ai/mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Cursor
|
|
38
|
+
|
|
39
|
+
Add to `.cursor/mcp.json`:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"gitstar": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "@gitstar-ai/mcp"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Login
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx @gitstar-ai/mcp login
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This opens your browser to authenticate with GitHub via Gitstar. One-time setup.
|
|
59
|
+
|
|
60
|
+
## Tools
|
|
61
|
+
|
|
62
|
+
| Tool | Description |
|
|
63
|
+
|------|-------------|
|
|
64
|
+
| `gitstar_get_feed` | Narrated activity feed for a repo |
|
|
65
|
+
| `gitstar_get_repo` | Repo metadata and stats |
|
|
66
|
+
| `gitstar_get_pr` | PR details, diff, and files |
|
|
67
|
+
| `gitstar_get_pr_checks` | CI/CD check statuses |
|
|
68
|
+
| `gitstar_get_issue` | Issue details and timeline |
|
|
69
|
+
| `gitstar_comment_pr` | Comment on a PR |
|
|
70
|
+
| `gitstar_comment_issue` | Comment on an issue |
|
|
71
|
+
| `gitstar_review_pr` | Approve or request changes on a PR |
|
|
72
|
+
| `gitstar_merge_pr` | Merge a PR |
|
|
73
|
+
| `gitstar_close_pr` | Close a PR |
|
|
74
|
+
| `gitstar_close_issue` | Close an issue |
|
|
75
|
+
| `gitstar_search` | Search repos and users |
|
|
76
|
+
| `gitstar_trending` | Trending repos |
|
|
77
|
+
|
|
78
|
+
## Example Prompts
|
|
79
|
+
|
|
80
|
+
- "Show me what's happening in facebook/react"
|
|
81
|
+
- "Review PR #42 in vercel/next.js for security issues"
|
|
82
|
+
- "What's the CI status on PR #15?"
|
|
83
|
+
- "Merge PR #10 using squash"
|
|
84
|
+
- "Close issue #33, it's a duplicate"
|
|
85
|
+
- "What repos are trending?"
|
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const GITSTAR_DIR = join(homedir(), '.gitstar');
|
|
6
|
+
const AUTH_FILE = join(GITSTAR_DIR, 'auth.json');
|
|
7
|
+
const CONFIG_FILE = join(GITSTAR_DIR, 'config.json');
|
|
8
|
+
const DEFAULT_API_URL = 'https://www.gitstar.ai';
|
|
9
|
+
function ensureDir() {
|
|
10
|
+
if (!existsSync(GITSTAR_DIR)) {
|
|
11
|
+
mkdirSync(GITSTAR_DIR, { recursive: true, mode: 0o700 });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function getApiUrl() {
|
|
15
|
+
try {
|
|
16
|
+
const config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
17
|
+
return config.api_url || DEFAULT_API_URL;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return DEFAULT_API_URL;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function getToken() {
|
|
24
|
+
try {
|
|
25
|
+
const data = JSON.parse(readFileSync(AUTH_FILE, 'utf-8'));
|
|
26
|
+
if (!data.key || !data.login)
|
|
27
|
+
return null;
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function saveToken(data) {
|
|
35
|
+
ensureDir();
|
|
36
|
+
writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
export async function login() {
|
|
39
|
+
const apiUrl = getApiUrl();
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const server = createServer((req, res) => {
|
|
42
|
+
const url = new URL(req.url, `http://localhost`);
|
|
43
|
+
if (url.pathname === '/callback') {
|
|
44
|
+
const key = url.searchParams.get('key');
|
|
45
|
+
const login = url.searchParams.get('login');
|
|
46
|
+
if (key && login) {
|
|
47
|
+
saveToken({ key, login });
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
49
|
+
res.end('<html><body><h1>Logged in to Gitstar!</h1><p>You can close this window.</p></body></html>');
|
|
50
|
+
server.close();
|
|
51
|
+
console.log(`Logged in as ${login}`);
|
|
52
|
+
resolve();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
56
|
+
res.end('<html><body><h1>Login failed</h1><p>Missing API key. Try again.</p></body></html>');
|
|
57
|
+
server.close();
|
|
58
|
+
reject(new Error('Login failed: no API key received'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
server.listen(9876, () => {
|
|
63
|
+
const addr = server.address();
|
|
64
|
+
const port = typeof addr === 'object' && addr ? addr.port : 9876;
|
|
65
|
+
const callbackUrl = encodeURIComponent(`http://localhost:${port}/callback`);
|
|
66
|
+
const authUrl = `${apiUrl}/auth/mcp?callback=${callbackUrl}`;
|
|
67
|
+
console.log(`Opening browser for login...`);
|
|
68
|
+
console.log(`If browser doesn't open, visit: ${authUrl}`);
|
|
69
|
+
import('open').then(({ default: open }) => open(authUrl));
|
|
70
|
+
});
|
|
71
|
+
server.on('error', () => {
|
|
72
|
+
server.listen(0, () => {
|
|
73
|
+
const addr = server.address();
|
|
74
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
75
|
+
const callbackUrl = encodeURIComponent(`http://localhost:${port}/callback`);
|
|
76
|
+
const authUrl = `${apiUrl}/auth/mcp?callback=${callbackUrl}`;
|
|
77
|
+
console.log(`Opening browser for login...`);
|
|
78
|
+
console.log(`If browser doesn't open, visit: ${authUrl}`);
|
|
79
|
+
import('open').then(({ default: open }) => open(authUrl));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
server.close();
|
|
84
|
+
reject(new Error('Login timed out after 5 minutes'));
|
|
85
|
+
}, 5 * 60 * 1000);
|
|
86
|
+
});
|
|
87
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { createRemoteTransport } from './proxy.js';
|
|
7
|
+
import { getToken, getApiUrl, login } from './auth.js';
|
|
8
|
+
function jsonSchemaToZod(prop, required) {
|
|
9
|
+
let schema;
|
|
10
|
+
const enumValues = prop.enum;
|
|
11
|
+
if (enumValues && enumValues.length > 0) {
|
|
12
|
+
schema = z.enum(enumValues);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
switch (prop.type) {
|
|
16
|
+
case 'number':
|
|
17
|
+
case 'integer':
|
|
18
|
+
schema = z.number();
|
|
19
|
+
break;
|
|
20
|
+
case 'boolean':
|
|
21
|
+
schema = z.boolean();
|
|
22
|
+
break;
|
|
23
|
+
case 'array':
|
|
24
|
+
schema = z.array(z.unknown());
|
|
25
|
+
break;
|
|
26
|
+
case 'object':
|
|
27
|
+
schema = z.record(z.unknown());
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
schema = z.string();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (prop.description) {
|
|
34
|
+
schema = schema.describe(prop.description);
|
|
35
|
+
}
|
|
36
|
+
if (!required) {
|
|
37
|
+
schema = schema.optional();
|
|
38
|
+
}
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
async function main() {
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
if (args[0] === 'login') {
|
|
44
|
+
try {
|
|
45
|
+
await login();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error('Login failed:', err.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const auth = getToken();
|
|
54
|
+
if (!auth) {
|
|
55
|
+
console.error('Not logged in. Run: npx @gitstar-ai/mcp login');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const apiUrl = getApiUrl();
|
|
59
|
+
// Create local MCP server that proxies to the remote HTTP server
|
|
60
|
+
const server = new McpServer({
|
|
61
|
+
name: 'gitstar',
|
|
62
|
+
version: '0.2.0',
|
|
63
|
+
});
|
|
64
|
+
// Connect to remote server as a client
|
|
65
|
+
const remoteTransport = createRemoteTransport(auth.key, apiUrl);
|
|
66
|
+
const client = new Client({ name: 'gitstar-proxy', version: '0.2.0' });
|
|
67
|
+
await client.connect(remoteTransport);
|
|
68
|
+
// Get available tools from remote and register them locally as proxies
|
|
69
|
+
const { tools } = await client.listTools();
|
|
70
|
+
for (const tool of tools) {
|
|
71
|
+
const properties = (tool.inputSchema?.properties ?? {});
|
|
72
|
+
const requiredFields = (tool.inputSchema?.required ?? []);
|
|
73
|
+
const shape = {};
|
|
74
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
75
|
+
shape[key] = jsonSchemaToZod(prop, requiredFields.includes(key));
|
|
76
|
+
}
|
|
77
|
+
server.tool(tool.name, tool.description || '', shape, async (params) => {
|
|
78
|
+
const result = await client.callTool({ name: tool.name, arguments: params });
|
|
79
|
+
return result;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Connect local server to stdio
|
|
83
|
+
const transport = new StdioServerTransport();
|
|
84
|
+
await server.connect(transport);
|
|
85
|
+
}
|
|
86
|
+
main().catch((err) => {
|
|
87
|
+
console.error('Fatal:', err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
package/dist/proxy.d.ts
ADDED
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
2
|
+
export function createRemoteTransport(apiKey, apiUrl) {
|
|
3
|
+
return new StreamableHTTPClientTransport(new URL(`${apiUrl}/mcp`), {
|
|
4
|
+
requestInit: {
|
|
5
|
+
headers: {
|
|
6
|
+
Authorization: `Bearer ${apiKey}`,
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gitstar-ai/mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Gitstar MCP server — interact with GitHub through Gitstar from Claude, Cursor, and other AI tools",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gitstar-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"login": "node dist/index.js login"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["mcp", "github", "gitstar", "claude", "cursor", "ai"],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
20
|
+
"open": "^10.1.0",
|
|
21
|
+
"zod": "^3.23.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.5.0",
|
|
25
|
+
"@types/node": "^20.0.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"files": ["dist"]
|
|
31
|
+
}
|