Merge branch 'main' into pr/224

This commit is contained in:
Justin Spahr-Summers
2024-12-05 23:27:30 +00:00
37 changed files with 2775 additions and 306 deletions

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
@@ -7,56 +6,71 @@ import {
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
import {
GitHubForkSchema,
GitHubReferenceSchema,
GitHubRepositorySchema,
GitHubIssueSchema,
GitHubPullRequestSchema,
GitHubContentSchema,
GitHubCreateUpdateFileResponseSchema,
GitHubSearchResponseSchema,
GitHubTreeSchema,
GitHubCommitSchema,
GitHubListCommitsSchema,
CreateRepositoryOptionsSchema,
CreateIssueOptionsSchema,
CreatePullRequestOptionsSchema,
CreateBranchOptionsSchema,
type GitHubFork,
type GitHubReference,
type GitHubRepository,
type GitHubIssue,
type GitHubPullRequest,
type GitHubContent,
type GitHubCreateUpdateFileResponse,
type GitHubSearchResponse,
type GitHubTree,
type GitHubCommit,
type FileOperation,
CreateOrUpdateFileSchema,
SearchRepositoriesSchema,
CreateRepositorySchema,
GetFileContentsSchema,
PushFilesSchema,
CreateIssueSchema,
CreatePullRequestSchema,
ForkRepositorySchema,
CreateBranchSchema,
ListCommitsSchema,
GitHubListCommits
} from './schemas.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
CreateBranchOptionsSchema,
CreateBranchSchema,
CreateIssueOptionsSchema,
CreateIssueSchema,
CreateOrUpdateFileSchema,
CreatePullRequestOptionsSchema,
CreatePullRequestSchema,
CreateRepositoryOptionsSchema,
CreateRepositorySchema,
ForkRepositorySchema,
GetFileContentsSchema,
GitHubCommitSchema,
GitHubContentSchema,
GitHubCreateUpdateFileResponseSchema,
GitHubForkSchema,
GitHubIssueSchema,
GitHubListCommits,
GitHubListCommitsSchema,
GitHubPullRequestSchema,
GitHubReferenceSchema,
GitHubRepositorySchema,
GitHubSearchResponseSchema,
GitHubTreeSchema,
IssueCommentSchema,
ListCommitsSchema,
ListIssuesOptionsSchema,
PushFilesSchema,
SearchCodeResponseSchema,
SearchCodeSchema,
SearchIssuesResponseSchema,
SearchIssuesSchema,
SearchRepositoriesSchema,
SearchUsersResponseSchema,
SearchUsersSchema,
UpdateIssueOptionsSchema,
type FileOperation,
type GitHubCommit,
type GitHubContent,
type GitHubCreateUpdateFileResponse,
type GitHubFork,
type GitHubIssue,
type GitHubPullRequest,
type GitHubReference,
type GitHubRepository,
type GitHubSearchResponse,
type GitHubTree,
type SearchCodeResponse,
type SearchIssuesResponse,
type SearchUsersResponse
} from './schemas.js';
const server = new Server({
name: "github-mcp-server",
version: "0.1.0",
}, {
capabilities: {
tools: {}
const server = new Server(
{
name: "github-mcp-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
});
);
const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
@@ -70,17 +84,17 @@ async function forkRepository(
repo: string,
organization?: string
): Promise<GitHubFork> {
const url = organization
const url = organization
? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}`
: `https://api.github.com/repos/${owner}/${repo}/forks`;
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
@@ -96,21 +110,21 @@ async function createBranch(
options: z.infer<typeof CreateBranchOptionsSchema>
): Promise<GitHubReference> {
const fullRef = `refs/heads/${options.ref}`;
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs`,
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
ref: fullRef,
sha: options.sha
})
sha: options.sha,
}),
}
);
@@ -129,10 +143,10 @@ async function getDefaultBranchSHA(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`,
{
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
}
);
@@ -141,15 +155,17 @@ async function getDefaultBranchSHA(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
{
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
}
);
if (!masterResponse.ok) {
throw new Error("Could not find default branch (tried 'main' and 'master')");
throw new Error(
"Could not find default branch (tried 'main' and 'master')"
);
}
const data = GitHubReferenceSchema.parse(await masterResponse.json());
@@ -173,10 +189,10 @@ async function getFileContents(
const response = await fetch(url, {
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
@@ -187,7 +203,7 @@ async function getFileContents(
// If it's a file, decode the content
if (!Array.isArray(data) && data.content) {
data.content = Buffer.from(data.content, 'base64').toString('utf8');
data.content = Buffer.from(data.content, "base64").toString("utf8");
}
return data;
@@ -203,12 +219,12 @@ async function createIssue(
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify(options)
body: JSON.stringify(options),
}
);
@@ -229,12 +245,12 @@ async function createPullRequest(
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify(options)
body: JSON.stringify(options),
}
);
@@ -254,7 +270,7 @@ async function createOrUpdateFile(
branch: string,
sha?: string
): Promise<GitHubCreateUpdateFileResponse> {
const encodedContent = Buffer.from(content).toString('base64');
const encodedContent = Buffer.from(content).toString("base64");
let currentSha = sha;
if (!currentSha) {
@@ -264,28 +280,30 @@ async function createOrUpdateFile(
currentSha = existingFile.sha;
}
} catch (error) {
console.error('Note: File does not exist in branch, will create new file');
console.error(
"Note: File does not exist in branch, will create new file"
);
}
}
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
const body = {
message,
content: encodedContent,
branch,
...(currentSha ? { sha: currentSha } : {})
...(currentSha ? { sha: currentSha } : {}),
};
const response = await fetch(url, {
method: "PUT",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify(body)
body: JSON.stringify(body),
});
if (!response.ok) {
@@ -301,11 +319,11 @@ async function createTree(
files: FileOperation[],
baseTree?: string
): Promise<GitHubTree> {
const tree = files.map(file => ({
const tree = files.map((file) => ({
path: file.path,
mode: '100644' as const,
type: 'blob' as const,
content: file.content
mode: "100644" as const,
type: "blob" as const,
content: file.content,
}));
const response = await fetch(
@@ -313,15 +331,15 @@ async function createTree(
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
tree,
base_tree: baseTree
})
base_tree: baseTree,
}),
}
);
@@ -344,16 +362,16 @@ async function createCommit(
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
message,
tree,
parents
})
parents,
}),
}
);
@@ -375,15 +393,15 @@ async function updateReference(
{
method: "PATCH",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify({
sha,
force: true
})
force: true,
}),
}
);
@@ -405,10 +423,10 @@ async function pushFiles(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
{
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
}
);
@@ -420,7 +438,9 @@ async function pushFiles(
const commitSha = ref.object.sha;
const tree = await createTree(owner, repo, files, commitSha);
const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]);
const commit = await createCommit(owner, repo, message, tree.sha, [
commitSha,
]);
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
}
@@ -436,10 +456,10 @@ async function searchRepositories(
const response = await fetch(url.toString(), {
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
@@ -455,12 +475,12 @@ async function createRepository(
const response = await fetch("https://api.github.com/user/repos", {
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
body: JSON.stringify(options)
body: JSON.stringify(options),
});
if (!response.ok) {
@@ -480,10 +500,10 @@ async function listCommits(
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/commits`);
url.searchParams.append("page", page.toString());
url.searchParams.append("per_page", perPage.toString());
if (sha) {
if (sha) {
url.searchParams.append("sha", sha);
}
const response = await fetch(
url.toString(),
{
@@ -504,60 +524,261 @@ async function listCommits(
return GitHubListCommitsSchema.parse(await response.json());
}
async function listIssues(
owner: string,
repo: string,
options: Omit<z.infer<typeof ListIssuesOptionsSchema>, 'owner' | 'repo'>
): Promise<GitHubIssue[]> {
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`);
// Add query parameters
if (options.state) url.searchParams.append('state', options.state);
if (options.labels) url.searchParams.append('labels', options.labels.join(','));
if (options.sort) url.searchParams.append('sort', options.sort);
if (options.direction) url.searchParams.append('direction', options.direction);
if (options.since) url.searchParams.append('since', options.since);
if (options.page) url.searchParams.append('page', options.page.toString());
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
const response = await fetch(url.toString(), {
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return z.array(GitHubIssueSchema).parse(await response.json());
}
async function updateIssue(
owner: string,
repo: string,
issueNumber: number,
options: Omit<z.infer<typeof UpdateIssueOptionsSchema>, 'owner' | 'repo' | 'issue_number'>
): Promise<GitHubIssue> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
{
method: "PATCH",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
},
body: JSON.stringify({
title: options.title,
body: options.body,
state: options.state,
labels: options.labels,
assignees: options.assignees,
milestone: options.milestone
})
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubIssueSchema.parse(await response.json());
}
async function addIssueComment(
owner: string,
repo: string,
issueNumber: number,
body: string
): Promise<z.infer<typeof IssueCommentSchema>> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
{
method: "POST",
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
"Content-Type": "application/json"
},
body: JSON.stringify({ body })
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return IssueCommentSchema.parse(await response.json());
}
async function searchCode(
params: z.infer<typeof SearchCodeSchema>
): Promise<SearchCodeResponse> {
const url = new URL("https://api.github.com/search/code");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchCodeResponseSchema.parse(await response.json());
}
async function searchIssues(
params: z.infer<typeof SearchIssuesSchema>
): Promise<SearchIssuesResponse> {
const url = new URL("https://api.github.com/search/issues");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchIssuesResponseSchema.parse(await response.json());
}
async function searchUsers(
params: z.infer<typeof SearchUsersSchema>
): Promise<SearchUsersResponse> {
const url = new URL("https://api.github.com/search/users");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchUsersResponseSchema.parse(await response.json());
}
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "create_or_update_file",
description: "Create or update a single file in a GitHub repository",
inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema)
inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema),
},
{
name: "search_repositories",
description: "Search for GitHub repositories",
inputSchema: zodToJsonSchema(SearchRepositoriesSchema)
inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
},
{
name: "create_repository",
description: "Create a new GitHub repository in your account",
inputSchema: zodToJsonSchema(CreateRepositorySchema)
inputSchema: zodToJsonSchema(CreateRepositorySchema),
},
{
name: "get_file_contents",
description: "Get the contents of a file or directory from a GitHub repository",
inputSchema: zodToJsonSchema(GetFileContentsSchema)
description:
"Get the contents of a file or directory from a GitHub repository",
inputSchema: zodToJsonSchema(GetFileContentsSchema),
},
{
name: "push_files",
description: "Push multiple files to a GitHub repository in a single commit",
inputSchema: zodToJsonSchema(PushFilesSchema)
description:
"Push multiple files to a GitHub repository in a single commit",
inputSchema: zodToJsonSchema(PushFilesSchema),
},
{
name: "create_issue",
description: "Create a new issue in a GitHub repository",
inputSchema: zodToJsonSchema(CreateIssueSchema)
inputSchema: zodToJsonSchema(CreateIssueSchema),
},
{
name: "create_pull_request",
description: "Create a new pull request in a GitHub repository",
inputSchema: zodToJsonSchema(CreatePullRequestSchema)
inputSchema: zodToJsonSchema(CreatePullRequestSchema),
},
{
name: "fork_repository",
description: "Fork a GitHub repository to your account or specified organization",
inputSchema: zodToJsonSchema(ForkRepositorySchema)
description:
"Fork a GitHub repository to your account or specified organization",
inputSchema: zodToJsonSchema(ForkRepositorySchema),
},
{
name: "create_branch",
description: "Create a new branch in a GitHub repository",
inputSchema: zodToJsonSchema(CreateBranchSchema)
inputSchema: zodToJsonSchema(CreateBranchSchema),
},
{
name: "list_commits",
description: "Get list of commits of a branch in a GitHub repository",
inputSchema: zodToJsonSchema(ListCommitsSchema)
}
]
},
{
name: "list_issues",
description: "List issues in a GitHub repository with filtering options",
inputSchema: zodToJsonSchema(ListIssuesOptionsSchema)
},
{
name: "update_issue",
description: "Update an existing issue in a GitHub repository",
inputSchema: zodToJsonSchema(UpdateIssueOptionsSchema)
},
{
name: "add_issue_comment",
description: "Add a comment to an existing issue",
inputSchema: zodToJsonSchema(IssueCommentSchema)
},
{
name: "search_code",
description: "Search for code across GitHub repositories",
inputSchema: zodToJsonSchema(SearchCodeSchema),
},
{
name: "search_issues",
description:
"Search for issues and pull requests across GitHub repositories",
inputSchema: zodToJsonSchema(SearchIssuesSchema),
},
{
name: "search_users",
description: "Search for users on GitHub",
inputSchema: zodToJsonSchema(SearchUsersSchema),
},
],
};
});
@@ -570,8 +791,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case "fork_repository": {
const args = ForkRepositorySchema.parse(request.params.arguments);
const fork = await forkRepository(args.owner, args.repo, args.organization);
return { content: [{ type: "text", text: JSON.stringify(fork, null, 2) }] };
const fork = await forkRepository(
args.owner,
args.repo,
args.organization
);
return {
content: [{ type: "text", text: JSON.stringify(fork, null, 2) }],
};
}
case "create_branch": {
@@ -582,10 +809,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
`https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`,
{
headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server"
}
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
}
);
@@ -601,28 +828,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const branch = await createBranch(args.owner, args.repo, {
ref: args.branch,
sha
sha,
});
return { content: [{ type: "text", text: JSON.stringify(branch, null, 2) }] };
return {
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
};
}
case "search_repositories": {
const args = SearchRepositoriesSchema.parse(request.params.arguments);
const results = await searchRepositories(args.query, args.page, args.perPage);
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
const results = await searchRepositories(
args.query,
args.page,
args.perPage
);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
case "create_repository": {
const args = CreateRepositorySchema.parse(request.params.arguments);
const repository = await createRepository(args);
return { content: [{ type: "text", text: JSON.stringify(repository, null, 2) }] };
return {
content: [
{ type: "text", text: JSON.stringify(repository, null, 2) },
],
};
}
case "get_file_contents": {
const args = GetFileContentsSchema.parse(request.params.arguments);
const contents = await getFileContents(args.owner, args.repo, args.path, args.branch);
return { content: [{ type: "text", text: JSON.stringify(contents, null, 2) }] };
const contents = await getFileContents(
args.owner,
args.repo,
args.path,
args.branch
);
return {
content: [{ type: "text", text: JSON.stringify(contents, null, 2) }],
};
}
case "create_or_update_file": {
@@ -636,7 +882,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
args.branch,
args.sha
);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "push_files": {
@@ -648,21 +896,74 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
args.files,
args.message
);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
case "create_issue": {
const args = CreateIssueSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const issue = await createIssue(owner, repo, options);
return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }] };
return {
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
};
}
case "create_pull_request": {
const args = CreatePullRequestSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const pullRequest = await createPullRequest(owner, repo, options);
return { content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }] };
return {
content: [
{ type: "text", text: JSON.stringify(pullRequest, null, 2) },
],
};
}
case "search_code": {
const args = SearchCodeSchema.parse(request.params.arguments);
const results = await searchCode(args);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
case "search_issues": {
const args = SearchIssuesSchema.parse(request.params.arguments);
const results = await searchIssues(args);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
case "search_users": {
const args = SearchUsersSchema.parse(request.params.arguments);
const results = await searchUsers(args);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
case "list_issues": {
const args = ListIssuesOptionsSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const issues = await listIssues(owner, repo, options);
return { toolResult: issues };
}
case "update_issue": {
const args = UpdateIssueOptionsSchema.parse(request.params.arguments);
const { owner, repo, issue_number, ...options } = args;
const issue = await updateIssue(owner, repo, issue_number, options);
return { toolResult: issue };
}
case "add_issue_comment": {
const args = IssueCommentSchema.parse(request.params.arguments);
const { owner, repo, issue_number, body } = args;
const comment = await addIssueComment(owner, repo, issue_number, body);
return { toolResult: comment };
}
case "list_commits": {
@@ -676,7 +977,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
throw new Error(
`Invalid arguments: ${error.errors
.map(
(e: z.ZodError["errors"][number]) =>
`${e.path.join(".")}: ${e.message}`
)
.join(", ")}`
);
}
throw error;
}
@@ -691,4 +999,4 @@ async function runServer() {
runServer().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
});