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

```
├── .github
│   ├── dependabot.yml
│   └── workflows
│       ├── ci.yml
│       └── dependabot-auto-merge.yml
├── .gitignore
├── Dockerfile
├── notes.md
├── package-lock.json
├── package.json
├── README.md
├── run-server.bat
├── smithery.yaml
├── src
│   ├── common
│   │   ├── errors.ts
│   │   ├── types.ts
│   │   ├── utils.ts
│   │   └── version.ts
│   ├── index.ts
│   └── operations
│       └── actions.ts
└── tsconfig.json
```

# Files

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

```
# Dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
yarn.lock

# Build output
dist/
build/
out/
.next/
.nuxt/

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor settings
.idea/
.vscode/
*.swp
*.swo
.DS_Store
.prettierrc

# Test
coverage/
.nyc_output/

# Others
.cache/
tmp/
temp/

# Claude Crew
.claude-crew/
task-analysis/

```

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

```markdown
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/cad0f49e-1c4d-4ab1-97e4-2312da835454)
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/ko1ynnky-github-actions-mcp-server-badge.png)](https://mseep.ai/app/ko1ynnky-github-actions-mcp-server)

# GitHub Actions MCP Server

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

> **⚠️ Archive Notice**: This repository will be archived soon as the official GitHub MCP server is adding Actions support. See [github/github-mcp-server#491](https://github.com/github/github-mcp-server/pull/491) for details on the official implementation.

MCP Server for the GitHub Actions API, enabling AI assistants to manage and operate GitHub Actions workflows. Compatible with multiple AI coding assistants including Claude Desktop, Codeium, and Windsurf.

### Features

- **Complete Workflow Management**: List, view, trigger, cancel, and rerun workflows
- **Workflow Run Analysis**: Get detailed information about workflow runs and their jobs
- **Comprehensive Error Handling**: Clear error messages with enhanced details
- **Flexible Type Validation**: Robust type checking with graceful handling of API variations
- **Security-Focused Design**: Timeout handling, rate limiting, and strict URL validation

## Tools

1. `list_workflows`
   - List workflows in a GitHub repository
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `page` (optional number): Page number for pagination
     - `perPage` (optional number): Results per page (max 100)
   - Returns: List of workflows in the repository

2. `get_workflow`
   - Get details of a specific workflow
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `workflowId` (string or number): The ID of the workflow or filename
   - Returns: Detailed information about the workflow

3. `get_workflow_usage`
   - Get usage statistics of a workflow
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `workflowId` (string or number): The ID of the workflow or filename
   - Returns: Usage statistics including billable minutes

4. `list_workflow_runs`
   - List all workflow runs for a repository or a specific workflow
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `workflowId` (optional string or number): The ID of the workflow or filename
     - `actor` (optional string): Filter by user who triggered the workflow
     - `branch` (optional string): Filter by branch
     - `event` (optional string): Filter by event type
     - `status` (optional string): Filter by status
     - `created` (optional string): Filter by creation date (YYYY-MM-DD)
     - `excludePullRequests` (optional boolean): Exclude PR-triggered runs
     - `checkSuiteId` (optional number): Filter by check suite ID
     - `page` (optional number): Page number for pagination
     - `perPage` (optional number): Results per page (max 100)
   - Returns: List of workflow runs matching the criteria

5. `get_workflow_run`
   - Get details of a specific workflow run
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `runId` (number): The ID of the workflow run
   - Returns: Detailed information about the specific workflow run

6. `get_workflow_run_jobs`
   - Get jobs for a specific workflow run
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `runId` (number): The ID of the workflow run
     - `filter` (optional string): Filter jobs by completion status ('latest', 'all')
     - `page` (optional number): Page number for pagination
     - `perPage` (optional number): Results per page (max 100)
   - Returns: List of jobs in the workflow run

7. `trigger_workflow`
   - Trigger a workflow run
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `workflowId` (string or number): The ID of the workflow or filename
     - `ref` (string): The reference to run the workflow on (branch, tag, or SHA)
     - `inputs` (optional object): Input parameters for the workflow
   - Returns: Information about the triggered workflow run

8. `cancel_workflow_run`
   - Cancel a workflow run
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `runId` (number): The ID of the workflow run
   - Returns: Status of the cancellation operation

9. `rerun_workflow`
   - Re-run a workflow run
   - Inputs:
     - `owner` (string): Repository owner (username or organization)
     - `repo` (string): Repository name
     - `runId` (number): The ID of the workflow run
   - Returns: Status of the re-run operation

### Usage with AI Coding Assistants

This MCP server is compatible with multiple AI coding assistants including Claude Desktop, Codeium, and Windsurf.

#### Claude Desktop

First, make sure you have built the project (see Build section below). Then, add the following to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "github-actions": {
      "command": "node",
      "args": [
        "<path-to-mcp-server>/dist/index.js"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      }
    }
  }
}
```

#### Codeium

Add the following configuration to your Codeium MCP config file (typically at `~/.codeium/windsurf/mcp_config.json` on Unix-based systems or `%USERPROFILE%\.codeium\windsurf\mcp_config.json` on Windows):

```json
{
  "mcpServers": {
    "github-actions": {
      "command": "node",
      "args": [
        "<path-to-mcp-server>/dist/index.js"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      }
    }
  }
}
```

#### Windsurf

