From 4ac78a996cb4ec4e1af161e390de4af4645bca76 Mon Sep 17 00:00:00 2001 From: Raduan77 Date: Thu, 28 Nov 2024 09:51:03 +0100 Subject: [PATCH] code for search in github --- src/github/index.ts | 558 +++++++++++++++++++++++++--------------- src/github/package.json | 2 + src/github/schemas.ts | 351 ++++++++++++++++++++----- 3 files changed, 639 insertions(+), 272 deletions(-) diff --git a/src/github/index.ts b/src/github/index.ts index 0676a34c..bddd9f46 100644 --- a/src/github/index.ts +++ b/src/github/index.ts @@ -41,19 +41,32 @@ import { CreateIssueSchema, CreatePullRequestSchema, ForkRepositorySchema, - CreateBranchSchema -} from './schemas.js'; -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; + CreateBranchSchema, + SearchCodeSchema, + SearchIssuesSchema, + 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({ - name: "github-mcp-server", - version: "0.1.0", -}, { - capabilities: { - tools: {} +const server = new Server( + { + name: "github-mcp-server", + version: "0.1.0", + }, + { + capabilities: { + tools: {}, + }, } -}); +); const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; @@ -67,17 +80,17 @@ async function forkRepository( repo: string, organization?: string ): Promise { - const url = organization + const url = organization ? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}` : `https://api.github.com/repos/${owner}/${repo}/forks`; const response = await fetch(url, { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, }); if (!response.ok) { @@ -93,21 +106,21 @@ async function createBranch( options: z.infer ): Promise { const fullRef = `refs/heads/${options.ref}`; - + const response = await fetch( `https://api.github.com/repos/${owner}/${repo}/git/refs`, { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify({ ref: fullRef, - sha: options.sha - }) + sha: options.sha, + }), } ); @@ -126,10 +139,10 @@ async function getDefaultBranchSHA( `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, { headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, } ); @@ -138,15 +151,17 @@ async function getDefaultBranchSHA( `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`, { headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, } ); if (!masterResponse.ok) { - throw new Error("Could not find default branch (tried 'main' and 'master')"); + throw new Error( + "Could not find default branch (tried 'main' and 'master')" + ); } const data = GitHubReferenceSchema.parse(await masterResponse.json()); @@ -170,10 +185,10 @@ async function getFileContents( const response = await fetch(url, { headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, }); if (!response.ok) { @@ -184,7 +199,7 @@ async function getFileContents( // If it's a file, decode the content if (!Array.isArray(data) && data.content) { - data.content = Buffer.from(data.content, 'base64').toString('utf8'); + data.content = Buffer.from(data.content, "base64").toString("utf8"); } return data; @@ -200,12 +215,12 @@ async function createIssue( { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(options) + body: JSON.stringify(options), } ); @@ -226,12 +241,12 @@ async function createPullRequest( { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(options) + body: JSON.stringify(options), } ); @@ -251,7 +266,7 @@ async function createOrUpdateFile( branch: string, sha?: string ): Promise { - const encodedContent = Buffer.from(content).toString('base64'); + const encodedContent = Buffer.from(content).toString("base64"); let currentSha = sha; if (!currentSha) { @@ -261,28 +276,30 @@ async function createOrUpdateFile( currentSha = existingFile.sha; } } catch (error) { - console.error('Note: File does not exist in branch, will create new file'); + console.error( + "Note: File does not exist in branch, will create new file" + ); } } const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`; - + const body = { message, content: encodedContent, branch, - ...(currentSha ? { sha: currentSha } : {}) + ...(currentSha ? { sha: currentSha } : {}), }; const response = await fetch(url, { method: "PUT", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(body) + body: JSON.stringify(body), }); if (!response.ok) { @@ -298,11 +315,11 @@ async function createTree( files: FileOperation[], baseTree?: string ): Promise { - const tree = files.map(file => ({ + const tree = files.map((file) => ({ path: file.path, - mode: '100644' as const, - type: 'blob' as const, - content: file.content + mode: "100644" as const, + type: "blob" as const, + content: file.content, })); const response = await fetch( @@ -310,15 +327,15 @@ async function createTree( { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify({ tree, - base_tree: baseTree - }) + base_tree: baseTree, + }), } ); @@ -341,16 +358,16 @@ async function createCommit( { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify({ message, tree, - parents - }) + parents, + }), } ); @@ -372,15 +389,15 @@ async function updateReference( { method: "PATCH", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify({ sha, - force: true - }) + force: true, + }), } ); @@ -402,10 +419,10 @@ async function pushFiles( `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, } ); @@ -417,7 +434,9 @@ async function pushFiles( const commitSha = ref.object.sha; const tree = await createTree(owner, repo, files, commitSha); - const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]); + const commit = await createCommit(owner, repo, message, tree.sha, [ + commitSha, + ]); return await updateReference(owner, repo, `heads/${branch}`, commit.sha); } @@ -433,10 +452,10 @@ async function searchRepositories( const response = await fetch(url.toString(), { headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", - "User-Agent": "github-mcp-server" - } + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, }); if (!response.ok) { @@ -452,12 +471,12 @@ async function createRepository( const response = await fetch("https://api.github.com/user/repos", { method: "POST", headers: { - "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, - "Accept": "application/vnd.github.v3+json", + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", "User-Agent": "github-mcp-server", - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(options) + body: JSON.stringify(options), }); if (!response.ok) { @@ -467,172 +486,307 @@ async function createRepository( return GitHubRepositorySchema.parse(await response.json()); } +async function searchCode( + params: z.infer +): Promise { + 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 +): Promise { + 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 +): Promise { + const url = new URL("https://api.github.com/search/users"); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + url.searchParams.append(key, value.toString()); + } + }); + + const response = await fetch(url.toString(), { + headers: { + Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + Accept: "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + }, + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return SearchUsersResponseSchema.parse(await response.json()); +} + server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_or_update_file", description: "Create or update a single file in a GitHub repository", - inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema) + inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema), }, { name: "search_repositories", description: "Search for GitHub repositories", - inputSchema: zodToJsonSchema(SearchRepositoriesSchema) + inputSchema: zodToJsonSchema(SearchRepositoriesSchema), }, { name: "create_repository", description: "Create a new GitHub repository in your account", - inputSchema: zodToJsonSchema(CreateRepositorySchema) + inputSchema: zodToJsonSchema(CreateRepositorySchema), }, { name: "get_file_contents", - description: "Get the contents of a file or directory from a GitHub repository", - inputSchema: zodToJsonSchema(GetFileContentsSchema) + description: + "Get the contents of a file or directory from a GitHub repository", + inputSchema: zodToJsonSchema(GetFileContentsSchema), }, { name: "push_files", - description: "Push multiple files to a GitHub repository in a single commit", - inputSchema: zodToJsonSchema(PushFilesSchema) + description: + "Push multiple files to a GitHub repository in a single commit", + inputSchema: zodToJsonSchema(PushFilesSchema), }, { name: "create_issue", description: "Create a new issue in a GitHub repository", - inputSchema: zodToJsonSchema(CreateIssueSchema) + inputSchema: zodToJsonSchema(CreateIssueSchema), }, { name: "create_pull_request", description: "Create a new pull request in a GitHub repository", - inputSchema: zodToJsonSchema(CreatePullRequestSchema) + inputSchema: zodToJsonSchema(CreatePullRequestSchema), }, { name: "fork_repository", - description: "Fork a GitHub repository to your account or specified organization", - inputSchema: zodToJsonSchema(ForkRepositorySchema) + description: + "Fork a GitHub repository to your account or specified organization", + inputSchema: zodToJsonSchema(ForkRepositorySchema), }, { name: "create_branch", description: "Create a new branch in a GitHub repository", - inputSchema: zodToJsonSchema(CreateBranchSchema) - } - ] + inputSchema: zodToJsonSchema(CreateBranchSchema), + }, + { + name: "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) => { - try { - if (!request.params.arguments) { - throw new Error("Arguments are required"); - } - - switch (request.params.name) { - case "fork_repository": { - const args = ForkRepositorySchema.parse(request.params.arguments); - const fork = await forkRepository(args.owner, args.repo, args.organization); - return { toolResult: fork }; +server.setRequestHandler( + CallToolRequestSchema, + async (request: CallToolRequest) => { + try { + if (!request.params.arguments) { + throw new Error("Arguments are required"); } - case "create_branch": { - 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" - } - } + switch (request.params.name) { + case "fork_repository": { + const args = ForkRepositorySchema.parse(request.params.arguments); + const fork = await forkRepository( + args.owner, + args.repo, + args.organization ); - - if (!response.ok) { - throw new Error(`Source branch '${args.from_branch}' not found`); - } - - const data = GitHubReferenceSchema.parse(await response.json()); - sha = data.object.sha; - } else { - sha = await getDefaultBranchSHA(args.owner, args.repo); + return { toolResult: fork }; } - const branch = await createBranch(args.owner, args.repo, { - ref: args.branch, - sha - }); + case "create_branch": { + 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", + }, + } + ); - return { toolResult: branch }; + if (!response.ok) { + throw new Error(`Source branch '${args.from_branch}' not found`); + } + + const data = GitHubReferenceSchema.parse(await response.json()); + sha = data.object.sha; + } else { + sha = await getDefaultBranchSHA(args.owner, args.repo); + } + + const branch = await createBranch(args.owner, args.repo, { + ref: args.branch, + sha, + }); + + return { toolResult: branch }; + } + + case "search_repositories": { + const args = SearchRepositoriesSchema.parse(request.params.arguments); + const results = await searchRepositories( + args.query, + args.page, + args.perPage + ); + return { toolResult: results }; + } + + 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 }; + } + + 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: + throw new Error(`Unknown tool: ${request.params.name}`); } - - case "search_repositories": { - const args = SearchRepositoriesSchema.parse(request.params.arguments); - const results = await searchRepositories(args.query, args.page, args.perPage); - return { toolResult: results }; - } - - 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 + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error( + `Invalid arguments: ${error.errors + .map( + (e: z.ZodError["errors"][number]) => + `${e.path.join(".")}: ${e.message}` + ) + .join(", ")}` ); - 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}`); + throw error; } - } catch (error) { - if (error instanceof z.ZodError) { - throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); - } - throw error; } -}); +); async function runServer() { const transport = new StdioServerTransport(); @@ -643,4 +797,4 @@ async function runServer() { runServer().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/src/github/package.json b/src/github/package.json index bc7710e4..25c52b72 100644 --- a/src/github/package.json +++ b/src/github/package.json @@ -20,8 +20,10 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "0.6.0", + "@types/node": "^20.11.0", "@types/node-fetch": "^2.6.12", "node-fetch": "^3.3.2", + "zod": "^3.22.4", "zod-to-json-schema": "^3.23.5" }, "devDependencies": { diff --git a/src/github/schemas.ts b/src/github/schemas.ts index 213458eb..7acb0ab4 100644 --- a/src/github/schemas.ts +++ b/src/github/schemas.ts @@ -1,10 +1,10 @@ -import { z } from 'zod'; +import { z } from "zod"; // Base schemas for common types export const GitHubAuthorSchema = z.object({ name: z.string(), email: z.string(), - date: z.string() + date: z.string(), }); // Repository related schemas @@ -15,7 +15,7 @@ export const GitHubOwnerSchema = z.object({ avatar_url: z.string(), url: z.string(), html_url: z.string(), - type: z.string() + type: z.string(), }); export const GitHubRepositorySchema = z.object({ @@ -35,7 +35,7 @@ export const GitHubRepositorySchema = z.object({ git_url: z.string(), ssh_url: z.string(), clone_url: z.string(), - default_branch: z.string() + default_branch: z.string(), }); // File content schemas @@ -50,7 +50,7 @@ export const GitHubFileContentSchema = z.object({ url: z.string(), git_url: z.string(), html_url: z.string(), - download_url: z.string() + download_url: z.string(), }); export const GitHubDirectoryContentSchema = z.object({ @@ -62,35 +62,35 @@ export const GitHubDirectoryContentSchema = z.object({ url: z.string(), git_url: z.string(), html_url: z.string(), - download_url: z.string().nullable() + download_url: z.string().nullable(), }); export const GitHubContentSchema = z.union([ GitHubFileContentSchema, - z.array(GitHubDirectoryContentSchema) + z.array(GitHubDirectoryContentSchema), ]); // Operation schemas export const FileOperationSchema = z.object({ path: z.string(), - content: 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']), + mode: z.enum(["100644", "100755", "040000", "160000", "120000"]), + type: z.enum(["blob", "tree", "commit"]), size: z.number().optional(), sha: z.string(), - url: z.string() + url: z.string(), }); export const GitHubTreeSchema = z.object({ sha: z.string(), url: z.string(), tree: z.array(GitHubTreeEntrySchema), - truncated: z.boolean() + truncated: z.boolean(), }); export const GitHubCommitSchema = z.object({ @@ -102,12 +102,14 @@ export const GitHubCommitSchema = z.object({ message: z.string(), tree: z.object({ sha: z.string(), - url: z.string() + url: z.string(), }), - parents: z.array(z.object({ - sha: z.string(), - url: z.string() - })) + parents: z.array( + z.object({ + sha: z.string(), + url: z.string(), + }) + ), }); // Reference schema @@ -118,8 +120,8 @@ export const GitHubReferenceSchema = z.object({ object: z.object({ sha: z.string(), type: z.string(), - url: z.string() - }) + url: z.string(), + }), }); // Input schemas for operations @@ -127,7 +129,7 @@ export const CreateRepositoryOptionsSchema = z.object({ name: z.string(), description: z.string().optional(), private: z.boolean().optional(), - auto_init: z.boolean().optional() + auto_init: z.boolean().optional(), }); export const CreateIssueOptionsSchema = z.object({ @@ -135,7 +137,7 @@ export const CreateIssueOptionsSchema = z.object({ body: z.string().optional(), assignees: z.array(z.string()).optional(), milestone: z.number().optional(), - labels: z.array(z.string()).optional() + labels: z.array(z.string()).optional(), }); export const CreatePullRequestOptionsSchema = z.object({ @@ -144,12 +146,12 @@ export const CreatePullRequestOptionsSchema = z.object({ head: z.string(), base: z.string(), maintainer_can_modify: z.boolean().optional(), - draft: z.boolean().optional() + draft: z.boolean().optional(), }); export const CreateBranchOptionsSchema = z.object({ ref: z.string(), - sha: z.string() + sha: z.string(), }); // Response schemas for operations @@ -164,21 +166,23 @@ export const GitHubCreateUpdateFileResponseSchema = z.object({ 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() - })) - }) + }), + 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) + items: z.array(GitHubRepositorySchema), }); // Fork related schemas @@ -188,14 +192,14 @@ export const GitHubForkParentSchema = z.object({ owner: z.object({ login: z.string(), id: z.number(), - avatar_url: z.string() + avatar_url: z.string(), }), - html_url: z.string() + html_url: z.string(), }); export const GitHubForkSchema = GitHubRepositorySchema.extend({ parent: GitHubForkParentSchema, - source: GitHubForkParentSchema + source: GitHubForkParentSchema, }); // Issue related schemas @@ -206,7 +210,7 @@ export const GitHubLabelSchema = z.object({ name: z.string(), color: z.string(), default: z.boolean(), - description: z.string().optional() + description: z.string().optional(), }); export const GitHubIssueAssigneeSchema = z.object({ @@ -214,7 +218,7 @@ export const GitHubIssueAssigneeSchema = z.object({ id: z.number(), avatar_url: z.string(), url: z.string(), - html_url: z.string() + html_url: z.string(), }); export const GitHubMilestoneSchema = z.object({ @@ -226,7 +230,7 @@ export const GitHubMilestoneSchema = z.object({ number: z.number(), title: z.string(), description: z.string(), - state: z.string() + state: z.string(), }); export const GitHubIssueSchema = z.object({ @@ -251,7 +255,7 @@ export const GitHubIssueSchema = z.object({ created_at: z.string(), updated_at: z.string(), closed_at: z.string().nullable(), - body: z.string() + body: z.string(), }); // Pull Request related schemas @@ -260,7 +264,7 @@ export const GitHubPullRequestHeadSchema = z.object({ ref: z.string(), sha: z.string(), user: GitHubIssueAssigneeSchema, - repo: GitHubRepositorySchema + repo: GitHubRepositorySchema, }); export const GitHubPullRequestSchema = z.object({ @@ -285,12 +289,12 @@ export const GitHubPullRequestSchema = z.object({ assignee: GitHubIssueAssigneeSchema.nullable(), assignees: z.array(GitHubIssueAssigneeSchema), head: GitHubPullRequestHeadSchema, - base: GitHubPullRequestHeadSchema + base: GitHubPullRequestHeadSchema, }); const RepoParamsSchema = z.object({ 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({ @@ -298,81 +302,288 @@ export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({ 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)") + 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)") + 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") + 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") + 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") + 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"), + 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") + 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") + 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)") + 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)") + from_branch: z + .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 type GitHubAuthor = z.infer; export type GitHubFork = z.infer; export type GitHubIssue = z.infer; -export type GitHubPullRequest = z.infer;export type GitHubRepository = z.infer; +export type GitHubPullRequest = z.infer; +export type GitHubRepository = z.infer; export type GitHubFileContent = z.infer; -export type GitHubDirectoryContent = z.infer; +export type GitHubDirectoryContent = z.infer< + typeof GitHubDirectoryContentSchema +>; export type GitHubContent = z.infer; export type FileOperation = z.infer; export type GitHubTree = z.infer; export type GitHubCommit = z.infer; export type GitHubReference = z.infer; -export type CreateRepositoryOptions = z.infer; +export type CreateRepositoryOptions = z.infer< + typeof CreateRepositoryOptionsSchema +>; export type CreateIssueOptions = z.infer; -export type CreatePullRequestOptions = z.infer; +export type CreatePullRequestOptions = z.infer< + typeof CreatePullRequestOptionsSchema +>; export type CreateBranchOptions = z.infer; -export type GitHubCreateUpdateFileResponse = z.infer; -export type GitHubSearchResponse = z.infer; \ No newline at end of file +export type GitHubCreateUpdateFileResponse = z.infer< + typeof GitHubCreateUpdateFileResponseSchema +>; +export type GitHubSearchResponse = z.infer; +export type SearchCodeItem = z.infer; +export type SearchCodeResponse = z.infer; +export type SearchIssueItem = z.infer; +export type SearchIssuesResponse = z.infer; +export type SearchUserItem = z.infer; +export type SearchUsersResponse = z.infer;