A standalone Rust binary that acts as an Anthropic-to-Copilot proxy. This adapter enables Claude Code users with GitHub Copilot subscriptions to leverage those subscriptions by translating Anthropic API requests to GitHub Copilot's API format.
- GitHub OAuth device flow — authenticate through your browser in seconds
- Anthropic-compatible API —
POST /v1/messagesfor native Claude Code integration - Token counting —
POST /v1/messages/count_tokensfor pre-flight token estimation (tiktoken-rs) - Model discovery —
GET /v1/modelswith dynamic fetching and caching - SSE streaming — real-time token-by-token responses
- Vision / image support — image uploads translated to OpenAI multimodal format (base64 and URL)
- Tool/function support — native OpenAI function calling with automatic XML fallback
- Dynamic model discovery — fetches available models from Copilot API with caching and fallback
- Automatic token management — Copilot tokens refreshed 5 min before expiry
- Secure credential storage — OS keyring (macOS Keychain / Windows Credential Manager / Linux Secret Service) with encrypted file fallback
- Background daemon — runs as a background process on all platforms
- Concurrent clients — serves multiple simultaneous requests
# From source
cargo install --path .
# Or build manually
cargo build --release
# Binary: target/release/copilot-adapter (or .exe on Windows)copilot-adapter startOn first run, the adapter will:
- Detect missing authentication and start the OAuth flow
- Offer to open the GitHub authorization URL in your browser
- Wait for you to authorize the application
- Display configuration instructions for Claude Code
The adapter starts listening on http://127.0.0.1:6767 by default.
Note: You can still authenticate separately with
copilot-adapter authif you prefer.
Choose one of these methods:
Method A: Environment Variables (session)
Linux / macOS (bash/zsh):
export ANTHROPIC_BASE_URL=http://127.0.0.1:6767
export ANTHROPIC_API_KEY=dummy # Required by Claude Code but unused by the adapterWindows (Command Prompt):
set ANTHROPIC_BASE_URL=http://127.0.0.1:6767
set ANTHROPIC_API_KEY=dummyWindows (PowerShell):
$env:ANTHROPIC_BASE_URL = "http://127.0.0.1:6767"
$env:ANTHROPIC_API_KEY = "dummy"Tip: Add these to your shell profile (
.bashrc,.zshrc, PowerShell$PROFILE) for persistence across terminal sessions.
Method B: Claude Code Settings (recommended, persistent)
Create or edit ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:6767",
"ANTHROPIC_API_KEY": "dummy"
}
}For project-specific configuration, create .claude/settings.json in your project root.
Settings precedence (highest to lowest):
<project>/.claude/settings.local.json(gitignored, for personal overrides)<project>/.claude/settings.json(committed, for team sharing)~/.claude/settings.json(user-level defaults)
claudeClaude Code will automatically route requests through the adapter to GitHub Copilot.
| Command | Description |
|---|---|
copilot-adapter auth |
Authenticate with GitHub (device flow) |
copilot-adapter auth --force |
Re-authenticate, overwriting stored credentials |
copilot-adapter start |
Start adapter (auto-authenticates if needed) |
copilot-adapter start --daemon |
Start as background daemon (requires prior auth) |
copilot-adapter start --skip-auth |
Start without auto-authentication |
copilot-adapter start --quiet |
Start without displaying setup guidance |
copilot-adapter start -p 9090 |
Start on a custom port |
copilot-adapter start --host 0.0.0.0 |
Bind to all interfaces |
copilot-adapter start --log-level debug |
Enable debug logging |
copilot-adapter start --log-file /tmp/adapter.log |
Log to a file |
copilot-adapter start --models-cache-ttl 600 |
Set model list cache TTL (seconds, default: 300) |
copilot-adapter start --static-models |
Use static model list (skip API fetch) |
copilot-adapter start --disable-native-tools |
Disable native OpenAI tools and force XML-based tool injection |
copilot-adapter status |
Check if the adapter is running |
copilot-adapter stop |
Stop the running daemon |
copilot-adapter logout |
Clear stored credentials |
Root path handler for health probes. Claude Code sends HEAD / requests to check if the adapter is reachable. This endpoint requires no authentication.
# GET returns JSON body
curl http://127.0.0.1:6767/
# {"status": "ok"}
# HEAD returns 200 OK with empty body
curl -I http://127.0.0.1:6767/
# HTTP/1.1 200 OKHealth check endpoint.
curl http://127.0.0.1:6767/health
# {"status": "ok"}Pre-flight token counting endpoint. Counts the number of input tokens in a request without sending it to the upstream API. Uses tiktoken-rs with the cl100k_base encoding for accurate BPE tokenization.
This is useful for context window management and cost estimation before making a full /v1/messages request.
# Simple message
curl -X POST http://127.0.0.1:6767/v1/messages/count_tokens \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# {"input_tokens": 5}With system prompt and tools:
curl -X POST http://127.0.0.1:6767/v1/messages/count_tokens \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Search for foo"}],
"system": "You are a helpful assistant.",
"tools": [{
"name": "Grep",
"description": "Search files",
"input_schema": {
"type": "object",
"properties": {"pattern": {"type": "string"}}
}
}]
}'
# {"input_tokens": 57}Supported request parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | Model identifier (used for request validation; counting uses cl100k_base) |
messages |
array | Yes | Array of message objects (role + content) |
system |
string or array | No | System prompt (string or content blocks) |
tools |
array | No | Tool definitions (counted as JSON tokens) |
Token counting details:
| Content Type | Counting Method |
|---|---|
| Text | Full BPE tokenization (cl100k_base) |
| Images | Fixed estimate (~85 tokens per image) |
| Documents | Fixed estimate (~85 tokens per document) |
| Tool definitions | JSON-serialized and tokenized |
| Per-message overhead | 4 tokens per message |
Error responses:
400 Bad Request— Missing required fields (model,messages)422 Unprocessable Entity— Invalid JSON structure
Anthropic-compatible messages endpoint. This is the recommended endpoint for Claude Code as it matches Claude's native API format. The adapter translates requests to OpenAI format internally before forwarding to Copilot.
Non-streaming:
curl -X POST http://127.0.0.1:6767/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: dummy" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}]
}'Streaming:
curl -X POST http://127.0.0.1:6767/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: dummy" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'Supported request parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | Model identifier (mapped to Copilot model internally) |
messages |
array | Yes | Array of message objects (role + content). Content can be a string or an array of content blocks (text, image, document) for multimodal messages. See Vision / Image Support. |
max_tokens |
integer | Yes | Maximum tokens in the response |
stream |
boolean | No | Enable SSE streaming (default: false) |
system |
string | No | System prompt |
temperature |
number | No | Sampling temperature (0–1) |
top_p |
number | No | Nucleus sampling parameter |
stop_sequences |
array | No | Stop sequences |
List available models. By default, models are fetched dynamically from the Copilot API and cached in memory (TTL: 5 minutes). If the API is unreachable, the adapter falls back to a built-in static list.
curl http://127.0.0.1:6767/v1/modelsGet details for a specific model.
curl http://127.0.0.1:6767/v1/models/gpt-4Dynamic models behaviour:
- On first request (or after cache expires), the adapter fetches the model list from
https://api.githubcopilot.com/models - Subsequent requests within the cache TTL return the cached list without an API call
- If the Copilot API is unavailable, the adapter returns a static fallback list (gpt-4o, gpt-4, gpt-4-turbo, gpt-3.5-turbo)
- Use
--static-modelsto disable dynamic fetching entirely
The adapter supports multimodal (image) content in the Anthropic /v1/messages endpoint. When Claude Code or any Anthropic-compatible client sends image content blocks, the adapter translates them to OpenAI's image_url format before forwarding to the Copilot API.
| Content Block | Support | Details |
|---|---|---|
text |
✅ Full | Passed through as-is |
image (base64) |
✅ Full | Converted to data:{media_type};base64,{data} data URI |
image (URL) |
✅ Full | URL passed through unchanged |
document |
Not supported by OpenAI format; skipped with a warning log | |
cache_control |
✅ Accepted | Deserialized without error; not forwarded to Copilot API |
Image uploads work automatically — no additional flags or configuration required. Use a vision-capable model (e.g., gpt-4o) for best results.
Base64 image:
curl -s -X POST http://127.0.0.1:6767/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: dummy" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "gpt-4o",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image."},
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": "<base64-encoded image data>"
}
}
]
}]
}'URL image:
curl -s -X POST http://127.0.0.1:6767/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: dummy" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "gpt-4o",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What do you see?"},
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/photo.jpg"
}
}
]
}]
}'- The adapter receives an Anthropic-format request with
imagecontent blocks - Each
imageblock is translated to an OpenAIimage_urlcontent block:- Base64 sources →
data:{media_type};base64,{data}data URI - URL sources → URL passed through directly
- Base64 sources →
documentblocks are skipped with a warning log (OpenAI format has no equivalent)cache_controlmetadata is accepted on any content block (prevents deserialization errors) but is not forwarded to the upstream API- The translated multimodal message is sent to the Copilot API
- Document blocks not supported: PDF and other document uploads are silently skipped. The adapter logs a warning with the document title (if provided) but continues processing the rest of the message.
cache_controlnot forwarded: Anthropic'scache_controlmetadata is accepted to prevent errors but has no effect on the upstream Copilot API.- Model must support vision: Use a model with vision capabilities (e.g.,
gpt-4o). Non-vision models may ignore or error on image content.
The adapter supports tool/function calling with native OpenAI function calling enabled by default, with automatic fallback to XML prompt injection.
By default, the adapter passes tool definitions natively to the Copilot API in OpenAI function calling format:
Benefits:
- Progressive streaming — text and tool calls appear as they are generated
- Type preservation — parameter types (number, boolean, etc.) are preserved from schemas
- Better UX — matches native Anthropic API behavior with incremental output
The adapter automatically falls back to XML injection if the upstream API does not support native tools.
If native tools are disabled or not supported by the upstream API, the adapter uses XML prompt injection:
- Injecting tool definitions into the system prompt as XML (following the Anthropic Cookbook format)
- Instructing the model to respond with structured XML when it wants to call a tool
- Parsing tool calls from
<function_calls>XML blocks in the model's text response - Returning them as
tool_usecontent blocks (Anthropic format)
This mode buffers the entire response before emitting SSE events.
To force XML-only mode (disable native tools), use the --disable-native-tools flag:
copilot-adapter start --disable-native-toolsThis may be useful if native tools cause issues with your specific Copilot API configuration.
Note:
--native-toolsis now the default behavior, so this flag is no longer needed.
OpenAI has a 64-character limit for function names. Long tool names (common with MCP tools like mcp__codemogger__codemogger_search) are automatically truncated with a deterministic hash suffix and restored in responses. This is transparent to clients.
In XML mode, the adapter coerces parameter values to their schema-defined types (number, boolean, object, array) using tool schemas from the request. This prevents MCP validation errors where typed parameters were incorrectly passed as strings.
Claude Code's native tool use (file operations, bash commands, etc.) works automatically through the adapter:
export ANTHROPIC_BASE_URL=http://127.0.0.1:6767
export ANTHROPIC_API_KEY=dummy
claude # Tools like bash, file read/write will workSend standard Anthropic-format tool definitions in your requests:
curl -X POST http://127.0.0.1:6767/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: dummy" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "What is the weather in London?"}],
"tools": [{
"name": "get_weather",
"description": "Get the current weather",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}]
}'Native Tools Mode (default):
tool_choicelimited: Only"auto"behavior is supported.- No
parallel_tool_calls: Not explicitly supported. - Fallback behavior: Automatically falls back to XML mode if the upstream API does not support native tools.
XML Mode (fallback or when --disable-native-tools is used):
- Best-effort parsing: Tool call parsing is based on XML extraction from text. The model may not always respond in the expected format. When parsing fails, the response gracefully degrades to a plain text message.
tool_choicelimited: Only"auto"behavior is supported. Thetool_choicefield is accepted but not enforced.- No
parallel_tool_calls: Theparallel_tool_callsparameter is not supported. - Increased token usage: Tool definitions are injected into the system prompt, increasing the token count.
- Streaming support: Tool calls in streaming responses are detected via buffering — the adapter buffers the full response, parses tool calls, then replays modified chunks.
If web search, web fetch, or other tools aren't working as expected, see the comprehensive debugging guide:
→ docs/development/debugging-tool-calls.md
Quick start:
# Linux/macOS
./scripts/debug-responses.sh
# Windows
scripts\debug-responses.batThis will run the adapter with trace-level logging to capture:
- What model is being requested
- What tools are being injected
- The raw response content from Copilot
- Whether tool calls are being parsed
See the debugging guide for how to interpret the logs and troubleshoot common issues.
# Custom port
copilot-adapter start --port 9090
# Bind to all interfaces (for remote access — use with caution)
copilot-adapter start --host 0.0.0.0# Log levels: trace, debug, info, warn, error
copilot-adapter start --log-level debug
# Log to file (useful with --daemon)
copilot-adapter start --daemon --log-file /var/log/copilot-adapter.log
# Or use RUST_LOG environment variable
RUST_LOG=debug copilot-adapter startThe --log-level flag takes precedence over RUST_LOG when explicitly set.
By default, the adapter fetches the model list from the Copilot API and caches it in memory. You can configure this behaviour:
# Set cache TTL to 10 minutes (default: 300 seconds / 5 minutes)
copilot-adapter start --models-cache-ttl 600
# Disable caching (always fetch fresh)
copilot-adapter start --models-cache-ttl 0
# Use the built-in static model list (no API calls for /v1/models)
copilot-adapter start --static-models| Flag | Default | Description |
|---|---|---|
--models-cache-ttl <seconds> |
300 |
How long to cache the model list (0 = no caching) |
--static-models |
false |
Always return the built-in static list; skip Copilot API calls |
When the Copilot API is unreachable (network error, auth failure, HTTP error), the adapter logs a warning and returns the static fallback list. This ensures the /v1/models endpoint never fails.
Credentials are stored in priority order:
- OS Keyring (preferred) — macOS Keychain, Windows Credential Manager, or Linux Secret Service (via D-Bus)
- Encrypted file (fallback) —
~/.config/copilot-adapter/credentials.jsonon Linux/macOS,%APPDATA%\copilot-adapter\credentials.jsonon Windows
To re-authenticate: copilot-adapter auth --force
To remove all credentials: copilot-adapter logout
Claude Code ──→ copilot-adapter (localhost:6767) ──→ GitHub Copilot API
│
┌─────┴─────┐
│ Token Mgr │ Auto-refresh Copilot tokens
│ Credential │ OS keyring / encrypted file
│ SSE Stream │ Real-time streaming support
└───────────┘
The adapter:
- Accepts Anthropic-format requests on localhost
- Authenticates with GitHub via stored OAuth token
- Exchanges the GitHub token for a short-lived Copilot token (auto-refreshed)
- Translates requests to OpenAI format and forwards to the Copilot API with required headers
- Translates responses back to Anthropic format and returns them
- Rust 1.75 or later
- Install via rustup (recommended):
# Linux/macOS curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Windows # Download and run: https://rustup.rs/
- Or install cargo directly:
# Debian/Ubuntu sudo apt install cargo # Fedora/RHEL sudo dnf install cargo # Arch Linux sudo pacman -S rust # macOS (Homebrew) brew install rust # Windows (Chocolatey) choco install rust
- Install via rustup (recommended):
- Platform-specific:
- Linux:
libdbus-1-devandlibsecret-1-dev(for keyring support) - macOS: Xcode command line tools
- Windows: Visual Studio Build Tools
- Linux:
# Development build
cargo build
# Optimized release build
cargo build --release
# Cross-compilation examples
cargo build --release --target x86_64-pc-windows-msvc
cargo build --release --target x86_64-apple-darwin
cargo build --release --target x86_64-unknown-linux-musl# All tests
cargo test
# Unit tests only
cargo test --test unit
# Integration tests only
cargo test --test integration
# With output
cargo test -- --nocaptureRun copilot-adapter auth to authenticate. If you're already authenticated, try copilot-adapter auth --force to refresh credentials.
Daemon mode cannot perform interactive authentication. Run copilot-adapter auth
first, or start in foreground mode (copilot-adapter start without --daemon)
to authenticate interactively.
The adapter waits 10 seconds for you to press Enter before opening the browser. If your system doesn't support automatic browser opening, copy the URL manually. On headless systems, the browser launch is skipped automatically.
Another instance is running. Stop it first:
copilot-adapter stopIf the process crashed, the stale PID file will be cleaned up automatically on the next start or status command.
- Verify the adapter is running:
copilot-adapter status - Check the port matches your
ANTHROPIC_BASE_URLsetting - Ensure no firewall is blocking localhost connections
The adapter auto-refreshes Copilot tokens 5 minutes before expiry. If you see refresh errors:
- Check your GitHub Copilot subscription is active
- Re-authenticate:
copilot-adapter auth --force - Check logs for details:
copilot-adapter start --log-level debug
On headless Linux systems, the OS keyring may not be available. The adapter falls back to encrypted file storage automatically. No action needed.
The adapter forwards rate limit responses from the Copilot API with the Retry-After header. Reduce request frequency or wait for the retry-after period.
For detailed troubleshooting, enable debug or trace logging:
copilot-adapter start --log-level trace
# or
RUST_LOG=trace copilot-adapter startSee docs/known-issues.md for information about:
- Multiple responses when using Claude Code
See LICENSE file for details.