# How to Bundle Skills, Hooks, MCP Servers, and Agents as a Plugin Directory in the Copilot SDK

> Learn to bundle skills, hooks, MCP servers, and agents into a plugin directory for the Copilot SDK. Master self-contained Copilot SDK plugin creation for enhanced functionality.

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

---

**A Copilot SDK plugin directory is a self-contained bundle that contributes skills, lifecycle hooks, MCP servers, and agents to a Copilot session by placing a [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) manifest, [`SKILL.md`](https://github.com/github/copilot-sdk/blob/main/SKILL.md) files, hook scripts under `.github/hooks/`, and MCP entry points in a single folder that the SDK scans at startup.**

The GitHub Copilot SDK enables developers to extend AI capabilities through a modular plugin architecture. When you bundle skills, hooks, MCP servers, and agents as a plugin directory in the Copilot SDK, you create a portable extension that the runtime discovers, validates, and loads automatically during session initialization.

## Plugin Directory Structure and Components

A valid plugin directory follows a convention-based layout. The SDK treats any folder containing a [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) manifest as a plugin root and parses its contents to register contributions.

### The Manifest File (plugin.json)

The [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) file serves as the entry point. It declares the plugin metadata and enumerates its capabilities. According to the test fixtures in [[`nodejs/test/e2e/rpc_server_plugins.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_server_plugins.e2e.test.ts#L105-L115), a minimal manifest looks like this:

```json
{
  "name": "my-awesome-plugin",
  "description": "Demo plugin exposing a skill and an MCP server",
  "version": "0.1.0",
  "mcp": {
    "entry": "dist/mcp-server.js"
  },
  "skills": [
    { "path": "SKILL.md" }
  ]
}

```

The `mcp.entry` field points to the Model Control Protocol server executable, while the `skills` array references markdown files describing tool capabilities.

### Skills via SKILL.md Files

Skills are declarative capabilities written in markdown. The SDK extracts these definitions during plugin scanning and adds them to the session’s skill cache, making them selectable by the model. Place [`SKILL.md`](https://github.com/github/copilot-sdk/blob/main/SKILL.md) files in the plugin root or subdirectories and reference them in [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json).

### Lifecycle Hooks in .github/hooks/

Hooks provide callback-based interception points for tool execution lifecycle events. Store hook scripts inside `.github/hooks/` within your plugin directory. When the session starts, the SDK loads these scripts and exposes them through the JSON-RPC `hooks.invoke` method, as implemented in the generated RPC surface at [[`nodejs/src/generated/rpc.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/src/generated/rpc.ts#L15491).

### MCP Servers and Agents

MCP servers implement the Model Control Protocol (a JSON-RPC surface) and run as separate processes. The SDK spawns the process defined in [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json)'s `mcp.entry` field and registers its endpoint as an agent accessible via `rpc.mcp.*` calls. The end-to-end tests in [[`nodejs/test/e2e/rpc_mcp_and_skills.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_mcp_and_skills.e2e.test.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_mcp_and_skills.e2e.test.ts#L284-L290) validate that these servers are discovered and their exposed skills are usable.

## Loading Plugin Directories at Runtime

To activate plugins, pass the `pluginDirectories` option when constructing the `CopilotClient`. This option is defined in [[`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts#L2481) as `pluginDirectories?: string[]`.

```typescript
import { CopilotClient } from '@github/copilot-sdk';
import { join } from 'path';

const client = new CopilotClient({
  pluginDirectories: [join(__dirname, 'my-plugin')],
});

await client.start();

```

The client initialization logic in [[`nodejs/src/client.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts#L1487) forwards these paths to the session, which then scans each directory, validates manifests, and starts MCP processes.

## Hook Registration and Execution

Hooks can be provided either as filesystem scripts in `.github/hooks/` or as callback functions during client initialization. The `Session.registerHooks` method in [[`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts#L1766) stores these handlers for runtime invocation.

```typescript
const client = new CopilotClient({
  pluginDirectories: [join(__dirname, 'my-plugin')],
  hooks: {
    onPreToolUse: async (params) => {
      console.log('Intercepting tool use:', params);
      return { allowed: true };
    },
    onPostToolUse: async (result) => {
      console.log('Tool completed:', result);
    },
  },
});

```

The runtime invokes these handlers at lifecycle points defined by the SDK, forwarding events through the `hooks.invoke` RPC method.

## Runtime Reloading and Updates

After installing, updating, or removing a plugin, you can refresh the plugin set without tearing down the session. Call `session.rpc.plugins.reload()` to re-scan directories, restart MCP servers, and reload hooks. This functionality is demonstrated in [[`nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts#L336-L344):

```typescript
const session = await client.createSession();

// Install or modify plugins in the filesystem
// ...

// Reload to pick up changes
await session.rpc.plugins.reload();

// Verify new skills are available
const { skills } = await session.rpc.skills.list();
console.log('Updated skills:', skills.map(s => s.name));

```

## Complete Plugin Directory Example

Here is a fully functional plugin layout that bundles all four contribution types:

```text
my-copilot-plugin/
├── plugin.json
├── SKILL.md
├── dist/
│   └── mcp-server.js
└── .github/
    └── hooks/
        └── preToolUse.js

```

**plugin.json:**

```json
{
  "name": "my-copilot-plugin",
  "description": "Bundle with skills, hooks, and MCP server",
  "version": "1.0.0",
  "mcp": {
    "entry": "dist/mcp-server.js"
  },
  "skills": [
    { "path": "SKILL.md" }
  ]
}

```

**SKILL.md:**

```markdown

# Database Query Tool

Use this tool to execute SQL queries against the application database.
Requires read-only access.

```

**.github/hooks/preToolUse.js:**

```javascript
module.exports = async function(params) {
  console.log('Validating tool use:', params.toolName);
  return { allowed: true };
};

```

**Initialization:**

```typescript
import { CopilotClient } from '@github/copilot-sdk';
import { join } from 'path';

const client = new CopilotClient({
  pluginDirectories: [join(__dirname, 'my-copilot-plugin')],
});

await client.start();
const session = await client.createSession();

// List available contributions
const { skills } = await session.rpc.skills.list();
console.log('Loaded skills:', skills);

```

## Summary

- A plugin directory requires a [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) manifest at its root to declare metadata, MCP server entry points, and skill paths.
- Skills are defined in [`SKILL.md`](https://github.com/github/copilot-sdk/blob/main/SKILL.md) markdown files and registered automatically when the directory is scanned.
- Hooks reside under `.github/hooks/` and execute via the `hooks.invoke` RPC method, or can be provided as callbacks to `CopilotClient`.
- MCP servers are spawned as separate processes and expose agents accessible through the `rpc.mcp` namespace.
- Use `session.rpc.plugins.reload()` after filesystem changes to refresh plugins without restarting the client, as validated in the SDK's end-to-end tests.

## Frequently Asked Questions

### What file structure is required for a Copilot SDK plugin directory?

A valid plugin directory must contain a [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) manifest file at its root. Optionally, it can include [`SKILL.md`](https://github.com/github/copilot-sdk/blob/main/SKILL.md) files for skills, an `.github/hooks/` subdirectory containing hook scripts, and an MCP server entry point referenced by the manifest. The SDK scans these paths recursively during session initialization.

### How do I reload plugins after making changes to the code?

Call `await session.rpc.plugins.reload()` to trigger a re-scan of all `pluginDirectories`. This restarts MCP server processes, re-reads [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) manifests, and reloads hook scripts without requiring you to destroy and recreate the client session. The reload functionality is tested in [[`rpc_session_state_extras.e2e.test.ts`](https://github.com/github/copilot-sdk/blob/main/rpc_session_state_extras.e2e.test.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/test/e2e/rpc_session_state_extras.e2e.test.ts#L336-L344).

### Can I provide hooks as JavaScript functions instead of files?

Yes. While you can place hook scripts in `.github/hooks/`, you can also pass callback functions directly to the `CopilotClient` constructor via the `hooks` option. These are registered internally via `Session.registerHooks` in [[`nodejs/src/session.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts)](https://github.com/github/copilot-sdk/blob/main/nodejs/src/session.ts#L1766) and invoked alongside filesystem-based hooks.

### How does the SDK discover MCP servers inside a plugin?

The SDK reads the `mcp.entry` field from [`plugin.json`](https://github.com/github/copilot-sdk/blob/main/plugin.json) to locate the server executable. It spawns this process and establishes a JSON-RPC connection, registering the server as an agent. The server then becomes available for `rpc.mcp.*` calls, allowing the model to interact with tools exposed by the MCP protocol.