How to Enable Read-Only Mode in the GitHub MCP Server to Disable Write Operations
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:
-
Configuration Entry Point: In
cmd/github-mcp-server/main.go, the--read-onlyflag is defined and bound to Viper under the keyread-only. This value is then injected intoStdioServerConfig.ReadOnlyorgithub.MCPServerConfig.ReadOnly. -
Inventory Building: The
pkg/inventory/builder.gofile contains theWithReadOnly(true)method. When the server initializes, it passes the configuration flag to the builder via this method. -
Tool Filtering: During
Builder.Build(), the inventory stores thereadOnlyboolean. WhenInventory.AvailableTools()orInventory.AllTools()is called, the code checks each tool'sAnnotations.ReadOnlyHint. If the server is in read-only mode and the hint isfalse, the tool is excluded from the returned slice. -
HTTP Transport: For HTTP-based deployments,
pkg/http/headers/headers.godefines theX-MCP-Readonlyconstant. The HTTP request parser checks for this header (or the/readonlyURL 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:
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:
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:
{
"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:
{
"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.
Programmatic Configuration
If you are embedding the GitHub MCP server in your own Go application, set the ReadOnly field in the configuration struct:
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.
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, 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-onlyCLI flag, theGITHUB_READ_ONLYenvironment variable, theX-MCP-ReadonlyHTTP header, or the/readonlyURL path. - In
cmd/github-mcp-server/main.go, the flag is bound to Viper and injected intoStdioServerConfig.ReadOnlyorMCPServerConfig.ReadOnly. - The
pkg/inventory/builder.gofile implements the filtering logic viaWithReadOnly(true), which causesAvailableTools()to return only tools withReadOnlyHint: 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 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. 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →