Skip to main content

Build your first Copilot-powered app

In this tutorial, you'll use the Copilot SDK to build a command-line assistant. You'll start with the basics, add streaming responses, then add custom tools - giving Copilot the ability to call your code.

What you'll build:

You: What's the weather like in Seattle?
Copilot: Let me check the weather for Seattle...
         Currently 62°F and cloudy with a chance of rain.
         Typical Seattle weather!

You: How about Tokyo?
Copilot: In Tokyo it's 75°F and sunny. Great day to be outside!

Prerequisites

Before you begin, make sure you have:

  • GitHub Copilot CLI installed and authenticated (the Node.js, Python, and .NET SDKs provide the CLI automatically—see Default setup (bundled CLI). Required for Go, Java, and Rust unless using their application-level CLI bundling features.)
  • Your preferred language runtime:
    • Node.js 20+ or Python 3.11+ or Go 1.24+ or Rust 1.94+ or Java 17+ or .NET 8.0+

Verify the CLI is working:

copilot --version

Step 1: install the SDK

Code languages navigation

TypeScript

First, create a new directory and initialize your project:

mkdir copilot-demo && cd copilot-demo
npm init -y --init-type module

Then install the SDK and TypeScript runner:

npm install @github/copilot-sdk tsx

Step 2: send your first message

Create a new file and add the following code. This is the simplest way to use the SDK—about 5 lines of code.

Code languages navigation

TypeScript

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.ts

You should see:

4

Congratulations! You just built your first Copilot-powered app.

Step 3: add streaming responses

Right now, you wait for the complete response before seeing anything. Let's make it interactive by streaming the response as it's generated.

Code languages navigation

TypeScript

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
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: "Tell me a short joke" });

await client.stop();
process.exit(0);

Run the code again. You'll see the response appear word by word.

Event subscription methods

The SDK provides methods for subscribing to session events:

MethodDescription
on(handler)Subscribe to all events; returns unsubscribe function
on(eventType, handler)Subscribe to specific event type (Node.js/TypeScript only); returns unsubscribe function
subscribe()Subscribe to all events (Rust); filter by event_type

Code languages navigation

TypeScript
// Subscribe to all events
const unsubscribeAll = session.on((event) => {
    console.log("Event:", event.type);
});

// Subscribe to specific event type
const unsubscribeIdle = session.on("session.idle", (event) => {
    console.log("Session is idle");
});

// Later, to unsubscribe:
unsubscribeAll();
unsubscribeIdle();

Step 4: add a custom tool

Now for the powerful part. Let's give Copilot the ability to call your code by defining a custom tool. We'll create a simple weather lookup tool.

Code languages navigation

TypeScript

Update index.ts:

import { CopilotClient, defineTool } 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() * 30) + 50;
        const condition = conditions[Math.floor(Math.random() * conditions.length)];
        return { city, temperature: `${temp}°F`, condition };
    },
});

const client = new CopilotClient();
const session = await client.createSession({
    model: "auto",
    streaming: true,
    tools: [getWeather],
});

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: "What's the weather like in Seattle and Tokyo?",
});

await client.stop();
process.exit(0);

Run it and you'll see Copilot call your tool to get weather data, then respond with the results!

Step 5: build an interactive assistant

Let's put it all together into a useful interactive assistant:

Code languages navigation

TypeScript
import { CopilotClient, defineTool } 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() * 30) + 50;
        const condition = conditions[Math.floor(Math.random() * conditions.length)];
        return { city, temperature: `${temp}°F`, condition };
    },
});

const client = new CopilotClient();
const session = await client.createSession({
    model: "auto",
    streaming: true,
    tools: [getWeather],
});

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 Paris?'\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 with:

npx tsx weather-assistant.ts

Example session:

🌤️  Weather Assistant (type 'exit' to quit)
   Try: 'What's the weather in Paris?' or 'Compare weather in NYC and LA'

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

You've built an assistant with a custom tool that Copilot can call!

How tools work

When you define a tool, you're telling Copilot:

  1. What the tool does (description)
  2. What parameters it needs (schema)
  3. What code to run (handler)

Copilot decides when to call your tool based on the user's question. When it does:

  1. Copilot sends a tool call request with the parameters
  2. The SDK runs your handler function
  3. The result is sent back to Copilot
  4. Copilot incorporates the result into its response

What's next?

Now that you've got the basics, here are more powerful features to explore:

Connect to MCP servers

MCP (Model Context Protocol) servers provide pre-built tools. Connect to GitHub's MCP server to give Copilot access to repositories, issues, and pull requests:

const session = await client.createSession({
    mcpServers: {
        github: {
            type: "http",
            url: "https://api.githubcopilot.com/mcp/",
        },
    },
});

📖 Using MCP servers with the GitHub Copilot SDK - Learn about local vs remote servers, all configuration options, and troubleshooting.

Create custom agents

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.",
    }],
});

Tip

You can also set agent: "pr-reviewer" in the session config to pre-select this agent from the start. See the Custom agents and sub-agent orchestration for details.

Customize the system message

Control the AI's behavior and personality by appending instructions:

const session = await client.createSession({
    systemMessage: {
        content: "You are a helpful assistant for our engineering team. Always be concise.",
    },
});

