Updated Github to Zod
This commit is contained in:
parent
c60d4564f2
commit
859c7b8520
4 changed files with 553 additions and 798 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
378
src/github/schemas.ts
Normal 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>;
|
||||||
Loading…
Reference in a new issue