Windsurf uses the same configuration format as Codeium. Add the server to your Windsurf MCP configuration as shown above for Codeium.

## Build

### Unix/Linux/macOS

Clone the repository and build:

```bash
git clone https://github.com/ko1ynnky/github-actions-mcp-server.git
cd github-actions-mcp-server
npm install
npm run build
```

### Windows

For Windows systems, use the Windows-specific build command:

```bash
git clone https://github.com/ko1ynnky/github-actions-mcp-server.git
cd github-actions-mcp-server
npm install
npm run build:win
```

Alternatively, you can use the included batch file:

```bash
run-server.bat [optional-github-token]
```

This will create the necessary files in the `dist` directory that you'll need to run the MCP server.

#### Windows-Specific Instructions

**Prerequisites**
- Node.js (v14 or higher)
- npm (v6 or higher)

**Running the Server on Windows**

1. Using the batch file (simplest method):
   ```
   run-server.bat [optional-github-token]
   ```
   This will check if the build exists, build if needed, and start the server.

2. Using npm directly:
   ```
   npm run start
   ```

**Setting GitHub Personal Access Token on Windows**

For full functionality and to avoid rate limiting, you need to set your GitHub Personal Access Token.

Options:
1. Pass it as a parameter to the batch file:
   ```
   run-server.bat your_github_token_here
   ```

2. Set it as an environment variable:
   ```
   set GITHUB_PERSONAL_ACCESS_TOKEN=your_github_token_here
   npm run start
   ```

**Troubleshooting Windows Issues**

If you encounter issues:

1. **Build errors**: Make sure TypeScript is installed correctly.
   ```
   npm install -g typescript
   ```

2. **Permission issues**: Ensure you're running the commands in a command prompt with appropriate permissions.

3. **Node.js errors**: Verify you're using a compatible Node.js version.
   ```
   node --version
   ```

## Usage Examples

List workflows in a repository:

```javascript
const result = await listWorkflows({
  owner: "your-username",
  repo: "your-repository"
});
```

Trigger a workflow:

```javascript
const result = await triggerWorkflow({
  owner: "your-username",
  repo: "your-repository",
  workflowId: "ci.yml",
  ref: "main",
  inputs: {
    environment: "production"
  }
});
```

## Troubleshooting

### Common Issues

1. **Authentication Errors**:
   - Ensure your GitHub token has the correct permissions
   - Check that the token is correctly set as an environment variable

2. **Rate Limiting**:
   - The server implements rate limiting to avoid hitting GitHub API limits
   - If you encounter rate limit errors, reduce the frequency of requests

3. **Type Validation Errors**:
   - GitHub API responses might sometimes differ from expected schemas
   - The server implements flexible validation to handle most variations
   - If you encounter persistent errors, please open an issue

## License

This MCP server is licensed under the MIT License.

```

--------------------------------------------------------------------------------
/src/common/version.ts:
--------------------------------------------------------------------------------

```typescript
// Store version here for easy updates
export const VERSION = "0.1.0";

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "declaration": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
```

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

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

# Create app directory
WORKDIR /app

# Install app dependencies
COPY package.json package-lock.json tsconfig.json ./
COPY src ./src

# Install dependencies and build
RUN npm install --ignore-scripts && npm run build

# Expose any ports if needed (MCP over stdio, no ports)

# Default command to run the MCP server
CMD ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/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:
      - githubPersonalAccessToken
    properties:
      githubPersonalAccessToken:
        type: string
        description: GitHub Personal Access Token for API access
  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/index.js'], env: { GITHUB_PERSONAL_ACCESS_TOKEN: config.githubPersonalAccessToken } })
  exampleConfig:
    githubPersonalAccessToken: ghp_exampletoken1234567890

```

--------------------------------------------------------------------------------
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------

```yaml
name: Dependabot auto-merge
on: pull_request

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          
      - name: Auto-merge for dev dependency updates
        if: ${{steps.metadata.outputs.dependency-type == 'direct:development' && (steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch')}}
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

```

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

```json
{
  "name": "github-actions-mcp",
  "version": "0.1.0",
  "description": "MCP server for using the GitHub Actions API",
  "license": "MIT",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "github-actions-mcp": "dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('./dist/index.js', 0o755)\"",
    "build:win": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc && node dist/index.js",
    "watch": "tsc --watch",
    "lint": "tsc --noEmit",
    "test": "echo No tests specified && exit 0"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.12.1",
    "@types/node": "22.15.29",
    "node-fetch": "^3.3.2",
    "@octokit/rest": "^22.0.0",
    "universal-user-agent": "^7.0.3",
    "zod": "^3.25.46",
    "zod-to-json-schema": "^3.24.5"
  },
  "devDependencies": {
    "typescript": "^5.8.3"
  }
}
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  push:
    branches: [main]
    paths-ignore:
      - "**.md"
      - "docs/**"
  pull_request:
    branches: [main]
    paths-ignore:
      - "**.md"
      - "docs/**"
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: read

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Generate package-lock.json
        run: npm install --package-lock-only

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build
        env:
          CI: true

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/
          retention-days: 7

      - name: Upload package-lock.json
        uses: actions/upload-artifact@v4
        with:
          name: package-lock
          path: package-lock.json
          retention-days: 7

```

--------------------------------------------------------------------------------
/run-server.bat:
--------------------------------------------------------------------------------

