# How to Add and Integrate Custom Tools with the Copilot SDK

> Learn how to add and integrate custom tools with the Copilot SDK. Extend Copilot's chat capabilities by registering tool schemas and handler functions for structured LLM responses.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The Copilot SDK allows you to extend Copilot's capabilities by registering JSON-serializable tool schemas and handler functions that the runtime invokes during chat sessions, returning structured results back to the LLM.**

The GitHub Copilot SDK enables developers to enhance AI-assisted coding workflows by adding custom tools that Copilot can invoke during conversations. When you add and integrate custom tools with the Copilot SDK, you create a bridge between the LLM and your external APIs or business logic. This article walks through the complete implementation process using the actual source code from the `github/copilot-sdk` repository.

## Define the Tool Schema

Start by creating a **Tool** object that describes the function signature using JSON Schema. In [`nodejs/src/toolSet.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/toolSet.ts), the `Tool` type defines the structure including `name`, `description`, `args`, and optional flags.

The schema must specify:

- **name**: Unique identifier for the tool
- **description**: Natural language explanation for the LLM to understand when to invoke the tool
- **args**: JSON Schema object defining required and optional parameters

### Skip Permission Prompts

Set `skipPermission: true` in the tool definition to bypass the user confirmation UI for trusted internal tools. This flag is evaluated during the permission check phase in the session lifecycle.

## Register Custom Tools

Before starting a session, call **registerTools()** (or **registerTool()** for single registrations) on the SDK client instance. According to [`nodejs/src/index.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/index.ts), this method merges your custom definitions with the SDK's built-in tool set.

The registration requires two arguments:

1. An array of `Tool` schema objects
2. A handler function that receives `ToolInvocation` and returns `Promise<ToolResult>` or `ToolResult`

## Implement the Handler Function

The handler receives a **ToolInvocation** object containing the parsed arguments and must return a **ToolResult** or **ToolBinaryResult** for binary data. As defined in [`nodejs/src/toolSet.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/toolSet.ts), these types enforce the contract between your implementation and the LLM.

Handlers can be synchronous or asynchronous. The SDK automatically wraps the return value into a `ToolExecutionComplete` event and streams it back to the LLM as a structured response.

## Session Execution Flow

During active sessions managed in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts), the SDK listens for `toolRequest` events from Copilot. When the LLM decides to invoke a tool, the SDK:

- Creates a `ToolInvocation` object with validated arguments
- Executes your registered handler
- Emits `ToolExecutionProgressData` events for streaming partial results (optional)
- Returns the final `ToolResult` to the LLM via the `ToolExecutionComplete` event

## Optional Hooks and Permissions

The SDK provides lifecycle hooks for governance and observability. As documented in [`docs/hooks/pre-tool-use.md`](https://github.com/github/copilot-sdk/blob/main/docs/hooks/pre-tool-use.md) and implemented in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts):

- **onPreToolUse**: Validate arguments or enforce policies before the tool executes
- **onPostToolUse**: Process results for logging, telemetry, or side effects

## Complete Working Example

Below is a minimal Node.js example that adds a weather-lookup tool and integrates it into a Copilot chat session.

```typescript
import { Copilot } from '@github/copilot-sdk/nodejs';
import { Tool, ToolInvocation, ToolResult } from '@github/copilot-sdk/nodejs';

// 1️⃣ Define the custom tool
const weatherTool: Tool = {
  name: 'weather',
  description: 'Look up the current weather for a city',
  args: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'Name of the city' },
      units: { type: 'string', enum: ['metric', 'imperial'], default: 'metric' },
    },
    required: ['city'],
  },
  // Trusted internal tool – skip the permission prompt
  skipPermission: true,
};

// 2️⃣ Provide the implementation
async function runWeatherTool(invocation: ToolInvocation): Promise<ToolResult> {
  const { city, units } = invocation.args as { city: string; units?: string };
  // (In a real app you would call a weather API here)
  const fakeTemp = 22; // placeholder
  const response = `The temperature in ${city} is ${fakeTemp}°${units === 'imperial' ? 'F' : 'C'}.`;
  return { result: response };
}

// 3️⃣ Register the tool with the SDK client
const client = new Copilot();
client.registerTools([weatherTool], runWeatherTool);

// 4️⃣ Start a chat session
async function chat() {
  const session = client.createSession({ streaming: true });
  const response = await session.prompt('What’s the weather like in Paris?');
  console.log(response); // → “The temperature in Paris is …”
}
chat();

```

## Summary

- Define tools using the `Tool` type in [`nodejs/src/toolSet.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/toolSet.ts) with JSON Schema arguments
- Register handlers via `registerTools()` on the client before creating sessions
- Return `ToolResult` objects from handlers to communicate results to the LLM
- Use `skipPermission: true` for trusted tools to bypass confirmation dialogs
- Leverage hooks in [`session.ts`](https://github.com/github/copilot-sdk/blob/main/session.ts) for pre-flight validation and post-execution logging

## Frequently Asked Questions

### What is the difference between registerTools() and registerTool()?

**registerTools()** accepts an array of tool definitions and registers them in a single call, while **registerTool()** registers one tool at a time. Both methods are available on the `Copilot` client instance exposed in [`nodejs/src/index.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/index.ts) and merge your definitions with the SDK's internal tool set.

### Can custom tools return binary data?

Yes. When your tool produces binary output (such as images or files), return a **ToolBinaryResult** instead of a `ToolResult`. The SDK handles binary serialization differently from text results, ensuring proper encoding when streaming back to the LLM via the `ToolExecutionComplete` event.

### How do I handle errors in custom tool handlers?

Throwing an error inside your handler function will cause the SDK to emit an error event back to the LLM. Alternatively, you can return a `ToolResult` with an error message in the result field. Use the `onPreToolUse` hook in [`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts) to catch validation errors before the handler executes.

### Where can I find complete runnable examples?

The [`nodejs/examples/basic-example.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/examples/basic-example.ts) file in the repository contains a fully functional demonstration of a chat session with custom tool integration. For step-by-step guidance, see [`docs/getting-started.md`](https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md) Section "Step 4: add a custom tool".