# How to Test Applications Integrated With the Copilot SDK

> Easily test apps using the Copilot SDK. Learn to validate JSON-RPC interactions with a Node.js mock MCP server and YAML snapshot testing, bypassing the real Copilot CLI.

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

---

**Test applications integrated with the Copilot SDK using the Node.js mock MCP server and YAML snapshot testing to validate JSON-RPC interactions without requiring the real Copilot CLI binary.**

The github/copilot-sdk repository provides a unified testing strategy for applications that integrate with the Copilot SDK. Because the SDK communicates via a JSON-RPC protocol, the repository ships with a language-agnostic test harness that mocks the Copilot CLI server, enabling deterministic unit and integration tests across Node.js, Python, Go, .NET, Java, and Rust.

## Understanding the Test Harness Architecture

The Copilot SDK testing strategy centers on a record-and-replay mechanism that intercepts JSON-RPC traffic between your application and a mock server.

### The Mock MCP Server

At the core of the testing infrastructure is the Node.js-based mock server defined in [`test/harness/server.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/server.ts). This lightweight implementation simulates the Copilot CLI's Multi-Modal Copilot (MCP) protocol, responding to standard JSON-RPC methods such as `initialize`, `tools/list`, and `session/send`.

When you invoke `startMockMcpServer()`, the harness spawns a mock server on a random free port. Your test then injects this URL into the SDK client via the `endpoint` parameter or environment variables, isolating your application from real Copilot services.

### Snapshot-Driven Validation

The harness implements record-and-replay testing through YAML snapshot files stored in `test/snapshots/`. These files define sequences of expected JSON-RPC requests and responses. During test execution, the harness compares actual traffic against these snapshots using `assertSnapshotMatches`, failing the test if any request parameter or response body diverges from the recorded baseline.

This approach ensures that breaking changes in the SDK's public contract are caught immediately. For example, the snapshot at [`test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml`](https://github.com/github/copilot-sdk/blob/main/test/snapshots/tools/should_execute_multiple_custom_tools_in_parallel_single_turn.yaml) validates that custom tools execute correctly during parallel invocations.

### Cross-Language Reusability

All language-specific SDKs reuse the same Node.js harness, creating a uniform testing experience across the monorepo. Whether you are testing a TypeScript application with Vitest or a Python module with pytest, the underlying mock server and snapshot format remain identical. This consistency is enforced by each SDK's CI script, which boots the harness (`cd test/harness && npm ci`) before executing language-specific tests.

## Setting Up the Test Harness

Before writing tests, install the harness dependencies once:

```bash
cd test/harness && npm ci

```

This installs the mock server and utilities required by all subsequent tests. The harness exports configuration from [`test/harness/vitest.config.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/vitest.config.ts) for Node.js projects, while other languages import the running server via setup hooks.

## Writing Tests for Copilot SDK Applications

The following patterns demonstrate how to test applications integrated with the Copilot SDK using the provided harness.

### Basic Session Testing

Create a test that initializes a session, sends a prompt, and verifies the mock server's response:

```typescript
import { createCopilotSession } from '@github/copilot-sdk';
import { expect } from 'vitest';
import { startMockMcpServer } from '../test/harness/server';

test('session sends a prompt and receives a response', async () => {
  // Start the mock MCP server
  const { url, close } = await startMockMcpServer();

  // Create a client pointing at the mock server
  const session = await createCopilotSession({
    endpoint: url,
    tools: [],
    permissionHandler: () => true,
  });

  // Send a prompt
  const reply = await session.send({ prompt: 'Hello, world!' });

  // Verify the mock response
  expect(reply).toContain('Hello, world!');

  // Cleanup
  await session.dispose();
  await close();
});

```

This pattern appears throughout the Node.js test suite and demonstrates the standard lifecycle: spawn server, inject endpoint, execute logic, assert results, dispose resources.

### Testing Custom Tools and Permission Handlers

Validate that your permission handling logic correctly allows or denies tool execution:

```typescript
import { createCopilotSession, defineTool } from '@github/copilot-sdk';
import { expect } from 'vitest';
import { startMockMcpServer } from '../test/harness/server';

test('custom tool execution respects permission handler', async () => {
  const { url, close } = await startMockMcpServer();

  const myTool = defineTool({
    name: 'echo',
    description: 'Echoes back the supplied string',
    parameters: { type: 'object', properties: { text: { type: 'string' } } },
    handler: async ({ text }) => ({ result: text }),
  });

  const session = await createCopilotSession({
    endpoint: url,
    tools: [myTool],
    permissionHandler: () => false, // Deny all tools
  });

  const result = await session.send({ prompt: 'Please echo "test".' });

  expect(result).toMatch(/denied/i);

  await session.dispose();
  await close();
});

```