```
@echo off
echo GitHub Actions MCP Server for Windows
echo ----------------------------------

rem Check if Node.js is installed
where node >nul 2>nul
if %ERRORLEVEL% neq 0 (
    echo Error: Node.js is not installed or not in PATH
    echo Please install Node.js from https://nodejs.org/
    exit /b 1
)

rem Check if dist folder exists; if not, build the project
if not exist dist (
    echo Building project...
    call npm run build:win
    if %ERRORLEVEL% neq 0 (
        echo Build failed. Please check for errors.
        exit /b 1
    )
)

rem Set GitHub Personal Access Token if provided as command line argument
if not "%~1"=="" (
    set GITHUB_PERSONAL_ACCESS_TOKEN=%~1
    echo Using provided GitHub Personal Access Token
) else (
    if defined GITHUB_PERSONAL_ACCESS_TOKEN (
        echo Using GitHub Personal Access Token from environment
    ) else (
        echo No GitHub Personal Access Token provided
        echo Some API calls may be rate-limited
    )
)

echo Starting MCP server...
echo Listening on stdio...
echo Press Ctrl+C to stop the server

node dist/index.js

echo Server stopped

```

--------------------------------------------------------------------------------
/notes.md:
--------------------------------------------------------------------------------

```markdown
if you could put questions in this file, I'll monitor it and try to answer them to prevent interactions.

---
### Questions / Actions (2025-04-26)

1. **Full Error Log** – please paste the entire stack-trace or log output that appears when Windsurf shows `failed to initialize: request failed`.
I don't know where that is located can you point me to it?

2. **Server Log Confirmation** – after the failure, does `dist/mcp-startup.log` still show the line `Connected via stdio transport.`?
Can you not check the log file?
[2025-04-26T21:20:03.956Z] [MCP Server Log] Log file cleared/initialized.
[2025-04-26T21:20:03.958Z] [MCP Server Log] Initializing GitHub Actions MCP Server...
[2025-04-26T21:20:03.958Z] [MCP Server Log] GitHub token found.
[2025-04-26T21:20:03.959Z] [MCP Server Log] Octokit initialized.
[2025-04-26T21:20:03.960Z] [MCP Server Log] Server initialization complete. Ready for connection.
[2025-04-26T21:20:03.961Z] [MCP Server Log] Connected via stdio transport.

3. **SDK Version Mismatch** – in the VS Code terminal, run `npm ls @modelcontextprotocol/sdk` and paste the output so we can verify the client/server versions match.
can you not run that? 
(base) PS E:\code\github-actions-mcp-server> npm ls @modelcontextprotocol/sdk
[email protected] E:\code\github-actions-mcp-server
`-- @modelcontextprotocol/[email protected]

4. **Proxy / Firewall** – are you running behind a proxy, VPN, or firewall that could block subprocesses or stdio pipes?  If yes, please give details.
No proxy or VPN, just windows firewall which is open outbound

_Add your answers below each question. Feel free to include any other clues._
```

--------------------------------------------------------------------------------
/src/common/errors.ts:
--------------------------------------------------------------------------------

```typescript
export class GitHubError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly response: unknown
  ) {
    super(message);
    this.name = "GitHubError";
  }
}

export class GitHubValidationError extends GitHubError {
  constructor(message: string, status: number, response: unknown) {
    super(message, status, response);
    this.name = "GitHubValidationError";
  }
}

export class GitHubResourceNotFoundError extends GitHubError {
  constructor(resource: string) {
    super(`Resource not found: ${resource}`, 404, { message: `${resource} not found` });
    this.name = "GitHubResourceNotFoundError";
  }
}

export class GitHubAuthenticationError extends GitHubError {
  constructor(message = "Authentication failed") {
    super(message, 401, { message });
    this.name = "GitHubAuthenticationError";
  }
}

export class GitHubPermissionError extends GitHubError {
  constructor(message = "Insufficient permissions") {
    super(message, 403, { message });
    this.name = "GitHubPermissionError";
  }
}

export class GitHubRateLimitError extends GitHubError {
  constructor(
    message = "Rate limit exceeded",
    public readonly resetAt: Date
  ) {
    super(message, 429, { message, reset_at: resetAt.toISOString() });
    this.name = "GitHubRateLimitError";
  }
}

export class GitHubTimeoutError extends GitHubError {
  constructor(
    message = "Request timed out",
    public readonly timeoutMs: number
  ) {
    super(message, 408, { message, timeout_ms: timeoutMs });
    this.name = "GitHubTimeoutError";
  }
}

export class GitHubNetworkError extends GitHubError {
  constructor(
    message = "Network error",
    public readonly errorCode: string
  ) {
    super(message, 500, { message, error_code: errorCode });
    this.name = "GitHubNetworkError";
  }
}

export class GitHubConflictError extends GitHubError {
  constructor(message: string) {
    super(message, 409, { message });
    this.name = "GitHubConflictError";
  }
}

export function isGitHubError(error: unknown): error is GitHubError {
  return error instanceof GitHubError;
}

// Add enhanced error factory function
export function createEnhancedGitHubError(error: Error & { cause?: { code: string } }): GitHubError {
  // Handle timeout errors
  if (error.name === 'AbortError') {
    return new GitHubTimeoutError(`Request timed out: ${error.message}`, 30000);
  }
  
  // Handle network errors
  if (error.cause?.code) {
    return new GitHubNetworkError(
      `Network error: ${error.message}`, 
      error.cause.code
    );
  }
  
  // Handle other errors
  return new GitHubError(error.message, 500, { message: error.message });
}

