Updated Github to Zod

This commit is contained in:
Mahesh Murag 2024-11-21 00:18:09 -05:00
commit 859c7b8520
No known key found for this signature in database
4 changed files with 553 additions and 798 deletions

4
.gitignore vendored
View file

@ -290,9 +290,11 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
.DS_Store
# PyCharm # PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/

View file

@ -8,23 +8,43 @@ import {
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { import {
GitHubContent, GitHubForkSchema,
GitHubCreateUpdateFileResponse, GitHubReferenceSchema,
GitHubSearchResponse, GitHubRepositorySchema,
GitHubRepository, GitHubIssueSchema,
GitHubTree, GitHubPullRequestSchema,
GitHubCommit, GitHubContentSchema,
GitHubReference, GitHubCreateUpdateFileResponseSchema,
CreateRepositoryOptions, GitHubSearchResponseSchema,
FileOperation, GitHubTreeSchema,
CreateTreeParams, GitHubCommitSchema,
GitHubPullRequest, CreateRepositoryOptionsSchema,
CreateIssueOptions, CreateIssueOptionsSchema,
CreatePullRequestOptions, CreatePullRequestOptionsSchema,
GitHubIssue, CreateBranchOptionsSchema,
GitHubFork, type GitHubFork,
CreateBranchOptions, type GitHubReference,
} from './interfaces.js'; 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
} from './schemas.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const server = new Server({ const server = new Server({
name: "github-mcp-server", name: "github-mcp-server",
@ -64,15 +84,14 @@ async function forkRepository(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubFork; return GitHubForkSchema.parse(await response.json());
} }
async function createBranch( async function createBranch(
owner: string, owner: string,
repo: string, repo: string,
options: CreateBranchOptions options: z.infer<typeof CreateBranchOptionsSchema>
): Promise<GitHubReference> { ): Promise<GitHubReference> {
// The ref needs to be in the format "refs/heads/branch-name"
const fullRef = `refs/heads/${options.ref}`; const fullRef = `refs/heads/${options.ref}`;
const response = await fetch( const response = await fetch(
@ -96,10 +115,9 @@ async function createBranch(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubReference; return GitHubReferenceSchema.parse(await response.json());
} }
// Helper function to get the default branch SHA
async function getDefaultBranchSHA( async function getDefaultBranchSHA(
owner: string, owner: string,
repo: string repo: string
@ -115,7 +133,6 @@ async function getDefaultBranchSHA(
} }
); );
// If main branch doesn't exist, try master
if (!response.ok) { if (!response.ok) {
const masterResponse = await fetch( const masterResponse = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`, `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
@ -132,15 +149,20 @@ async function getDefaultBranchSHA(
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 = await masterResponse.json() as GitHubReference; const data = GitHubReferenceSchema.parse(await masterResponse.json());
return data.object.sha; return data.object.sha;
} }
const data = await response.json() as GitHubReference; const data = GitHubReferenceSchema.parse(await response.json());
return data.object.sha; return data.object.sha;
} }
async function getFileContents(owner: string, repo: string, path: string, branch?: string): Promise<GitHubContent> { async function getFileContents(
owner: string,
repo: string,
path: string,
branch?: string
): Promise<GitHubContent> {
let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`; let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
if (branch) { if (branch) {
url += `?ref=${branch}`; url += `?ref=${branch}`;
@ -155,28 +177,23 @@ async function getFileContents(owner: string, repo: string, path: string, branch
}); });
if (!response.ok) { if (!response.ok) {
const errorData = await response.text(); throw new Error(`GitHub API error: ${response.statusText}`);
throw new Error(`GitHub API error (${response.status}): ${errorData}`);
} }
const data = await response.json() as GitHubContent; const data = GitHubContentSchema.parse(await response.json());
// If it's a file, decode the content // If it's a file, decode the content
if (!Array.isArray(data) && data.content) { if (!Array.isArray(data) && data.content) {
return { data.content = Buffer.from(data.content, 'base64').toString('utf8');
...data,
content: Buffer.from(data.content, 'base64').toString('utf8')
};
} }
return data; return data;
} }
async function createIssue( async function createIssue(
owner: string, owner: string,
repo: string, repo: string,
options: CreateIssueOptions options: z.infer<typeof CreateIssueOptionsSchema>
): Promise<GitHubIssue> { ): Promise<GitHubIssue> {
const response = await fetch( const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues`, `https://api.github.com/repos/${owner}/${repo}/issues`,
@ -196,13 +213,13 @@ async function createIssue(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubIssue; return GitHubIssueSchema.parse(await response.json());
} }
async function createPullRequest( async function createPullRequest(
owner: string, owner: string,
repo: string, repo: string,
options: CreatePullRequestOptions options: z.infer<typeof CreatePullRequestOptionsSchema>
): Promise<GitHubPullRequest> { ): Promise<GitHubPullRequest> {
const response = await fetch( const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/pulls`, `https://api.github.com/repos/${owner}/${repo}/pulls`,
@ -222,7 +239,7 @@ async function createPullRequest(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubPullRequest; return GitHubPullRequestSchema.parse(await response.json());
} }
async function createOrUpdateFile( async function createOrUpdateFile(
@ -234,19 +251,16 @@ async function createOrUpdateFile(
branch: string, branch: string,
sha?: string sha?: string
): Promise<GitHubCreateUpdateFileResponse> { ): Promise<GitHubCreateUpdateFileResponse> {
// Properly encode content to base64
const encodedContent = Buffer.from(content).toString('base64'); const encodedContent = Buffer.from(content).toString('base64');
let currentSha = sha; let currentSha = sha;
if (!currentSha) { if (!currentSha) {
// Try to get current file SHA if it exists in the specified branch
try { try {
const existingFile = await getFileContents(owner, repo, path, branch); const existingFile = await getFileContents(owner, repo, path, branch);
if (!Array.isArray(existingFile)) { if (!Array.isArray(existingFile)) {
currentSha = existingFile.sha; currentSha = existingFile.sha;
} }
} catch (error) { } catch (error) {
// File doesn't exist in this branch, which is fine for creation
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');
} }
} }
@ -272,11 +286,10 @@ async function createOrUpdateFile(
}); });
if (!response.ok) { if (!response.ok) {
const errorData = await response.text(); throw new Error(`GitHub API error: ${response.statusText}`);
throw new Error(`GitHub API error (${response.status}): ${errorData}`);
} }
return await response.json() as GitHubCreateUpdateFileResponse; return GitHubCreateUpdateFileResponseSchema.parse(await response.json());
} }
async function createTree( async function createTree(
@ -285,10 +298,10 @@ async function createTree(
files: FileOperation[], files: FileOperation[],
baseTree?: string baseTree?: string
): Promise<GitHubTree> { ): Promise<GitHubTree> {
const tree: CreateTreeParams[] = files.map(file => ({ const tree = files.map(file => ({
path: file.path, path: file.path,
mode: '100644', mode: '100644' as const,
type: 'blob', type: 'blob' as const,
content: file.content content: file.content
})); }));
@ -313,7 +326,7 @@ async function createTree(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubTree; return GitHubTreeSchema.parse(await response.json());
} }
async function createCommit( async function createCommit(
@ -345,7 +358,7 @@ async function createCommit(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubCommit; return GitHubCommitSchema.parse(await response.json());
} }
async function updateReference( async function updateReference(
@ -375,7 +388,7 @@ async function updateReference(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubReference; return GitHubReferenceSchema.parse(await response.json());
} }
async function pushFiles( async function pushFiles(
@ -400,7 +413,7 @@ async function pushFiles(
throw new Error(`GitHub API error: ${refResponse.statusText}`); throw new Error(`GitHub API error: ${refResponse.statusText}`);
} }
const ref = await refResponse.json() as GitHubReference; const ref = GitHubReferenceSchema.parse(await refResponse.json());
const commitSha = ref.object.sha; const commitSha = ref.object.sha;
const tree = await createTree(owner, repo, files, commitSha); const tree = await createTree(owner, repo, files, commitSha);
@ -430,10 +443,12 @@ async function searchRepositories(
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubSearchResponse; return GitHubSearchResponseSchema.parse(await response.json());
} }
async function createRepository(options: CreateRepositoryOptions): Promise<GitHubRepository> { async function createRepository(
options: z.infer<typeof CreateRepositoryOptionsSchema>
): Promise<GitHubRepository> {
const response = await fetch("https://api.github.com/user/repos", { const response = await fetch("https://api.github.com/user/repos", {
method: "POST", method: "POST",
headers: { headers: {
@ -449,7 +464,7 @@ async function createRepository(options: CreateRepositoryOptions): Promise<GitHu
throw new Error(`GitHub API error: ${response.statusText}`); throw new Error(`GitHub API error: ${response.statusText}`);
} }
return await response.json() as GitHubRepository; return GitHubRepositorySchema.parse(await response.json());
} }
server.setRequestHandler(ListToolsRequestSchema, async () => { server.setRequestHandler(ListToolsRequestSchema, async () => {
@ -458,473 +473,165 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
{ {
name: "create_or_update_file", name: "create_or_update_file",
description: "Create or update a single file in a GitHub repository", description: "Create or update a single file in a GitHub repository",
inputSchema: { inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema)
type: "object", },
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
path: {
type: "string",
description: "Path where to create/update the file"
},
content: {
type: "string",
description: "Content of the file"
},
message: {
type: "string",
description: "Commit message"
},
branch: {
type: "string",
description: "Branch to create/update the file in"
},
sha: {
type: "string",
description: "SHA of the file being replaced (required when updating existing files)"
}
},
required: ["owner", "repo", "path", "content", "message", "branch"]
}
},
{ {
name: "search_repositories", name: "search_repositories",
description: "Search for GitHub repositories", description: "Search for GitHub repositories",
inputSchema: { inputSchema: zodToJsonSchema(SearchRepositoriesSchema)
type: "object",
properties: {
query: {
type: "string",
description: "Search query (see GitHub search syntax)"
},
page: {
type: "number",
description: "Page number for pagination (default: 1)"
},
perPage: {
type: "number",
description: "Number of results per page (default: 30, max: 100)"
}
},
required: ["query"]
}
}, },
{ {
name: "create_repository", name: "create_repository",
description: "Create a new GitHub repository in your account", description: "Create a new GitHub repository in your account",
inputSchema: { inputSchema: zodToJsonSchema(CreateRepositorySchema)
type: "object",
properties: {
name: {
type: "string",
description: "Repository name"
},
description: {
type: "string",
description: "Repository description"
},
private: {
type: "boolean",
description: "Whether the repository should be private"
},
autoInit: {
type: "boolean",
description: "Initialize with README.md"
}
},
required: ["name"]
}
}, },
{ {
name: "get_file_contents", name: "get_file_contents",
description: "Get the contents of a file or directory from a GitHub repository", description: "Get the contents of a file or directory from a GitHub repository",
inputSchema: { inputSchema: zodToJsonSchema(GetFileContentsSchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
path: {
type: "string",
description: "Path to the file or directory"
}
},
required: ["owner", "repo", "path"]
}
}, },
{ {
name: "push_files", name: "push_files",
description: "Push multiple files to a GitHub repository in a single commit", description: "Push multiple files to a GitHub repository in a single commit",
inputSchema: { inputSchema: zodToJsonSchema(PushFilesSchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
branch: {
type: "string",
description: "Branch to push to (e.g., 'main' or 'master')"
},
files: {
type: "array",
description: "Array of files to push",
items: {
type: "object",
properties: {
path: {
type: "string",
description: "Path where to create the file"
},
content: {
type: "string",
description: "Content of the file"
}
},
required: ["path", "content"]
}
},
message: {
type: "string",
description: "Commit message"
}
},
required: ["owner", "repo", "branch", "files", "message"]
}
}, },
{ {
name: "create_issue", name: "create_issue",
description: "Create a new issue in a GitHub repository", description: "Create a new issue in a GitHub repository",
inputSchema: { inputSchema: zodToJsonSchema(CreateIssueSchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
title: {
type: "string",
description: "Issue title"
},
body: {
type: "string",
description: "Issue body/description"
},
assignees: {
type: "array",
items: { type: "string" },
description: "Array of usernames to assign"
},
labels: {
type: "array",
items: { type: "string" },
description: "Array of label names"
},
milestone: {
type: "number",
description: "Milestone number to assign"
}
},
required: ["owner", "repo", "title"]
}
}, },
{ {
name: "create_pull_request", name: "create_pull_request",
description: "Create a new pull request in a GitHub repository", description: "Create a new pull request in a GitHub repository",
inputSchema: { inputSchema: zodToJsonSchema(CreatePullRequestSchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
title: {
type: "string",
description: "Pull request title"
},
body: {
type: "string",
description: "Pull request body/description"
},
head: {
type: "string",
description: "The name of the branch where your changes are implemented"
},
base: {
type: "string",
description: "The name of the branch you want the changes pulled into"
},
draft: {
type: "boolean",
description: "Whether to create the pull request as a draft"
},
maintainer_can_modify: {
type: "boolean",
description: "Whether maintainers can modify the pull request"
}
},
required: ["owner", "repo", "title", "head", "base"]
}
}, },
{ {
name: "fork_repository", name: "fork_repository",
description: "Fork a GitHub repository to your account or specified organization", description: "Fork a GitHub repository to your account or specified organization",
inputSchema: { inputSchema: zodToJsonSchema(ForkRepositorySchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
organization: {
type: "string",
description: "Optional: organization to fork to (defaults to your personal account)"
}
},
required: ["owner", "repo"]
}
}, },
{ {
name: "create_branch", name: "create_branch",
description: "Create a new branch in a GitHub repository", description: "Create a new branch in a GitHub repository",
inputSchema: { inputSchema: zodToJsonSchema(CreateBranchSchema)
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (username or organization)"
},
repo: {
type: "string",
description: "Repository name"
},
branch: {
type: "string",
description: "Name for the new branch"
},
from_branch: {
type: "string",
description: "Optional: source branch to create from (defaults to the repository's default branch)"
}
},
required: ["owner", "repo", "branch"]
}
} }
] ]
}; };
}); });
server.setRequestHandler(CallToolRequestSchema, async (request) => { server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (request.params.name === "fork_repository") {
if (!request.params.arguments) { if (!request.params.arguments) {
throw new Error("Arguments are required"); throw new Error("Arguments are required");
} }
const args = request.params.arguments as { switch (request.params.name) {
owner: string; case "fork_repository": {
repo: string; const args = ForkRepositorySchema.parse(request.params.arguments);
organization?: string; const fork = await forkRepository(args.owner, args.repo, args.organization);
}; return { toolResult: fork };
}
const fork = await forkRepository(args.owner, args.repo, args.organization); case "create_branch": {
return { toolResult: fork }; const args = CreateBranchSchema.parse(request.params.arguments);
} let sha: string;
if (args.from_branch) {
const response = await fetch(
`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"
}
}
);
if (request.params.name === "create_branch") { if (!response.ok) {
if (!request.params.arguments) { throw new Error(`Source branch '${args.from_branch}' not found`);
throw new Error("Arguments are required");
}
const args = request.params.arguments as {
owner: string;
repo: string;
branch: string;
from_branch?: string;
};
// If no source branch is specified, use the default branch
let sha: string;
if (args.from_branch) {
const response = await fetch(
`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"
} }
const data = GitHubReferenceSchema.parse(await response.json());
sha = data.object.sha;
} else {
sha = await getDefaultBranchSHA(args.owner, args.repo);
} }
);
if (!response.ok) { const branch = await createBranch(args.owner, args.repo, {
throw new Error(`Source branch '${args.from_branch}' not found`); ref: args.branch,
sha
});
return { toolResult: branch };
} }
const data = await response.json() as GitHubReference; case "search_repositories": {
sha = data.object.sha; const args = SearchRepositoriesSchema.parse(request.params.arguments);
} else { const results = await searchRepositories(args.query, args.page, args.perPage);
sha = await getDefaultBranchSHA(args.owner, args.repo); return { toolResult: results };
}
const branch = await createBranch(args.owner, args.repo, {
ref: args.branch,
sha: sha
});
return { toolResult: branch };
}
if (request.params.name === "search_repositories") {
const { query, page, perPage } = request.params.arguments as {
query: string;
page?: number;
perPage?: number;
};
const results = await searchRepositories(query, page, perPage);
return { toolResult: results };
}
if (request.params.name === "create_repository") {
const options = request.params.arguments as CreateRepositoryOptions;
const repository = await createRepository(options);
return { toolResult: repository };
}
if (request.params.name === "get_file_contents") {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
const args = request.params.arguments as {
owner: string;
repo: string;
path: string;
branch?: string;
};
const contents = await getFileContents(args.owner, args.repo, args.path, args.branch);
return { toolResult: contents };
}
if (request.params.name === "create_or_update_file") {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
const args = request.params.arguments as {
owner: string;
repo: string;
path: string;
content: string;
message: string;
branch: string;
sha?: string;
};
try {
const result = await createOrUpdateFile(
args.owner,
args.repo,
args.path,
args.content,
args.message,
args.branch,
args.sha
);
return { toolResult: result };
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to create/update file: ${error.message}`);
} }
throw error;
case "create_repository": {
const args = CreateRepositorySchema.parse(request.params.arguments);
const repository = await createRepository(args);
return { toolResult: repository };
}
case "get_file_contents": {
const args = GetFileContentsSchema.parse(request.params.arguments);
const contents = await getFileContents(args.owner, args.repo, args.path, args.branch);
return { toolResult: contents };
}
case "create_or_update_file": {
const args = CreateOrUpdateFileSchema.parse(request.params.arguments);
const result = await createOrUpdateFile(
args.owner,
args.repo,
args.path,
args.content,
args.message,
args.branch,
args.sha
);
return { toolResult: result };
}
case "push_files": {
const args = PushFilesSchema.parse(request.params.arguments);
const result = await pushFiles(
args.owner,
args.repo,
args.branch,
args.files,
args.message
);
return { toolResult: result };
}
case "create_issue": {
const args = CreateIssueSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const issue = await createIssue(owner, repo, options);
return { toolResult: issue };
}
case "create_pull_request": {
const args = CreatePullRequestSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const pullRequest = await createPullRequest(owner, repo, options);
return { toolResult: pullRequest };
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
} }
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
}
throw error;
} }
if (request.params.name === "push_files") {
const { owner, repo, branch, files, message } = request.params.arguments as {
owner: string;
repo: string;
branch: string;
files: FileOperation[];
message: string;
};
const result = await pushFiles(owner, repo, branch, files, message);
return { toolResult: result };
}
if (request.params.name === "create_issue") {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
const args = request.params.arguments as {
owner: string;
repo: string;
title: string;
body?: string;
assignees?: string[];
milestone?: number;
labels?: string[];
};
const { owner, repo, ...options } = args;
const issue = await createIssue(owner, repo, options);
return { toolResult: issue };
}
if (request.params.name === "create_pull_request") {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
const args = request.params.arguments as {
owner: string;
repo: string;
title: string;
body?: string;
head: string;
base: string;
maintainer_can_modify?: boolean;
draft?: boolean;
};
const { owner, repo, ...options } = args;
const pullRequest = await createPullRequest(owner, repo, options);
return { toolResult: pullRequest };
}
throw new Error("Tool not found");
}); });
async function runServer() { async function runServer() {

View file

@ -1,332 +0,0 @@
// GitHub API Response Types
export interface GitHubErrorResponse {
message: string;
documentation_url?: string;
}
export interface GitHubFileContent {
type: string;
encoding: string;
size: number;
name: string;
path: string;
content: string;
sha: string;
url: string;
git_url: string;
html_url: string;
download_url: string;
}
export interface GitHubDirectoryContent {
type: string;
size: number;
name: string;
path: string;
sha: string;
url: string;
git_url: string;
html_url: string;
download_url: string | null;
}
export type GitHubContent = GitHubFileContent | GitHubDirectoryContent[];
export interface GitHubCreateUpdateFileResponse {
content: GitHubFileContent | null;
commit: {
sha: string;
node_id: string;
url: string;
html_url: string;
author: GitHubAuthor;
committer: GitHubAuthor;
message: string;
tree: {
sha: string;
url: string;
};
parents: Array<{
sha: string;
url: string;
html_url: string;
}>;
};
}
export interface GitHubAuthor {
name: string;
email: string;
date: string;
}
export interface GitHubTree {
sha: string;
url: string;
tree: Array<{
path: string;
mode: string;
type: string;
size?: number;
sha: string;
url: string;
}>;
truncated: boolean;
}
export interface GitHubCommit {
sha: string;
node_id: string;
url: string;
author: GitHubAuthor;
committer: GitHubAuthor;
message: string;
tree: {
sha: string;
url: string;
};
parents: Array<{
sha: string;
url: string;
}>;
}
export interface GitHubReference {
ref: string;
node_id: string;
url: string;
object: {
sha: string;
type: string;
url: string;
};
}
export interface GitHubRepository {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: {
login: string;
id: number;
node_id: string;
avatar_url: string;
url: string;
html_url: string;
type: string;
};
html_url: string;
description: string | null;
fork: boolean;
url: string;
created_at: string;
updated_at: string;
pushed_at: string;
git_url: string;
ssh_url: string;
clone_url: string;
default_branch: string;
}
export interface GitHubSearchResponse {
total_count: number;
incomplete_results: boolean;
items: GitHubRepository[];
}
// Request Types
export interface CreateRepositoryOptions {
name?: string;
description?: string;
private?: boolean;
auto_init?: boolean;
}
export interface CreateTreeParams {
path: string;
mode: '100644' | '100755' | '040000' | '160000' | '120000';
type: 'blob' | 'tree' | 'commit';
content?: string;
sha?: string;
}
export interface FileOperation {
path: string;
content: string;
}
export interface GitHubIssue {
url: string;
repository_url: string;
labels_url: string;
comments_url: string;
events_url: string;
html_url: string;
id: number;
node_id: string;
number: number;
title: string;
user: {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
labels: Array<{
id: number;
node_id: string;
url: string;
name: string;
color: string;
default: boolean;
description?: string;
}>;
state: string;
locked: boolean;
assignee: null | {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
assignees: Array<{
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
}>;
milestone: null | {
url: string;
html_url: string;
labels_url: string;
id: number;
node_id: string;
number: number;
title: string;
description: string;
state: string;
};
comments: number;
created_at: string;
updated_at: string;
closed_at: string | null;
body: string;
}
export interface CreateIssueOptions {
title: string;
body?: string;
assignees?: string[];
milestone?: number;
labels?: string[];
}
export interface GitHubPullRequest {
url: string;
id: number;
node_id: string;
html_url: string;
diff_url: string;
patch_url: string;
issue_url: string;
number: number;
state: string;
locked: boolean;
title: string;
user: {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
body: string;
created_at: string;
updated_at: string;
closed_at: string | null;
merged_at: string | null;
merge_commit_sha: string;
assignee: null | {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
assignees: Array<{
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
}>;
head: {
label: string;
ref: string;
sha: string;
user: {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
repo: GitHubRepository;
};
base: {
label: string;
ref: string;
sha: string;
user: {
login: string;
id: number;
avatar_url: string;
url: string;
html_url: string;
};
repo: GitHubRepository;
};
}
export interface CreatePullRequestOptions {
title: string;
body?: string;
head: string;
base: string;
maintainer_can_modify?: boolean;
draft?: boolean;
}
export interface GitHubFork extends GitHubRepository {
// Fork specific fields
parent: {
name: string;
full_name: string;
owner: {
login: string;
id: number;
avatar_url: string;
};
html_url: string;
};
source: {
name: string;
full_name: string;
owner: {
login: string;
id: number;
avatar_url: string;
};
html_url: string;
};
}
export interface CreateBranchOptions {
ref: string; // The name for the new branch
sha: string; // The SHA of the commit to branch from
}

378
src/github/schemas.ts Normal file
View file

@ -0,0 +1,378 @@
import { z } from 'zod';
// Base schemas for common types
export const GitHubAuthorSchema = z.object({
name: z.string(),
email: z.string(),
date: z.string()
});
// Repository related schemas
export const GitHubOwnerSchema = z.object({
login: z.string(),
id: z.number(),
node_id: z.string(),
avatar_url: z.string(),
url: z.string(),
html_url: z.string(),
type: z.string()
});
export const GitHubRepositorySchema = z.object({
id: z.number(),
node_id: z.string(),
name: z.string(),
full_name: z.string(),
private: z.boolean(),
owner: GitHubOwnerSchema,
html_url: z.string(),
description: z.string().nullable(),
fork: z.boolean(),
url: z.string(),
created_at: z.string(),
updated_at: z.string(),
pushed_at: z.string(),
git_url: z.string(),
ssh_url: z.string(),
clone_url: z.string(),
default_branch: z.string()
});
// File content schemas
export const GitHubFileContentSchema = z.object({
type: z.string(),
encoding: z.string(),
size: z.number(),
name: z.string(),
path: z.string(),
content: z.string(),
sha: z.string(),
url: z.string(),
git_url: z.string(),
html_url: z.string(),
download_url: z.string()
});
export const GitHubDirectoryContentSchema = z.object({
type: z.string(),
size: z.number(),
name: z.string(),
path: z.string(),
sha: z.string(),
url: z.string(),
git_url: z.string(),
html_url: z.string(),
download_url: z.string().nullable()
});
export const GitHubContentSchema = z.union([
GitHubFileContentSchema,
z.array(GitHubDirectoryContentSchema)
]);
// Operation schemas
export const FileOperationSchema = z.object({
path: z.string(),
content: z.string()
});
// Tree and commit schemas
export const GitHubTreeEntrySchema = z.object({
path: z.string(),
mode: z.enum(['100644', '100755', '040000', '160000', '120000']),
type: z.enum(['blob', 'tree', 'commit']),
size: z.number().optional(),
sha: z.string(),
url: z.string()
});
export const GitHubTreeSchema = z.object({
sha: z.string(),
url: z.string(),
tree: z.array(GitHubTreeEntrySchema),
truncated: z.boolean()
});
export const GitHubCommitSchema = z.object({
sha: z.string(),
node_id: z.string(),
url: z.string(),
author: GitHubAuthorSchema,
committer: GitHubAuthorSchema,
message: z.string(),
tree: z.object({
sha: z.string(),
url: z.string()
}),
parents: z.array(z.object({
sha: z.string(),
url: z.string()
}))
});
// Reference schema
export const GitHubReferenceSchema = z.object({
ref: z.string(),
node_id: z.string(),
url: z.string(),
object: z.object({
sha: z.string(),
type: z.string(),
url: z.string()
})
});
// Input schemas for operations
export const CreateRepositoryOptionsSchema = z.object({
name: z.string(),
description: z.string().optional(),
private: z.boolean().optional(),
auto_init: z.boolean().optional()
});
export const CreateIssueOptionsSchema = z.object({
title: z.string(),
body: z.string().optional(),
assignees: z.array(z.string()).optional(),
milestone: z.number().optional(),
labels: z.array(z.string()).optional()
});
export const CreatePullRequestOptionsSchema = z.object({
title: z.string(),
body: z.string().optional(),
head: z.string(),
base: z.string(),
maintainer_can_modify: z.boolean().optional(),
draft: z.boolean().optional()
});
export const CreateBranchOptionsSchema = z.object({
ref: z.string(),
sha: z.string()
});
// Response schemas for operations
export const GitHubCreateUpdateFileResponseSchema = z.object({
content: GitHubFileContentSchema.nullable(),
commit: z.object({
sha: z.string(),
node_id: z.string(),
url: z.string(),
html_url: z.string(),
author: GitHubAuthorSchema,
committer: GitHubAuthorSchema,
message: z.string(),
tree: z.object({
sha: z.string(),
url: z.string()
}),
parents: z.array(z.object({
sha: z.string(),
url: z.string(),
html_url: z.string()
}))
})
});
export const GitHubSearchResponseSchema = z.object({
total_count: z.number(),
incomplete_results: z.boolean(),
items: z.array(GitHubRepositorySchema)
});
// Fork related schemas
export const GitHubForkParentSchema = z.object({
name: z.string(),
full_name: z.string(),
owner: z.object({
login: z.string(),
id: z.number(),
avatar_url: z.string()
}),
html_url: z.string()
});
export const GitHubForkSchema = GitHubRepositorySchema.extend({
parent: GitHubForkParentSchema,
source: GitHubForkParentSchema
});
// Issue related schemas
export const GitHubLabelSchema = z.object({
id: z.number(),
node_id: z.string(),
url: z.string(),
name: z.string(),
color: z.string(),
default: z.boolean(),
description: z.string().optional()
});
export const GitHubIssueAssigneeSchema = z.object({
login: z.string(),
id: z.number(),
avatar_url: z.string(),
url: z.string(),
html_url: z.string()
});
export const GitHubMilestoneSchema = z.object({
url: z.string(),
html_url: z.string(),
labels_url: z.string(),
id: z.number(),
node_id: z.string(),
number: z.number(),
title: z.string(),
description: z.string(),
state: z.string()
});
export const GitHubIssueSchema = z.object({
url: z.string(),
repository_url: z.string(),
labels_url: z.string(),
comments_url: z.string(),
events_url: z.string(),
html_url: z.string(),
id: z.number(),
node_id: z.string(),
number: z.number(),
title: z.string(),
user: GitHubIssueAssigneeSchema,
labels: z.array(GitHubLabelSchema),
state: z.string(),
locked: z.boolean(),
assignee: GitHubIssueAssigneeSchema.nullable(),
assignees: z.array(GitHubIssueAssigneeSchema),
milestone: GitHubMilestoneSchema.nullable(),
comments: z.number(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
body: z.string()
});
// Pull Request related schemas
export const GitHubPullRequestHeadSchema = z.object({
label: z.string(),
ref: z.string(),
sha: z.string(),
user: GitHubIssueAssigneeSchema,
repo: GitHubRepositorySchema
});
export const GitHubPullRequestSchema = z.object({
url: z.string(),
id: z.number(),
node_id: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
issue_url: z.string(),
number: z.number(),
state: z.string(),
locked: z.boolean(),
title: z.string(),
user: GitHubIssueAssigneeSchema,
body: z.string(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
merged_at: z.string().nullable(),
merge_commit_sha: z.string(),
assignee: GitHubIssueAssigneeSchema.nullable(),
assignees: z.array(GitHubIssueAssigneeSchema),
head: GitHubPullRequestHeadSchema,
base: GitHubPullRequestHeadSchema
});
const RepoParamsSchema = z.object({
owner: z.string().describe("Repository owner (username or organization)"),
repo: z.string().describe("Repository name")
});
export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
path: z.string().describe("Path where to create/update the file"),
content: z.string().describe("Content of the file"),
message: z.string().describe("Commit message"),
branch: z.string().describe("Branch to create/update the file in"),
sha: z.string().optional()
.describe("SHA of the file being replaced (required when updating existing files)")
});
export const SearchRepositoriesSchema = z.object({
query: z.string().describe("Search query (see GitHub search syntax)"),
page: z.number().optional().describe("Page number for pagination (default: 1)"),
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)")
});
export const CreateRepositorySchema = z.object({
name: z.string().describe("Repository name"),
description: z.string().optional().describe("Repository description"),
private: z.boolean().optional().describe("Whether the repository should be private"),
autoInit: z.boolean().optional().describe("Initialize with README.md")
});
export const GetFileContentsSchema = RepoParamsSchema.extend({
path: z.string().describe("Path to the file or directory"),
branch: z.string().optional().describe("Branch to get contents from")
});
export const PushFilesSchema = RepoParamsSchema.extend({
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
files: z.array(z.object({
path: z.string().describe("Path where to create the file"),
content: z.string().describe("Content of the file")
})).describe("Array of files to push"),
message: z.string().describe("Commit message")
});
export const CreateIssueSchema = RepoParamsSchema.extend({
title: z.string().describe("Issue title"),
body: z.string().optional().describe("Issue body/description"),
assignees: z.array(z.string()).optional().describe("Array of usernames to assign"),
labels: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.number().optional().describe("Milestone number to assign")
});
export const CreatePullRequestSchema = RepoParamsSchema.extend({
title: z.string().describe("Pull request title"),
body: z.string().optional().describe("Pull request body/description"),
head: z.string().describe("The name of the branch where your changes are implemented"),
base: z.string().describe("The name of the branch you want the changes pulled into"),
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
maintainer_can_modify: z.boolean().optional()
.describe("Whether maintainers can modify the pull request")
});
export const ForkRepositorySchema = RepoParamsSchema.extend({
organization: z.string().optional()
.describe("Optional: organization to fork to (defaults to your personal account)")
});
export const CreateBranchSchema = RepoParamsSchema.extend({
branch: z.string().describe("Name for the new branch"),
from_branch: z.string().optional()
.describe("Optional: source branch to create from (defaults to the repository's default branch)")
});
// Export types
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
export type GitHubDirectoryContent = z.infer<typeof GitHubDirectoryContentSchema>;
export type GitHubContent = z.infer<typeof GitHubContentSchema>;
export type FileOperation = z.infer<typeof FileOperationSchema>;
export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>;
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
export type CreatePullRequestOptions = z.infer<typeof CreatePullRequestOptionsSchema>;
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
export type GitHubCreateUpdateFileResponse = z.infer<typeof GitHubCreateUpdateFileResponseSchema>;
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;