Tools

Extending Copilot with custom capabilities.

What Are Tools?

Tools are functions that Copilot can call during a conversation. When Copilot needs information it doesn’t have (like current weather, database records, or external APIs), it calls your tools.

User: "What's the weather in Tokyo?"

Copilot thinks: "I need current weather data. Let me call the weather tool."

Your tool executes: getWeather("Tokyo")

Tool returns: { temp: 22, condition: "sunny" }

Copilot: "It's currently 22°C and sunny in Tokyo."

Tool Anatomy

A tool has three parts:

1. Definition (what Copilot sees)

{
  name: "get_weather",
  description: "Get current weather for a city",
  parameters: {
    type: "object",
    properties: {
      city: {
        type: "string",
        description: "City name"
      }
    },
    required: ["city"]
  }
}

2. Implementation (what runs)

async function getWeather(params: { city: string }) {
  const response = await fetch(`https://api.weather.com/${params.city}`);
  return response.json();
}

3. Handler (connecting them)

function handleToolCall(name: string, params: unknown) {
  if (name === "get_weather") {
    return getWeather(params as { city: string });
  }
}

Defining Tools (Node.js)

Manual definition

const weatherTool = {
  name: "get_weather",
  description: "Get current weather for a location",
  parameters: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name" },
      units: {
        type: "string",
        enum: ["celsius", "fahrenheit"],
        description: "Temperature units"
      }
    },
    required: ["city"]
  }
};

The SDK can generate schemas from Zod types:

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const WeatherParams = z.object({
  city: z.string().describe("City name"),
  units: z.enum(["celsius", "fahrenheit"]).optional().describe("Temperature units")
});

const weatherTool = {
  name: "get_weather",
  description: "Get current weather for a location",
  parameters: zodToJsonSchema(WeatherParams)
};

Using Tools in Sessions

const session = await client.createSession({
  tools: [weatherTool]
});

// Handle tool calls
for await (const event of session.send("What's the weather in Tokyo?")) {
  if (event.type === "toolCall") {
    const { name, arguments: args } = event;

    // Execute tool
    let result;
    if (name === "get_weather") {
      result = await getWeather(JSON.parse(args));
    }

    // Return result to Copilot
    await session.submitToolResult(event.callId, JSON.stringify(result));
  }
}

Tool Call Flow

┌─────────────────────────────────────────────────────────────────────────┐
│                          TOOL CALL SEQUENCE                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. Copilot decides to call tool                                       │
│     ↓                                                                   │
│  2. toolCall event received by your app                                │
│     { type: "toolCall", callId: "abc", name: "get_weather", args: ... }│
│     ↓                                                                   │
│  3. Your handler executes the tool                                     │
│     const result = await getWeather(args);                             │
│     ↓                                                                   │
│  4. Submit result back                                                 │
│     await session.submitToolResult(callId, result);                    │
│     ↓                                                                   │
│  5. Copilot continues (may call more tools or respond)                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Writing Good Tool Descriptions

Copilot uses descriptions to decide when and how to use tools. Good descriptions are critical.

Bad descriptions

// Too vague
description: "Gets stuff"

// Too technical
description: "Executes HTTP GET request to /api/v2/weather endpoint"

// Missing context
description: "Returns temperature"

Good descriptions

// Clear purpose
description: "Get the current weather conditions for a city"

// With constraints
description: "Search for products by name. Returns up to 10 results. Use for finding specific items."

// With guidance
description: "Execute a SQL query against the database. Only use for read operations. Prefer specific queries over SELECT *."

Parameter Descriptions

Each parameter should explain:

  • What it’s for
  • What format/values are valid
  • Any constraints
parameters: {
  type: "object",
  properties: {
    query: {
      type: "string",
      description: "SQL SELECT query. Must not modify data."
    },
    limit: {
      type: "number",
      description: "Maximum rows to return. Default 100, max 1000."
    },
    format: {
      type: "string",
      enum: ["json", "csv"],
      description: "Output format. Use 'csv' for large datasets."
    }
  }
}

Tool Results

Results should be structured for Copilot to understand:

// Good: Structured, includes context
return {
  city: "Tokyo",
  temperature: 22,
  units: "celsius",
  condition: "sunny",
  lastUpdated: "2024-01-15T10:30:00Z"
};

// Bad: Unstructured string
return "22 degrees sunny";

// For errors: Include error info
return {
  error: true,
  message: "City not found",
  suggestion: "Did you mean 'Kyoto'?"
};

Error Handling in Tools

async function handleToolCall(name: string, args: string) {
  try {
    const params = JSON.parse(args);

    if (name === "get_weather") {
      return await getWeather(params);
    }

    return { error: true, message: `Unknown tool: ${name}` };

  } catch (error) {
    // Return error to Copilot so it can recover
    return {
      error: true,
      message: error.message,
      details: "The tool encountered an error. Please try again or use different parameters."
    };
  }
}

Tool Patterns

Confirmation tools

For destructive actions, require confirmation:

const deleteFileTool = {
  name: "delete_file",
  description: "Delete a file. Requires user confirmation first.",
  parameters: {
    properties: {
      path: { type: "string" },
      confirmed: {
        type: "boolean",
        description: "Must be true. Ask user for confirmation before setting."
      }
    },
    required: ["path", "confirmed"]
  }
};

function deleteFile({ path, confirmed }) {
  if (!confirmed) {
    return { error: true, message: "Please confirm deletion with the user first" };
  }
  // Actually delete
}

Multi-step tools

Tools can guide Copilot through workflows:

// Step 1: Search
const searchTool = {
  name: "search_docs",
  description: "Search documentation. Use results to find relevant doc_id, then use get_doc to retrieve full content."
};

// Step 2: Retrieve
const getDocTool = {
  name: "get_doc",
  description: "Get full document content by doc_id (from search_docs results)"
};

Tools with side effects

Be explicit about what tools do:

const sendEmailTool = {
  name: "send_email",
  description: "Send an email. THIS ACTUALLY SENDS - verify recipient and content with user before calling."
};

MCP (Model Context Protocol) Tools

The SDK supports MCP servers, which provide pre-built tool sets:

const session = await client.createSession({
  mcpServers: [{
    name: "github",
    command: "npx",
    args: ["@github/mcp-server"]
  }]
});

MCP servers provide tools like:

  • GitHub operations (issues, PRs, repos)
  • File system access
  • Database connections
  • External API integrations

See MCP documentation for available servers.

Next: Events & Streaming