export function createGitHubError(status: number, response: any): GitHubError {
  switch (status) {
    case 401:
      return new GitHubAuthenticationError(response?.message);
    case 403:
      return new GitHubPermissionError(response?.message);
    case 404:
      return new GitHubResourceNotFoundError(response?.message || "Resource");
    case 409:
      return new GitHubConflictError(response?.message || "Conflict occurred");
    case 422:
      return new GitHubValidationError(
        response?.message || "Validation failed",
        status,
        response
      );
    case 429:
      return new GitHubRateLimitError(
        response?.message,
        new Date(response?.reset_at || Date.now() + 60000)
      );
    default:
      return new GitHubError(
        response?.message || "GitHub API error",
        status,
        response
      );
  }
}
```

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

```typescript
import { z } from "zod";

// Base GitHub types
export const GitHubAuthorSchema = z.object({
  name: z.string(),
  email: z.string(),
  date: z.string().optional(),
}).passthrough();

// GitHub Workflow Run types
export const WorkflowRunSchema = z.object({
  id: z.number(),
  name: z.string().nullable(),
  node_id: z.string(),
  head_branch: z.string().nullable(),
  head_sha: z.string(),
  path: z.string(),
  display_title: z.string().nullable(),
  run_number: z.number(),
  event: z.string(),
  status: z.string().nullable(),
  conclusion: z.string().nullable(),
  workflow_id: z.number(),
  check_suite_id: z.number(),
  check_suite_node_id: z.string(),
  url: z.string(),
  html_url: z.string(),
  created_at: z.string().nullable().optional(),
  updated_at: z.string().nullable().optional(),
  run_attempt: z.number(),
  run_started_at: z.string().nullable().optional(),
  jobs_url: z.string(),
  logs_url: z.string(),
  check_suite_url: z.string(),
  artifacts_url: z.string(),
  cancel_url: z.string(),
  rerun_url: z.string(),
  previous_attempt_url: z.string().nullable(),
  workflow_url: z.string(),
  repository: z.object({
    id: z.number(),
    node_id: z.string(),
    name: z.string(),
    full_name: z.string(),
    owner: z.object({
      login: z.string(),
      id: z.number(),
      node_id: z.string(),
      avatar_url: z.string(),
      url: z.string(),
      html_url: z.string(),
      type: z.string(),
    }).passthrough(),
    html_url: z.string(),
    description: z.string().nullable(),
    fork: z.boolean(),
    url: z.string(),
    created_at: z.string().nullable().optional(),
    updated_at: z.string().nullable().optional(),
  }).passthrough(),
  head_repository: z.object({
    id: z.number(),
    node_id: z.string(),
    name: z.string(),
    full_name: z.string(),
    owner: z.object({
      login: z.string(),
      id: z.number(),
      node_id: z.string(),
      avatar_url: z.string(),
      url: z.string(),
      html_url: z.string(),
      type: z.string(),
    }).passthrough(),
    html_url: z.string(),
    description: z.string().nullable(),
    fork: z.boolean(),
    url: z.string(),
    created_at: z.string().nullable().optional(),
    updated_at: z.string().nullable().optional(),
  }).passthrough(),
}).passthrough();

export const WorkflowRunsSchema = z.object({
  total_count: z.number(),
  workflow_runs: z.array(WorkflowRunSchema),
}).passthrough();

// GitHub Workflow Job types
export const JobSchema = z.object({
  id: z.number(),
  run_id: z.number(),
  workflow_name: z.string(),
  head_branch: z.string(),
  run_url: z.string(),
  run_attempt: z.number(),
  node_id: z.string(),
  head_sha: z.string(),
  url: z.string(),
  html_url: z.string(),
  status: z.string(),
  conclusion: z.string().nullable(),
  created_at: z.string(),
  started_at: z.string(),
  completed_at: z.string().nullable(),
  name: z.string(),
  steps: z.array(
    z.object({
      name: z.string(),
      status: z.string(),
      conclusion: z.string().nullable(),
      number: z.number(),
      started_at: z.string().nullable(),
      completed_at: z.string().nullable(),
    }).passthrough()
  ),
  check_run_url: z.string(),
  labels: z.array(z.string()),
  runner_id: z.number().nullable(),
  runner_name: z.string().nullable(),
  runner_group_id: z.number().nullable(),
  runner_group_name: z.string().nullable(),
}).passthrough();

export const JobsSchema = z.object({
  total_count: z.number(),
  jobs: z.array(JobSchema),
}).passthrough();

// GitHub Workflow types
export const WorkflowSchema = z.object({
  id: z.number(),
  node_id: z.string(),
  name: z.string(),
  path: z.string(),
  state: z.string(),
  created_at: z.string(),
  updated_at: z.string(),
  url: z.string(),
  html_url: z.string(),
  badge_url: z.string(),
}).passthrough();

export const WorkflowsSchema = z.object({
  total_count: z.number(),
  workflows: z.array(WorkflowSchema),
}).passthrough();

// GitHub Workflow Usage types
export const WorkflowUsageSchema = z.object({
  billable: z.object({
    UBUNTU: z.object({
      total_ms: z.number().optional(),
      jobs: z.number().optional(),
    }).passthrough().optional(),
    MACOS: z.object({
      total_ms: z.number().optional(),
      jobs: z.number().optional(),
    }).passthrough().optional(),
    WINDOWS: z.object({
      total_ms: z.number().optional(),
      jobs: z.number().optional(),
    }).passthrough().optional(),
  }).passthrough().optional(),
}).passthrough();

export type WorkflowRun = z.infer<typeof WorkflowRunSchema>;
export type WorkflowRunsResponse = z.infer<typeof WorkflowRunsSchema>;
export type Job = z.infer<typeof JobSchema>;
export type JobsResponse = z.infer<typeof JobsSchema>;
export type Workflow = z.infer<typeof WorkflowSchema>;
export type WorkflowsResponse = z.infer<typeof WorkflowsSchema>;
export type WorkflowUsage = z.infer<typeof WorkflowUsageSchema>;
```

--------------------------------------------------------------------------------
/src/common/utils.ts:
--------------------------------------------------------------------------------

```typescript
import { getUserAgent } from "universal-user-agent";
import { createGitHubError, GitHubTimeoutError, GitHubNetworkError, GitHubError, createEnhancedGitHubError } from "./errors.js";
import { VERSION } from "./version.js";

type RequestOptions = {
  method?: string;
  body?: unknown;
  headers?: Record<string, string>;
}

async function parseResponseBody(response: Response): Promise<unknown> {
  const contentType = response.headers.get("content-type");
  if (contentType?.includes("application/json")) {
    try {
      return await response.json();
    } catch (error) {
      console.error("Error parsing JSON response:", error);
      throw new Error(`Error parsing JSON response: ${error}`);
    }
  }
  return response.text();
}

export function buildUrl(baseUrl: string, params: Record<string, string | number | undefined>): string {
  const url = new URL(baseUrl);
  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined) {
      url.searchParams.append(key, value.toString());
    }
  });
  return url.toString();
}

const USER_AGENT = `github-actions-mcp/v${VERSION} ${getUserAgent()}`;

// Default timeout for GitHub API requests (30 seconds)
const DEFAULT_TIMEOUT = 30000;

// Rate limiting constants
const MAX_REQUESTS_PER_MINUTE = 60; // GitHub API rate limit is typically 5000/hour for authenticated requests
let requestCount = 0;
let requestCountResetTime = Date.now() + 60000;

/**
 * Make a request to the GitHub API with security enhancements
 * 
 * @param url The URL to send the request to
 * @param options Request options including method, body, headers, and timeout
 * @returns The response body
 */
export async function githubRequest(
  url: string,
  options: RequestOptions & { timeout?: number } = {}
): Promise<unknown> {
  // Implement basic rate limiting
  if (Date.now() > requestCountResetTime) {
    requestCount = 0;
    requestCountResetTime = Date.now() + 60000;
  }
  
  if (requestCount >= MAX_REQUESTS_PER_MINUTE) {
    const waitTime = requestCountResetTime - Date.now();
    throw new Error(`Rate limit exceeded. Please try again in ${Math.ceil(waitTime / 1000)} seconds.`);
  }
  requestCount++;

  // Validate URL to ensure it's a GitHub API URL (security measure)
  if (!url.startsWith('https://api.github.com/')) {
    throw new Error('Invalid GitHub API URL. Only https://api.github.com/ URLs are allowed.');
  }

  const headers: Record<string, string> = {
    "Accept": "application/vnd.github.v3+json",
    "Content-Type": "application/json",
    "User-Agent": USER_AGENT,
    ...options.headers,
  };

  if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
    headers["Authorization"] = `Bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`;
  }

  // Set up request timeout
  const timeout = options.timeout || DEFAULT_TIMEOUT;
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      method: options.method || "GET",
      headers,
      body: options.body ? JSON.stringify(options.body) : undefined,
      signal: controller.signal
    });

    const responseBody = await parseResponseBody(response);

    if (!response.ok) {
      throw createGitHubError(response.status, responseBody);
    }

    return responseBody;
  } catch (error: unknown) {
    if ((error as Error).name === 'AbortError') {
      throw new GitHubTimeoutError(`Request timeout after ${timeout}ms`, timeout);
    }
    if ((error as { cause?: { code: string } }).cause?.code === 'ENOTFOUND' || 
        (error as { cause?: { code: string } }).cause?.code === 'ECONNREFUSED') {
      throw new GitHubNetworkError(`Unable to connect to GitHub API`, 
        (error as { cause?: { code: string } }).cause!.code);
    }
    if (!(error instanceof GitHubError)) {
      throw createEnhancedGitHubError(error as Error & { cause?: { code: string } });
    }
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}

export function validateRepositoryName(name: string): string {
  const sanitized = name.trim().toLowerCase();
  if (!sanitized) {
    throw new Error("Repository name cannot be empty");
  }
  if (!/^[a-z0-9_.-]+$/.test(sanitized)) {
    throw new Error(
      "Repository name can only contain lowercase letters, numbers, hyphens, periods, and underscores"
    );
  }
  if (sanitized.startsWith(".") || sanitized.endsWith(".")) {
    throw new Error("Repository name cannot start or end with a period");
  }
  return sanitized;
}

