code for search in github

This commit is contained in:
Raduan77
2024-11-28 09:51:03 +01:00
parent 77e22c5c3e
commit 4ac78a996c
3 changed files with 639 additions and 272 deletions

View File

@@ -41,19 +41,32 @@ import {
CreateIssueSchema, CreateIssueSchema,
CreatePullRequestSchema, CreatePullRequestSchema,
ForkRepositorySchema, ForkRepositorySchema,
CreateBranchSchema CreateBranchSchema,
} from './schemas.js'; SearchCodeSchema,
import { z } from 'zod'; SearchIssuesSchema,
import { zodToJsonSchema } from 'zod-to-json-schema'; SearchUsersSchema,
SearchCodeResponseSchema,
SearchIssuesResponseSchema,
SearchUsersResponseSchema,
type SearchCodeResponse,
type SearchIssuesResponse,
type SearchUsersResponse,
} from "./schemas.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import { z } from "zod";
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
const server = new Server({ const server = new Server(
{
name: "github-mcp-server", name: "github-mcp-server",
version: "0.1.0", version: "0.1.0",
}, { },
{
capabilities: { capabilities: {
tools: {} tools: {},
},
} }
}); );
const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
@@ -74,10 +87,10 @@ async function forkRepository(
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
}); });
if (!response.ok) { if (!response.ok) {
@@ -99,15 +112,15 @@ async function createBranch(
{ {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
ref: fullRef, ref: fullRef,
sha: options.sha sha: options.sha,
}) }),
} }
); );
@@ -126,10 +139,10 @@ async function getDefaultBranchSHA(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`,
{ {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
} }
); );
@@ -138,15 +151,17 @@ async function getDefaultBranchSHA(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`, `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
{ {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
} }
); );
if (!masterResponse.ok) { if (!masterResponse.ok) {
throw new Error("Could not find default branch (tried 'main' and 'master')"); throw new Error(
"Could not find default branch (tried 'main' and 'master')"
);
} }
const data = GitHubReferenceSchema.parse(await masterResponse.json()); const data = GitHubReferenceSchema.parse(await masterResponse.json());
@@ -170,10 +185,10 @@ async function getFileContents(
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
}); });
if (!response.ok) { if (!response.ok) {
@@ -184,7 +199,7 @@ async function getFileContents(
// 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) {
data.content = Buffer.from(data.content, 'base64').toString('utf8'); data.content = Buffer.from(data.content, "base64").toString("utf8");
} }
return data; return data;
@@ -200,12 +215,12 @@ async function createIssue(
{ {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify(options) body: JSON.stringify(options),
} }
); );
@@ -226,12 +241,12 @@ async function createPullRequest(
{ {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify(options) body: JSON.stringify(options),
} }
); );
@@ -251,7 +266,7 @@ async function createOrUpdateFile(
branch: string, branch: string,
sha?: string sha?: string
): Promise<GitHubCreateUpdateFileResponse> { ): Promise<GitHubCreateUpdateFileResponse> {
const encodedContent = Buffer.from(content).toString('base64'); const encodedContent = Buffer.from(content).toString("base64");
let currentSha = sha; let currentSha = sha;
if (!currentSha) { if (!currentSha) {
@@ -261,7 +276,9 @@ async function createOrUpdateFile(
currentSha = existingFile.sha; currentSha = existingFile.sha;
} }
} catch (error) { } catch (error) {
console.error('Note: File does not exist in branch, will create new file'); console.error(
"Note: File does not exist in branch, will create new file"
);
} }
} }
@@ -271,18 +288,18 @@ async function createOrUpdateFile(
message, message,
content: encodedContent, content: encodedContent,
branch, branch,
...(currentSha ? { sha: currentSha } : {}) ...(currentSha ? { sha: currentSha } : {}),
}; };
const response = await fetch(url, { const response = await fetch(url, {
method: "PUT", method: "PUT",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify(body) body: JSON.stringify(body),
}); });
if (!response.ok) { if (!response.ok) {
@@ -298,11 +315,11 @@ async function createTree(
files: FileOperation[], files: FileOperation[],
baseTree?: string baseTree?: string
): Promise<GitHubTree> { ): Promise<GitHubTree> {
const tree = files.map(file => ({ const tree = files.map((file) => ({
path: file.path, path: file.path,
mode: '100644' as const, mode: "100644" as const,
type: 'blob' as const, type: "blob" as const,
content: file.content content: file.content,
})); }));
const response = await fetch( const response = await fetch(
@@ -310,15 +327,15 @@ async function createTree(
{ {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
tree, tree,
base_tree: baseTree base_tree: baseTree,
}) }),
} }
); );
@@ -341,16 +358,16 @@ async function createCommit(
{ {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
message, message,
tree, tree,
parents parents,
}) }),
} }
); );
@@ -372,15 +389,15 @@ async function updateReference(
{ {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
sha, sha,
force: true force: true,
}) }),
} }
); );
@@ -402,10 +419,10 @@ async function pushFiles(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
{ {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
} }
); );
@@ -417,7 +434,9 @@ async function pushFiles(
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);
const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]); const commit = await createCommit(owner, repo, message, tree.sha, [
commitSha,
]);
return await updateReference(owner, repo, `heads/${branch}`, commit.sha); return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
} }
@@ -433,10 +452,10 @@ async function searchRepositories(
const response = await fetch(url.toString(), { const response = await fetch(url.toString(), {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
}); });
if (!response.ok) { if (!response.ok) {
@@ -452,12 +471,12 @@ async function createRepository(
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: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server", "User-Agent": "github-mcp-server",
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
body: JSON.stringify(options) body: JSON.stringify(options),
}); });
if (!response.ok) { if (!response.ok) {
@@ -467,59 +486,155 @@ async function createRepository(
return GitHubRepositorySchema.parse(await response.json()); return GitHubRepositorySchema.parse(await response.json());
} }
async function searchCode(
params: z.infer<typeof SearchCodeSchema>
): Promise<SearchCodeResponse> {
const url = new URL("https://api.github.com/search/code");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchCodeResponseSchema.parse(await response.json());
}
async function searchIssues(
params: z.infer<typeof SearchIssuesSchema>
): Promise<SearchIssuesResponse> {
const url = new URL("https://api.github.com/search/issues");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchIssuesResponseSchema.parse(await response.json());
}
async function searchUsers(
params: z.infer<typeof SearchUsersSchema>
): Promise<SearchUsersResponse> {
const url = new URL("https://api.github.com/search/users");
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.toString());
}
});
const response = await fetch(url.toString(), {
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server",
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return SearchUsersResponseSchema.parse(await response.json());
}
server.setRequestHandler(ListToolsRequestSchema, async () => { server.setRequestHandler(ListToolsRequestSchema, async () => {
return { return {
tools: [ tools: [
{ {
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: zodToJsonSchema(CreateOrUpdateFileSchema) inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema),
}, },
{ {
name: "search_repositories", name: "search_repositories",
description: "Search for GitHub repositories", description: "Search for GitHub repositories",
inputSchema: zodToJsonSchema(SearchRepositoriesSchema) inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
}, },
{ {
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: zodToJsonSchema(CreateRepositorySchema) inputSchema: zodToJsonSchema(CreateRepositorySchema),
}, },
{ {
name: "get_file_contents", name: "get_file_contents",
description: "Get the contents of a file or directory from a GitHub repository", description:
inputSchema: zodToJsonSchema(GetFileContentsSchema) "Get the contents of a file or directory from a GitHub repository",
inputSchema: zodToJsonSchema(GetFileContentsSchema),
}, },
{ {
name: "push_files", name: "push_files",
description: "Push multiple files to a GitHub repository in a single commit", description:
inputSchema: zodToJsonSchema(PushFilesSchema) "Push multiple files to a GitHub repository in a single commit",
inputSchema: zodToJsonSchema(PushFilesSchema),
}, },
{ {
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: zodToJsonSchema(CreateIssueSchema) inputSchema: zodToJsonSchema(CreateIssueSchema),
}, },
{ {
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: zodToJsonSchema(CreatePullRequestSchema) inputSchema: zodToJsonSchema(CreatePullRequestSchema),
}, },
{ {
name: "fork_repository", name: "fork_repository",
description: "Fork a GitHub repository to your account or specified organization", description:
inputSchema: zodToJsonSchema(ForkRepositorySchema) "Fork a GitHub repository to your account or specified organization",
inputSchema: zodToJsonSchema(ForkRepositorySchema),
}, },
{ {
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: zodToJsonSchema(CreateBranchSchema) inputSchema: zodToJsonSchema(CreateBranchSchema),
} },
] {
name: "search_code",
description: "Search for code across GitHub repositories",
inputSchema: zodToJsonSchema(SearchCodeSchema),
},
{
name: "search_issues",
description:
"Search for issues and pull requests across GitHub repositories",
inputSchema: zodToJsonSchema(SearchIssuesSchema),
},
{
name: "search_users",
description: "Search for users on GitHub",
inputSchema: zodToJsonSchema(SearchUsersSchema),
},
],
}; };
}); });
server.setRequestHandler(CallToolRequestSchema, async (request) => { server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
try { try {
if (!request.params.arguments) { if (!request.params.arguments) {
throw new Error("Arguments are required"); throw new Error("Arguments are required");
@@ -528,7 +643,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) { switch (request.params.name) {
case "fork_repository": { case "fork_repository": {
const args = ForkRepositorySchema.parse(request.params.arguments); const args = ForkRepositorySchema.parse(request.params.arguments);
const fork = await forkRepository(args.owner, args.repo, args.organization); const fork = await forkRepository(
args.owner,
args.repo,
args.organization
);
return { toolResult: fork }; return { toolResult: fork };
} }
@@ -540,10 +659,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
`https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`, `https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`,
{ {
headers: { headers: {
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
"Accept": "application/vnd.github.v3+json", Accept: "application/vnd.github.v3+json",
"User-Agent": "github-mcp-server" "User-Agent": "github-mcp-server",
} },
} }
); );
@@ -559,7 +678,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const branch = await createBranch(args.owner, args.repo, { const branch = await createBranch(args.owner, args.repo, {
ref: args.branch, ref: args.branch,
sha sha,
}); });
return { toolResult: branch }; return { toolResult: branch };
@@ -567,7 +686,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "search_repositories": { case "search_repositories": {
const args = SearchRepositoriesSchema.parse(request.params.arguments); const args = SearchRepositoriesSchema.parse(request.params.arguments);
const results = await searchRepositories(args.query, args.page, args.perPage); const results = await searchRepositories(
args.query,
args.page,
args.perPage
);
return { toolResult: results }; return { toolResult: results };
} }
@@ -579,7 +702,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "get_file_contents": { case "get_file_contents": {
const args = GetFileContentsSchema.parse(request.params.arguments); const args = GetFileContentsSchema.parse(request.params.arguments);
const contents = await getFileContents(args.owner, args.repo, args.path, args.branch); const contents = await getFileContents(
args.owner,
args.repo,
args.path,
args.branch
);
return { toolResult: contents }; return { toolResult: contents };
} }
@@ -623,16 +751,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
return { toolResult: pullRequest }; return { toolResult: pullRequest };
} }
case "search_code": {
const args = SearchCodeSchema.parse(request.params.arguments);
const results = await searchCode(args);
return { toolResult: results };
}
case "search_issues": {
const args = SearchIssuesSchema.parse(request.params.arguments);
const results = await searchIssues(args);
return { toolResult: results };
}
case "search_users": {
const args = SearchUsersSchema.parse(request.params.arguments);
const results = await searchUsers(args);
return { toolResult: results };
}
default: default:
throw new Error(`Unknown tool: ${request.params.name}`); throw new Error(`Unknown tool: ${request.params.name}`);
} }
} catch (error) { } catch (error) {
if (error instanceof z.ZodError) { if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); throw new Error(
`Invalid arguments: ${error.errors
.map(
(e: z.ZodError["errors"][number]) =>
`${e.path.join(".")}: ${e.message}`
)
.join(", ")}`
);
} }
throw error; throw error;
} }
}); }
);
async function runServer() { async function runServer() {
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();

View File

@@ -20,8 +20,10 @@
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "0.6.0", "@modelcontextprotocol/sdk": "0.6.0",
"@types/node": "^20.11.0",
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,10 +1,10 @@
import { z } from 'zod'; import { z } from "zod";
// Base schemas for common types // Base schemas for common types
export const GitHubAuthorSchema = z.object({ export const GitHubAuthorSchema = z.object({
name: z.string(), name: z.string(),
email: z.string(), email: z.string(),
date: z.string() date: z.string(),
}); });
// Repository related schemas // Repository related schemas
@@ -15,7 +15,7 @@ export const GitHubOwnerSchema = z.object({
avatar_url: z.string(), avatar_url: z.string(),
url: z.string(), url: z.string(),
html_url: z.string(), html_url: z.string(),
type: z.string() type: z.string(),
}); });
export const GitHubRepositorySchema = z.object({ export const GitHubRepositorySchema = z.object({
@@ -35,7 +35,7 @@ export const GitHubRepositorySchema = z.object({
git_url: z.string(), git_url: z.string(),
ssh_url: z.string(), ssh_url: z.string(),
clone_url: z.string(), clone_url: z.string(),
default_branch: z.string() default_branch: z.string(),
}); });
// File content schemas // File content schemas
@@ -50,7 +50,7 @@ export const GitHubFileContentSchema = z.object({
url: z.string(), url: z.string(),
git_url: z.string(), git_url: z.string(),
html_url: z.string(), html_url: z.string(),
download_url: z.string() download_url: z.string(),
}); });
export const GitHubDirectoryContentSchema = z.object({ export const GitHubDirectoryContentSchema = z.object({
@@ -62,35 +62,35 @@ export const GitHubDirectoryContentSchema = z.object({
url: z.string(), url: z.string(),
git_url: z.string(), git_url: z.string(),
html_url: z.string(), html_url: z.string(),
download_url: z.string().nullable() download_url: z.string().nullable(),
}); });
export const GitHubContentSchema = z.union([ export const GitHubContentSchema = z.union([
GitHubFileContentSchema, GitHubFileContentSchema,
z.array(GitHubDirectoryContentSchema) z.array(GitHubDirectoryContentSchema),
]); ]);
// Operation schemas // Operation schemas
export const FileOperationSchema = z.object({ export const FileOperationSchema = z.object({
path: z.string(), path: z.string(),
content: z.string() content: z.string(),
}); });
// Tree and commit schemas // Tree and commit schemas
export const GitHubTreeEntrySchema = z.object({ export const GitHubTreeEntrySchema = z.object({
path: z.string(), path: z.string(),
mode: z.enum(['100644', '100755', '040000', '160000', '120000']), mode: z.enum(["100644", "100755", "040000", "160000", "120000"]),
type: z.enum(['blob', 'tree', 'commit']), type: z.enum(["blob", "tree", "commit"]),
size: z.number().optional(), size: z.number().optional(),
sha: z.string(), sha: z.string(),
url: z.string() url: z.string(),
}); });
export const GitHubTreeSchema = z.object({ export const GitHubTreeSchema = z.object({
sha: z.string(), sha: z.string(),
url: z.string(), url: z.string(),
tree: z.array(GitHubTreeEntrySchema), tree: z.array(GitHubTreeEntrySchema),
truncated: z.boolean() truncated: z.boolean(),
}); });
export const GitHubCommitSchema = z.object({ export const GitHubCommitSchema = z.object({
@@ -102,12 +102,14 @@ export const GitHubCommitSchema = z.object({
message: z.string(), message: z.string(),
tree: z.object({ tree: z.object({
sha: z.string(), sha: z.string(),
url: z.string() url: z.string(),
}), }),
parents: z.array(z.object({ parents: z.array(
z.object({
sha: z.string(), sha: z.string(),
url: z.string() url: z.string(),
})) })
),
}); });
// Reference schema // Reference schema
@@ -118,8 +120,8 @@ export const GitHubReferenceSchema = z.object({
object: z.object({ object: z.object({
sha: z.string(), sha: z.string(),
type: z.string(), type: z.string(),
url: z.string() url: z.string(),
}) }),
}); });
// Input schemas for operations // Input schemas for operations
@@ -127,7 +129,7 @@ export const CreateRepositoryOptionsSchema = z.object({
name: z.string(), name: z.string(),
description: z.string().optional(), description: z.string().optional(),
private: z.boolean().optional(), private: z.boolean().optional(),
auto_init: z.boolean().optional() auto_init: z.boolean().optional(),
}); });
export const CreateIssueOptionsSchema = z.object({ export const CreateIssueOptionsSchema = z.object({
@@ -135,7 +137,7 @@ export const CreateIssueOptionsSchema = z.object({
body: z.string().optional(), body: z.string().optional(),
assignees: z.array(z.string()).optional(), assignees: z.array(z.string()).optional(),
milestone: z.number().optional(), milestone: z.number().optional(),
labels: z.array(z.string()).optional() labels: z.array(z.string()).optional(),
}); });
export const CreatePullRequestOptionsSchema = z.object({ export const CreatePullRequestOptionsSchema = z.object({
@@ -144,12 +146,12 @@ export const CreatePullRequestOptionsSchema = z.object({
head: z.string(), head: z.string(),
base: z.string(), base: z.string(),
maintainer_can_modify: z.boolean().optional(), maintainer_can_modify: z.boolean().optional(),
draft: z.boolean().optional() draft: z.boolean().optional(),
}); });
export const CreateBranchOptionsSchema = z.object({ export const CreateBranchOptionsSchema = z.object({
ref: z.string(), ref: z.string(),
sha: z.string() sha: z.string(),
}); });
// Response schemas for operations // Response schemas for operations
@@ -165,20 +167,22 @@ export const GitHubCreateUpdateFileResponseSchema = z.object({
message: z.string(), message: z.string(),
tree: z.object({ tree: z.object({
sha: z.string(), sha: z.string(),
url: z.string() url: z.string(),
}), }),
parents: z.array(z.object({ parents: z.array(
z.object({
sha: z.string(), sha: z.string(),
url: z.string(), url: z.string(),
html_url: z.string() html_url: z.string(),
}))
}) })
),
}),
}); });
export const GitHubSearchResponseSchema = z.object({ export const GitHubSearchResponseSchema = z.object({
total_count: z.number(), total_count: z.number(),
incomplete_results: z.boolean(), incomplete_results: z.boolean(),
items: z.array(GitHubRepositorySchema) items: z.array(GitHubRepositorySchema),
}); });
// Fork related schemas // Fork related schemas
@@ -188,14 +192,14 @@ export const GitHubForkParentSchema = z.object({
owner: z.object({ owner: z.object({
login: z.string(), login: z.string(),
id: z.number(), id: z.number(),
avatar_url: z.string() avatar_url: z.string(),
}), }),
html_url: z.string() html_url: z.string(),
}); });
export const GitHubForkSchema = GitHubRepositorySchema.extend({ export const GitHubForkSchema = GitHubRepositorySchema.extend({
parent: GitHubForkParentSchema, parent: GitHubForkParentSchema,
source: GitHubForkParentSchema source: GitHubForkParentSchema,
}); });
// Issue related schemas // Issue related schemas
@@ -206,7 +210,7 @@ export const GitHubLabelSchema = z.object({
name: z.string(), name: z.string(),
color: z.string(), color: z.string(),
default: z.boolean(), default: z.boolean(),
description: z.string().optional() description: z.string().optional(),
}); });
export const GitHubIssueAssigneeSchema = z.object({ export const GitHubIssueAssigneeSchema = z.object({
@@ -214,7 +218,7 @@ export const GitHubIssueAssigneeSchema = z.object({
id: z.number(), id: z.number(),
avatar_url: z.string(), avatar_url: z.string(),
url: z.string(), url: z.string(),
html_url: z.string() html_url: z.string(),
}); });
export const GitHubMilestoneSchema = z.object({ export const GitHubMilestoneSchema = z.object({
@@ -226,7 +230,7 @@ export const GitHubMilestoneSchema = z.object({
number: z.number(), number: z.number(),
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
state: z.string() state: z.string(),
}); });
export const GitHubIssueSchema = z.object({ export const GitHubIssueSchema = z.object({
@@ -251,7 +255,7 @@ export const GitHubIssueSchema = z.object({
created_at: z.string(), created_at: z.string(),
updated_at: z.string(), updated_at: z.string(),
closed_at: z.string().nullable(), closed_at: z.string().nullable(),
body: z.string() body: z.string(),
}); });
// Pull Request related schemas // Pull Request related schemas
@@ -260,7 +264,7 @@ export const GitHubPullRequestHeadSchema = z.object({
ref: z.string(), ref: z.string(),
sha: z.string(), sha: z.string(),
user: GitHubIssueAssigneeSchema, user: GitHubIssueAssigneeSchema,
repo: GitHubRepositorySchema repo: GitHubRepositorySchema,
}); });
export const GitHubPullRequestSchema = z.object({ export const GitHubPullRequestSchema = z.object({
@@ -285,12 +289,12 @@ export const GitHubPullRequestSchema = z.object({
assignee: GitHubIssueAssigneeSchema.nullable(), assignee: GitHubIssueAssigneeSchema.nullable(),
assignees: z.array(GitHubIssueAssigneeSchema), assignees: z.array(GitHubIssueAssigneeSchema),
head: GitHubPullRequestHeadSchema, head: GitHubPullRequestHeadSchema,
base: GitHubPullRequestHeadSchema base: GitHubPullRequestHeadSchema,
}); });
const RepoParamsSchema = z.object({ const RepoParamsSchema = z.object({
owner: z.string().describe("Repository owner (username or organization)"), owner: z.string().describe("Repository owner (username or organization)"),
repo: z.string().describe("Repository name") repo: z.string().describe("Repository name"),
}); });
export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({ export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
@@ -298,81 +302,288 @@ export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
content: z.string().describe("Content of the file"), content: z.string().describe("Content of the file"),
message: z.string().describe("Commit message"), message: z.string().describe("Commit message"),
branch: z.string().describe("Branch to create/update the file in"), branch: z.string().describe("Branch to create/update the file in"),
sha: z.string().optional() sha: z
.describe("SHA of the file being replaced (required when updating existing files)") .string()
.optional()
.describe(
"SHA of the file being replaced (required when updating existing files)"
),
}); });
export const SearchRepositoriesSchema = z.object({ export const SearchRepositoriesSchema = z.object({
query: z.string().describe("Search query (see GitHub search syntax)"), query: z.string().describe("Search query (see GitHub search syntax)"),
page: z.number().optional().describe("Page number for pagination (default: 1)"), page: z
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)") .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({ export const CreateRepositorySchema = z.object({
name: z.string().describe("Repository name"), name: z.string().describe("Repository name"),
description: z.string().optional().describe("Repository description"), description: z.string().optional().describe("Repository description"),
private: z.boolean().optional().describe("Whether the repository should be private"), private: z
autoInit: z.boolean().optional().describe("Initialize with README.md") .boolean()
.optional()
.describe("Whether the repository should be private"),
autoInit: z.boolean().optional().describe("Initialize with README.md"),
}); });
export const GetFileContentsSchema = RepoParamsSchema.extend({ export const GetFileContentsSchema = RepoParamsSchema.extend({
path: z.string().describe("Path to the file or directory"), path: z.string().describe("Path to the file or directory"),
branch: z.string().optional().describe("Branch to get contents from") branch: z.string().optional().describe("Branch to get contents from"),
}); });
export const PushFilesSchema = RepoParamsSchema.extend({ export const PushFilesSchema = RepoParamsSchema.extend({
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"), branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
files: z.array(z.object({ files: z
.array(
z.object({
path: z.string().describe("Path where to create the file"), path: z.string().describe("Path where to create the file"),
content: z.string().describe("Content of the file") content: z.string().describe("Content of the file"),
})).describe("Array of files to push"), })
message: z.string().describe("Commit message") )
.describe("Array of files to push"),
message: z.string().describe("Commit message"),
}); });
export const CreateIssueSchema = RepoParamsSchema.extend({ export const CreateIssueSchema = RepoParamsSchema.extend({
title: z.string().describe("Issue title"), title: z.string().describe("Issue title"),
body: z.string().optional().describe("Issue body/description"), body: z.string().optional().describe("Issue body/description"),
assignees: z.array(z.string()).optional().describe("Array of usernames to assign"), assignees: z
.array(z.string())
.optional()
.describe("Array of usernames to assign"),
labels: z.array(z.string()).optional().describe("Array of label names"), labels: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.number().optional().describe("Milestone number to assign") milestone: z.number().optional().describe("Milestone number to assign"),
}); });
export const CreatePullRequestSchema = RepoParamsSchema.extend({ export const CreatePullRequestSchema = RepoParamsSchema.extend({
title: z.string().describe("Pull request title"), title: z.string().describe("Pull request title"),
body: z.string().optional().describe("Pull request body/description"), body: z.string().optional().describe("Pull request body/description"),
head: z.string().describe("The name of the branch where your changes are implemented"), head: z
base: z.string().describe("The name of the branch you want the changes pulled into"), .string()
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"), .describe("The name of the branch where your changes are implemented"),
maintainer_can_modify: z.boolean().optional() base: z
.describe("Whether maintainers can modify the pull request") .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({ export const ForkRepositorySchema = RepoParamsSchema.extend({
organization: z.string().optional() organization: z
.describe("Optional: organization to fork to (defaults to your personal account)") .string()
.optional()
.describe(
"Optional: organization to fork to (defaults to your personal account)"
),
}); });
export const CreateBranchSchema = RepoParamsSchema.extend({ export const CreateBranchSchema = RepoParamsSchema.extend({
branch: z.string().describe("Name for the new branch"), branch: z.string().describe("Name for the new branch"),
from_branch: z.string().optional() from_branch: z
.describe("Optional: source branch to create from (defaults to the repository's default branch)") .string()
.optional()
.describe(
"Optional: source branch to create from (defaults to the repository's default branch)"
),
});
// Search Response Schemas
export const SearchCodeItemSchema = z.object({
name: z.string(),
path: z.string(),
sha: z.string(),
url: z.string(),
git_url: z.string(),
html_url: z.string(),
repository: GitHubRepositorySchema,
score: z.number(),
});
export const SearchCodeResponseSchema = z.object({
total_count: z.number(),
incomplete_results: z.boolean(),
items: z.array(SearchCodeItemSchema),
});
export const SearchIssueItemSchema = 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),
comments: z.number(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
body: z.string(),
score: z.number(),
pull_request: z
.object({
url: z.string(),
html_url: z.string(),
diff_url: z.string(),
patch_url: z.string(),
})
.optional(),
});
export const SearchIssuesResponseSchema = z.object({
total_count: z.number(),
incomplete_results: z.boolean(),
items: z.array(SearchIssueItemSchema),
});
export const SearchUserItemSchema = z.object({
login: z.string(),
id: z.number(),
node_id: z.string(),
avatar_url: z.string(),
gravatar_id: z.string(),
url: z.string(),
html_url: z.string(),
followers_url: z.string(),
following_url: z.string(),
gists_url: z.string(),
starred_url: z.string(),
subscriptions_url: z.string(),
organizations_url: z.string(),
repos_url: z.string(),
events_url: z.string(),
received_events_url: z.string(),
type: z.string(),
site_admin: z.boolean(),
score: z.number(),
});
export const SearchUsersResponseSchema = z.object({
total_count: z.number(),
incomplete_results: z.boolean(),
items: z.array(SearchUserItemSchema),
});
// Search Input Schemas
export const SearchCodeSchema = z.object({
q: z.string().describe("Search query (see GitHub code search syntax)"),
sort: z
.enum(["", "indexed"])
.optional()
.describe("Sort field (only 'indexed' is supported)"),
order: z
.enum(["asc", "desc"])
.optional()
.describe("Sort order (asc or desc)"),
per_page: z
.number()
.min(1)
.max(100)
.optional()
.describe("Results per page (max 100)"),
page: z.number().min(1).optional().describe("Page number"),
});
export const SearchIssuesSchema = z.object({
q: z.string().describe("Search query (see GitHub issues search syntax)"),
sort: z
.enum([
"comments",
"reactions",
"reactions-+1",
"reactions--1",
"reactions-smile",
"reactions-thinking_face",
"reactions-heart",
"reactions-tada",
"interactions",
"created",
"updated",
])
.optional()
.describe("Sort field"),
order: z
.enum(["asc", "desc"])
.optional()
.describe("Sort order (asc or desc)"),
per_page: z
.number()
.min(1)
.max(100)
.optional()
.describe("Results per page (max 100)"),
page: z.number().min(1).optional().describe("Page number"),
});
export const SearchUsersSchema = z.object({
q: z.string().describe("Search query (see GitHub users search syntax)"),
sort: z
.enum(["followers", "repositories", "joined"])
.optional()
.describe("Sort field"),
order: z
.enum(["asc", "desc"])
.optional()
.describe("Sort order (asc or desc)"),
per_page: z
.number()
.min(1)
.max(100)
.optional()
.describe("Results per page (max 100)"),
page: z.number().min(1).optional().describe("Page number"),
}); });
// Export types // Export types
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>; export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
export type GitHubFork = z.infer<typeof GitHubForkSchema>; export type GitHubFork = z.infer<typeof GitHubForkSchema>;
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>; export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>; export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;
export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>; export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
export type GitHubDirectoryContent = z.infer<typeof GitHubDirectoryContentSchema>; export type GitHubDirectoryContent = z.infer<
typeof GitHubDirectoryContentSchema
>;
export type GitHubContent = z.infer<typeof GitHubContentSchema>; export type GitHubContent = z.infer<typeof GitHubContentSchema>;
export type FileOperation = z.infer<typeof FileOperationSchema>; export type FileOperation = z.infer<typeof FileOperationSchema>;
export type GitHubTree = z.infer<typeof GitHubTreeSchema>; export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>; export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>; export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>; export type CreateRepositoryOptions = z.infer<
typeof CreateRepositoryOptionsSchema
>;
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>; export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
export type CreatePullRequestOptions = z.infer<typeof CreatePullRequestOptionsSchema>; export type CreatePullRequestOptions = z.infer<
typeof CreatePullRequestOptionsSchema
>;
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>; export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
export type GitHubCreateUpdateFileResponse = z.infer<typeof GitHubCreateUpdateFileResponseSchema>; export type GitHubCreateUpdateFileResponse = z.infer<
typeof GitHubCreateUpdateFileResponseSchema
>;
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>; export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;
export type SearchCodeItem = z.infer<typeof SearchCodeItemSchema>;
export type SearchCodeResponse = z.infer<typeof SearchCodeResponseSchema>;
export type SearchIssueItem = z.infer<typeof SearchIssueItemSchema>;
export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>;
export type SearchUserItem = z.infer<typeof SearchUserItemSchema>;
export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;