"""
GitHub Copilot CLI MCP Server - STDIO Transport
Wraps the GitHub Copilot CLI (gh copilot) as an MCP server.
"""
from mcp.server.fastmcp import FastMCP
import subprocess
import re
import shutil
mcp = FastMCP("GitHub Copilot CLI")
def strip_ansi(text: str) -> str:
"""Remove ANSI escape sequences from string."""
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|[\[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text)
def filter_stderr(stderr: str) -> str:
"""
Filter out Copilot CLI noise from stderr, keeping only real errors.
Filters out:
- Progress indicators
- Status messages
- Informational output
"""
if not stderr:
return ""
# Patterns to filter out (noise, not real errors)
noise_patterns = [
r'^\[.*\].*$', # Progress/status messages in brackets
r'^Loading.*$', # Loading messages
r'^Initializing.*$', # Initialization messages
r'^Connected.*$', # Connection status
r'^\s*$', # Empty lines
]
# Compile patterns
compiled_patterns = [re.compile(p, re.MULTILINE) for p in noise_patterns]
# Filter lines
lines = stderr.strip().split('\n')
filtered_lines = []
for line in lines:
line = line.strip()
if not line:
continue
is_noise = False
for pattern in compiled_patterns:
if pattern.match(line):
is_noise = True
break
if not is_noise:
filtered_lines.append(line)
return '\n'.join(filtered_lines)
def process_copilot_output(result: subprocess.CompletedProcess) -> str:
"""
Process Copilot CLI output, handling stdout and stderr appropriately.
Args:
result: The subprocess result to process.
Returns clean output with only relevant errors included.
"""
# Get stdout
output = strip_ansi(result.stdout).strip() if result.stdout else ""
# Filter and process stderr
filtered_stderr = filter_stderr(strip_ansi(result.stderr)) if result.stderr else ""
# Only append stderr if there are real errors
if filtered_stderr:
if output:
output += f"\n\n[ERRORS]\n{filtered_stderr}"
else:
output = f"[ERRORS]\n{filtered_stderr}"
# Check return code for unexpected failures
if result.returncode != 0 and not output:
return f"Error: Copilot CLI exited with code {result.returncode}"
return output if output else "No output returned"
def get_gh_path() -> str:
"""Get the path to the gh executable."""
gh_path = shutil.which("gh")
if not gh_path:
raise FileNotFoundError("'gh' executable not found")
return gh_path
@mcp.tool()
def copilot_suggest(
prompt: str,
target: str = "shell"
) -> str:
"""
Get a command suggestion from GitHub Copilot based on natural language.
Args:
prompt: Natural language description of what you want to do.
target: Target type for suggestion - 'shell' (default), 'gh', or 'git'.
Examples:
- "Install git" with target="shell"
- "Create pull request" with target="gh"
- "Undo the most recent local commits" with target="git"
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
if target not in ["shell", "gh", "git"]:
return f"Error: Invalid target '{target}'. Must be 'shell', 'gh', or 'git'."
cmd = [gh_path, "copilot", "suggest", "-t", target, prompt]
try:
# Run with input to handle any interactive prompts
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8',
input="\n" # Auto-accept first suggestion
)
return process_copilot_output(result)
except Exception as e:
return f"Error executing Copilot suggest: {str(e)}"
@mcp.tool()
def copilot_explain(command: str) -> str:
"""
Get an explanation of a command from GitHub Copilot.
Args:
command: The command to explain (e.g., "git log --oneline --graph").
Examples:
- "du -sh | sort -h" - Explain disk usage command
- "git log --oneline --graph --decorate --all" - Explain git history command
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
cmd = [gh_path, "copilot", "explain", command]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8'
)
return process_copilot_output(result)
except Exception as e:
return f"Error executing Copilot explain: {str(e)}"
@mcp.tool()
def copilot_version() -> str:
"""
Get the GitHub Copilot CLI version information.
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
cmd = [gh_path, "copilot", "--version"]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8'
)
return process_copilot_output(result)
except Exception as e:
return f"Error getting version: {str(e)}"
@mcp.tool()
def copilot_help(command: str = "") -> str:
"""
Get GitHub Copilot CLI help information.
Args:
command: Optional command to get help for ('suggest', 'explain', 'config', 'alias').
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
if command:
cmd = [gh_path, "copilot", command, "--help"]
else:
cmd = [gh_path, "copilot", "--help"]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8'
)
return process_copilot_output(result)
except Exception as e:
return f"Error getting help: {str(e)}"
@mcp.tool()
def copilot_config_get(key: str = "") -> str:
"""
Get GitHub Copilot CLI configuration.
Args:
key: Optional specific config key to get. If empty, shows all config.
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
if key:
cmd = [gh_path, "copilot", "config", "get", key]
else:
cmd = [gh_path, "copilot", "config", "list"]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8'
)
return process_copilot_output(result)
except Exception as e:
return f"Error getting config: {str(e)}"
@mcp.tool()
def copilot_alias(shell: str = "bash") -> str:
"""
Generate shell aliases for GitHub Copilot CLI convenience commands.
Args:
shell: Shell type - 'bash', 'zsh', 'fish', or 'powershell'.
"""
try:
gh_path = get_gh_path()
except FileNotFoundError:
return "Error: 'gh' executable not found. Please install GitHub CLI (https://cli.github.com/)."
if shell not in ["bash", "zsh", "fish", "powershell"]:
return f"Error: Invalid shell '{shell}'. Must be 'bash', 'zsh', 'fish', or 'powershell'."
cmd = [gh_path, "copilot", "alias", shell]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
encoding='utf-8'
)
return process_copilot_output(result)
except Exception as e:
return f"Error generating aliases: {str(e)}"
@mcp.tool()
def copilot_suggest_git(prompt: str) -> str:
"""
Get a git command suggestion from GitHub Copilot.
Convenience wrapper for copilot_suggest with target='git'.
Args:
prompt: Natural language description of the git operation.
Examples:
- "Undo the most recent local commits"
- "Clean up local branches"
- "Setup LFS for images"
"""
return copilot_suggest(prompt, target="git")
@mcp.tool()
def copilot_suggest_gh(prompt: str) -> str:
"""
Get a GitHub CLI (gh) command suggestion from GitHub Copilot.
Convenience wrapper for copilot_suggest with target='gh'.
Args:
prompt: Natural language description of the GitHub operation.
Examples:
- "Create pull request"
- "List pull requests waiting for my review"
- "Summarize work I have done in issues and pull requests"
"""
return copilot_suggest(prompt, target="gh")
@mcp.tool()
def copilot_suggest_shell(prompt: str) -> str:
"""
Get a shell command suggestion from GitHub Copilot.
Convenience wrapper for copilot_suggest with target='shell'.
Args:
prompt: Natural language description of the shell operation.
Examples:
- "Kill processes holding onto deleted files"
- "Test whether there are SSL/TLS issues with github.com"
- "Convert SVG to PNG and resize"
"""
return copilot_suggest(prompt, target="shell")
if __name__ == "__main__":
mcp.run()