# How to Enable Read-Only Mode in the GitHub MCP Server to Disable Write Operations

> Secure your GitHub MCP Server by enabling read-only mode. Learn to disable write operations using CLI flags, environment variables, or HTTP headers for enhanced control.

- Repository: [GitHub/github-mcp-server](https://github.com/github/github-mcp-server)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You can enable read-only mode in the GitHub MCP server using the `--read-only` CLI flag, the `GITHUB_READ_ONLY=true` environment variable, or the `X-MCP-Readonly` HTTP header to automatically filter out all write-capable tools.**

The GitHub MCP server (`github/github-mcp-server`) supports a dedicated read-only configuration that prevents any tool performing destructive or write operations—such as `create_issue`, `delete_repo`, or `pull_request_merge`—from being registered with the Model Context Protocol (MCP) client. This safety mechanism is enforced deep in the server's inventory system before any toolset or individual tool selection is processed.

## What Is Read-Only Mode in the GitHub MCP Server?

Read-only mode is a server-wide boolean flag that instructs the tool inventory builder to exclude any tool not explicitly marked as read-safe. When activated, the server scans every available tool's metadata for the `ReadOnlyHint` annotation. Tools where this annotation is `false`—meaning they perform POST, PUT, DELETE, or other mutating GitHub API operations—are silently dropped from the final tool list returned to the MCP client.

This filtering occurs **after** toolset expansion but **before** the server registers tools with the MCP protocol, ensuring that even explicitly requested write tools are blocked when read-only mode is active.

## How Read-Only Filtering Works Under the Hood

The implementation spans several key files in the repository:

1. **Configuration Entry Point**: In [`cmd/github-mcp-server/main.go`](https://github.com/github/github-mcp-server/blob/main/cmd/github-mcp-server/main.go), the `--read-only` flag is defined and bound to Viper under the key `read-only`. This value is then injected into `StdioServerConfig.ReadOnly` or `github.MCPServerConfig.ReadOnly`.

2. **Inventory Building**: The [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go) file contains the `WithReadOnly(true)` method. When the server initializes, it passes the configuration flag to the builder via this method.

3. **Tool Filtering**: During `Builder.Build()`, the inventory stores the `readOnly` boolean. When `Inventory.AvailableTools()` or `Inventory.AllTools()` is called, the code checks each tool's `Annotations.ReadOnlyHint`. If the server is in read-only mode and the hint is `false`, the tool is excluded from the returned slice.

4. **HTTP Transport**: For HTTP-based deployments, [`pkg/http/headers/headers.go`](https://github.com/github/github-mcp-server/blob/main/pkg/http/headers/headers.go) defines the `X-MCP-Readonly` constant. The HTTP request parser checks for this header (or the `/readonly` URL path prefix) and sets the per-request configuration flag accordingly.

## Methods to Enable Read-Only Mode

You can activate read-only mode through five different mechanisms depending on your deployment method.

### Using the CLI Flag

When running the server locally via stdio transport, pass the `--read-only` flag:

```bash
go run ./cmd/github-mcp-server stdio \
  --read-only \
  --toolsets=issues,repos

```

Even though `issues` and `repos` toolsets normally contain write operations, only the read-only tools within those sets will be registered.

### Using Environment Variables

The server uses Viper for configuration management, which automatically binds environment variables. Set `GITHUB_READ_ONLY` to `true`:

```bash
export GITHUB_READ_ONLY=true
export GITHUB_TOKEN=ghp_xxxxxxxxxxxx
go run ./cmd/github-mcp-server stdio --toolsets=issues

```

This achieves the same result as the CLI flag and is useful for containerized deployments.

### Using HTTP Headers

For HTTP-based MCP servers, clients can request read-only mode by including the `X-MCP-Readonly` header:

```json
{
  "type": "http",
  "url": "https://api.githubcopilot.com/mcp/",
  "headers": {
    "Authorization": "Bearer ${GITHUB_TOKEN}",
    "X-MCP-Toolsets": "issues,repos",
    "X-MCP-Readonly": "true"
  }
}

```

The server parses this header in the HTTP transport layer and applies the filter before processing the tool list request.

### Using the URL Path Shortcut

As a convenience, the HTTP server also recognizes the `/readonly` path prefix:

```json
{
  "type": "http",
  "url": "https://api.githubcopilot.com/mcp/readonly",
  "headers": {
    "Authorization": "Bearer ${GITHUB_TOKEN}",
    "X-MCP-Toolsets": "issues,repos"
  }
}

```

This is functionally equivalent to sending the `X-MCP-Readonly: true` header and is documented in [`docs/server-configuration.md`](https://github.com/github/github-mcp-server/blob/main/docs/server-configuration.md).

### Programmatic Configuration

If you are embedding the GitHub MCP server in your own Go application, set the `ReadOnly` field in the configuration struct:

```go
package main

import (
    "context"
    "log"
    "os"

    ghmcp "github.com/github/github-mcp-server/internal/ghmcp"
)

func main() {
    cfg := ghmcp.StdioServerConfig{
        Version:         "v1.0.0",
        Host:            "github.com",
        Token:           os.Getenv("GITHUB_TOKEN"),
        EnabledToolsets: []string{"issues", "repos"},
        ReadOnly:        true, // Disables all write operations
    }

    server, err := ghmcp.NewStdioMCPServer(context.Background(), cfg)
    if err != nil {
        log.Fatal(err)
    }

    // Server now only exposes read-only tools
    _ = server
}

```

This directly invokes the `WithReadOnly(true)` method on the inventory builder inside [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go).

## Verifying Read-Only Mode Is Active

When read-only mode is successfully enabled, the server logs will indicate that write tools are being filtered. You can verify this by checking the available tools list returned to your MCP client—operations like `create_issue`, `update_issue`, `delete_branch`, or `merge_pull_request` should be absent from the tool catalog even if the corresponding toolsets were requested.

According to the source code in [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go), the filtering happens during the `Build()` phase, ensuring that the final inventory contains only tools where `ReadOnlyHint` is explicitly `true`.

## Summary

- **Read-only mode** in the GitHub MCP server blocks all write-capable tools by filtering the tool inventory before registration.
- The flag can be activated via the `--read-only` CLI flag, the `GITHUB_READ_ONLY` environment variable, the `X-MCP-Readonly` HTTP header, or the `/readonly` URL path.
- In [`cmd/github-mcp-server/main.go`](https://github.com/github/github-mcp-server/blob/main/cmd/github-mcp-server/main.go), the flag is bound to Viper and injected into `StdioServerConfig.ReadOnly` or `MCPServerConfig.ReadOnly`.
- The [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go) file implements the filtering logic via `WithReadOnly(true)`, which causes `AvailableTools()` to return only tools with `ReadOnlyHint: true`.
- This safety mechanism applies regardless of toolset selection, ensuring write operations are impossible even if explicitly requested.

## Frequently Asked Questions

### What happens if I request write tools while read-only mode is enabled?

The server silently filters out any write-capable tools before registering them with the MCP client. Even if you explicitly request tools like `create_issue` or specify toolsets that normally include write operations, the final available tool list will contain only read-only operations. This filtering occurs in [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go) during the inventory build phase.

### Can I enable read-only mode for only specific toolsets?

No, read-only mode is a global server setting that applies to the entire tool inventory. When activated via the `--read-only` flag, `GITHUB_READ_ONLY` environment variable, or `X-MCP-Readonly` header, it filters all available tools regardless of which toolsets were requested. You cannot selectively apply read-only restrictions to individual toolsets while allowing writes in others.

### Is read-only mode available in both stdio and HTTP server modes?

Yes, the GitHub MCP server supports read-only mode in both transport modes. For stdio mode, use the `--read-only` CLI flag or the `GITHUB_READ_ONLY` environment variable as defined in [`cmd/github-mcp-server/main.go`](https://github.com/github/github-mcp-server/blob/main/cmd/github-mcp-server/main.go). For HTTP mode, send the `X-MCP-Readonly: true` header or use the `/readonly` URL path prefix, both of which are handled by the HTTP transport layer in the server configuration.

### How does the server determine which tools are read-only?

Each tool in the GitHub MCP server includes metadata annotations, specifically the `ReadOnlyHint` boolean field. When the inventory builder in [`pkg/inventory/builder.go`](https://github.com/github/github-mcp-server/blob/main/pkg/inventory/builder.go) receives the `WithReadOnly(true)` configuration, it filters the tool list during the `Build()` process, retaining only tools where `ReadOnlyHint` is `true`. Write operations like creating issues, merging pull requests, or deleting repositories have `ReadOnlyHint` set to `false` and are therefore excluded from the final inventory.