How to Bundle Skills, Hooks, MCP Servers, and Agents as a Plugin Directory in the Copilot SDK
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 manifest, 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 manifest as a plugin root and parses its contents to register contributions.
The Manifest File (plugin.json)
The 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#L105-L115), a minimal manifest looks like this:
{
"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 files in the plugin root or subdirectories and reference them in 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#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'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#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#L2481) as pluginDirectories?: string[].
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#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#L1766) stores these handlers for runtime invocation.
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#L336-L344):
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:
my-copilot-plugin/
├── plugin.json
├── SKILL.md
├── dist/
│ └── mcp-server.js
└── .github/
└── hooks/
└── preToolUse.js
plugin.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:
# Database Query Tool
Use this tool to execute SQL queries against the application database.
Requires read-only access.
.github/hooks/preToolUse.js:
module.exports = async function(params) {
console.log('Validating tool use:', params.toolName);
return { allowed: true };
};
Initialization:
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.jsonmanifest at its root to declare metadata, MCP server entry points, and skill paths. - Skills are defined in
SKILL.mdmarkdown files and registered automatically when the directory is scanned. - Hooks reside under
.github/hooks/and execute via thehooks.invokeRPC method, or can be provided as callbacks toCopilotClient. - MCP servers are spawned as separate processes and expose agents accessible through the
rpc.mcpnamespace. - 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 manifest file at its root. Optionally, it can include 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 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/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#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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →