#
tokens: 5551/50000 11/11 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── github-service.ts
│   ├── mcp.d.ts
│   ├── server.ts
│   ├── test-client.ts
│   └── types.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/

# Build output
dist/

# Environment variables
.env

# Logs
*.log
npm-debug.log*

# Editor directories and files
.idea/
.vscode/
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# GitHub PR Comments MCP Server

[![smithery badge](https://smithery.ai/badge/github-pr-mcp)](https://smithery.ai/server/github-pr-mcp)

This is a Model Context Protocol (MCP) server that fetches GitHub Pull Request comments using a GitHub personal access token.

<a href="https://glama.ai/mcp/servers/@shaileshahuja/github-pr-mcp">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@shaileshahuja/github-pr-mcp/badge" alt="GitHub PR Comments Server MCP server" />
</a>

## Features

- Fetches PR comments with file paths, line ranges, and replies
- Uses GitHub API via Octokit
- Implements MCP server with StdioServerTransport
- Returns comments in a structured JSON format

## Installation

### Installing via Smithery

To install github-pr-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/github-pr-mcp):

```bash
npx -y @smithery/cli install github-pr-mcp --client claude
```

### Installing Manually
1. Clone the repository
2. Install dependencies:

   ```
   npm install
   ```

3. Create a `.env` file with your GitHub token:

   ```
   GITHUB_TOKEN=your_github_token_here
   ```

## Usage

1. Build the project:

   ```
   npm run build
   ```

2. Run the server:

   ```
   npm start
   ```

   Or directly with a GitHub token:

   ```
   node dist/server.js your_github_token_here
   ```

3. The server exposes a tool called `get_pr_comments` that accepts the following parameters:
   - `owner`: Repository owner (username or organization)
   - `repo`: Repository name
   - `pull_number`: Pull request number

## Integration with Cursor

To integrate with Cursor, use the following command in Cursor's MCP server configuration:

```
node /path/to/dist/server.js your_github_token_here
```

Replace `/path/to` with the actual path to your project, and `your_github_token_here` with your GitHub personal access token.

## Testing

A test client is included to verify the server functionality:

1. Build the project:

   ```
   npm run build
   ```

2. Run the test client:

   ```
   npm test
   ```

The test client will start the server, connect to it, and call the `get_pr_comments` tool with sample parameters.

## Response Format

The server returns comments in the following format:

```json
{
  "comments": [
    {
      "id": 123456789,
      "path": "src/example.js",
      "body": "This is a comment on a specific line",
      "line": 42,
      "start_line": 40,
      "user": {
        "login": "username"
      },
      "created_at": "2023-01-01T00:00:00Z",
      "replies": [
        {
          "id": 987654321,
          "body": "This is a reply to the comment",
          "user": {
            "login": "another-username"
          },
          "created_at": "2023-01-02T00:00:00Z"
        }
      ]
    }
  ]
}
```

## Development

To run the server in development mode:

```
npm run dev
```

## License

ISC
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install --ignore-scripts

# Copy the rest of the project and build
COPY . .
RUN npm run build

# Expose any ports if needed (not specified here)

# Start the server with the GitHub token passed as argument
CMD ["node", "dist/server.js"]

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
export interface CommentReply {
    id: number;
    body: string;
    user: {
        login: string;
    };
    created_at: string;
}

export interface Comment {
    id: number;
    path: string;
    body: string;
    line?: number;
    start_line?: number;
    user: {
        login: string;
    };
    created_at: string;
    replies: CommentReply[];
}

export interface GetPRCommentsParams {
    owner: string;
    repo: string;
    pull_number: number;
}

export interface GetPRCommentsResponse {
    comments: Comment[];
} 
```

--------------------------------------------------------------------------------
/src/mcp.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module '@modelcontextprotocol/sdk/server/mcp.js' {
    import { z } from 'zod';

    export class McpServer {
        constructor(options: { name: string; version: string; description?: string });

        tool(
            name: string,
            schema: Record<string, z.ZodType<any, any, any>>,
            handler: (args: any) => Promise<any>,
            options?: { description?: string }
        ): void;

        connect(transport: any): Promise<void>;
    }

    export class ResourceTemplate {
        constructor(template: string, options: { list: undefined });
    }
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "github-pr-mcp",
  "version": "1.0.0",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js",
    "dev": "ts-node --esm src/server.ts",
    "test": "node dist/test-client.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.1",
    "@octokit/rest": "^21.1.1",
    "@types/express": "^5.0.0",
    "@types/node": "^22.13.10",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2",
    "zod": "^3.22.4"
  }
}
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - githubToken
    properties:
      githubToken:
        type: string
        description: Your GitHub personal access token
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/server.js', config.githubToken],
      env: {}
    })
  exampleConfig:
    githubToken: your_github_token_here

```

--------------------------------------------------------------------------------
/src/test-client.ts:
--------------------------------------------------------------------------------

```typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import path from 'path';
import { fileURLToPath } from 'url';
import dotenv from 'dotenv';

// Load environment variables for the test
dotenv.config();

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Get GitHub token from environment
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
    console.error('GITHUB_TOKEN environment variable is required for testing');
    process.exit(1);
}

async function main() {
    // Create a transport that connects to the server and passes the token
    const transport = new StdioClientTransport({
        command: 'node',
        args: [path.join(__dirname, 'server.js'), githubToken as string]
    });

    // Create a client
    const client = new Client(
        {
            name: 'test-client',
            version: '1.0.0'
        },
        {
            capabilities: {
                tools: {}
            }
        }
    );

    try {
        // Connect to the server
        await client.connect(transport);
        console.log('Connected to server');

        // Call the get_pr_comments tool
        const result = await client.callTool({
            name: 'get_pr_comments',
            arguments: {
                owner: 'octocat',
                repo: 'Hello-World',
                pull_number: 1
            }
        });

        console.log('Tool result:', JSON.stringify(result, null, 2));
    } catch (error) {
        console.error('Error:', error);
    }
}

main().catch(console.error); 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import dotenv from 'dotenv';
import { GitHubService } from './github-service.js';
import { GetPRCommentsParams } from './types.js';

// Parse command-line arguments
let githubToken: string | undefined = process.argv[2]; // Get token from command-line arguments

// If no token provided via command line, try loading from .env file
if (!githubToken) {
    // Load environment variables
    dotenv.config();
    githubToken = process.env.GITHUB_TOKEN;
}

// Check if we have a token from either source
if (!githubToken) {
    console.error('GitHub token is required. Provide it as a command-line argument or set GITHUB_TOKEN environment variable.');
    process.exit(1);
}

// Create GitHub service
const githubService = new GitHubService(githubToken);

// Create MCP server
const server = new McpServer({
    name: 'github-pr-comments',
    version: '1.0.0',
    description: 'MCP server that fetches GitHub Pull Request comments'
});

// Register tool to fetch PR comments
// @ts-ignore - Ignoring type error for now as the MCP SDK types seem to be incompatible
server.tool(
    'get_pr_comments',
    {
        owner: z.string().min(1).describe('Repository owner (username or organization)'),
        repo: z.string().min(1).describe('Repository name'),
        pull_number: z.number().int().positive().describe('Pull request number')
    },
    async (args: { owner: string; repo: string; pull_number: number }) => {
        try {
            const params: GetPRCommentsParams = {
                owner: args.owner,
                repo: args.repo,
                pull_number: args.pull_number
            };

            const comments = await githubService.getPRComments(params);

            return {
                content: [
                    {
                        type: 'text' as const,
                        text: JSON.stringify({ comments }, null, 2)
                    }
                ]
            };
        } catch (error) {
            console.error('Error in get_pr_comments tool:', error);
            throw error;
        }
    },
    {
        description: 'Fetches comments from a GitHub pull request with their file paths, line ranges, and replies'
    }
);

// Start the server with StdioServerTransport
async function startServer() {
    try {
        const transport = new StdioServerTransport();
        await server.connect(transport);
        console.error('MCP server started with StdioServerTransport');
    } catch (error) {
        console.error('Failed to start MCP server:', error);
        process.exit(1);
    }
}

startServer(); 
```

--------------------------------------------------------------------------------
/src/github-service.ts:
--------------------------------------------------------------------------------

```typescript
import { Octokit } from '@octokit/rest';
import { GetPRCommentsParams, Comment, CommentReply } from './types.js';

export class GitHubService {
    private octokit: Octokit;

    constructor(token: string) {
        this.octokit = new Octokit({
            auth: token
        });
    }

    async getPRComments({ owner, repo, pull_number }: GetPRCommentsParams): Promise<Comment[]> {
        try {
            // Fetch review comments on the pull request (line comments)
            const { data: reviewComments } = await this.octokit.pulls.listReviewComments({
                owner,
                repo,
                pull_number,
                per_page: 100
            });

            // Group comments by their path and in_reply_to_id
            const groupedComments = new Map<number, Comment>();
            const replyMap = new Map<number, CommentReply[]>();

            // Process all comments and separate top-level comments from replies
            for (const comment of reviewComments) {
                if (comment.in_reply_to_id) {
                    // This is a reply to another comment
                    const reply: CommentReply = {
                        id: comment.id,
                        body: comment.body,
                        user: {
                            login: comment.user?.login || 'unknown'
                        },
                        created_at: comment.created_at
                    };

                    if (!replyMap.has(comment.in_reply_to_id)) {
                        replyMap.set(comment.in_reply_to_id, []);
                    }
                    replyMap.get(comment.in_reply_to_id)?.push(reply);
                } else {
                    // This is a top-level comment
                    const newComment: Comment = {
                        id: comment.id,
                        path: comment.path,
                        body: comment.body,
                        line: comment.line || undefined,
                        start_line: comment.start_line || undefined,
                        user: {
                            login: comment.user?.login || 'unknown'
                        },
                        created_at: comment.created_at,
                        replies: []
                    };
                    groupedComments.set(comment.id, newComment);
                }
            }

            // Attach replies to their parent comments
            for (const [commentId, replies] of replyMap.entries()) {
                const parentComment = groupedComments.get(commentId);
                if (parentComment) {
                    parentComment.replies = replies;
                }
            }

            return Array.from(groupedComments.values());
        } catch (error) {
            console.error('Error fetching PR comments:', error);
            throw error;
        }
    }
} 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */
    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    /* Language and Environment */
    "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "libReplacement": true,                           /* Enable lib replacement. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
    /* Modules */
    "module": "NodeNext", /* Specify what module code is generated. */
    "moduleResolution": "NodeNext",
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
    "resolveJsonModule": true, /* Enable importing .json files. */
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    "outDir": "./dist", /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
    // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
    /* Type Checking */
    "strict": true, /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ],
  "rootDir": "./src"
}
```