The snapshot test [`test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml`](https://github.com/github/copilot-sdk/blob/main/test/snapshots/tools/denies_custom_tool_when_permission_denied.yaml) provides the baseline for this validation, ensuring consistent behavior across SDK versions.

### Using Utility Helpers

The harness provides platform-agnostic utilities in [`test/harness/util.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/util.ts) for asynchronous test patterns:

```typescript
import { iife, sleep, ShellConfig } from '../test/harness/util';

iife(async () => {
  console.log('Running on shell:', ShellConfig.bash.shellToolName);
  await sleep(500);
});

```

These helpers include `iife` for immediately invoked async functions, `sleep` for timed delays, and `ShellConfig` for cross-platform shell compatibility.

## Running Tests Across Language SDKs

Each language SDK follows the same workflow: boot the harness, then execute the native test suite.

**Node.js / TypeScript:**

```bash
cd nodejs && npm test

```

**Python:**

```bash
cd python && uv run pytest

```

**Go:**

```bash
cd go && go test ./...

```

**.NET:**

```bash
cd dotnet && dotnet test test/GitHub.Copilot.SDK.Test.csproj

```

**Java:**

```bash
cd java && mvn verify -Dskip.test.harness=true

```

As documented in [`CONTRIBUTING.md`](https://github.com/github/copilot-sdk/blob/main/CONTRIBUTING.md) (lines 42-90), these commands share the underlying mock server implementation, ensuring consistent behavior validation across all supported languages.

## Key Testing Files and References

Understanding the repository structure helps navigate the testing codebase:

- [`test/harness/server.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/server.ts) – Mock MCP server implementing JSON-RPC methods
- [`test/harness/util.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/util.ts) – Async helpers (`iife`, `sleep`) and shell configuration
- [`test/harness/vitest.config.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/vitest.config.ts) – Vitest configuration for Node.js tests
- `test/snapshots/**/*.yaml` – Record-and-replay test definitions
- [`CONTRIBUTING.md`](https://github.com/github/copilot-sdk/blob/main/CONTRIBUTING.md) – Cross-language testing workflow documentation
- [`docs/troubleshooting/mcp-debugging.md`](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/mcp-debugging.md) – Manual MCP server debugging guide

## Summary

- **Test applications integrated with the Copilot SDK** using the Node.js mock MCP server to eliminate dependencies on the real Copilot CLI binary.
- The harness validates JSON-RPC interactions through YAML snapshots stored in `test/snapshots/`, enabling record-and-replay testing.
- All language SDKs (Node, Python, Go, .NET, Java, Rust) reuse the same harness, ensuring uniform testing across the monorepo.
- Use `startMockMcpServer()` to spawn isolated mock servers, inject the endpoint URL into `createCopilotSession()`, and assert against expected snapshots.
- Run language-specific commands (`npm test`, `uv run pytest`, `go test ./...`) after booting the harness with `cd test/harness && npm ci`.

## Frequently Asked Questions

### Do I need the Copilot CLI installed to run tests?

No. The [`test/harness/server.ts`](https://github.com/github/copilot-sdk/blob/main/test/harness/server.ts) mock server implements the complete JSON-RPC protocol required by the SDK, allowing you to test applications integrated with the Copilot SDK without installing or launching the real Copilot CLI binary. This isolation makes tests fast, deterministic, and CI-friendly.

### How do I add new test cases for custom behavior?

Create a new YAML snapshot file in `test/snapshots/` describing the expected JSON-RPC request and response sequence. The harness will automatically compare live traffic against your snapshot using `assertSnapshotMatches`. You can also write imperative tests using `startMockMcpServer()` directly for dynamic behavior that doesn't fit the snapshot pattern.

### Can I use the test harness for end-to-end testing?

Yes. While primarily designed for unit and integration tests, the harness supports end-to-end scenarios by simulating complex interaction patterns such as permission handler rejections, tool-call errors, and custom agent reloads. The snapshot files under `test/snapshots/hooks/` and `test/snapshots/elicitation/` demonstrate advanced use cases.

### Why does the harness use YAML snapshots instead of JSON?

YAML snapshots provide better human readability for the complex nested structures of JSON-RPC messages while maintaining strict parsing compatibility. The format allows developers to easily review diffs when the protocol changes, and the `assertSnapshotMatches` utility processes YAML files to validate exact request/response matching.