export function validateOwnerName(owner: string): string {
  const sanitized = owner.trim().toLowerCase();
  if (!sanitized) {
    throw new Error("Owner name cannot be empty");
  }
  if (!/^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,38}$/.test(sanitized)) {
    throw new Error(
      "Owner name must start with a letter or number and can contain up to 39 characters"
    );
  }
  return sanitized;
}
```

--------------------------------------------------------------------------------
/src/operations/actions.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { githubRequest, buildUrl, validateOwnerName, validateRepositoryName } from "../common/utils.js";
import {
  WorkflowRunsSchema,
  WorkflowRunSchema,
  JobsSchema,
  WorkflowsSchema,
  WorkflowSchema,
  WorkflowUsageSchema
} from "../common/types.js";

/**
 * Schema definitions
 */

// List workflows schemas
export const ListWorkflowsSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  page: z.number().optional().describe("Page number for pagination"),
  perPage: z.number().optional().describe("Results per page (max 100)"),
});

// Get workflow schema
export const GetWorkflowSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  workflowId: z.string().describe("The ID of the workflow or filename (string or number)"),
});

// Get workflow usage schema
export const GetWorkflowUsageSchema = GetWorkflowSchema;

// List workflow runs schema
export const ListWorkflowRunsSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  workflowId: z.string().optional().describe("The ID of the workflow or filename (string or number)"),
  actor: z.string().optional().describe("Returns someone's workflow runs. Use the login for the user"),
  branch: z.string().optional().describe("Returns workflow runs associated with a branch"),
  event: z.string().optional().describe("Returns workflow runs triggered by the event"),
  status: z.enum(['completed', 'action_required', 'cancelled', 'failure', 'neutral', 'skipped', 'stale', 'success', 'timed_out', 'in_progress', 'queued', 'requested', 'waiting', 'pending']).optional().describe("Returns workflow runs with the check run status"),
  created: z.string().optional().describe("Returns workflow runs created within date range (YYYY-MM-DD)"),
  excludePullRequests: z.boolean().optional().describe("If true, pull requests are omitted from the response"),
  checkSuiteId: z.number().optional().describe("Returns workflow runs with the check_suite_id"),
  page: z.number().optional().describe("Page number for pagination"),
  perPage: z.number().optional().describe("Results per page (max 100)"),
});

// Get workflow run schema
export const GetWorkflowRunSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  runId: z.number().describe("The ID of the workflow run"),
});

// Get workflow run jobs schema
export const GetWorkflowRunJobsSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  runId: z.number().describe("The ID of the workflow run"),
  filter: z.enum(['latest', 'all']).optional().describe("Filter jobs by their completed_at date"),
  page: z.number().optional().describe("Page number for pagination"),
  perPage: z.number().optional().describe("Results per page (max 100)"),
});

// Trigger workflow schema
export const TriggerWorkflowSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  workflowId: z.string().describe("The ID of the workflow or filename (string or number)"),
  ref: z.string().describe("The reference of the workflow run (branch, tag, or SHA)"),
  inputs: z.record(z.string(), z.string()).optional().describe("Input parameters for the workflow"),
});

// Cancel workflow run schema
export const CancelWorkflowRunSchema = z.object({
  owner: z.string().describe("Repository owner (username or organization)"),
  repo: z.string().describe("Repository name"),
  runId: z.number().describe("The ID of the workflow run"),
});

// Rerun workflow schema
export const RerunWorkflowSchema = CancelWorkflowRunSchema;

/**
 * Function implementations
 */

// List workflows in a repository
export async function listWorkflows(
  owner: string, 
  repo: string, 
  page?: number, 
  perPage?: number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = buildUrl(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, {
    page: page,
    per_page: perPage
  });

  const response = await githubRequest(url);
  return WorkflowsSchema.parse(response);
}

// Get a workflow
export async function getWorkflow(
  owner: string, 
  repo: string, 
  workflowId: string | number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflowId}`;
  const response = await githubRequest(url);
  return WorkflowSchema.parse(response);
}

// Get workflow usage
export async function getWorkflowUsage(
  owner: string, 
  repo: string, 
  workflowId: string | number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflowId}/timing`;
  const response = await githubRequest(url);
  return WorkflowUsageSchema.parse(response);
}

// List workflow runs
export async function listWorkflowRuns(
  owner: string, 
  repo: string, 
  options: {
    workflowId?: string | number,
    actor?: string,
    branch?: string,
    event?: string,
    status?: string,
    created?: string,
    excludePullRequests?: boolean,
    checkSuiteId?: number,
    page?: number,
    perPage?: number
  } = {}
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  let url;
  if (options.workflowId) {
    url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${options.workflowId}/runs`;
  } else {
    url = `https://api.github.com/repos/${owner}/${repo}/actions/runs`;
  }

  url = buildUrl(url, {
    actor: options.actor,
    branch: options.branch,
    event: options.event,
    status: options.status,
    created: options.created,
    exclude_pull_requests: options.excludePullRequests ? "true" : undefined,
    check_suite_id: options.checkSuiteId,
    page: options.page,
    per_page: options.perPage
  });

  const response = await githubRequest(url);
  return WorkflowRunsSchema.parse(response);
}

// Get a workflow run
export async function getWorkflowRun(
  owner: string, 
  repo: string, 
  runId: number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${runId}`;
  const response = await githubRequest(url);
  return WorkflowRunSchema.parse(response);
}

// Get workflow run jobs
export async function getWorkflowRunJobs(
  owner: string, 
  repo: string, 
  runId: number, 
  filter?: 'latest' | 'all', 
  page?: number, 
  perPage?: number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = buildUrl(`https://api.github.com/repos/${owner}/${repo}/actions/runs/${runId}/jobs`, {
    filter: filter,
    page: page,
    per_page: perPage
  });

  const response = await githubRequest(url);
  return JobsSchema.parse(response);
}

