# Directory Structure ``` ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # GitHub Integration 2 | 3 | The GitHub MCP server provides functionality to extract diffs from Pull Requests. 4 | 5 | ## Available Tools 6 | 7 | ### get_diff_pr 8 | Retrieves the diff content from a GitHub Pull Request. 9 | 10 | **Parameters**: 11 | - `owner`: Repository owner/organization name 12 | - `repo`: Repository name 13 | - `pr_number`: Pull Request number 14 | 15 | **Returns**: Object containing: 16 | - `content`: String containing the PR diff 17 | 18 | ## Authentication 19 | 20 | **Required**: Set the GitHub Personal Access Token as an environment variable: 21 | ```bash 22 | export GITHUB_TOKEN=<your-github-token> 23 | ``` 24 | 25 | The token needs at least `repo` scope permissions to access private repositories. For public repositories, a token with `public_repo` scope is sufficient. 26 | 27 | ## Error Handling 28 | 29 | The server implements standard error handling: 30 | - Missing/invalid token returns `ErrorCode.AuthenticationError` 31 | - Invalid repository details return `ErrorCode.InvalidParams` 32 | - Non-existent PR returns `ErrorCode.NotFound` 33 | - Failed diff fetches return formatted error messages 34 | - Graceful shutdown on SIGINT 35 | 36 | ## Technical Details 37 | 38 | - Built using the Highlight AI MCP SDK 39 | - Uses GitHub REST API v3 40 | - Input validation via Zod 41 | - Runs as a stdio-based MCP server 42 | - Supports Node.js >=18.0.0 43 | 44 | ## Limitations 45 | 46 | - Rate limits apply based on GitHub API restrictions 47 | - Large diffs may be truncated according to GitHub API limits 48 | - Token requires appropriate repository access permissions 49 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | // "module": "Node16", 5 | // "moduleResolution": "Node16", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "outDir": ".", 12 | "rootDir": ".", 13 | "moduleResolution": "Node", 14 | "module": "CommonJS" 15 | }, 16 | "include": [ 17 | "**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "github-mcp-server", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "scripts": { 6 | "build": "tsc", 7 | "test": "jest", 8 | "start": "node dist/index.js" 9 | }, 10 | "dependencies": { 11 | "@highlight-ai/mcp-sdk": "^0.0.7", 12 | "@octokit/rest": "^21.1.0", 13 | "zod-to-json-schema": "^3.23.5" 14 | }, 15 | "devDependencies": { 16 | "@types/jest": "^29.5.11", 17 | "@types/node": "^20.10.5", 18 | "jest": "^29.7.0", 19 | "ts-jest": "^29.1.1", 20 | "typescript": "^5.3.3" 21 | } 22 | } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@highlight-ai/mcp-sdk/server/index.js' 3 | import { StdioServerTransport } from '@highlight-ai/mcp-sdk/server/stdio.js' 4 | import { 5 | ListToolsRequestSchema, 6 | GetAuthTokenRequestSchema, 7 | CallToolRequestSchema, 8 | ErrorCode, 9 | McpError, 10 | } from '@highlight-ai/mcp-sdk/types.js' 11 | import { z } from 'zod' 12 | import { Octokit } from '@octokit/rest' 13 | 14 | async function getPRDiff(owner: string, repo: string, pullNumber: number, token: string): Promise<string> { 15 | const octokit = new Octokit({ 16 | auth: token, 17 | }) 18 | 19 | const response = await octokit.pulls.get({ 20 | owner, 21 | repo, 22 | pull_number: pullNumber, 23 | mediaType: { 24 | format: 'diff', 25 | }, 26 | }) 27 | 28 | return response.data as unknown as string 29 | } 30 | 31 | class GithubServer { 32 | private server: Server 33 | private githubToken: string 34 | 35 | constructor() { 36 | const token = process.env.GITHUB_TOKEN 37 | if (!token) { 38 | throw new Error('GITHUB_TOKEN environment variable is required') 39 | } 40 | this.githubToken = token 41 | 42 | this.server = new Server( 43 | { 44 | name: 'github-server', 45 | version: '0.0.1', 46 | }, 47 | { 48 | capabilities: { 49 | resources: {}, 50 | tools: {}, 51 | }, 52 | }, 53 | ) 54 | 55 | this.setupHandlers() 56 | this.setupErrorHandling() 57 | } 58 | 59 | private setupErrorHandling(): void { 60 | this.server.onerror = (error) => { 61 | console.error('[MCP Error]', error) 62 | } 63 | 64 | process.on('SIGINT', async () => { 65 | await this.server.close() 66 | process.exit(0) 67 | }) 68 | } 69 | 70 | private setupHandlers(): void { 71 | this.setupToolHandlers() 72 | } 73 | 74 | private setupToolHandlers(): void { 75 | // List available tools 76 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 77 | tools: [ 78 | { 79 | name: 'get_pr_diff', 80 | description: 'Get the diff of a pull request', 81 | inputSchema: { 82 | type: 'object', 83 | properties: { 84 | owner: { 85 | type: 'string', 86 | description: 'The owner of the repository', 87 | }, 88 | repo: { 89 | type: 'string', 90 | description: 'The repository name', 91 | }, 92 | pullNumber: { 93 | type: 'number', 94 | description: 'The pull request number', 95 | }, 96 | }, 97 | required: ['owner', 'repo', 'pullNumber'], 98 | }, 99 | }, 100 | ], 101 | })) 102 | 103 | // Handle tool calls 104 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 105 | if (request.params.name !== 'get_pr_diff') { 106 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`) 107 | } 108 | 109 | const pullRequestParamsSchema = z.object({ 110 | owner: z.string(), 111 | repo: z.string(), 112 | pullNumber: z.number(), 113 | }) 114 | 115 | const parsedParams = pullRequestParamsSchema.safeParse(request.params.arguments) 116 | if (!parsedParams.success) { 117 | throw new Error('Invalid arguments') 118 | } 119 | 120 | const diff = await getPRDiff( 121 | parsedParams.data.owner, 122 | parsedParams.data.repo, 123 | parsedParams.data.pullNumber, 124 | this.githubToken 125 | ) 126 | 127 | return { 128 | content: [ 129 | { 130 | type: 'text', 131 | text: diff, 132 | }, 133 | ], 134 | } 135 | }) 136 | } 137 | 138 | async run(): Promise<void> { 139 | const transport = new StdioServerTransport() 140 | await this.server.connect(transport) 141 | console.log('Github MCP server running on stdio') 142 | } 143 | } 144 | 145 | const server = new GithubServer() 146 | server.run().catch(console.error) 147 | ```