├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── implementation-plan.md ├── jest.config.js ├── package.json ├── src ├── __tests__ │ └── formatters.test.ts ├── index.ts ├── scripts │ └── test-url-conversion.ts ├── server.ts ├── tools │ ├── delete-thread-subscription.ts │ ├── get-thread-subscription.ts │ ├── get-thread.ts │ ├── list-notifications.ts │ ├── list-repo-notifications.ts │ ├── manage-repo-subscription.ts │ ├── mark-notifications-read.ts │ ├── mark-repo-notifications-read.ts │ ├── mark-thread-done.ts │ ├── mark-thread-read.ts │ └── set-thread-subscription.ts ├── types │ └── github-api.ts └── utils │ ├── api.ts │ └── formatters.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # GitHub Personal Access Token with 'notifications' or 'repo' scope 2 | GITHUB_TOKEN=your_github_personal_access_token_here 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | build/ 6 | dist/ 7 | 8 | # Environment variables 9 | .env 10 | 11 | # Logs 12 | *.log 13 | npm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea/ 17 | .vscode/ 18 | *.swp 19 | *.swo 20 | 21 | # OS specific 22 | .DS_Store 23 | Thumbs.db 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matteo Collina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Notifications MCP Server 2 | 3 | An MCP (Model Context Protocol) server that provides tools for managing GitHub notifications. This server allows AI assistants like Claude to help you manage your GitHub notifications through natural language commands. 4 | 5 | ## Features 6 | 7 | - List and filter your GitHub notifications 8 | - Mark notifications as read 9 | - View notification thread details 10 | - Subscribe or unsubscribe from notification threads 11 | - Mark threads as done 12 | - Manage repository-specific notifications 13 | - Control repository notification settings (all activity, default, or mute) 14 | 15 | ## Prerequisites 16 | 17 | - Node.js 18 or higher 18 | - GitHub Personal Access Token (classic) with `notifications` or `repo` scope 19 | 20 | ## Installation 21 | 22 | 1. Clone this repository 23 | ``` 24 | git clone https://github.com/yourusername/github-notifications-mcp-server.git 25 | cd github-notifications-mcp-server 26 | ``` 27 | 28 | 2. Install dependencies 29 | ``` 30 | npm install 31 | ``` 32 | 33 | 3. Build the project 34 | ``` 35 | npm run build 36 | ``` 37 | 38 | 4. Create a `.env` file with your GitHub token 39 | ``` 40 | GITHUB_TOKEN=your_github_personal_access_token_here 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Running the server directly 46 | 47 | ``` 48 | npm start 49 | ``` 50 | 51 | ### Using with Claude Desktop 52 | 53 | Add the server to your `claude_desktop_config.json` file: 54 | 55 | ```json 56 | { 57 | "mcpServers": { 58 | "github-notifications": { 59 | "command": "node", 60 | "args": ["/absolute/path/to/github-notifications-mcp-server/build/index.js"], 61 | "env": { 62 | "GITHUB_TOKEN": "your_github_personal_access_token_here" 63 | } 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | ## Available Tools 70 | 71 | | Tool Name | Description | 72 | |-----------|-------------| 73 | | `list-notifications` | List all GitHub notifications for the authenticated user | 74 | | `mark-notifications-read` | Mark all notifications as read | 75 | | `get-thread` | Get information about a notification thread | 76 | | `mark-thread-read` | Mark a specific thread as read | 77 | | `mark-thread-done` | Mark a thread as done | 78 | | `get-thread-subscription` | Get subscription status for a thread | 79 | | `set-thread-subscription` | Subscribe to a thread | 80 | | `delete-thread-subscription` | Unsubscribe from a thread | 81 | | `list-repo-notifications` | List notifications for a specific repository | 82 | | `mark-repo-notifications-read` | Mark notifications for a repository as read | 83 | | `manage-repo-subscription` | Manage repository subscriptions: all_activity, default (participating and @mentions), or ignore (mute) | 84 | 85 | ## Example Prompts 86 | 87 | Here are some example prompts you can use with Claude Desktop once the server is connected: 88 | 89 | - "Can you check my GitHub notifications?" 90 | - "Show me my unread notifications from the last 24 hours." 91 | - "Mark all my notifications as read." 92 | - "Can you tell me about notification thread 12345?" 93 | - "Unsubscribe me from thread 12345." 94 | - "What notifications do I have for the octocat/Hello-World repository?" 95 | - "Mark all notifications from the octocat/Hello-World repository as read." 96 | - "Watch all activity on the octocat/Hello-World repository." 97 | - "Set the octocat/Hello-World repository to default settings (participating and @mentions)." 98 | - "Check my notification settings for the octocat/Hello-World repository." 99 | - "Mute all notifications from the octocat/Hello-World repository." 100 | 101 | ## Development 102 | 103 | ### URL Handling 104 | 105 | This server automatically converts GitHub API URLs to their corresponding web UI URLs. For example: 106 | 107 | - API URL: `https://api.github.com/repos/nodejs/node/pulls/57557` 108 | - Converted to: `https://github.com/nodejs/node/pull/57557` 109 | 110 | The conversion handles: 111 | - Domain conversion from `api.github.com/repos` to `github.com` 112 | - Path correction for pull requests (changing `pulls` to `pull`) 113 | - Preservation of additional path segments 114 | 115 | ### Project Structure 116 | 117 | ``` 118 | github-notifications-mcp-server/ 119 | ├── src/ # Source code 120 | │ ├── tools/ # Tool implementations 121 | │ ├── types/ # Type definitions 122 | │ ├── utils/ # Utility functions 123 | │ ├── index.ts # Entry point 124 | │ └── server.ts # Server configuration 125 | ├── build/ # Compiled JavaScript 126 | ├── .env # Environment variables 127 | ├── package.json # Dependencies 128 | ├── tsconfig.json # TypeScript configuration 129 | └── README.md # Documentation 130 | ``` 131 | 132 | ### Building 133 | 134 | ``` 135 | npm run build 136 | ``` 137 | 138 | ### Testing 139 | 140 | Run the automated tests: 141 | 142 | ``` 143 | npm test 144 | ``` 145 | 146 | Test URL conversion manually: 147 | 148 | ``` 149 | npm run test:url 150 | ``` 151 | 152 | ## License 153 | 154 | MIT 155 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # GitHub Notifications MCP Server - TODO List 2 | 3 | This document outlines the tasks required to implement the GitHub Notifications MCP server. Tasks are organized by priority and component. 4 | 5 | ## Initial Setup 6 | 7 | - [x] Create project directory structure 8 | - [x] Initialize TypeScript project (`npm init -y` & `tsc --init`) 9 | - [x] Configure TypeScript (`tsconfig.json`) 10 | - [x] Add dependencies: 11 | - [x] `@modelcontextprotocol/sdk` - MCP SDK 12 | - [x] `zod` - Schema validation 13 | - [x] `dotenv` - Environment variable management 14 | - [x] TypeScript dev dependencies 15 | - [x] Create build and start scripts in `package.json` 16 | - [x] Set up environment variable handling (`.env` file) 17 | 18 | ## API Client Implementation 19 | 20 | - [x] Create base API client utility (`src/utils/api.ts`) 21 | - [x] Implement `githubGet()` function using fetch API 22 | - [x] Implement `githubPut()` function using fetch API 23 | - [x] Implement `githubPatch()` function using fetch API 24 | - [x] Implement `githubDelete()` function using fetch API 25 | - [x] Add error handling and status code processing 26 | - [x] Implement rate limit monitoring 27 | - [x] Create GitHub API type definitions (`src/types/github-api.ts`) 28 | - [x] Define `NotificationResponse` interface 29 | - [x] Define `ThreadSubscription` interface 30 | - [x] Define other necessary types 31 | 32 | ## Formatting Utilities 33 | 34 | - [x] Create response formatters (`src/utils/formatters.ts`) 35 | - [x] Implement `formatNotification()` function 36 | - [x] Implement `formatSubscription()` function 37 | - [x] Implement `formatError()` function 38 | - [x] Add reason code descriptions 39 | 40 | ## Tool Implementations 41 | 42 | ### User Notifications Tools 43 | - [x] Implement `list-notifications` tool 44 | - [x] Define input schema with Zod 45 | - [x] Implement handler function 46 | - [x] Add pagination support 47 | - [x] Format response for readability 48 | - [x] Implement `mark-notifications-read` tool 49 | - [x] Define input schema with Zod 50 | - [x] Implement handler function 51 | - [x] Add validation for parameters 52 | 53 | ### Thread Management Tools 54 | - [x] Implement `get-thread` tool 55 | - [x] Define input schema with Zod 56 | - [x] Implement handler function 57 | - [x] Format thread details for readability 58 | - [x] Implement `mark-thread-read` tool 59 | - [x] Define input schema with Zod 60 | - [x] Implement handler function 61 | - [x] Add response handling 62 | - [x] Implement `mark-thread-done` tool 63 | - [x] Define input schema with Zod 64 | - [x] Implement handler function 65 | - [x] Add response handling 66 | 67 | ### Thread Subscription Tools 68 | - [x] Implement `get-thread-subscription` tool 69 | - [x] Define input schema with Zod 70 | - [x] Implement handler function 71 | - [x] Format subscription information 72 | - [x] Implement `set-thread-subscription` tool 73 | - [x] Define input schema with Zod 74 | - [x] Implement handler function 75 | - [x] Validate parameters 76 | - [x] Implement `delete-thread-subscription` tool 77 | - [x] Define input schema with Zod 78 | - [x] Implement handler function 79 | - [x] Add response handling 80 | 81 | ### Repository Notifications Tools 82 | - [x] Implement `list-repo-notifications` tool 83 | - [x] Define input schema with Zod 84 | - [x] Implement handler function 85 | - [x] Support pagination 86 | - [x] Format response for readability 87 | - [x] Implement `mark-repo-notifications-read` tool 88 | - [x] Define input schema with Zod 89 | - [x] Implement handler function 90 | - [x] Add validation for parameters 91 | 92 | ## Server Implementation 93 | 94 | - [x] Create main server setup (`src/server.ts`) 95 | - [x] Initialize MCP server with name and version 96 | - [x] Register all tools with the server 97 | - [x] Configure stdio transport 98 | - [x] Add error handling 99 | - [x] Create entry point (`src/index.ts`) 100 | - [x] Handle environment variables with dotenv 101 | - [x] Call server startup function 102 | - [x] Add proper shutdown handling 103 | 104 | ## Testing and Quality Assurance 105 | 106 | - [ ] Manual testing of each tool 107 | - [ ] Test with valid inputs 108 | - [ ] Test with invalid inputs 109 | - [ ] Test with edge cases 110 | - [x] Write clear error messages 111 | - [ ] Test with MCP Inspector 112 | - [ ] Configure and test with Claude Desktop 113 | 114 | ## Documentation 115 | 116 | - [x] Write README.md 117 | - [x] Installation instructions 118 | - [x] Usage guide 119 | - [x] Available tools documentation 120 | - [x] Environment setup 121 | - [x] Add inline code documentation 122 | - [x] JSDocs for functions 123 | - [x] Comments for complex logic 124 | - [x] Create example configuration 125 | 126 | ## Deployment Preparation 127 | 128 | - [x] Create build script for production 129 | - [x] Verify environment variable handling 130 | - [ ] Test full workflow with Claude Desktop 131 | - [x] Document Claude Desktop integration 132 | -------------------------------------------------------------------------------- /implementation-plan.md: -------------------------------------------------------------------------------- 1 | # GitHub Notifications MCP Server Implementation Plan 2 | 3 | This document outlines the implementation plan for an MCP server that exposes the GitHub Notifications API endpoints as tools. The server will allow users to manage their GitHub notifications through Claude or other MCP-compatible clients. 4 | 5 | ## 1. Overview 6 | 7 | The GitHub Notifications MCP server will be implemented using TypeScript with the Model Context Protocol (MCP) SDK. It will allow users to: 8 | 9 | - List and manage their GitHub notifications 10 | - Mark notifications as read or done 11 | - Subscribe/unsubscribe from notification threads 12 | - Work with repository-specific notifications 13 | 14 | ## 2. Prerequisites 15 | 16 | - Node.js and npm 17 | - TypeScript 18 | - GitHub Personal Access Token (classic) with `notifications` or `repo` scope 19 | - MCP SDK (`@modelcontextprotocol/sdk`) 20 | 21 | ## 3. Project Structure 22 | 23 | ``` 24 | github-notifications-server/ 25 | ├── src/ 26 | │ ├── index.ts # Main entry point 27 | │ ├── server.ts # MCP server configuration 28 | │ ├── tools/ # Tool implementations 29 | │ │ ├── list-notifications.ts 30 | │ │ ├── mark-as-read.ts 31 | │ │ ├── thread-management.ts 32 | │ │ ├── thread-subscriptions.ts 33 | │ │ └── repo-notifications.ts 34 | │ ├── types/ # Type definitions 35 | │ │ ├── github-api.ts # GitHub API response types 36 | │ │ └── tools.ts # Tool input/output types 37 | │ └── utils/ # Utility functions 38 | │ ├── api.ts # GitHub API client 39 | │ └── validation.ts # Input validation 40 | ├── tsconfig.json # TypeScript configuration 41 | ├── package.json # Dependencies and scripts 42 | └── README.md # Documentation 43 | ``` 44 | 45 | ## 4. API Endpoints & Corresponding Tools 46 | 47 | Based on the GitHub Notifications API, the following tools will be implemented: 48 | 49 | ### 4.1. User Notifications Tools 50 | 51 | | Tool Name | GitHub API Endpoint | Description | 52 | |-----------|---------------------|-------------| 53 | | `list-notifications` | `GET /notifications` | List all notifications for the authenticated user | 54 | | `mark-notifications-read` | `PUT /notifications` | Mark all notifications as read | 55 | 56 | ### 4.2. Thread Management Tools 57 | 58 | | Tool Name | GitHub API Endpoint | Description | 59 | |-----------|---------------------|-------------| 60 | | `get-thread` | `GET /notifications/threads/{thread_id}` | Get information about a notification thread | 61 | | `mark-thread-read` | `PATCH /notifications/threads/{thread_id}` | Mark a thread as read | 62 | | `mark-thread-done` | `DELETE /notifications/threads/{thread_id}` | Mark a thread as done | 63 | 64 | ### 4.3. Thread Subscription Tools 65 | 66 | | Tool Name | GitHub API Endpoint | Description | 67 | |-----------|---------------------|-------------| 68 | | `get-thread-subscription` | `GET /notifications/threads/{thread_id}/subscription` | Get thread subscription status | 69 | | `set-thread-subscription` | `PUT /notifications/threads/{thread_id}/subscription` | Subscribe to a thread | 70 | | `delete-thread-subscription` | `DELETE /notifications/threads/{thread_id}/subscription` | Unsubscribe from a thread | 71 | 72 | ### 4.4. Repository Notifications Tools 73 | 74 | | Tool Name | GitHub API Endpoint | Description | 75 | |-----------|---------------------|-------------| 76 | | `list-repo-notifications` | `GET /repos/{owner}/{repo}/notifications` | List repository notifications | 77 | | `mark-repo-notifications-read` | `PUT /repos/{owner}/{repo}/notifications` | Mark repository notifications as read | 78 | 79 | ## 5. Implementation Details 80 | 81 | ### 5.1. Tool Implementation Pattern 82 | 83 | Each tool will follow a similar pattern: 84 | 85 | ```typescript 86 | // Example: list-notifications.ts 87 | import { z } from "zod"; 88 | import { githubGet } from "../utils/api"; 89 | 90 | // Input schema with Zod validation 91 | const listNotificationsSchema = z.object({ 92 | all: z.boolean().optional().default(false), 93 | participating: z.boolean().optional().default(false), 94 | since: z.string().optional(), 95 | before: z.string().optional(), 96 | page: z.number().optional().default(1), 97 | per_page: z.number().optional().default(50).max(100) 98 | }); 99 | 100 | // Tool handler function 101 | export async function listNotifications(args: z.infer) { 102 | try { 103 | // Call GitHub API using fetch 104 | const notifications = await githubGet("/notifications", { 105 | params: args 106 | }); 107 | 108 | // Return formatted results 109 | return { 110 | content: [{ 111 | type: "text", 112 | text: JSON.stringify(notifications, null, 2) 113 | }] 114 | }; 115 | } catch (error) { 116 | // Handle and return error 117 | return { 118 | isError: true, 119 | content: [{ 120 | type: "text", 121 | text: `Error fetching notifications: ${error.message}` 122 | }] 123 | }; 124 | } 125 | } 126 | ``` 127 | 128 | ### 5.2. GitHub API Client 129 | 130 | A utility module for making authenticated requests to the GitHub API using fetch: 131 | 132 | ```typescript 133 | // api.ts 134 | // Using native fetch API 135 | 136 | const BASE_URL = "https://api.github.com"; 137 | const DEFAULT_HEADERS = { 138 | "Accept": "application/vnd.github+json", 139 | "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`, 140 | "X-GitHub-Api-Version": "2022-11-28" 141 | }; 142 | 143 | export async function githubGet(path: string, options = {}) { 144 | const url = new URL(path, BASE_URL); 145 | 146 | // Add query parameters if provided 147 | if (options.params) { 148 | Object.entries(options.params).forEach(([key, value]) => { 149 | if (value !== undefined && value !== null) { 150 | url.searchParams.append(key, String(value)); 151 | } 152 | }); 153 | } 154 | 155 | const response = await fetch(url.toString(), { 156 | method: 'GET', 157 | headers: DEFAULT_HEADERS 158 | }); 159 | 160 | // Error handling 161 | if (!response.ok) { 162 | throw new Error(`GitHub API error (${response.status}): ${await response.text()}`); 163 | } 164 | 165 | return await response.json() as T; 166 | } 167 | 168 | // Similar implementations for PUT, PATCH, DELETE methods 169 | ``` 170 | 171 | ### 5.3. Server Configuration 172 | 173 | The main server setup: 174 | 175 | ```typescript 176 | // server.ts 177 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 178 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 179 | import { listNotifications } from "./tools/list-notifications"; 180 | import { markNotificationsRead } from "./tools/mark-as-read"; 181 | // Import other tools... 182 | 183 | export async function startServer() { 184 | // Create MCP server 185 | const server = new McpServer({ 186 | name: "github-notifications", 187 | version: "1.0.0" 188 | }); 189 | 190 | // Register tools 191 | server.tool( 192 | "list-notifications", 193 | "List GitHub notifications for the authenticated user", 194 | listNotificationsSchema.shape, 195 | listNotifications 196 | ); 197 | 198 | server.tool( 199 | "mark-notifications-read", 200 | "Mark notifications as read", 201 | markNotificationsReadSchema.shape, 202 | markNotificationsRead 203 | ); 204 | 205 | // Register other tools... 206 | 207 | // Start server with stdio transport 208 | const transport = new StdioServerTransport(); 209 | await server.connect(transport); 210 | console.error("GitHub Notifications MCP Server running on stdio"); 211 | } 212 | ``` 213 | 214 | ## 6. User Experience 215 | 216 | When a user interacts with this server through an MCP client (like Claude Desktop), they will be able to: 217 | 218 | 1. Check their GitHub notifications 219 | ``` 220 | Can you show me my unread GitHub notifications? 221 | ``` 222 | 223 | 2. Manage notification subscriptions 224 | ``` 225 | Can you unsubscribe me from thread 123456? 226 | ``` 227 | 228 | 3. Mark notifications as read 229 | ``` 230 | Mark all my notifications from the acme/rocket-project repository as read. 231 | ``` 232 | 233 | ## 7. Implementation Plan 234 | 235 | ### Phase 1: Basic Setup and Core Tools 236 | 237 | 1. Set up project structure and dependencies 238 | 2. Implement GitHub API client with authentication 239 | 3. Implement core tools: 240 | - `list-notifications` 241 | - `mark-notifications-read` 242 | - `get-thread` 243 | - `mark-thread-read` 244 | 245 | ### Phase 2: Complete Tool Implementation 246 | 247 | 1. Implement subscription management tools: 248 | - `get-thread-subscription` 249 | - `set-thread-subscription` 250 | - `delete-thread-subscription` 251 | 252 | 2. Implement repository-specific tools: 253 | - `list-repo-notifications` 254 | - `mark-repo-notifications-read` 255 | 256 | 3. Implement the remaining tool: 257 | - `mark-thread-done` 258 | 259 | ### Phase 3: Testing and Documentation 260 | 261 | 1. Test all tools with various input parameters 262 | 2. Add comprehensive error handling 263 | 3. Create detailed documentation with examples 264 | 4. Prepare for deployment 265 | 266 | ## 8. Environment Variables 267 | 268 | The server will require the following environment variables: 269 | 270 | - `GITHUB_TOKEN`: Personal Access Token with `notifications` or `repo` scope 271 | 272 | ## 9. Example Usage Configuration 273 | 274 | For use with Claude Desktop, add the following to your `claude_desktop_config.json`: 275 | 276 | ```json 277 | { 278 | "mcpServers": { 279 | "github-notifications": { 280 | "command": "node", 281 | "args": ["/path/to/github-notifications-server/build/index.js"], 282 | "env": { 283 | "GITHUB_TOKEN": "" 284 | } 285 | } 286 | } 287 | } 288 | ``` 289 | 290 | ## 10. Next Steps and Future Improvements 291 | 292 | 1. Enhanced result formatting for better readability 293 | 2. Add support for notification filtering (by reason, repository, etc.) 294 | 3. Implement pagination helpers for large result sets 295 | 4. Add support for GitHub Enterprise instances 296 | 5. Implement caching to reduce API calls 297 | 6. Add rate limiting protection 298 | 299 | This implementation plan provides a comprehensive approach to creating a GitHub Notifications MCP server that exposes all the relevant API endpoints as tools, making it easy for users to manage their GitHub notifications through Claude or other MCP clients. 300 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.ts$': ['ts-jest', { useESM: true }], 6 | }, 7 | extensionsToTreatAsEsm: ['.ts'], 8 | moduleNameMapper: { 9 | '^(\\.{1,2}/.*)\\.js$': '$1', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-notifications-mcp-server", 3 | "version": "1.1.0", 4 | "description": "MCP server providing GitHub Notifications API tools", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node build/index.js", 10 | "dev": "tsc -w", 11 | "test": "jest", 12 | "test:url": "tsx src/scripts/test-url-conversion.ts" 13 | }, 14 | "keywords": [ 15 | "github", 16 | "notifications", 17 | "mcp", 18 | "model-context-protocol" 19 | ], 20 | "author": "", 21 | "license": "MIT", 22 | "dependencies": { 23 | "@modelcontextprotocol/sdk": "^1.7.0", 24 | "dotenv": "^16.4.1", 25 | "zod": "^3.22.4" 26 | }, 27 | "devDependencies": { 28 | "@types/jest": "^29.5.11", 29 | "@types/node": "^20.11.5", 30 | "jest": "^29.7.0", 31 | "ts-jest": "^29.1.1", 32 | "tsx": "^4.7.0", 33 | "typescript": "^5.3.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/__tests__/formatters.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests for formatter utilities 3 | */ 4 | import { convertApiUrlToHtmlUrl } from '../utils/formatters'; 5 | 6 | describe('URL Conversion Tests', () => { 7 | test('converts pull request API URLs to HTML URLs correctly', () => { 8 | const apiUrl = 'https://api.github.com/repos/nodejs/node/pulls/57557'; 9 | const expectedHtmlUrl = 'https://github.com/nodejs/node/pull/57557'; 10 | 11 | expect(convertApiUrlToHtmlUrl(apiUrl)).toBe(expectedHtmlUrl); 12 | }); 13 | 14 | test('converts issue API URLs to HTML URLs correctly', () => { 15 | const apiUrl = 'https://api.github.com/repos/nodejs/node/issues/12345'; 16 | const expectedHtmlUrl = 'https://github.com/nodejs/node/issues/12345'; 17 | 18 | expect(convertApiUrlToHtmlUrl(apiUrl)).toBe(expectedHtmlUrl); 19 | }); 20 | 21 | test('converts repository API URLs to HTML URLs correctly', () => { 22 | const apiUrl = 'https://api.github.com/repos/nodejs/node'; 23 | const expectedHtmlUrl = 'https://github.com/nodejs/node'; 24 | 25 | expect(convertApiUrlToHtmlUrl(apiUrl)).toBe(expectedHtmlUrl); 26 | }); 27 | 28 | test('handles URLs with additional path segments', () => { 29 | const apiUrl = 'https://api.github.com/repos/nodejs/node/pulls/57557/comments'; 30 | const expectedHtmlUrl = 'https://github.com/nodejs/node/pull/57557/comments'; 31 | 32 | expect(convertApiUrlToHtmlUrl(apiUrl)).toBe(expectedHtmlUrl); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry point for GitHub Notifications MCP Server 3 | */ 4 | import * as dotenv from 'dotenv'; 5 | import { startServer } from './server.js'; 6 | 7 | // Load environment variables from .env file 8 | dotenv.config(); 9 | 10 | // Verify required environment variables 11 | if (!process.env.GITHUB_TOKEN) { 12 | console.error("Error: GITHUB_TOKEN environment variable is required"); 13 | console.error("Please create a GitHub Personal Access Token with 'notifications' or 'repo' scope"); 14 | console.error("and set it in the environment or .env file"); 15 | process.exit(1); 16 | } 17 | 18 | // Handle uncaught exceptions and unhandled rejections 19 | process.on('uncaughtException', (error) => { 20 | console.error('Uncaught Exception:', error); 21 | process.exit(1); 22 | }); 23 | 24 | process.on('unhandledRejection', (reason, promise) => { 25 | console.error('Unhandled Rejection at:', promise, 'reason:', reason); 26 | process.exit(1); 27 | }); 28 | 29 | // Start the server 30 | startServer().catch(error => { 31 | console.error("Failed to start server:", error); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /src/scripts/test-url-conversion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manual test script for URL conversion 3 | * 4 | * Run with: 5 | * ts-node src/scripts/test-url-conversion.ts 6 | */ 7 | import { convertApiUrlToHtmlUrl } from '../utils/formatters.js'; 8 | 9 | // Test cases 10 | const testCases = [ 11 | 'https://api.github.com/repos/nodejs/node/pulls/57557', 12 | 'https://api.github.com/repos/nodejs/node/pulls/57557/comments', 13 | 'https://api.github.com/repos/nodejs/node/issues/12345', 14 | 'https://api.github.com/repos/nodejs/node', 15 | ]; 16 | 17 | // Run tests 18 | console.log('Testing URL conversion:\n'); 19 | 20 | testCases.forEach(apiUrl => { 21 | const htmlUrl = convertApiUrlToHtmlUrl(apiUrl); 22 | console.log(`API URL: ${apiUrl}`); 23 | console.log(`HTML URL: ${htmlUrl}`); 24 | console.log(''); 25 | }); 26 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Notifications MCP Server 3 | */ 4 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 5 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 6 | 7 | // Import tool registrations 8 | import { registerListNotificationsTool } from "./tools/list-notifications.js"; 9 | import { registerMarkNotificationsReadTool } from "./tools/mark-notifications-read.js"; 10 | import { registerGetThreadTool } from "./tools/get-thread.js"; 11 | import { registerMarkThreadReadTool } from "./tools/mark-thread-read.js"; 12 | import { registerMarkThreadDoneTool } from "./tools/mark-thread-done.js"; 13 | import { registerGetThreadSubscriptionTool } from "./tools/get-thread-subscription.js"; 14 | import { registerSetThreadSubscriptionTool } from "./tools/set-thread-subscription.js"; 15 | import { registerDeleteThreadSubscriptionTool } from "./tools/delete-thread-subscription.js"; 16 | import { registerListRepoNotificationsTool } from "./tools/list-repo-notifications.js"; 17 | import { registerMarkRepoNotificationsReadTool } from "./tools/mark-repo-notifications-read.js"; 18 | import { registerManageRepoSubscriptionTool } from "./tools/manage-repo-subscription.js"; 19 | 20 | /** 21 | * Initialize and start the MCP server 22 | */ 23 | export async function startServer() { 24 | try { 25 | // Create MCP server 26 | const server = new McpServer({ 27 | name: "github-notifications", 28 | version: "1.0.0" 29 | }, { 30 | capabilities: { 31 | tools: {} 32 | } 33 | }); 34 | 35 | console.error("Initializing GitHub Notifications MCP Server..."); 36 | 37 | // Register all tools 38 | registerListNotificationsTool(server); 39 | registerMarkNotificationsReadTool(server); 40 | registerGetThreadTool(server); 41 | registerMarkThreadReadTool(server); 42 | registerMarkThreadDoneTool(server); 43 | registerGetThreadSubscriptionTool(server); 44 | registerSetThreadSubscriptionTool(server); 45 | registerDeleteThreadSubscriptionTool(server); 46 | registerListRepoNotificationsTool(server); 47 | registerMarkRepoNotificationsReadTool(server); 48 | registerManageRepoSubscriptionTool(server); 49 | 50 | console.error("All tools registered successfully."); 51 | 52 | // Connect using stdio transport 53 | const transport = new StdioServerTransport(); 54 | await server.connect(transport); 55 | 56 | console.error("GitHub Notifications MCP Server running on stdio transport."); 57 | console.error("Authentication: Using GITHUB_TOKEN environment variable."); 58 | } catch (error) { 59 | console.error("Error starting server:", error); 60 | process.exit(1); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tools/delete-thread-subscription.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for deleting a GitHub thread subscription 3 | */ 4 | import { z } from "zod"; 5 | import { githubDelete } from "../utils/api.js"; 6 | import { formatError } from "../utils/formatters.js"; 7 | 8 | /** 9 | * Schema for delete-thread-subscription tool input parameters 10 | */ 11 | export const deleteThreadSubscriptionSchema = z.object({ 12 | thread_id: z.string().describe("The ID of the notification thread to unsubscribe from") 13 | }); 14 | 15 | /** 16 | * Tool implementation for deleting a thread subscription 17 | */ 18 | export async function deleteThreadSubscriptionHandler(args: z.infer) { 19 | try { 20 | // Make request to GitHub API 21 | await githubDelete(`/notifications/threads/${args.thread_id}/subscription`); 22 | 23 | return { 24 | content: [{ 25 | type: "text", 26 | text: `Successfully unsubscribed from thread ${args.thread_id}.` 27 | }] 28 | }; 29 | } catch (error) { 30 | if (error instanceof Error && error.message.includes("404")) { 31 | return { 32 | content: [{ 33 | type: "text", 34 | text: `You were not subscribed to thread ${args.thread_id}.` 35 | }] 36 | }; 37 | } 38 | 39 | return { 40 | isError: true, 41 | content: [{ 42 | type: "text", 43 | text: formatError(`Failed to unsubscribe from thread ${args.thread_id}`, error) 44 | }] 45 | }; 46 | } 47 | } 48 | 49 | /** 50 | * Register this tool with the server 51 | */ 52 | export function registerDeleteThreadSubscriptionTool(server: any) { 53 | server.tool( 54 | "delete-thread-subscription", 55 | "Unsubscribe from a GitHub notification thread", 56 | deleteThreadSubscriptionSchema.shape, 57 | deleteThreadSubscriptionHandler 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/tools/get-thread-subscription.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for getting a GitHub thread subscription status 3 | */ 4 | import { z } from "zod"; 5 | import { githubGet } from "../utils/api.js"; 6 | import { formatSubscription, formatError } from "../utils/formatters.js"; 7 | import { ThreadSubscription } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for get-thread-subscription tool input parameters 11 | */ 12 | export const getThreadSubscriptionSchema = z.object({ 13 | thread_id: z.string().describe("The ID of the notification thread to check subscription status") 14 | }); 15 | 16 | /** 17 | * Tool implementation for getting a thread subscription 18 | */ 19 | export async function getThreadSubscriptionHandler(args: z.infer) { 20 | try { 21 | // Make request to GitHub API 22 | const subscription = await githubGet(`/notifications/threads/${args.thread_id}/subscription`); 23 | 24 | // Format the subscription for better readability 25 | const formattedSubscription = formatSubscription(subscription); 26 | 27 | return { 28 | content: [{ 29 | type: "text", 30 | text: `Subscription status for thread ${args.thread_id}:\n\n${formattedSubscription}` 31 | }] 32 | }; 33 | } catch (error) { 34 | if (error instanceof Error && error.message.includes("404")) { 35 | return { 36 | content: [{ 37 | type: "text", 38 | text: `You are not subscribed to thread ${args.thread_id}.` 39 | }] 40 | }; 41 | } 42 | 43 | return { 44 | isError: true, 45 | content: [{ 46 | type: "text", 47 | text: formatError(`Failed to fetch subscription for thread ${args.thread_id}`, error) 48 | }] 49 | }; 50 | } 51 | } 52 | 53 | /** 54 | * Register this tool with the server 55 | */ 56 | export function registerGetThreadSubscriptionTool(server: any) { 57 | server.tool( 58 | "get-thread-subscription", 59 | "Get subscription status for a GitHub notification thread", 60 | getThreadSubscriptionSchema.shape, 61 | getThreadSubscriptionHandler 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/tools/get-thread.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for getting a GitHub notification thread 3 | */ 4 | import { z } from "zod"; 5 | import { githubGet } from "../utils/api.js"; 6 | import { formatNotification, formatError } from "../utils/formatters.js"; 7 | import { NotificationResponse } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for get-thread tool input parameters 11 | */ 12 | export const getThreadSchema = z.object({ 13 | thread_id: z.string().describe("The ID of the notification thread to retrieve") 14 | }); 15 | 16 | /** 17 | * Tool implementation for getting a GitHub notification thread 18 | */ 19 | export async function getThreadHandler(args: z.infer) { 20 | try { 21 | // Make request to GitHub API 22 | const thread = await githubGet(`/notifications/threads/${args.thread_id}`); 23 | 24 | // Format the thread for better readability 25 | const formattedThread = formatNotification(thread); 26 | 27 | return { 28 | content: [{ 29 | type: "text", 30 | text: `Thread details:\n\n${formattedThread}` 31 | }] 32 | }; 33 | } catch (error) { 34 | return { 35 | isError: true, 36 | content: [{ 37 | type: "text", 38 | text: formatError(`Failed to fetch thread ${args.thread_id}`, error) 39 | }] 40 | }; 41 | } 42 | } 43 | 44 | /** 45 | * Register this tool with the server 46 | */ 47 | export function registerGetThreadTool(server: any) { 48 | server.tool( 49 | "get-thread", 50 | "Get information about a GitHub notification thread", 51 | getThreadSchema.shape, 52 | getThreadHandler 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/tools/list-notifications.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for listing GitHub notifications 3 | */ 4 | import { z } from "zod"; 5 | import { githubGet } from "../utils/api.js"; 6 | import { formatNotification, formatError } from "../utils/formatters.js"; 7 | import { NotificationResponse } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for list-notifications tool input parameters 11 | */ 12 | export const listNotificationsSchema = z.object({ 13 | all: z.boolean().optional().describe("If true, show notifications marked as read"), 14 | participating: z.boolean().optional().describe("If true, only shows notifications where user is directly participating"), 15 | since: z.string().optional().describe("ISO 8601 timestamp - only show notifications updated after this time"), 16 | before: z.string().optional().describe("ISO 8601 timestamp - only show notifications updated before this time"), 17 | page: z.number().optional().describe("Page number for pagination"), 18 | per_page: z.number().optional().describe("Number of results per page (max 100)") 19 | }); 20 | 21 | /** 22 | * Tool implementation for listing GitHub notifications 23 | */ 24 | export async function listNotificationsHandler(args: z.infer) { 25 | try { 26 | const perPage = args.per_page || 50; 27 | const page = args.page || 1; 28 | 29 | // Make request to GitHub API 30 | const notifications = await githubGet("/notifications", { 31 | params: { 32 | all: args.all, 33 | participating: args.participating, 34 | since: args.since, 35 | before: args.before, 36 | page: page, 37 | per_page: perPage, 38 | } 39 | }); 40 | 41 | // If no notifications, return simple message 42 | if (notifications.length === 0) { 43 | return { 44 | content: [{ 45 | type: "text", 46 | text: "No notifications found with the given criteria." 47 | }] 48 | }; 49 | } 50 | 51 | // Format the notifications for better readability 52 | const formattedNotifications = notifications.map(formatNotification).join("\n\n"); 53 | 54 | // Check for pagination - simplified approach without headers 55 | let paginationInfo = ""; 56 | 57 | if (notifications.length === perPage) { 58 | paginationInfo = "\n\nMore notifications may be available. You can view the next page by specifying 'page: " + 59 | (page + 1) + "' in the request."; 60 | } 61 | 62 | return { 63 | content: [{ 64 | type: "text", 65 | text: `${notifications.length} notifications found: 66 | 67 | ${formattedNotifications}${paginationInfo}` 68 | }] 69 | }; 70 | } catch (error) { 71 | return { 72 | isError: true, 73 | content: [{ 74 | type: "text", 75 | text: formatError("Failed to fetch notifications", error) 76 | }] 77 | }; 78 | } 79 | } 80 | 81 | /** 82 | * Register this tool with the server 83 | */ 84 | export function registerListNotificationsTool(server: any) { 85 | server.tool( 86 | "list-notifications", 87 | "List GitHub notifications for the authenticated user", 88 | listNotificationsSchema.shape, 89 | listNotificationsHandler 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/tools/list-repo-notifications.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for listing repository notifications 3 | */ 4 | import { z } from "zod"; 5 | import { githubGet } from "../utils/api.js"; 6 | import { formatNotification, formatError } from "../utils/formatters.js"; 7 | import { NotificationResponse } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for list-repo-notifications tool input parameters 11 | */ 12 | export const listRepoNotificationsSchema = z.object({ 13 | owner: z.string().describe("The account owner of the repository"), 14 | repo: z.string().describe("The name of the repository"), 15 | all: z.boolean().optional().describe("If true, show notifications marked as read"), 16 | participating: z.boolean().optional().describe("If true, only shows notifications where user is directly participating"), 17 | since: z.string().optional().describe("ISO 8601 timestamp - only show notifications updated after this time"), 18 | before: z.string().optional().describe("ISO 8601 timestamp - only show notifications updated before this time"), 19 | page: z.number().optional().describe("Page number for pagination"), 20 | per_page: z.number().optional().describe("Number of results per page (max 100)") 21 | }); 22 | 23 | /** 24 | * Tool implementation for listing repository notifications 25 | */ 26 | export async function listRepoNotificationsHandler(args: z.infer) { 27 | try { 28 | const perPage = args.per_page || 30; 29 | const page = args.page || 1; 30 | 31 | // Make request to GitHub API 32 | const notifications = await githubGet(`/repos/${args.owner}/${args.repo}/notifications`, { 33 | params: { 34 | all: args.all, 35 | participating: args.participating, 36 | since: args.since, 37 | before: args.before, 38 | page: page, 39 | per_page: perPage, 40 | } 41 | }); 42 | 43 | // If no notifications, return simple message 44 | if (notifications.length === 0) { 45 | return { 46 | content: [{ 47 | type: "text", 48 | text: `No notifications found for repository ${args.owner}/${args.repo} with the given criteria.` 49 | }] 50 | }; 51 | } 52 | 53 | // Format the notifications for better readability 54 | const formattedNotifications = notifications.map(formatNotification).join("\n\n"); 55 | 56 | // Check for pagination - simplified approach without headers 57 | let paginationInfo = ""; 58 | 59 | if (notifications.length === perPage) { 60 | paginationInfo = "\n\nMore notifications may be available. You can view the next page by specifying 'page: " + 61 | (page + 1) + "' in the request."; 62 | } 63 | 64 | return { 65 | content: [{ 66 | type: "text", 67 | text: `${notifications.length} notifications found for repository ${args.owner}/${args.repo}: 68 | 69 | ${formattedNotifications}${paginationInfo}` 70 | }] 71 | }; 72 | } catch (error) { 73 | return { 74 | isError: true, 75 | content: [{ 76 | type: "text", 77 | text: formatError(`Failed to fetch notifications for repository ${args.owner}/${args.repo}`, error) 78 | }] 79 | }; 80 | } 81 | } 82 | 83 | /** 84 | * Register this tool with the server 85 | */ 86 | export function registerListRepoNotificationsTool(server: any) { 87 | server.tool( 88 | "list-repo-notifications", 89 | "List GitHub notifications for a specific repository", 90 | listRepoNotificationsSchema.shape, 91 | listRepoNotificationsHandler 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /src/tools/manage-repo-subscription.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { githubPut, githubDelete, githubGet } from '../utils/api.js'; 3 | import { formatError } from '../utils/formatters.js'; 4 | 5 | /** 6 | * Schema for manage-repo-subscription tool input parameters 7 | */ 8 | export const manageRepoSubscriptionSchema = z.object({ 9 | owner: z.string().min(1, 'Repository owner is required') 10 | .describe('The account owner of the repository. The name is not case sensitive.'), 11 | repo: z.string().min(1, 'Repository name is required') 12 | .describe('The name of the repository without the .git extension. The name is not case sensitive.'), 13 | action: z.enum(['all_activity', 'default', 'ignore', 'get']) 14 | .describe('The action to perform: all_activity (watch all), default (participating and @mentions only), ignore (mute notifications), or get (view current settings)'), 15 | options: z.object({ 16 | subscribed: z.boolean().optional() 17 | .describe('Whether to receive notifications from this repository'), 18 | ignored: z.boolean().optional() 19 | .describe('Whether to ignore all notifications from this repository') 20 | }).optional() 21 | .describe('Optional settings for custom subscription configuration') 22 | }); 23 | 24 | export type ManageRepoSubscriptionArgs = z.infer; 25 | 26 | interface SubscriptionResponse { 27 | subscribed: boolean; 28 | ignored: boolean; 29 | reason: string | null; 30 | created_at: string; 31 | url: string; 32 | repository_url: string; 33 | } 34 | 35 | /** 36 | * Tool implementation for managing repository subscriptions 37 | */ 38 | export async function manageRepoSubscriptionHandler(args: ManageRepoSubscriptionArgs) { 39 | const { owner, repo, action, options } = args; 40 | 41 | try { 42 | switch (action) { 43 | case 'get': 44 | try { 45 | const response = await githubGet(`/repos/${owner}/${repo}/subscription`); 46 | const formattedDate = new Date(response.created_at).toLocaleString(); 47 | return { 48 | content: [{ 49 | type: 'text', 50 | text: `Subscription status for ${owner}/${repo}: 51 | • API Subscription: ${response.subscribed ? 'Watching all activity' : 'Not watching'} 52 | • Notifications: ${response.ignored ? 'Ignored' : 'Active'} 53 | • Created at: ${formattedDate} 54 | • Web Interface: https://github.com/${owner}/${repo}` 55 | }] 56 | }; 57 | } catch (error: any) { 58 | // Special handling for 404 in get action - could be default or custom settings 59 | if (error.message?.includes('Resource not found')) { 60 | return { 61 | content: [{ 62 | type: 'text', 63 | text: `Subscription status for ${owner}/${repo}: 64 | • Default settings (participating and @mentions only) 65 | • or Custom through the GitHub web interface at: 66 | https://github.com/${owner}/${repo}` 67 | }] 68 | }; 69 | } 70 | throw error; // Re-throw other errors to be handled by the outer catch 71 | } 72 | 73 | case 'all_activity': 74 | await githubPut(`/repos/${owner}/${repo}/subscription`, { 75 | subscribed: true, 76 | ignored: false 77 | }); 78 | return { 79 | content: [{ 80 | type: 'text', 81 | text: `Successfully set ${owner}/${repo} to watch all activity` 82 | }] 83 | }; 84 | 85 | case 'default': 86 | await githubDelete(`/repos/${owner}/${repo}/subscription`); 87 | return { 88 | content: [{ 89 | type: 'text', 90 | text: `Successfully set ${owner}/${repo} to default settings (participating and @mentions only)` 91 | }] 92 | }; 93 | 94 | case 'ignore': 95 | await githubPut(`/repos/${owner}/${repo}/subscription`, { 96 | subscribed: false, 97 | ignored: true 98 | }); 99 | return { 100 | content: [{ 101 | type: 'text', 102 | text: `Successfully set ${owner}/${repo} to ignore all notifications` 103 | }] 104 | }; 105 | } 106 | } catch (error: any) { 107 | return { 108 | isError: true, 109 | content: [{ 110 | type: 'text', 111 | text: formatError(`Failed to manage repository subscription for ${owner}/${repo}`, error) 112 | }] 113 | }; 114 | } 115 | } 116 | 117 | /** 118 | * Register this tool with the server 119 | */ 120 | export function registerManageRepoSubscriptionTool(server: any) { 121 | server.tool( 122 | 'manage-repo-subscription', 123 | 'Manage repository subscription settings including fine-grained notification preferences', 124 | manageRepoSubscriptionSchema.shape, 125 | manageRepoSubscriptionHandler 126 | ); 127 | } -------------------------------------------------------------------------------- /src/tools/mark-notifications-read.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for marking GitHub notifications as read 3 | */ 4 | import { z } from "zod"; 5 | import { githubPut } from "../utils/api.js"; 6 | import { formatError } from "../utils/formatters.js"; 7 | import { MarkNotificationsReadResponse } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for mark-notifications-read tool input parameters 11 | */ 12 | export const markNotificationsReadSchema = z.object({ 13 | last_read_at: z.string().optional().describe( 14 | "ISO 8601 timestamp - marks notifications updated at or before this time as read. Default is current time." 15 | ), 16 | read: z.boolean().optional().default(true).describe( 17 | "Whether to mark notifications as read or unread" 18 | ) 19 | }); 20 | 21 | /** 22 | * Tool implementation for marking GitHub notifications as read 23 | */ 24 | export async function markNotificationsReadHandler(args: z.infer) { 25 | try { 26 | // Prepare request body 27 | const requestBody = { 28 | last_read_at: args.last_read_at, 29 | read: args.read 30 | }; 31 | 32 | // Make request to GitHub API 33 | const response = await githubPut("/notifications", requestBody); 34 | 35 | // Check if notifications are processed asynchronously 36 | if (response.message) { 37 | return { 38 | content: [{ 39 | type: "text", 40 | text: `${response.message}` 41 | }] 42 | }; 43 | } 44 | 45 | // Default success message 46 | return { 47 | content: [{ 48 | type: "text", 49 | text: `Successfully marked notifications as ${args.read ? 'read' : 'unread'}.${ 50 | args.last_read_at 51 | ? ` Notifications updated on or before ${new Date(args.last_read_at).toLocaleString()} were affected.` 52 | : '' 53 | }` 54 | }] 55 | }; 56 | } catch (error) { 57 | return { 58 | isError: true, 59 | content: [{ 60 | type: "text", 61 | text: formatError("Failed to mark notifications as read", error) 62 | }] 63 | }; 64 | } 65 | } 66 | 67 | /** 68 | * Register this tool with the server 69 | */ 70 | export function registerMarkNotificationsReadTool(server: any) { 71 | server.tool( 72 | "mark-notifications-read", 73 | "Mark GitHub notifications as read", 74 | markNotificationsReadSchema.shape, 75 | markNotificationsReadHandler 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/tools/mark-repo-notifications-read.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for marking repository notifications as read 3 | */ 4 | import { z } from "zod"; 5 | import { githubPut } from "../utils/api.js"; 6 | import { formatError } from "../utils/formatters.js"; 7 | import { MarkNotificationsReadResponse } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for mark-repo-notifications-read tool input parameters 11 | */ 12 | export const markRepoNotificationsReadSchema = z.object({ 13 | owner: z.string().describe("The account owner of the repository"), 14 | repo: z.string().describe("The name of the repository"), 15 | last_read_at: z.string().optional().describe( 16 | "ISO 8601 timestamp - marks notifications updated at or before this time as read. Default is current time." 17 | ), 18 | read: z.boolean().optional().default(true).describe( 19 | "Whether to mark notifications as read or unread" 20 | ) 21 | }); 22 | 23 | /** 24 | * Tool implementation for marking repository notifications as read 25 | */ 26 | export async function markRepoNotificationsReadHandler(args: z.infer) { 27 | try { 28 | // Prepare request body 29 | const requestBody = { 30 | last_read_at: args.last_read_at, 31 | read: args.read 32 | }; 33 | 34 | // Make request to GitHub API 35 | const response = await githubPut( 36 | `/repos/${args.owner}/${args.repo}/notifications`, 37 | requestBody 38 | ); 39 | 40 | // Check if notifications are processed asynchronously 41 | if (response.message) { 42 | return { 43 | content: [{ 44 | type: "text", 45 | text: `${response.message}` 46 | }] 47 | }; 48 | } 49 | 50 | // Default success message 51 | return { 52 | content: [{ 53 | type: "text", 54 | text: `Successfully marked notifications for repository ${args.owner}/${args.repo} as ${args.read ? 'read' : 'unread'}.${ 55 | args.last_read_at 56 | ? ` Notifications updated on or before ${new Date(args.last_read_at).toLocaleString()} were affected.` 57 | : '' 58 | }` 59 | }] 60 | }; 61 | } catch (error) { 62 | return { 63 | isError: true, 64 | content: [{ 65 | type: "text", 66 | text: formatError(`Failed to mark notifications as read for repository ${args.owner}/${args.repo}`, error) 67 | }] 68 | }; 69 | } 70 | } 71 | 72 | /** 73 | * Register this tool with the server 74 | */ 75 | export function registerMarkRepoNotificationsReadTool(server: any) { 76 | server.tool( 77 | "mark-repo-notifications-read", 78 | "Mark GitHub notifications for a specific repository as read", 79 | markRepoNotificationsReadSchema.shape, 80 | markRepoNotificationsReadHandler 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/tools/mark-thread-done.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for marking a GitHub notification thread as done 3 | */ 4 | import { z } from "zod"; 5 | import { githubDelete } from "../utils/api.js"; 6 | import { formatError } from "../utils/formatters.js"; 7 | 8 | /** 9 | * Schema for mark-thread-done tool input parameters 10 | */ 11 | export const markThreadDoneSchema = z.object({ 12 | thread_id: z.string().describe("The ID of the notification thread to mark as done") 13 | }); 14 | 15 | /** 16 | * Tool implementation for marking a GitHub notification thread as done 17 | */ 18 | export async function markThreadDoneHandler(args: z.infer) { 19 | try { 20 | // Make request to GitHub API 21 | await githubDelete(`/notifications/threads/${args.thread_id}`); 22 | 23 | return { 24 | content: [{ 25 | type: "text", 26 | text: `Successfully marked thread ${args.thread_id} as done.` 27 | }] 28 | }; 29 | } catch (error) { 30 | return { 31 | isError: true, 32 | content: [{ 33 | type: "text", 34 | text: formatError(`Failed to mark thread ${args.thread_id} as done`, error) 35 | }] 36 | }; 37 | } 38 | } 39 | 40 | /** 41 | * Register this tool with the server 42 | */ 43 | export function registerMarkThreadDoneTool(server: any) { 44 | server.tool( 45 | "mark-thread-done", 46 | "Mark a GitHub notification thread as done", 47 | markThreadDoneSchema.shape, 48 | markThreadDoneHandler 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/tools/mark-thread-read.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for marking a GitHub notification thread as read 3 | */ 4 | import { z } from "zod"; 5 | import { githubPatch } from "../utils/api.js"; 6 | import { formatError } from "../utils/formatters.js"; 7 | 8 | /** 9 | * Schema for mark-thread-read tool input parameters 10 | */ 11 | export const markThreadReadSchema = z.object({ 12 | thread_id: z.string().describe("The ID of the notification thread to mark as read") 13 | }); 14 | 15 | /** 16 | * Tool implementation for marking a GitHub notification thread as read 17 | */ 18 | export async function markThreadReadHandler(args: z.infer) { 19 | try { 20 | // Make request to GitHub API 21 | await githubPatch(`/notifications/threads/${args.thread_id}`); 22 | 23 | return { 24 | content: [{ 25 | type: "text", 26 | text: `Successfully marked thread ${args.thread_id} as read.` 27 | }] 28 | }; 29 | } catch (error) { 30 | return { 31 | isError: true, 32 | content: [{ 33 | type: "text", 34 | text: formatError(`Failed to mark thread ${args.thread_id} as read`, error) 35 | }] 36 | }; 37 | } 38 | } 39 | 40 | /** 41 | * Register this tool with the server 42 | */ 43 | export function registerMarkThreadReadTool(server: any) { 44 | server.tool( 45 | "mark-thread-read", 46 | "Mark a GitHub notification thread as read", 47 | markThreadReadSchema.shape, 48 | markThreadReadHandler 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/tools/set-thread-subscription.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tool implementation for setting a GitHub thread subscription 3 | */ 4 | import { z } from "zod"; 5 | import { githubPut } from "../utils/api.js"; 6 | import { formatSubscription, formatError } from "../utils/formatters.js"; 7 | import { ThreadSubscription } from "../types/github-api.js"; 8 | 9 | /** 10 | * Schema for set-thread-subscription tool input parameters 11 | */ 12 | export const setThreadSubscriptionSchema = z.object({ 13 | thread_id: z.string().describe("The ID of the notification thread to subscribe to"), 14 | ignored: z.boolean().optional().default(false).describe("If true, notifications will be ignored") 15 | }); 16 | 17 | /** 18 | * Tool implementation for setting a thread subscription 19 | */ 20 | export async function setThreadSubscriptionHandler(args: z.infer) { 21 | try { 22 | // Prepare request body 23 | const requestBody = { 24 | ignored: args.ignored 25 | }; 26 | 27 | // Make request to GitHub API 28 | const subscription = await githubPut( 29 | `/notifications/threads/${args.thread_id}/subscription`, 30 | requestBody 31 | ); 32 | 33 | // Format the subscription for better readability 34 | const formattedSubscription = formatSubscription(subscription); 35 | const status = args.ignored ? "ignoring" : "subscribing to"; 36 | 37 | return { 38 | content: [{ 39 | type: "text", 40 | text: `Successfully updated subscription by ${status} thread ${args.thread_id}:\n\n${formattedSubscription}` 41 | }] 42 | }; 43 | } catch (error) { 44 | return { 45 | isError: true, 46 | content: [{ 47 | type: "text", 48 | text: formatError(`Failed to update subscription for thread ${args.thread_id}`, error) 49 | }] 50 | }; 51 | } 52 | } 53 | 54 | /** 55 | * Register this tool with the server 56 | */ 57 | export function registerSetThreadSubscriptionTool(server: any) { 58 | server.tool( 59 | "set-thread-subscription", 60 | "Subscribe to a GitHub notification thread", 61 | setThreadSubscriptionSchema.shape, 62 | setThreadSubscriptionHandler 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/types/github-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Repository owner information 3 | */ 4 | export interface RepositoryOwner { 5 | login: string; 6 | id: number; 7 | node_id: string; 8 | avatar_url: string; 9 | gravatar_id: string; 10 | url: string; 11 | html_url: string; 12 | followers_url: string; 13 | following_url: string; 14 | gists_url: string; 15 | starred_url: string; 16 | subscriptions_url: string; 17 | organizations_url: string; 18 | repos_url: string; 19 | events_url: string; 20 | received_events_url: string; 21 | type: string; 22 | site_admin: boolean; 23 | } 24 | 25 | /** 26 | * Repository information 27 | */ 28 | export interface Repository { 29 | id: number; 30 | node_id: string; 31 | name: string; 32 | full_name: string; 33 | owner: RepositoryOwner; 34 | private: boolean; 35 | html_url: string; 36 | description: string | null; 37 | fork: boolean; 38 | url: string; 39 | archive_url: string; 40 | assignees_url: string; 41 | blobs_url: string; 42 | branches_url: string; 43 | collaborators_url: string; 44 | comments_url: string; 45 | commits_url: string; 46 | compare_url: string; 47 | contents_url: string; 48 | contributors_url: string; 49 | deployments_url: string; 50 | downloads_url: string; 51 | events_url: string; 52 | forks_url: string; 53 | git_commits_url: string; 54 | git_refs_url: string; 55 | git_tags_url: string; 56 | git_url: string; 57 | issue_comment_url: string; 58 | issue_events_url: string; 59 | issues_url: string; 60 | keys_url: string; 61 | labels_url: string; 62 | languages_url: string; 63 | merges_url: string; 64 | milestones_url: string; 65 | notifications_url: string; 66 | pulls_url: string; 67 | releases_url: string; 68 | ssh_url: string; 69 | stargazers_url: string; 70 | statuses_url: string; 71 | subscribers_url: string; 72 | subscription_url: string; 73 | tags_url: string; 74 | teams_url: string; 75 | trees_url: string; 76 | hooks_url: string; 77 | } 78 | 79 | /** 80 | * Subject information of a notification 81 | */ 82 | export interface NotificationSubject { 83 | title: string; 84 | url: string; 85 | latest_comment_url: string; 86 | type: string; 87 | } 88 | 89 | /** 90 | * Notification reason types 91 | */ 92 | export type NotificationReason = 93 | | 'approval_requested' 94 | | 'assign' 95 | | 'author' 96 | | 'comment' 97 | | 'ci_activity' 98 | | 'invitation' 99 | | 'manual' 100 | | 'member_feature_requested' 101 | | 'mention' 102 | | 'review_requested' 103 | | 'security_alert' 104 | | 'security_advisory_credit' 105 | | 'state_change' 106 | | 'subscribed' 107 | | 'team_mention'; 108 | 109 | /** 110 | * Response type for notifications 111 | */ 112 | export interface NotificationResponse { 113 | id: string; 114 | repository: Repository; 115 | subject: NotificationSubject; 116 | reason: NotificationReason; 117 | unread: boolean; 118 | updated_at: string; 119 | last_read_at: string | null; 120 | url: string; 121 | subscription_url: string; 122 | } 123 | 124 | /** 125 | * Thread subscription information 126 | */ 127 | export interface ThreadSubscription { 128 | subscribed: boolean; 129 | ignored: boolean; 130 | reason: string | null; 131 | created_at: string; 132 | url: string; 133 | thread_url: string; 134 | } 135 | 136 | /** 137 | * Mark notifications as read response 138 | */ 139 | export interface MarkNotificationsReadResponse { 140 | message?: string; 141 | } 142 | 143 | /** 144 | * Set thread subscription request 145 | */ 146 | export interface SetThreadSubscriptionRequest { 147 | ignored?: boolean; 148 | } 149 | 150 | /** 151 | * Mark notifications as read request 152 | */ 153 | export interface MarkNotificationsReadRequest { 154 | last_read_at?: string; 155 | read?: boolean; 156 | } 157 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * API client for GitHub API requests using fetch 3 | */ 4 | 5 | // Check for required environment variable 6 | if (!process.env.GITHUB_TOKEN) { 7 | console.error("Error: GITHUB_TOKEN environment variable is required"); 8 | process.exit(1); 9 | } 10 | 11 | const BASE_URL = "https://api.github.com"; 12 | const DEFAULT_HEADERS = { 13 | "Accept": "application/vnd.github+json", 14 | "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`, 15 | "X-GitHub-Api-Version": "2022-11-28", 16 | "User-Agent": "GitHub-Notifications-MCP-Server/1.0.0" 17 | }; 18 | 19 | /** 20 | * Interface for request options 21 | */ 22 | export interface RequestOptions { 23 | params?: Record; 24 | headers?: Record; 25 | } 26 | 27 | /** 28 | * Helper function to handle GitHub API responses 29 | */ 30 | async function handleResponse(response: Response): Promise { 31 | // Log rate limit information for debugging 32 | const rateLimit = { 33 | limit: response.headers.get('x-ratelimit-limit'), 34 | remaining: response.headers.get('x-ratelimit-remaining'), 35 | reset: response.headers.get('x-ratelimit-reset'), 36 | used: response.headers.get('x-ratelimit-used') 37 | }; 38 | 39 | console.error(`GitHub API call: ${response.url} [Rate limit: ${rateLimit.remaining}/${rateLimit.limit}]`); 40 | 41 | // Handle error responses 42 | if (!response.ok) { 43 | const statusCode = response.status; 44 | let errorData; 45 | 46 | try { 47 | errorData = await response.json(); 48 | } catch (e) { 49 | errorData = { message: "Unknown error" }; 50 | } 51 | 52 | // Handle specific status codes 53 | switch (statusCode) { 54 | case 401: 55 | throw new Error("Authentication failed. Please check your GitHub token."); 56 | case 403: 57 | // Handle rate limiting 58 | if (rateLimit.remaining === '0') { 59 | const resetTime = new Date(parseInt(rateLimit.reset || '0') * 1000); 60 | throw new Error(`GitHub API rate limit exceeded. Resets at ${resetTime.toLocaleTimeString()}`); 61 | } 62 | throw new Error(`Access forbidden: ${errorData.message}`); 63 | case 404: 64 | throw new Error(`Resource not found: ${errorData.message}`); 65 | case 422: 66 | throw new Error(`Validation failed: ${errorData.message} ${errorData.errors ? JSON.stringify(errorData.errors) : ''}`); 67 | default: 68 | throw new Error(`GitHub API error (${statusCode}): ${errorData.message}`); 69 | } 70 | } 71 | 72 | // For 204 No Content responses, return empty object 73 | if (response.status === 204) { 74 | return {} as T; 75 | } 76 | 77 | // Parse JSON response 78 | return await response.json() as T; 79 | } 80 | 81 | /** 82 | * Builds a URL with query parameters 83 | */ 84 | function buildUrl(path: string, params?: Record): string { 85 | const url = new URL(path, BASE_URL); 86 | 87 | if (params) { 88 | Object.entries(params).forEach(([key, value]) => { 89 | if (value !== undefined && value !== null) { 90 | url.searchParams.append(key, String(value)); 91 | } 92 | }); 93 | } 94 | 95 | return url.toString(); 96 | } 97 | 98 | /** 99 | * Makes a GET request to the GitHub API 100 | * 101 | * @param path API endpoint path 102 | * @param options Request options 103 | * @returns Promise with the API response 104 | */ 105 | export async function githubGet(path: string, options: RequestOptions = {}): Promise { 106 | const url = buildUrl(path, options.params); 107 | 108 | const response = await fetch(url, { 109 | method: 'GET', 110 | headers: { 111 | ...DEFAULT_HEADERS, 112 | ...options.headers 113 | } 114 | }); 115 | 116 | return handleResponse(response); 117 | } 118 | 119 | /** 120 | * Makes a PUT request to the GitHub API 121 | * 122 | * @param path API endpoint path 123 | * @param data Request body data 124 | * @param options Request options 125 | * @returns Promise with the API response 126 | */ 127 | export async function githubPut(path: string, data?: any, options: RequestOptions = {}): Promise { 128 | const url = buildUrl(path, options.params); 129 | 130 | const response = await fetch(url, { 131 | method: 'PUT', 132 | headers: { 133 | ...DEFAULT_HEADERS, 134 | 'Content-Type': 'application/json', 135 | ...options.headers 136 | }, 137 | body: data ? JSON.stringify(data) : undefined 138 | }); 139 | 140 | return handleResponse(response); 141 | } 142 | 143 | /** 144 | * Makes a PATCH request to the GitHub API 145 | * 146 | * @param path API endpoint path 147 | * @param data Request body data 148 | * @param options Request options 149 | * @returns Promise with the API response 150 | */ 151 | export async function githubPatch(path: string, data?: any, options: RequestOptions = {}): Promise { 152 | const url = buildUrl(path, options.params); 153 | 154 | const response = await fetch(url, { 155 | method: 'PATCH', 156 | headers: { 157 | ...DEFAULT_HEADERS, 158 | 'Content-Type': 'application/json', 159 | ...options.headers 160 | }, 161 | body: data ? JSON.stringify(data) : undefined 162 | }); 163 | 164 | return handleResponse(response); 165 | } 166 | 167 | /** 168 | * Makes a DELETE request to the GitHub API 169 | * 170 | * @param path API endpoint path 171 | * @param options Request options 172 | * @returns Promise with the API response 173 | */ 174 | export async function githubDelete(path: string, options: RequestOptions = {}): Promise { 175 | const url = buildUrl(path, options.params); 176 | 177 | const response = await fetch(url, { 178 | method: 'DELETE', 179 | headers: { 180 | ...DEFAULT_HEADERS, 181 | ...options.headers 182 | } 183 | }); 184 | 185 | return handleResponse(response); 186 | } 187 | -------------------------------------------------------------------------------- /src/utils/formatters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for formatting responses 3 | */ 4 | import { NotificationResponse, ThreadSubscription, NotificationReason } from "../types/github-api.js"; 5 | 6 | /** 7 | * Converts a GitHub API URL to its HTML (web UI) equivalent 8 | */ 9 | export function convertApiUrlToHtmlUrl(apiUrl: string): string { 10 | // Replace API domain with web UI domain 11 | let htmlUrl = apiUrl.replace("api.github.com/repos", "github.com"); 12 | 13 | // Fix pull request URLs - change 'pulls' to 'pull' in the URL 14 | htmlUrl = htmlUrl.replace(/\/pulls\/([0-9]+)(\/?.*)/g, "/pull/$1$2"); 15 | 16 | // Fix issue URLs - keep plural 'issues' since GitHub uses this in both API and web URLs 17 | // This is just for completeness, as the API already uses the correct format 18 | 19 | return htmlUrl; 20 | } 21 | 22 | /** 23 | * Provides a human-readable description of a notification reason 24 | */ 25 | export function describeReason(reason: NotificationReason): string { 26 | const descriptions: Record = { 27 | approval_requested: "You were requested to review and approve a deployment", 28 | assign: "You were assigned to the issue", 29 | author: "You created the thread", 30 | comment: "You commented on the thread", 31 | ci_activity: "A GitHub Actions workflow run that you triggered was completed", 32 | invitation: "You accepted an invitation to contribute to the repository", 33 | manual: "You subscribed to the thread", 34 | member_feature_requested: "Organization members have requested to enable a feature", 35 | mention: "You were @mentioned in the content", 36 | review_requested: "You were requested to review a pull request", 37 | security_alert: "GitHub discovered a security vulnerability in your repository", 38 | security_advisory_credit: "You were credited for contributing to a security advisory", 39 | state_change: "You changed the thread state", 40 | subscribed: "You're watching the repository", 41 | team_mention: "You were on a team that was mentioned", 42 | }; 43 | 44 | return descriptions[reason] || "Unknown reason"; 45 | } 46 | 47 | /** 48 | * Formats a notification object into a human-readable string 49 | */ 50 | export function formatNotification(notification: NotificationResponse): string { 51 | const date = new Date(notification.updated_at).toLocaleString(); 52 | const repoName = notification.repository.full_name; 53 | const reason = describeReason(notification.reason); 54 | const status = notification.unread ? "Unread" : "Read"; 55 | const type = notification.subject.type; 56 | 57 | return `ID: ${notification.id} 58 | Title: ${notification.subject.title} 59 | Repository: ${repoName} 60 | Type: ${type} 61 | Reason: ${notification.reason} (${reason}) 62 | Status: ${status} 63 | Updated: ${date} 64 | URL: ${convertApiUrlToHtmlUrl(notification.subject.url)}`; 65 | } 66 | 67 | /** 68 | * Formats a thread subscription into a human-readable string 69 | */ 70 | export function formatSubscription(subscription: ThreadSubscription): string { 71 | return `Subscription Status: ${subscription.subscribed ? "Subscribed" : "Not Subscribed"} 72 | Ignored: ${subscription.ignored ? "Yes" : "No"} 73 | ${subscription.reason ? `Reason: ${subscription.reason}` : ""} 74 | Created: ${new Date(subscription.created_at).toLocaleString()}`; 75 | } 76 | 77 | /** 78 | * Format an error response 79 | */ 80 | export function formatError(message: string, error: unknown): string { 81 | if (error instanceof Error) { 82 | return `${message}: ${error.message}`; 83 | } else if (typeof error === 'string') { 84 | return `${message}: ${error}`; 85 | } else { 86 | return `${message}: Unknown error`; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "./build", 11 | "rootDir": "./src", 12 | "sourceMap": true, 13 | "declaration": true, 14 | "resolveJsonModule": true, 15 | "baseUrl": ".", 16 | "paths": { 17 | "@modelcontextprotocol/sdk/*": ["node_modules/@modelcontextprotocol/sdk/dist/*"] 18 | } 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules", "build", "src/__tests__/**/*", "**/*.test.ts", "**/*.spec.ts"] 22 | } 23 | --------------------------------------------------------------------------------