// Trigger a workflow run
export async function triggerWorkflow(
  owner: string, 
  repo: string, 
  workflowId: string | number, 
  ref: string, 
  inputs?: Record<string, string>
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/${workflowId}/dispatches`;
  
  const body: {
    ref: string;
    inputs?: Record<string, string>;
  } = { ref };
  
  if (inputs && Object.keys(inputs).length > 0) {
    body.inputs = inputs;
  }

  await githubRequest(url, {
    method: 'POST',
    body
  });

  // This endpoint doesn't return any data on success
  return { success: true, message: `Workflow ${workflowId} triggered on ${ref}` };
}

// Cancel a workflow run
export async function cancelWorkflowRun(
  owner: string, 
  repo: string, 
  runId: number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${runId}/cancel`;
  await githubRequest(url, { method: 'POST' });

  // This endpoint doesn't return any data on success
  return { success: true, message: `Workflow run ${runId} cancelled` };
}

// Rerun a workflow run
export async function rerunWorkflowRun(
  owner: string, 
  repo: string, 
  runId: number
) {
  owner = validateOwnerName(owner);
  repo = validateRepositoryName(repo);

  const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${runId}/rerun`;
  await githubRequest(url, { method: 'POST' });

  // This endpoint doesn't return any data on success
  return { success: true, message: `Workflow run ${runId} restarted` };
}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Use McpServer
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // Transport for Windsurf
import { 
    CallToolRequestSchema, 
    ListToolsRequestSchema 
} from "@modelcontextprotocol/sdk/types.js"; 
import { z } from 'zod'; 
import { zodToJsonSchema } from 'zod-to-json-schema';

// Restore GitHub specific imports
import { Octokit } from "@octokit/rest";
import * as actions from './operations/actions.js';
import { 
    GitHubError, 
    isGitHubError, 
    GitHubValidationError,
    GitHubResourceNotFoundError,
    GitHubAuthenticationError,
    GitHubPermissionError,
    GitHubRateLimitError,
    GitHubConflictError,
    GitHubTimeoutError,
    GitHubNetworkError,
} from './common/errors.js';
import { VERSION } from "./common/version.js";

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

const logFilePath = path.join(__dirname, '..', 'dist', 'mcp-startup.log'); // Ensure log path points to dist

// Simple file logger
function logToFile(message: string) {
  const timestamp = new Date().toISOString();
  try {
    // Ensure dist directory exists before logging
    const logDir = path.dirname(logFilePath);
    if (!fs.existsSync(logDir)){
        fs.mkdirSync(logDir, { recursive: true });
    }
    fs.appendFileSync(logFilePath, `[${timestamp}] ${message}\n`, 'utf8');
  } catch (err: any) { // Use any for broader catch
    const errorMsg = `[File Log Error] Failed to write to ${logFilePath}: ${err?.message || String(err)}`;
    console.error(errorMsg);
    if (err instanceof Error && err.stack) { // Check if error has stack
      console.error(err.stack);
    }
    console.error(`[Original Message] ${message}`);
  }
}

// Clear log file on startup
// Ensure dist directory exists before logging
const logDir = path.dirname(logFilePath);
try {
    if (!fs.existsSync(logDir)){
        fs.mkdirSync(logDir, { recursive: true });
    }
    fs.writeFileSync(logFilePath, '', 'utf8'); 
    logToFile('[MCP Server Log] Log file cleared/initialized.');
} catch (err: any) { 
    // Log critical startup error to stderr *only* if file logging setup failed
    // This is a last resort and might still interfere, but necessary if logging isn't possible.
    const errorMsg = `[MCP Startup Error] Failed to initialize log file at ${logFilePath}: ${err?.message || String(err)}`;
    console.error(errorMsg); 
    if (err instanceof Error && err.stack) {
        console.error(err.stack);
    }
    process.exit(1); // Exit if we can't even log
}

// Add a global handler for uncaught exceptions
process.on('uncaughtException', (err, origin) => {
  let message = `[MCP Server Log] Uncaught Exception. Origin: ${origin}. Error: ${err?.message || String(err)}`;
  logToFile(message);
  if (err && err.stack) {
    logToFile(err.stack);
  }
  // Optionally add more context
  logToFile('[MCP Server Log] Exiting due to uncaught exception.');
  process.exit(1); // Exit cleanly
});

logToFile('[MCP Server Log] Initializing GitHub Actions MCP Server...');

// Restore auth logic
// Allow token via CLI argument `--token=<token>` or fallback to env var
let cliToken: string | undefined;
for (const arg of process.argv) {
  if (arg.startsWith('--token=')) {
    cliToken = arg.substring('--token='.length);
    break;
  }
}

const GITHUB_TOKEN = cliToken || process.env.GITHUB_PERSONAL_ACCESS_TOKEN; // Restore env check
if (!GITHUB_TOKEN) {
  logToFile('FATAL: GITHUB_PERSONAL_ACCESS_TOKEN environment variable is not set.');
  process.exit(1);
}
logToFile('[MCP Server Log] GitHub token found.'); // Restore original log message
const octokit = new Octokit({ auth: GITHUB_TOKEN });
logToFile('[MCP Server Log] Octokit initialized.');

const server = new McpServer(
  {
    name: "github-actions-mcp-server",
    version: VERSION, 
    context: {
      octokit: octokit
    }
  }
);

// Restore error formatting function
function formatGitHubError(error: GitHubError): string {
  let message = `GitHub API Error: ${error.message}`;
  
  if (error instanceof GitHubValidationError) {
    message = `Validation Error: ${error.message}`;
    if (error.response) {
      message += `\nDetails: ${JSON.stringify(error.response)}`;
    }
  } else if (error instanceof GitHubResourceNotFoundError) {
    message = `Not Found: ${error.message}`;
  } else if (error instanceof GitHubAuthenticationError) {
    message = `Authentication Failed: ${error.message}`;
  } else if (error instanceof GitHubPermissionError) {
    message = `Permission Denied: ${error.message}`;
  } else if (error instanceof GitHubRateLimitError) {
    message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`;
  } else if (error instanceof GitHubConflictError) {
    message = `Conflict: ${error.message}`;
  } else if (error instanceof GitHubTimeoutError) {
    message = `Timeout: ${error.message}\nTimeout setting: ${error.timeoutMs}ms`;
  } else if (error instanceof GitHubNetworkError) {
    message = `Network Error: ${error.message}\nError code: ${error.errorCode}`;
  }

  return message;
}

