Go beyond the CLI — embed Copilot intelligence directly into your own applications.
In this chapter, you'll learn how to use the GitHub Copilot SDK to build custom applications powered by Copilot. Instead of interacting with Copilot through the terminal, you'll write code that talks to Copilot programmatically — sending messages, streaming responses, and defining custom tools that Copilot can call.
💡 Note: The Copilot SDK is a separate tool from the Copilot CLI you've used in previous chapters. The CLI is for terminal-based workflows; the SDK is for embedding Copilot capabilities into your own apps, scripts, and services.
By the end of this chapter, you'll be able to:
- Install and configure the Copilot SDK for TypeScript/Node.js and Python
- Send messages and receive responses from Copilot programmatically
- Stream responses in real-time for interactive experiences
- Define custom tools that Copilot can invoke
- Build an interactive assistant with tool integration
⏱️ Estimated Time: ~90 minutes (20 min reading + 70 min hands-on labs)
Think of the Copilot CLI as plugging a lamp into a wall outlet — it works great, it's easy, and you get light immediately.
The Copilot SDK is like connecting directly to the power grid. You can:
- Build your own appliances (custom apps)
- Control exactly how much power you use (streaming, tools)
- Wire up complex systems (multi-tool assistants)
- Create entirely new experiences (chatbots, automation, agents)
The CLI gives you a flashlight. The SDK lets you build a power plant.
The Copilot SDK is a library (available for TypeScript, Python, Go, Rust, .NET, and Java) that lets your code communicate with GitHub Copilot. Under the hood, the SDK manages a Copilot CLI process and communicates with it via RPC (Remote Procedure Call).
| Component | What It Does |
|---|---|
| Client | Starts and manages the connection to Copilot |
| Session | A conversation context — like a chat thread |
| Messages | Prompts you send and responses you receive |
| Streaming | Receive response chunks in real-time (word by word) |
| Tools | Functions you define that Copilot can call |
Your App → Copilot SDK → Copilot CLI (managed automatically) → GitHub Copilot
↑ ↓
←──────────────── responses / tool calls / streaming ←───────────────────┘
- Your app creates a Client (which starts the CLI behind the scenes)
- You open a Session (a conversation)
- You send a message (a prompt)
- Copilot responds — either all at once or streamed word by word
- If you defined tools, Copilot can call them and incorporate the results
Before starting the labs, make sure you have:
- GitHub Copilot CLI installed and authenticated (from Chapter 00)
- Node.js 20+ (for the TypeScript lab)
- Python 3.11+ (for the Python lab)
Verify:
copilot --version
node --version # Should be 20+
python3 --version # Should be 3.11+Build a weather assistant using the Copilot SDK with TypeScript.
mkdir copilot-ts-lab && cd copilot-ts-lab
npm init -y --init-type module
npm install @github/copilot-sdk tsx💡 What's
tsx? It's a TypeScript runner that lets you execute.tsfiles directly without a separate compile step. Think of it likets-nodebut faster.
Create index.ts:
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
const session = await client.createSession({ model: "auto" });
const response = await session.sendAndWait({ prompt: "What is 2 + 2?" });
console.log(response?.data.content);
await client.stop();
process.exit(0);Run it:
npx tsx index.tsYou should see:
4
That's it — five lines of code and you've got Copilot responding to your app! The SDK handles starting the CLI, authenticating, and managing the session for you.
Waiting for the full response before displaying anything isn't great for user experience. Let's stream the response word by word.
Update index.ts:
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
const session = await client.createSession({
model: "auto",
streaming: true,
});
// Listen for response chunks as they arrive
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
session.on("session.idle", () => {
console.log(); // New line when done
});
await session.sendAndWait({ prompt: "Explain what the GitHub Copilot SDK does." });
await client.stop();
process.exit(0);Run it again:
npx tsx index.tsNow you'll see the response appear word by word, just like a real chat experience.
Here's where it gets powerful. You can define tools — functions in your code that Copilot can decide to call based on the user's question.
Let's create a weather lookup tool:
Update index.ts:
import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk";
// Define a tool that Copilot can call
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 (args: { city: string }) => {
const { city } = args;
// In a real app, you'd call a weather API here
const conditions = ["sunny", "cloudy", "rainy", "partly cloudy"];
const temp = Math.floor(Math.random() * 3) + 20;
const condition = conditions[Math.floor(Math.random() * conditions.length)];
return { city, temperature: `${temp}°C`, condition };
},
});
const client = new CopilotClient();
const session = await client.createSession({
model: "auto",
streaming: true,
tools: [getWeather],
onPermissionRequest: approveAll
});
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
session.on("session.idle", () => {
console.log();
});
await session.sendAndWait({
prompt: "What's the weather like in Munich and Bonn?",
});
await client.stop();
process.exit(0);Run it:
npx tsx index.tsCopilot will call your get_weather function for each city, then incorporate the results into a natural language response. You didn't write any prompt engineering — Copilot figured out it needs weather data and called your tool automatically.
Let's put it all together into a REPL (Read-Eval-Print Loop) where you can chat with Copilot:
Create weather-assistant.ts:
import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk";
import * as readline from "readline";
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 }) => {
const conditions = ["sunny", "cloudy", "rainy", "partly cloudy"];
const temp = Math.floor(Math.random() * 3) + 20;
const condition = conditions[Math.floor(Math.random() * conditions.length)];
return { city, temperature: `${temp}°C`, condition };
},
});
const client = new CopilotClient();
const session = await client.createSession({
model: "auto",
streaming: true,
tools: [getWeather],
onPermissionRequest: approveAll
});
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log("🌤️ Weather Assistant (type 'exit' to quit)");
console.log(" Try: 'What's the weather in Berlin?'\n");
const prompt = () => {
rl.question("You: ", async (input) => {
if (input.toLowerCase() === "exit") {
await client.stop();
rl.close();
return;
}
process.stdout.write("Assistant: ");
await session.sendAndWait({ prompt: input });
console.log("\n");
prompt();
});
};
prompt();Run it:
npx tsx weather-assistant.tsExample session:
🌤️ Weather Assistant (type 'exit' to quit)
Try: 'What's the weather in Berlin?'
You: What's the weather in Seattle?
Assistant: Let me check the weather for Seattle...
It's currently 62°F and cloudy in Seattle.
You: How about Munich and London?
Assistant: I'll check both cities for you:
- Tokyo: 75°F and sunny
- London: 58°F and rainy
You: exit
Build the same weather assistant using the Copilot SDK with Python.
mkdir copilot-python-lab && cd copilot-python-lab
python3 -m venv .venv
source .venv/bin/activate
pip install github-copilot-sdk💡 Why a virtual environment? It keeps your project dependencies isolated. The
github-copilot-sdkpackage won't conflict with anything else on your system.
Create main.py:
import asyncio
from copilot import CopilotClient
from copilot.session import PermissionHandler
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="auto"
)
response = await session.send_and_wait("What is 2 + 2?")
print(response.data.content)
await client.stop()
asyncio.run(main())Run it:
python main.pyYou should see:
4
💡 Why
asyncio? The Copilot SDK uses async I/O to communicate with the CLI process. Theasync/awaitpattern lets your app do other work while waiting for Copilot's response.
Update main.py to stream responses as they arrive:
import asyncio
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.generated.session_events import SessionEventType
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="auto",
streaming=True
)
# Listen for response chunks
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() # New line when done
session.on(handle_event)
await session.send_and_wait("Explain what the GitHub Copilot SDK does.")
await client.stop()
asyncio.run(main())Run it:
python main.pyThe response streams in real-time — much better for interactive experiences.
Define a weather tool that Copilot can call. Python uses Pydantic models to describe tool parameters:
Update main.py:
import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
# Define the parameters using a Pydantic model
class GetWeatherParams(BaseModel):
city: str = Field(description="The name of the city to get weather for")
# Define a tool that Copilot can call
@define_tool(description="Get the current weather for a city")
async def get_weather(params: GetWeatherParams) -> dict:
city = params.city
# In a real app, you'd call a weather API here
conditions = ["sunny", "cloudy", "rainy", "partly cloudy"]
temp = random.randint(20, 40)
condition = random.choice(conditions)
return {"city": city, "temperature": f"{temp}°C", "condition": condition}
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="auto",
streaming=True,
tools=[get_weather]
)
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("What's the weather like in Munich and Bonn?")
await client.stop()
asyncio.run(main())Run it:
python main.pyCopilot calls your get_weather function for each city, then weaves the results into a natural response.
Create weather_assistant.py:
import asyncio
import random
import sys
from copilot import CopilotClient
from copilot.session import PermissionHandler
from copilot.tools import define_tool
from copilot.generated.session_events import SessionEventType
from pydantic import BaseModel, Field
class GetWeatherParams(BaseModel):
city: str = Field(description="The name of the city to get weather for")
@define_tool(description="Get the current weather for a city")
async def get_weather(params: GetWeatherParams) -> dict:
city = params.city
conditions = ["sunny", "cloudy", "rainy", "partly cloudy"]
temp = random.randint(20, 40)
condition = random.choice(conditions)
return {"city": city, "temperature": f"{temp}°C", "condition": condition}
async def main():
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
model="auto",
streaming=True,
tools=[get_weather]
)
def handle_event(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
sys.stdout.write(event.data.delta_content)
sys.stdout.flush()
session.on(handle_event)
print("🌤️ Weather Assistant (type 'exit' to quit)")
print(" Try: 'What's the weather in Berlin?' or 'Compare weather in Munich and Merano'\n")
while True:
try:
user_input = input("You: ")
except EOFError:
break
if user_input.lower() == "exit":
break
sys.stdout.write("Assistant: ")
await session.send_and_wait(user_input)
print("\n")
await client.stop()
asyncio.run(main())Run it:
python weather_assistant.pyExample session:
🌤️ Weather Assistant (type 'exit' to quit)
Try: 'What's the weather in Berlin?' or 'Compare weather in Munich and Merano'
You: What's the weather in Seattle?
Assistant: Let me check the weather for Seattle...
It's currently 62°F and cloudy in Seattle.
You: How about Tokyo and London?
Assistant: I'll check both cities for you:
- Tokyo: 75°F and sunny
- London: 58°F and rainy
You: exit
When you define a tool, you're telling Copilot three things:
- What the tool does — the description (e.g., "Get the current weather for a city")
- What parameters it needs — the schema (e.g.,
city: string) - What code to run — the handler function
Here's the flow when Copilot uses a tool:
User: "What's the weather in Munich?"
│
▼
Copilot sees the question matches the tool description
│
▼
Copilot sends a tool call: get_weather({ city: "Munich" })
│
▼
SDK runs YOUR handler function
│
▼
Result returned: { city: "Munich", temperature: "32°C", condition: "sunny" }
│
▼
Copilot incorporates result into natural language response
│
▼
User sees: "It's currently 32°C and sunny in Munich!"
💡 Key insight: You don't need to write prompt engineering to get Copilot to use your tools. Just describe what the tool does clearly, and Copilot figures out when to use it.
Once you're comfortable with the basics, explore these advanced features:
Give your app access to external tools via MCP (Model Context Protocol):
const session = await client.createSession({
mcpServers: {
github: {
type: "http",
url: "https://api.githubcopilot.com/mcp/",
},
},
});Define specialized AI personas for specific tasks:
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.",
}],
});Control Copilot's behavior and personality:
const session = await client.createSession({
systemMessage: {
content: "You are a helpful assistant for our engineering team. Always be concise.",
},
});For debugging or resource sharing, run the CLI separately:
# Start CLI in server mode
copilot --headless --port 4321Then connect your SDK client to it:
const client = new CopilotClient({
cliUrl: "localhost:4321"
});from copilot import CopilotClient, RuntimeConnection
client = CopilotClient(connection=RuntimeConnection.for_uri("localhost:4321"))Put everything you've learned into practice.
-
Extend the weather assistant: Add a second tool (e.g.,
get_timethat returns the current time in a timezone). Ask Copilot "What's the weather and time in Tokyo?" and watch it call both tools. -
Build a book assistant: Create a tool that searches your book collection (use
samples/book-app-project/data.jsonas the data source). Ask Copilot questions like "Do I have any books by Orwell?" or "What books were published before 1960?" -
Add system messages: Try adding a
systemMessageto your session that changes Copilot's personality. Make it respond like a pirate, or a formal butler, or a sports commentator.
Build a Copilot-powered assistant for the book collection app using either TypeScript or Python:
-
Define two tools:
search_books— searches books by title or author (reads fromsamples/book-app-project/data.json)add_book— adds a new book to the collection
-
Enable streaming so responses appear in real-time
-
Create an interactive loop where the user can chat with the assistant
-
Test these interactions:
- "Do I have any science fiction books?"
- "Add a new book: 'The Martian' by Andy Weir, published 2011"
- "List all books by their publication year"
Success criteria: Your assistant correctly uses the tools when relevant and responds naturally when no tool is needed.
💡 Hints (click to expand)
For the search_books tool:
- Read
data.jsonusingfs.readFileSync(TypeScript) orjson.load(Python) - Filter books where the title or author matches the search query (case-insensitive)
- Return the matching books as an array
For the add_book tool:
- Read the current data, append the new book, write back to the file
- Return a confirmation message
Tool parameters:
search_books:{ query: string }— the search termadd_book:{ title: string, author: string, year: number }— book details
Remember: You define WHAT the tool does. Copilot decides WHEN to use it.
🔧 Common Mistakes (click to expand)
| Mistake | What Happens | Fix |
|---|---|---|
Forgetting await client.stop() |
CLI process keeps running in the background | Always call stop() before exiting |
Not enabling streaming: true |
message_delta events never fire |
Set streaming: true in session config |
| Tool handler returns wrong type | Copilot gets confused or errors | Return a plain object/dict, not a string |
Missing process.exit(0) (TypeScript) |
Script hangs after completion | Add explicit exit at the end |
Not using sys.stdout.flush() (Python) |
Streamed text appears all at once | Flush after each write |
Forgetting await client.start() (Python) |
Session creation fails | Always start the client before creating sessions |
- The SDK embeds Copilot in your apps: Go beyond the terminal — build custom tools, assistants, and automation
- 5 lines to start: Create a client, open a session, send a message — that's the minimum
- Streaming makes it interactive: Real-time responses feel natural and responsive
- Tools are the superpower: Define what your code can do, and Copilot decides when to call it
- Same patterns, any language: The SDK works the same way across TypeScript, Python, Go, Rust, .NET, and Java
- GitHub Copilot SDK — Getting Started
- Node.js SDK Reference
- Python SDK Reference
- Using MCP Servers with the SDK
- Authentication & BYOK