workos-github-mcp-server 1.0.0__py3-none-any.whl
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.
- github_mcp_server/__init__.py +26 -0
- github_mcp_server/__main__.py +4 -0
- github_mcp_server/client.py +576 -0
- github_mcp_server/config.py +54 -0
- github_mcp_server/server.py +60 -0
- github_mcp_server/tools/__init__.py +1 -0
- github_mcp_server/tools/commits.py +35 -0
- github_mcp_server/tools/gists.py +45 -0
- github_mcp_server/tools/issues.py +139 -0
- github_mcp_server/tools/pulls.py +84 -0
- github_mcp_server/tools/repos.py +71 -0
- github_mcp_server/tools/users.py +26 -0
- workos_github_mcp_server-1.0.0.dist-info/METADATA +194 -0
- workos_github_mcp_server-1.0.0.dist-info/RECORD +18 -0
- workos_github_mcp_server-1.0.0.dist-info/WHEEL +5 -0
- workos_github_mcp_server-1.0.0.dist-info/entry_points.txt +2 -0
- workos_github_mcp_server-1.0.0.dist-info/licenses/LICENSE +21 -0
- workos_github_mcp_server-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub MCP Server — FastMCP instance with all tools registered.
|
|
3
|
+
|
|
4
|
+
This is the core server module. Tools are organized by domain
|
|
5
|
+
in the tools/ subpackage and registered here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
from github_mcp_server.config import GitHubConfig
|
|
14
|
+
from github_mcp_server.tools.repos import register_repo_tools
|
|
15
|
+
from github_mcp_server.tools.issues import register_issue_tools
|
|
16
|
+
from github_mcp_server.tools.pulls import register_pull_tools
|
|
17
|
+
from github_mcp_server.tools.commits import register_commit_tools
|
|
18
|
+
from github_mcp_server.tools.gists import register_gist_tools
|
|
19
|
+
from github_mcp_server.tools.users import register_user_tools
|
|
20
|
+
|
|
21
|
+
# Ensure logging goes to stderr (required for stdio MCP transport)
|
|
22
|
+
logging.basicConfig(
|
|
23
|
+
level=logging.INFO,
|
|
24
|
+
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
|
|
25
|
+
stream=sys.stderr,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("github_mcp_server")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_server() -> FastMCP:
|
|
32
|
+
"""Create and configure the GitHub MCP server.
|
|
33
|
+
|
|
34
|
+
Validates environment configuration on startup and registers
|
|
35
|
+
all GitHub tools with the FastMCP instance.
|
|
36
|
+
"""
|
|
37
|
+
# Validate config on startup
|
|
38
|
+
config = GitHubConfig()
|
|
39
|
+
config.validate_or_exit()
|
|
40
|
+
|
|
41
|
+
app = FastMCP(
|
|
42
|
+
name="github-mcp-server",
|
|
43
|
+
instructions=(
|
|
44
|
+
"MCP server for GitHub operations. "
|
|
45
|
+
"Manage repositories, issues, pull requests, commits, "
|
|
46
|
+
"gists, search code, and look up user profiles."
|
|
47
|
+
),
|
|
48
|
+
version="1.0.0",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Register all tool groups
|
|
52
|
+
register_repo_tools(app, config)
|
|
53
|
+
register_issue_tools(app, config)
|
|
54
|
+
register_pull_tools(app, config)
|
|
55
|
+
register_commit_tools(app, config)
|
|
56
|
+
register_gist_tools(app, config)
|
|
57
|
+
register_user_tools(app, config)
|
|
58
|
+
|
|
59
|
+
logger.info("GitHub MCP server configured with all tool groups")
|
|
60
|
+
return app
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""GitHub MCP Server tools — organized by domain."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""GitHub commit tools — list commits."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
6
|
+
from github_mcp_server.config import GitHubConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_commit_tools(app, config: GitHubConfig):
|
|
10
|
+
"""Register commit tools with the FastMCP app."""
|
|
11
|
+
|
|
12
|
+
@app.tool()
|
|
13
|
+
async def github_list_commits(
|
|
14
|
+
owner: str,
|
|
15
|
+
repo: str,
|
|
16
|
+
sha: Optional[str] = None,
|
|
17
|
+
per_page: int = 30,
|
|
18
|
+
) -> dict:
|
|
19
|
+
"""List recent commits in a GitHub repository.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
owner: Repository owner (e.g., 'octocat')
|
|
23
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
24
|
+
sha: Branch name or commit SHA to list from (default: repo's default branch)
|
|
25
|
+
per_page: Number of results (1-100, default: 30)
|
|
26
|
+
"""
|
|
27
|
+
per_page = min(max(per_page, 1), 100)
|
|
28
|
+
try:
|
|
29
|
+
async with GitHubClient(token=config.token) as client:
|
|
30
|
+
commits = await client.list_commits(
|
|
31
|
+
owner=owner, repo=repo, sha=sha, per_page=per_page,
|
|
32
|
+
)
|
|
33
|
+
return {"success": True, "count": len(commits), "commits": commits}
|
|
34
|
+
except GitHubAPIError as e:
|
|
35
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""GitHub gist tools — list and create gists."""
|
|
2
|
+
|
|
3
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
4
|
+
from github_mcp_server.config import GitHubConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def register_gist_tools(app, config: GitHubConfig):
|
|
8
|
+
"""Register gist tools with the FastMCP app."""
|
|
9
|
+
|
|
10
|
+
@app.tool()
|
|
11
|
+
async def github_list_gists(per_page: int = 10) -> dict:
|
|
12
|
+
"""List gists for the authenticated GitHub user.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
per_page: Number of results (1-100, default: 10)
|
|
16
|
+
"""
|
|
17
|
+
per_page = min(max(per_page, 1), 100)
|
|
18
|
+
try:
|
|
19
|
+
async with GitHubClient(token=config.token) as client:
|
|
20
|
+
gists = await client.list_gists(per_page=per_page)
|
|
21
|
+
return {"success": True, "count": len(gists), "gists": gists}
|
|
22
|
+
except GitHubAPIError as e:
|
|
23
|
+
return {"success": False, "error": e.message}
|
|
24
|
+
|
|
25
|
+
@app.tool()
|
|
26
|
+
async def github_create_gist(
|
|
27
|
+
files: dict[str, str],
|
|
28
|
+
description: str = "",
|
|
29
|
+
public: bool = False,
|
|
30
|
+
) -> dict:
|
|
31
|
+
"""Create a new GitHub gist.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
files: Map of filename to content (e.g., {'hello.py': 'print("hi")'})
|
|
35
|
+
description: Gist description
|
|
36
|
+
public: Whether the gist is public (default: false/secret)
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
async with GitHubClient(token=config.token) as client:
|
|
40
|
+
result = await client.create_gist(
|
|
41
|
+
files=files, description=description, public=public,
|
|
42
|
+
)
|
|
43
|
+
return {"success": True, **result}
|
|
44
|
+
except GitHubAPIError as e:
|
|
45
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""GitHub issue tools — list, create, get, update, search issues."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
6
|
+
from github_mcp_server.config import GitHubConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_issue_tools(app, config: GitHubConfig):
|
|
10
|
+
"""Register issue tools with the FastMCP app."""
|
|
11
|
+
|
|
12
|
+
@app.tool()
|
|
13
|
+
async def github_list_issues(
|
|
14
|
+
owner: str,
|
|
15
|
+
repo: str,
|
|
16
|
+
state: str = "open",
|
|
17
|
+
per_page: int = 30,
|
|
18
|
+
) -> dict:
|
|
19
|
+
"""List issues in a GitHub repository.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
owner: Repository owner (e.g., 'octocat')
|
|
23
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
24
|
+
state: Filter by state — 'open', 'closed', 'all' (default: 'open')
|
|
25
|
+
per_page: Number of results (1-100, default: 30)
|
|
26
|
+
"""
|
|
27
|
+
per_page = min(max(per_page, 1), 100)
|
|
28
|
+
try:
|
|
29
|
+
async with GitHubClient(token=config.token) as client:
|
|
30
|
+
issues = await client.list_issues(
|
|
31
|
+
owner=owner, repo=repo, state=state, per_page=per_page,
|
|
32
|
+
)
|
|
33
|
+
return {"success": True, "count": len(issues), "issues": issues}
|
|
34
|
+
except GitHubAPIError as e:
|
|
35
|
+
return {"success": False, "error": e.message}
|
|
36
|
+
|
|
37
|
+
@app.tool()
|
|
38
|
+
async def github_create_issue(
|
|
39
|
+
owner: str,
|
|
40
|
+
repo: str,
|
|
41
|
+
title: str,
|
|
42
|
+
body: str = "",
|
|
43
|
+
labels: Optional[list[str]] = None,
|
|
44
|
+
assignees: Optional[list[str]] = None,
|
|
45
|
+
) -> dict:
|
|
46
|
+
"""Create a new issue in a GitHub repository.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
owner: Repository owner (e.g., 'octocat')
|
|
50
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
51
|
+
title: Issue title
|
|
52
|
+
body: Issue body (supports GitHub Markdown)
|
|
53
|
+
labels: List of label names to apply (e.g., ['bug', 'urgent'])
|
|
54
|
+
assignees: List of GitHub usernames to assign
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
async with GitHubClient(token=config.token) as client:
|
|
58
|
+
result = await client.create_issue(
|
|
59
|
+
owner=owner, repo=repo, title=title,
|
|
60
|
+
body=body, labels=labels, assignees=assignees,
|
|
61
|
+
)
|
|
62
|
+
return {"success": True, **result}
|
|
63
|
+
except GitHubAPIError as e:
|
|
64
|
+
return {"success": False, "error": e.message}
|
|
65
|
+
|
|
66
|
+
@app.tool()
|
|
67
|
+
async def github_get_issue(
|
|
68
|
+
owner: str, repo: str, issue_number: int
|
|
69
|
+
) -> dict:
|
|
70
|
+
"""Get details of a specific issue.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
owner: Repository owner (e.g., 'octocat')
|
|
74
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
75
|
+
issue_number: Issue number (e.g., 42)
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
async with GitHubClient(token=config.token) as client:
|
|
79
|
+
result = await client.get_issue(
|
|
80
|
+
owner=owner, repo=repo, issue_number=issue_number,
|
|
81
|
+
)
|
|
82
|
+
return {"success": True, "issue": result}
|
|
83
|
+
except GitHubAPIError as e:
|
|
84
|
+
return {"success": False, "error": e.message}
|
|
85
|
+
|
|
86
|
+
@app.tool()
|
|
87
|
+
async def github_update_issue(
|
|
88
|
+
owner: str,
|
|
89
|
+
repo: str,
|
|
90
|
+
issue_number: int,
|
|
91
|
+
title: Optional[str] = None,
|
|
92
|
+
body: Optional[str] = None,
|
|
93
|
+
state: Optional[str] = None,
|
|
94
|
+
labels: Optional[list[str]] = None,
|
|
95
|
+
assignees: Optional[list[str]] = None,
|
|
96
|
+
) -> dict:
|
|
97
|
+
"""Update an existing issue in a GitHub repository.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
owner: Repository owner (e.g., 'octocat')
|
|
101
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
102
|
+
issue_number: Issue number to update
|
|
103
|
+
title: New title (omit to keep current)
|
|
104
|
+
body: New body (omit to keep current)
|
|
105
|
+
state: New state — 'open' or 'closed' (omit to keep current)
|
|
106
|
+
labels: Replace labels (omit to keep current)
|
|
107
|
+
assignees: Replace assignees (omit to keep current)
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
async with GitHubClient(token=config.token) as client:
|
|
111
|
+
result = await client.update_issue(
|
|
112
|
+
owner=owner, repo=repo, issue_number=issue_number,
|
|
113
|
+
title=title, body=body, state=state,
|
|
114
|
+
labels=labels, assignees=assignees,
|
|
115
|
+
)
|
|
116
|
+
return {"success": True, **result}
|
|
117
|
+
except GitHubAPIError as e:
|
|
118
|
+
return {"success": False, "error": e.message}
|
|
119
|
+
|
|
120
|
+
@app.tool()
|
|
121
|
+
async def github_search_issues(
|
|
122
|
+
query: str, sort: str = "created", per_page: int = 10
|
|
123
|
+
) -> dict:
|
|
124
|
+
"""Search issues and pull requests across GitHub.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
query: Search query (e.g., 'bug repo:octocat/Hello-World state:open')
|
|
128
|
+
sort: Sort by — 'created', 'updated', 'comments' (default: 'created')
|
|
129
|
+
per_page: Number of results (1-100, default: 10)
|
|
130
|
+
"""
|
|
131
|
+
per_page = min(max(per_page, 1), 100)
|
|
132
|
+
try:
|
|
133
|
+
async with GitHubClient(token=config.token) as client:
|
|
134
|
+
issues = await client.search_issues(
|
|
135
|
+
query=query, sort=sort, per_page=per_page,
|
|
136
|
+
)
|
|
137
|
+
return {"success": True, "count": len(issues), "results": issues}
|
|
138
|
+
except GitHubAPIError as e:
|
|
139
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""GitHub pull request tools — list, get, create PRs."""
|
|
2
|
+
|
|
3
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
4
|
+
from github_mcp_server.config import GitHubConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def register_pull_tools(app, config: GitHubConfig):
|
|
8
|
+
"""Register pull request tools with the FastMCP app."""
|
|
9
|
+
|
|
10
|
+
@app.tool()
|
|
11
|
+
async def github_list_pull_requests(
|
|
12
|
+
owner: str,
|
|
13
|
+
repo: str,
|
|
14
|
+
state: str = "open",
|
|
15
|
+
per_page: int = 30,
|
|
16
|
+
) -> dict:
|
|
17
|
+
"""List pull requests in a GitHub repository.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
owner: Repository owner (e.g., 'octocat')
|
|
21
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
22
|
+
state: Filter by state — 'open', 'closed', 'all' (default: 'open')
|
|
23
|
+
per_page: Number of results (1-100, default: 30)
|
|
24
|
+
"""
|
|
25
|
+
per_page = min(max(per_page, 1), 100)
|
|
26
|
+
try:
|
|
27
|
+
async with GitHubClient(token=config.token) as client:
|
|
28
|
+
prs = await client.list_prs(
|
|
29
|
+
owner=owner, repo=repo, state=state, per_page=per_page,
|
|
30
|
+
)
|
|
31
|
+
return {"success": True, "count": len(prs), "pull_requests": prs}
|
|
32
|
+
except GitHubAPIError as e:
|
|
33
|
+
return {"success": False, "error": e.message}
|
|
34
|
+
|
|
35
|
+
@app.tool()
|
|
36
|
+
async def github_get_pull_request(
|
|
37
|
+
owner: str, repo: str, pr_number: int
|
|
38
|
+
) -> dict:
|
|
39
|
+
"""Get details of a specific pull request.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
owner: Repository owner (e.g., 'octocat')
|
|
43
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
44
|
+
pr_number: Pull request number (e.g., 42)
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
async with GitHubClient(token=config.token) as client:
|
|
48
|
+
result = await client.get_pr(
|
|
49
|
+
owner=owner, repo=repo, pr_number=pr_number,
|
|
50
|
+
)
|
|
51
|
+
return {"success": True, "pull_request": result}
|
|
52
|
+
except GitHubAPIError as e:
|
|
53
|
+
return {"success": False, "error": e.message}
|
|
54
|
+
|
|
55
|
+
@app.tool()
|
|
56
|
+
async def github_create_pull_request(
|
|
57
|
+
owner: str,
|
|
58
|
+
repo: str,
|
|
59
|
+
title: str,
|
|
60
|
+
head: str,
|
|
61
|
+
base: str,
|
|
62
|
+
body: str = "",
|
|
63
|
+
draft: bool = False,
|
|
64
|
+
) -> dict:
|
|
65
|
+
"""Create a new pull request in a GitHub repository.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
owner: Repository owner (e.g., 'octocat')
|
|
69
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
70
|
+
title: PR title
|
|
71
|
+
head: Branch containing changes (e.g., 'feature-branch')
|
|
72
|
+
base: Branch to merge into (e.g., 'main')
|
|
73
|
+
body: PR description (supports GitHub Markdown)
|
|
74
|
+
draft: Create as draft PR (default: false)
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
async with GitHubClient(token=config.token) as client:
|
|
78
|
+
result = await client.create_pr(
|
|
79
|
+
owner=owner, repo=repo, title=title,
|
|
80
|
+
head=head, base=base, body=body, draft=draft,
|
|
81
|
+
)
|
|
82
|
+
return {"success": True, **result}
|
|
83
|
+
except GitHubAPIError as e:
|
|
84
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""GitHub repository tools — list, get details, search repos."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
6
|
+
from github_mcp_server.config import GitHubConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_repo_tools(app, config: GitHubConfig):
|
|
10
|
+
"""Register repository tools with the FastMCP app."""
|
|
11
|
+
|
|
12
|
+
@app.tool()
|
|
13
|
+
async def github_list_repos(
|
|
14
|
+
owner: Optional[str] = None,
|
|
15
|
+
repo_type: str = "owner",
|
|
16
|
+
sort: str = "updated",
|
|
17
|
+
per_page: int = 30,
|
|
18
|
+
) -> dict:
|
|
19
|
+
"""List GitHub repositories for the authenticated user or a specific owner.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
owner: GitHub username or org. If omitted, lists your own repos.
|
|
23
|
+
repo_type: Filter type — 'all', 'owner', 'member' (default: 'owner')
|
|
24
|
+
sort: Sort by — 'created', 'updated', 'pushed', 'full_name' (default: 'updated')
|
|
25
|
+
per_page: Number of results (1-100, default: 30)
|
|
26
|
+
"""
|
|
27
|
+
per_page = min(max(per_page, 1), 100)
|
|
28
|
+
try:
|
|
29
|
+
async with GitHubClient(token=config.token) as client:
|
|
30
|
+
repos = await client.list_repos(
|
|
31
|
+
owner=owner, repo_type=repo_type, sort=sort, per_page=per_page,
|
|
32
|
+
)
|
|
33
|
+
return {"success": True, "count": len(repos), "repos": repos}
|
|
34
|
+
except GitHubAPIError as e:
|
|
35
|
+
return {"success": False, "error": e.message}
|
|
36
|
+
|
|
37
|
+
@app.tool()
|
|
38
|
+
async def github_get_repo(owner: str, repo: str) -> dict:
|
|
39
|
+
"""Get detailed information about a GitHub repository.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
owner: Repository owner (user or org, e.g., 'octocat')
|
|
43
|
+
repo: Repository name (e.g., 'Hello-World')
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
async with GitHubClient(token=config.token) as client:
|
|
47
|
+
result = await client.get_repo(owner=owner, repo=repo)
|
|
48
|
+
return {"success": True, "repo": result}
|
|
49
|
+
except GitHubAPIError as e:
|
|
50
|
+
return {"success": False, "error": e.message}
|
|
51
|
+
|
|
52
|
+
@app.tool()
|
|
53
|
+
async def github_search_repos(
|
|
54
|
+
query: str, sort: str = "stars", per_page: int = 10
|
|
55
|
+
) -> dict:
|
|
56
|
+
"""Search for repositories on GitHub.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
query: Search query (e.g., 'machine learning language:python stars:>1000')
|
|
60
|
+
sort: Sort by — 'stars', 'forks', 'updated', 'help-wanted-issues' (default: 'stars')
|
|
61
|
+
per_page: Number of results (1-100, default: 10)
|
|
62
|
+
"""
|
|
63
|
+
per_page = min(max(per_page, 1), 100)
|
|
64
|
+
try:
|
|
65
|
+
async with GitHubClient(token=config.token) as client:
|
|
66
|
+
repos = await client.search_repos(
|
|
67
|
+
query=query, sort=sort, per_page=per_page,
|
|
68
|
+
)
|
|
69
|
+
return {"success": True, "count": len(repos), "repos": repos}
|
|
70
|
+
except GitHubAPIError as e:
|
|
71
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""GitHub user tools — look up user profiles."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from github_mcp_server.client import GitHubClient, GitHubAPIError
|
|
6
|
+
from github_mcp_server.config import GitHubConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_user_tools(app, config: GitHubConfig):
|
|
10
|
+
"""Register user tools with the FastMCP app."""
|
|
11
|
+
|
|
12
|
+
@app.tool()
|
|
13
|
+
async def github_get_user(username: Optional[str] = None) -> dict:
|
|
14
|
+
"""Get a GitHub user's profile information.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
username: GitHub username (e.g., 'octocat'). If omitted, returns your own profile.
|
|
18
|
+
|
|
19
|
+
Returns profile with name, bio, company, location, public repos, and follower counts.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
async with GitHubClient(token=config.token) as client:
|
|
23
|
+
profile = await client.get_user(username=username)
|
|
24
|
+
return {"success": True, "profile": profile}
|
|
25
|
+
except GitHubAPIError as e:
|
|
26
|
+
return {"success": False, "error": e.message}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: workos-github-mcp-server
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for GitHub operations — manage repos, issues, pull requests, commits, gists, and more.
|
|
5
|
+
Author: WorkOS Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/workos/workos-github-mcp-server
|
|
8
|
+
Project-URL: Repository, https://github.com/workos/workos-github-mcp-server
|
|
9
|
+
Project-URL: Issues, https://github.com/workos/workos-github-mcp-server/issues
|
|
10
|
+
Keywords: mcp,github,model-context-protocol,ai-tools,devtools
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# GitHub MCP Server
|
|
28
|
+
|
|
29
|
+
<!-- mcp-name: io.github.workos/github-mcp-server -->
|
|
30
|
+
|
|
31
|
+
A production-ready [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for GitHub operations. Connect any AI agent to GitHub — manage repos, issues, pull requests, commits, gists, search code, and look up user profiles.
|
|
32
|
+
|
|
33
|
+
**This is an independent, generalized MCP server.** It is not tied to any specific project and can be connected to any AI agent that supports MCP (Claude Desktop, Cursor, WorkOS, or custom agents).
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- 📦 **Repositories**: List, search, and get repo details
|
|
38
|
+
- 🐛 **Issues**: Create, read, update, search issues
|
|
39
|
+
- 🔀 **Pull Requests**: List, create, and inspect PRs
|
|
40
|
+
- 📝 **Commits**: Browse commit history
|
|
41
|
+
- 🔍 **Search**: Search code and issues across GitHub
|
|
42
|
+
- 👤 **Users**: Look up user profiles
|
|
43
|
+
- 📋 **Gists**: List and create gists
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install workos-github-mcp-server
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or install from source:
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/workos/workos-github-mcp-server
|
|
54
|
+
cd workos-github-mcp-server
|
|
55
|
+
pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export GITHUB_TOKEN="ghp_your-personal-access-token"
|
|
62
|
+
|
|
63
|
+
# Run the server
|
|
64
|
+
github-mcp-server
|
|
65
|
+
|
|
66
|
+
# Or run as a Python module
|
|
67
|
+
python -m github_mcp_server
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
### Environment Variables
|
|
73
|
+
|
|
74
|
+
| Variable | Required | Description |
|
|
75
|
+
|----------|----------|-------------|
|
|
76
|
+
| `GITHUB_TOKEN` | ✅ Yes | GitHub Personal Access Token (`ghp_...` or `github_pat_...`) |
|
|
77
|
+
|
|
78
|
+
### Getting a GitHub Token
|
|
79
|
+
|
|
80
|
+
1. Go to [github.com/settings/tokens](https://github.com/settings/tokens)
|
|
81
|
+
2. Click **"Generate new token (classic)"** or **"Fine-grained tokens"**
|
|
82
|
+
3. Select scopes:
|
|
83
|
+
- `repo` — full repository access (issues, PRs, commits)
|
|
84
|
+
- `gist` — create and list gists
|
|
85
|
+
- `read:user` — read user profiles
|
|
86
|
+
4. Copy the generated token
|
|
87
|
+
|
|
88
|
+
## Connecting to AI Agents
|
|
89
|
+
|
|
90
|
+
### Claude Desktop
|
|
91
|
+
|
|
92
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"github": {
|
|
98
|
+
"command": "github-mcp-server",
|
|
99
|
+
"env": {
|
|
100
|
+
"GITHUB_TOKEN": "ghp_your-token"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Cursor
|
|
108
|
+
|
|
109
|
+
Add to `.cursor/mcp.json`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"github": {
|
|
115
|
+
"command": "github-mcp-server",
|
|
116
|
+
"env": {
|
|
117
|
+
"GITHUB_TOKEN": "ghp_your-token"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### WorkOS / Custom Agents
|
|
125
|
+
|
|
126
|
+
Add to `.mcp.json`:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"mcpServers": {
|
|
131
|
+
"github": {
|
|
132
|
+
"transport": "stdio",
|
|
133
|
+
"command": "python",
|
|
134
|
+
"args": ["-m", "github_mcp_server"],
|
|
135
|
+
"env": {
|
|
136
|
+
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Available Tools (15)
|
|
144
|
+
|
|
145
|
+
| Tool | Description |
|
|
146
|
+
|------|-------------|
|
|
147
|
+
| `github_list_repos` | List repositories for a user or the authenticated user |
|
|
148
|
+
| `github_get_repo` | Get detailed repository information |
|
|
149
|
+
| `github_search_repos` | Search repositories across GitHub |
|
|
150
|
+
| `github_list_issues` | List issues in a repository |
|
|
151
|
+
| `github_create_issue` | Create a new issue |
|
|
152
|
+
| `github_get_issue` | Get issue details |
|
|
153
|
+
| `github_update_issue` | Update an existing issue |
|
|
154
|
+
| `github_search_issues` | Search issues and PRs across GitHub |
|
|
155
|
+
| `github_list_pull_requests` | List pull requests in a repository |
|
|
156
|
+
| `github_get_pull_request` | Get pull request details |
|
|
157
|
+
| `github_create_pull_request` | Create a new pull request |
|
|
158
|
+
| `github_list_commits` | List recent commits |
|
|
159
|
+
| `github_list_gists` | List gists for the authenticated user |
|
|
160
|
+
| `github_create_gist` | Create a new gist |
|
|
161
|
+
| `github_get_user` | Get user profile information |
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git clone https://github.com/workos/workos-github-mcp-server
|
|
167
|
+
cd workos-github-mcp-server
|
|
168
|
+
pip install -e .
|
|
169
|
+
|
|
170
|
+
# Run tests
|
|
171
|
+
pytest
|
|
172
|
+
|
|
173
|
+
# Run the server locally
|
|
174
|
+
GITHUB_TOKEN=ghp_test python -m github_mcp_server
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Publishing
|
|
178
|
+
|
|
179
|
+
### To PyPI
|
|
180
|
+
```bash
|
|
181
|
+
pip install build twine
|
|
182
|
+
python -m build
|
|
183
|
+
twine upload dist/*
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### To MCP Registry
|
|
187
|
+
```bash
|
|
188
|
+
mcp-publisher login github
|
|
189
|
+
mcp-publisher publish
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
github_mcp_server/__init__.py,sha256=7azQISf4XiPdAh-LZgdTpKbuTIcVJliigNK5TP1QAsg,629
|
|
2
|
+
github_mcp_server/__main__.py,sha256=vhBI6mgWUT7yYA_9Hx5J_DbRoIlWzCnqZ0MHYifRmQw,95
|
|
3
|
+
github_mcp_server/client.py,sha256=GNAhIVwKXnIFdpkTI3rnsG2hQ3AVSpqlkIt-ZIpt0r0,19196
|
|
4
|
+
github_mcp_server/config.py,sha256=36E3JLTVSa6cM8e5CwBqJtbLgiDOOBbEZN6pW79l3Jo,1577
|
|
5
|
+
github_mcp_server/server.py,sha256=WZ8V3ChZ-a-OH36DHtkN951TPNs33F2FP0z1mJ1aHRs,1856
|
|
6
|
+
github_mcp_server/tools/__init__.py,sha256=BzGGurgx8MN_TvITPuX7pEXljUaKtPXc2OZgLt0Kv3U,55
|
|
7
|
+
github_mcp_server/tools/commits.py,sha256=B2CsvhHfynlhZO_JvPpRNjFx6-N8O-AcIsAP6dbmFuk,1258
|
|
8
|
+
github_mcp_server/tools/gists.py,sha256=pGkM49D6gl3d254YrZlbY59d2HJlUEOwHJHXBz9gIaI,1646
|
|
9
|
+
github_mcp_server/tools/issues.py,sha256=9cqtUyDTUqU_NI6bVcSodaMHet4sE-BP_8LJhOssPac,5280
|
|
10
|
+
github_mcp_server/tools/pulls.py,sha256=HvEc5Ruam3gClfg4J7MajhDWvTE1iWlPQ4C-1xZblY0,3063
|
|
11
|
+
github_mcp_server/tools/repos.py,sha256=rH_qHCrEOxRsCRnTtQ2pawCmQcjp_XVnoWEKBbuSfzk,2867
|
|
12
|
+
github_mcp_server/tools/users.py,sha256=w6hJr-nS1TpwzO0_8BknTpqC4EbBzHsQILMJAts0u14,962
|
|
13
|
+
workos_github_mcp_server-1.0.0.dist-info/licenses/LICENSE,sha256=I5kS3eseQizRxuciMvxShkVRqCdLPAO74LpWKgbg9BU,1076
|
|
14
|
+
workos_github_mcp_server-1.0.0.dist-info/METADATA,sha256=qniU3n7zsqY3tMJ52IEUwm8kP_eOSKGoywchOSFPmxQ,5158
|
|
15
|
+
workos_github_mcp_server-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
16
|
+
workos_github_mcp_server-1.0.0.dist-info/entry_points.txt,sha256=zhyEtZo903eDyxuCBZc0LrPzyNkkh-LNL6dnTXazJT8,61
|
|
17
|
+
workos_github_mcp_server-1.0.0.dist-info/top_level.txt,sha256=dnBSR2GdgTdsysGOiZ8_GwY3xZBTZl1dUkUC1_WsJrQ,18
|
|
18
|
+
workos_github_mcp_server-1.0.0.dist-info/RECORD,,
|