// Restore ListTools using server.tool()
server.tool(
    "list_workflows",
    actions.ListWorkflowsSchema.shape,
    async (request: any) => {
      logToFile('[MCP Server Log] Received list_workflows request (via server.tool)');
      // Args are already parsed by the McpServer using the provided schema
      const result = await actions.listWorkflows(request.owner, request.repo, request.page, request.perPage);
      return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

// Register other tools using server.tool()
server.tool(
    "get_workflow",
    actions.GetWorkflowSchema.shape,
    async (request: any) => {
        const result = await actions.getWorkflow(request.owner, request.repo, request.workflowId);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "get_workflow_usage",
    actions.GetWorkflowUsageSchema.shape,
    async (request: any) => {
        const result = await actions.getWorkflowUsage(request.owner, request.repo, request.workflowId);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "list_workflow_runs",
    actions.ListWorkflowRunsSchema.shape,
    async (request: any) => {
        const { owner, repo, workflowId, ...options } = request;
        const result = await actions.listWorkflowRuns(owner, repo, { workflowId, ...options });
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "get_workflow_run",
    actions.GetWorkflowRunSchema.shape,
    async (request: any) => {
        const result = await actions.getWorkflowRun(request.owner, request.repo, request.runId);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "get_workflow_run_jobs",
    actions.GetWorkflowRunJobsSchema.shape,
    async (request: any) => {
        const { owner, repo, runId, filter, page, perPage } = request;
        const result = await actions.getWorkflowRunJobs(owner, repo, runId, filter, page, perPage);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "trigger_workflow",
    actions.TriggerWorkflowSchema.shape,
    async (request: any) => {
        const { owner, repo, workflowId, ref, inputs } = request;
        const result = await actions.triggerWorkflow(owner, repo, workflowId, ref, inputs);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "cancel_workflow_run",
    actions.CancelWorkflowRunSchema.shape,
    async (request: any) => {
        const result = await actions.cancelWorkflowRun(request.owner, request.repo, request.runId);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

server.tool(
    "rerun_workflow",
    actions.RerunWorkflowSchema.shape,
    async (request: any) => {
        const result = await actions.rerunWorkflowRun(request.owner, request.repo, request.runId);
        return { content: [{ type: "text", text: JSON.stringify(result) }] };
    }
);

// Wrap server logic in a try/catch for initialization errors
try {
    logToFile('[MCP Server Log] Server initialization complete. Ready for connection.');
    // Attach stdio transport so Windsurf can communicate
    const transport = new StdioServerTransport();
    await server.connect(transport);
    logToFile('[MCP Server Log] Connected via stdio transport.');
} catch (error: any) {
    // Ensure fatal errors during server setup are logged to the file.
    logToFile(`[MCP Server Log] FATAL Error during server setup: ${error?.message || String(error)}`);
    if (error instanceof Error && error.stack) {
        logToFile(error.stack);
    }
    // Do NOT use console.error here as it will interfere with MCP stdio
    process.exit(1);
}

// Add other process event handlers

// Catch unhandled promise rejections, log them to file, and exit gracefully.
process.on('unhandledRejection', (reason, promise) => {
  // Log unhandled promise rejections to the file.
  let reasonStr = reason instanceof Error ? reason.message : String(reason);
  // Including stack trace if available
  let stack = reason instanceof Error ? `\nStack: ${reason.stack}` : '';
  logToFile(`[MCP Server Log] Unhandled Rejection at: ${promise}, reason: ${reasonStr}${stack}`);
  // Consider exiting depending on the severity or application logic
  // process.exit(1); // Optionally exit
});

process.on('SIGINT', () => {
  logToFile('[MCP Server Log] Received SIGINT. Exiting gracefully.');
  // Add any cleanup logic here
  process.exit(0);
});

process.on('SIGTERM', () => {
  logToFile('[MCP Server Log] Received SIGTERM. Exiting gracefully.');
  // Add any cleanup logic here
  process.exit(0);
});
```