Build applications powered by GitHub Copilot using the Copilot SDK. Use when creating programmatic integrations with Copilot across Node.js/TypeScript, Python, Go, or .NET. Covers session management, custom tools, streaming, hooks, MCP servers, BYOK providers, session persistence, and custom agents. Requires GitHub Copilot CLI installed and a GitHub Copilot subscription (unless using BYOK).
>_
Quick Install
npxskills add microsoft/skills--skill copilot-sdk
Instructions
Loading…
Tags & Topics
agent-skillsagentsazurefoundrymcpsdk
GitHub Copilot SDK
Build applications that programmatically interact with GitHub Copilot. The SDK wraps the Copilot CLI via JSON-RPC, providing session management, custom tools, hooks, MCP server integration, and streaming across Node.js, Python, Go, and .NET.
Prerequisites
GitHub Copilot CLI installed and authenticated (copilot --version)
GitHub Copilot subscription (Individual, Business, or Enterprise) — not required for BYOK
The SDK communicates with the Copilot CLI via JSON-RPC over stdio (default) or TCP. The CLI manages model calls, tool execution, session state, and MCP server lifecycle.
Your App → SDK Client → [stdio/TCP] → Copilot CLI → Model Provider
↕
MCP Servers
Transport modes:
Mode
Description
Use Case
Stdio (default)
CLI as subprocess via pipes
Local dev, single process
TCP
CLI as network server
Multi-client, backend services
Core Pattern: Client → Session → Message
All SDK usage follows: create a client, create a session, send messages.
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-4.1" });
var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 2?" });
Console.WriteLine(response?.Data.Content);
Streaming Responses
Enable real-time output by setting streaming: true and subscribing to delta events.
from copilot.generated.session_events import SessionEventType
session = await client.create_session({"model": "gpt-4.1", "streaming": True})
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
sys.stdout.write(event.data.delta_content)
sys.stdout.flush()
if event.type == SessionEventType.SESSION_IDLE:
print()
session.on(handle_event)
await session.send_and_wait({"prompt": "Tell me a joke"})
Event Subscription
Method
Description
on(handler)
Subscribe to all events; returns unsubscribe function
on(eventType, handler)
Subscribe to specific event type (Node.js only)
Call the returned function to unsubscribe. In .NET, call .Dispose() on the returned disposable.
Custom Tools
Define tools that Copilot can call to extend its capabilities.
Node.js
import { CopilotClient, defineTool } from "@github/copilot-sdk";
const getWeather = defineTool("get_weather", {
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: { city: { type: "string", description: "The city name" } },
required: ["city"],
},
handler: async ({ city }) => ({ city, temperature: "72°F", condition: "sunny" }),
});
const session = await client.createSession({
model: "gpt-4.1",
tools: [getWeather],
});
Python
from copilot.tools import define_tool
from pydantic import BaseModel, Field
class GetWeatherParams(BaseModel):
city: str = Field(description="The city name")
@define_tool(description="Get the current weather for a city")
async def get_weather(params: GetWeatherParams) -> dict:
return {"city": params.city, "temperature": "72°F", "condition": "sunny"}
session = await client.create_session({"model": "gpt-4.1", "tools": [get_weather]})
Go
type WeatherParams struct {
City string `json:"city" jsonschema:"The city name"`
}
getWeather := copilot.DefineTool("get_weather", "Get weather for a city",
func(params WeatherParams, inv copilot.ToolInvocation) (WeatherResult, error) {
return WeatherResult{City: params.City, Temperature: "72°F"}, nil
},
)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
Tools: []copilot.Tool{getWeather},
})
.NET
using Microsoft.Extensions.AI;
using System.ComponentModel;
var getWeather = AIFunctionFactory.Create(
([Description("The city name")] string city) => new { city, temperature = "72°F" },
"get_weather", "Get the current weather for a city");
await using var session = await client.CreateSessionAsync(new SessionConfig {
Model = "gpt-4.1", Tools = [getWeather],
});
Tool Requirements
Handler must return JSON-serializable data (not undefined)
Parameters must follow JSON Schema format
Tool description should clearly state when the tool should be used
Hooks
Intercept and customize session behavior at key lifecycle points.
Hook
Trigger
Use Case
onPreToolUse
Before tool executes
Permission control, argument modification
onPostToolUse
After tool executes
Result transformation, logging, redaction
onUserPromptSubmitted
User sends message
Prompt modification, filtering, context injection
onSessionStart
Session begins (new or resumed)
Add context, configure session
onSessionEnd
Session ends
Cleanup, analytics, metrics
onErrorOccurred
Error happens
Custom error handling, retry logic, monitoring
Pre-Tool Use Hook
Control tool permissions, modify arguments, or inject context before tool execution.
Note: Bearer tokens expire (~1 hour). For long-running apps, refresh the token before each new session. The SDK does not auto-refresh tokens.
BYOK Limitations
Static credentials only — no native Entra ID, OIDC, or managed identity support
No auto-refresh — expired tokens require creating a new session
Keys not persisted — must re-provide provider config on session resume
Model availability — limited to what your provider offers
Session Persistence
Resume sessions across restarts by providing your own session ID.
// Create with explicit ID
const session = await client.createSession({
sessionId: "user-123-task-456",
model: "gpt-4.1",
});
// Resume later (even from a different client instance)
const resumed = await client.resumeSession("user-123-task-456");
await resumed.sendAndWait({ prompt: "What did we discuss?" });
Session Management
const sessions = await client.listSessions(); // List all
const lastId = await client.getLastSessionId(); // Get most recent
await client.deleteSession("user-123-task-456"); // Delete from storage
await session.destroy(); // Destroy active session
Resume Options
When resuming, you can reconfigure: model, systemMessage, availableTools, excludedTools, provider (required for BYOK), reasoningEffort, streaming, mcpServers, customAgents, skillDirectories, infiniteSessions.
Session ID Best Practices
Pattern
Example
Use Case
user-{userId}-{taskId}
user-alice-pr-review-42
Multi-user apps
tenant-{tenantId}-{workflow}
tenant-acme-onboarding
Multi-tenant SaaS
{userId}-{taskType}-{timestamp}
alice-deploy-1706932800
Time-based cleanup
What Gets Persisted
Session state is saved to ~/.copilot/session-state/{sessionId}/:
Data
Persisted?
Notes
Conversation history
✅ Yes
Full message thread
Tool call results
✅ Yes
Cached for context
Agent planning state
✅ Yes
plan.md file
Session artifacts
✅ Yes
In files/ directory
Provider/API keys
❌ No
Must re-provide on resume
In-memory tool state
❌ No
Design tools to be stateless
Infinite Sessions
For long-running workflows that may exceed context limits, enable auto-compaction:
const session = await client.createSession({
infiniteSessions: {
enabled: true,
backgroundCompactionThreshold: 0.80, // Start background compaction at 80%
bufferExhaustionThreshold: 0.95, // Block and compact at 95%
},
});
Thresholds are context utilization ratios (0.0–1.0), not absolute token counts.
Custom Agents
Define specialized AI personas:
const session = await client.createSession({
customAgents: [{
name: "pr-reviewer",
displayName: "PR Reviewer",
description: "Reviews pull requests for best practices",
prompt: "You are an expert code reviewer. Focus on security, performance, and maintainability.",
}],
});
System Message
Control AI behavior and personality:
const session = await client.createSession({
systemMessage: { content: "You are a helpful assistant. Always be concise." },
});
Skills Integration
Load skill directories to extend Copilot's capabilities:
Handle tool permissions and user input requests programmatically. The SDK uses a deny-by-default permission model — all permission requests are denied unless you provide a handler.