For more fine-grained control, use mode: "customize" to override individual sections of the system prompt while preserving the rest:

const session = await client.createSession({
    systemMessage: {
        mode: "customize",
        sections: {
            tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." },
            code_change_rules: { action: "remove" },
            guidelines: { action: "append", content: "\n* Always cite data sources" },
        },
        content: "Focus on financial analysis and reporting.",
    },
});

Available section IDs: preamble, identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, runtime_instructions, last_instructions.

identity and tool_instructions are section groups: they target a collection of related sub-sections as a unit. Use preamble to target just the identity preamble without affecting its sibling sub-sections.

Each override supports five actions: replace, remove, append, prepend, and preserve. The preserve action is a no-op that opts an individually-addressable section out of a group-level remove (for example, keep tone when removing the identity group). Unknown section IDs are handled gracefully: content from replace/append/prepend overrides is appended to additional instructions, and remove overrides are silently ignored.

See the language-specific SDK READMEs for examples in TypeScript, Python, Go, Rust, Java, and C#.

Connecting to an external CLI server

By default, the SDK automatically manages the Copilot CLI process lifecycle, starting and stopping the CLI as needed. However, you can also run the CLI in server mode separately and have the SDK connect to it. This can be useful for:

  • Debugging: Keep the CLI running between SDK restarts to inspect logs
  • Resource sharing: Multiple SDK clients can connect to the same CLI server
  • Development: Run the CLI with custom settings or in a different environment

Running the CLI in server mode

Start the CLI in server mode using the --headless flag and optionally specify a port:

copilot --headless --port 4321

If you don't specify a port, the CLI will choose a random available port.

By default the headless server only accepts connections from loopback (127.0.0.1), so the SDK must run on the same machine. To accept connections from other hosts (for example when running the CLI in a container or on a separate server), bind to a non-loopback address with --host:

# Listen on all interfaces
copilot --headless --host 0.0.0.0 --port 4321

Warning

Exposing the headless server on a non-loopback address makes it reachable by anyone who can route to that address. Pair it with network controls (firewall, private network, reverse proxy) and authentication appropriate for your environment.

Connecting the SDK to the external server

Once the CLI is running in server mode, configure your SDK client to connect to it using the "cli url" option:

Code languages navigation

TypeScript
import { CopilotClient, approveAll } from "@github/copilot-sdk";

const client = new CopilotClient({
    cliUrl: "localhost:4321"
});

// Use the client normally
const session = await client.createSession({ onPermissionRequest: approveAll });
// ...

Note: When cli_url / cliUrl / Go's URIConnection is provided, or Rust uses Transport::External, the SDK will not spawn or manage a CLI process - it will only connect to the existing server at the specified URL.

Telemetry and observability

The Copilot SDK supports OpenTelemetry for distributed tracing. Provide a telemetry configuration to the client to enable trace export from the CLI process and automatic W3C Trace Context propagation between the SDK and CLI.

Enabling telemetry

Pass a telemetry (or Telemetry) config when creating the client. This is the opt-in—no separate "enabled" flag is needed.

Code languages navigation

TypeScript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
  },
});

Optional peer dependency: @opentelemetry/api

TelemetryConfig options

OptionNode.jsPythonGoRustJava.NETDescription
OTLP endpointotlpEndpointotlp_endpointOTLPEndpointotlp_endpointotlpEndpointOtlpEndpointOTLP HTTP endpoint URL
OTLP protocolotlpProtocolotlp_protocolOTLPProtocolotlp_protocolotlpProtocolOtlpProtocolOTLP HTTP protocol for all signals: "http/json" or "http/protobuf"
File pathfilePathfile_pathFilePathfile_pathfilePathFilePathFile path for JSON-lines trace output
Exporter typeexporterTypeexporter_typeExporterTypeexporter_typeexporterTypeExporterType"otlp-http" or "file"
Source namesourceNamesource_nameSourceNamesource_namesourceNameSourceNameInstrumentation scope name
Capture contentcaptureContentcapture_contentCaptureContentcapture_contentcaptureContentCaptureContentWhether to capture message content

The OTLP protocol field configures the CLI's "otlp-http" exporter for all signals. Leave it unset to use the CLI default, or set it to "http/protobuf" to export protobuf over HTTP.

File export

To write traces to a local file instead of an OTLP endpoint:

const client = new CopilotClient({
  telemetry: {
    filePath: "./traces.jsonl",
    exporterType: "file",
  },
});

Trace context propagation

Trace context is propagated automatically—no manual instrumentation is needed:

  • SDK → CLI: traceparent and tracestate headers from the current span/activity are included in session.create, session.resume, and session.send RPC calls.
  • CLI → SDK: When the CLI invokes tool handlers, the trace context from the CLI's span is propagated so your tool code runs under the correct parent span.

📖 OpenTelemetry instrumentation for Copilot SDK—TelemetryConfig options, trace context propagation, and per-language dependencies.

Learn more

You did it! You've learned the core concepts of the GitHub Copilot SDK:

  • ✅ Creating a client and session
  • ✅ Sending messages and receiving responses
  • ✅ Streaming for real-time output
  • ✅ Defining custom tools that Copilot can call

Now go build something amazing! 🚀