> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/agentclientprotocol/typescript-sdk/llms.txt
> Use this file to discover all available pages before exploring further.

# Simple Client Example

> A complete working example of an ACP Client implementation in TypeScript

## Overview

This example demonstrates how to build a functional ACP Client using the TypeScript SDK. The client spawns an agent as a subprocess, establishes a connection, manages sessions, and handles user interactions including permission requests.

## What This Example Demonstrates

<CardGroup cols={2}>
  <Card title="Agent Process Management" icon="terminal">
    Spawning and communicating with an agent subprocess
  </Card>

  <Card title="Connection Lifecycle" icon="plug">
    Initializing connections and negotiating capabilities
  </Card>

  <Card title="Permission Handling" icon="shield-check">
    Prompting users for approval on sensitive operations
  </Card>

  <Card title="Session Updates" icon="refresh">
    Receiving and displaying real-time agent updates
  </Card>
</CardGroup>

## Complete Code

Here's the full implementation from `src/examples/client.ts`:

```typescript theme={null}
#!/usr/bin/env node

import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { Writable, Readable } from "node:stream";
import readline from "node:readline/promises";

import * as acp from "@agentclientprotocol/sdk";

class ExampleClient implements acp.Client {
  async requestPermission(
    params: acp.RequestPermissionRequest,
  ): Promise<acp.RequestPermissionResponse> {
    console.log(`\n🔐 Permission requested: ${params.toolCall.title}`);

    console.log(`\nOptions:`);
    params.options.forEach((option, index) => {
      console.log(`   ${index + 1}. ${option.name} (${option.kind})`);
    });

    while (true) {
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });

      const answer = await rl.question("\nChoose an option: ");
      const trimmedAnswer = answer.trim();

      const optionIndex = parseInt(trimmedAnswer) - 1;
      if (optionIndex >= 0 && optionIndex < params.options.length) {
        return {
          outcome: {
            outcome: "selected",
            optionId: params.options[optionIndex].optionId,
          },
        };
      } else {
        console.log("Invalid option. Please try again.");
      }
    }
  }

  async sessionUpdate(params: acp.SessionNotification): Promise<void> {
    const update = params.update;

    switch (update.sessionUpdate) {
      case "agent_message_chunk":
        if (update.content.type === "text") {
          console.log(update.content.text);
        } else {
          console.log(`[${update.content.type}]`);
        }
        break;
      case "tool_call":
        console.log(`\n🔧 ${update.title} (${update.status})`);
        break;
      case "tool_call_update":
        console.log(
          `\n🔧 Tool call \`${update.toolCallId}\` updated: ${update.status}\n`,
        );
        break;
      case "plan":
      case "agent_thought_chunk":
      case "user_message_chunk":
        console.log(`[${update.sessionUpdate}]`);
        break;
      default:
        break;
    }
  }

  async writeTextFile(
    params: acp.WriteTextFileRequest,
  ): Promise<acp.WriteTextFileResponse> {
    console.error(
      "[Client] Write text file called with:",
      JSON.stringify(params, null, 2),
    );

    return {};
  }

  async readTextFile(
    params: acp.ReadTextFileRequest,
  ): Promise<acp.ReadTextFileResponse> {
    console.error(
      "[Client] Read text file called with:",
      JSON.stringify(params, null, 2),
    );

    return {
      content: "Mock file content",
    };
  }
}

async function main() {
  // Get the current file's directory to find agent.ts
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = dirname(__filename);
  const agentPath = join(__dirname, "agent.ts");

  // Spawn the agent as a subprocess via npx (npx.cmd on Windows) using tsx
  const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
  const agentProcess = spawn(npxCmd, ["tsx", agentPath], {
    stdio: ["pipe", "pipe", "inherit"],
  });

  // Create streams to communicate with the agent
  const input = Writable.toWeb(agentProcess.stdin!);
  const output = Readable.toWeb(
    agentProcess.stdout!,
  ) as ReadableStream<Uint8Array>;

  // Create the client connection
  const client = new ExampleClient();
  const stream = acp.ndJsonStream(input, output);
  const connection = new acp.ClientSideConnection((_agent) => client, stream);

  try {
    // Initialize the connection
    const initResult = await connection.initialize({
      protocolVersion: acp.PROTOCOL_VERSION,
      clientCapabilities: {
        fs: {
          readTextFile: true,
          writeTextFile: true,
        },
      },
    });

    console.log(
      `✅ Connected to agent (protocol v${initResult.protocolVersion})`,
    );

    // Create a new session
    const sessionResult = await connection.newSession({
      cwd: process.cwd(),
      mcpServers: [],
    });

    console.log(`📝 Created session: ${sessionResult.sessionId}`);
    console.log(`💬 User: Hello, agent!\n`);
    process.stdout.write(" ");

    // Send a test prompt
    const promptResult = await connection.prompt({
      sessionId: sessionResult.sessionId,
      prompt: [
        {
          type: "text",
          text: "Hello, agent!",
        },
      ],
    });

    console.log(`\n\n✅ Agent completed with: ${promptResult.stopReason}`);
  } catch (error) {
    console.error("[Client] Error:", error);
  } finally {
    agentProcess.kill();
    process.exit(0);
  }
}

main().catch(console.error);
```

## Code Walkthrough

<Steps>
  <Step title="Implement the Client Interface">
    Create a class that implements the `acp.Client` interface with all required methods:

    ```typescript theme={null}
    class ExampleClient implements acp.Client {
      async requestPermission(params: acp.RequestPermissionRequest) { /* ... */ }
      async sessionUpdate(params: acp.SessionNotification) { /* ... */ }
      async writeTextFile(params: acp.WriteTextFileRequest) { /* ... */ }
      async readTextFile(params: acp.ReadTextFileRequest) { /* ... */ }
    }
    ```
  </Step>

  <Step title="Handle Permission Requests">
    Implement interactive permission prompts for sensitive operations:

    ```typescript theme={null}
    async requestPermission(
      params: acp.RequestPermissionRequest,
    ): Promise<acp.RequestPermissionResponse> {
      console.log(`\n🔐 Permission requested: ${params.toolCall.title}`);

      // Display options to the user
      params.options.forEach((option, index) => {
        console.log(`   ${index + 1}. ${option.name} (${option.kind})`);
      });

      // Get user input
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
      const answer = await rl.question("\nChoose an option: ");
      const optionIndex = parseInt(answer.trim()) - 1;

      // Return the selected option
      return {
        outcome: {
          outcome: "selected",
          optionId: params.options[optionIndex].optionId,
        },
      };
    }
    ```
  </Step>

  <Step title="Handle Session Updates">
    Display real-time updates from the agent:

    ```typescript theme={null}
    async sessionUpdate(params: acp.SessionNotification): Promise<void> {
      const update = params.update;

      switch (update.sessionUpdate) {
        case "agent_message_chunk":
          // Display text responses
          if (update.content.type === "text") {
            console.log(update.content.text);
          }
          break;
        case "tool_call":
          // Show tool execution
          console.log(`\n🔧 ${update.title} (${update.status})`);
          break;
        case "tool_call_update":
          // Show tool status changes
          console.log(`\n🔧 Tool call \`${update.toolCallId}\` updated: ${update.status}\n`);
          break;
        // Handle other update types...
      }
    }
    ```
  </Step>

  <Step title="Implement File System Operations">
    Provide file system capabilities to the agent:

    ```typescript theme={null}
    async writeTextFile(
      params: acp.WriteTextFileRequest,
    ): Promise<acp.WriteTextFileResponse> {
      // In a real client, write the file to disk
      console.error("[Client] Write text file called with:", params);
      return {};
    }

    async readTextFile(
      params: acp.ReadTextFileRequest,
    ): Promise<acp.ReadTextFileResponse> {
      // In a real client, read from disk
      console.error("[Client] Read text file called with:", params);
      return { content: "Mock file content" };
    }
    ```
  </Step>

  <Step title="Spawn the Agent Process">
    Launch the agent as a subprocess:

    ```typescript theme={null}
    const agentPath = join(__dirname, "agent.ts");
    const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";

    const agentProcess = spawn(npxCmd, ["tsx", agentPath], {
      stdio: ["pipe", "pipe", "inherit"],
    });
    ```

    <Info>
      We use `npx tsx` to run TypeScript files directly. Adjust this based on your agent's runtime requirements.
    </Info>
  </Step>

  <Step title="Create Communication Streams">
    Set up bidirectional streams for client-agent communication:

    ```typescript theme={null}
    // Agent stdin for sending messages
    const input = Writable.toWeb(agentProcess.stdin!);

    // Agent stdout for receiving messages
    const output = Readable.toWeb(
      agentProcess.stdout!,
    ) as ReadableStream<Uint8Array>;

    // Create the protocol stream
    const stream = acp.ndJsonStream(input, output);
    ```
  </Step>

  <Step title="Initialize the Connection">
    Create the connection and negotiate capabilities:

    ```typescript theme={null}
    const client = new ExampleClient();
    const connection = new acp.ClientSideConnection(
      (_agent) => client,
      stream
    );

    const initResult = await connection.initialize({
      protocolVersion: acp.PROTOCOL_VERSION,
      clientCapabilities: {
        fs: {
          readTextFile: true,
          writeTextFile: true,
        },
      },
    });
    ```
  </Step>

  <Step title="Create a Session and Send Prompts">
    Start a new session and interact with the agent:

    ```typescript theme={null}
    // Create a new session
    const sessionResult = await connection.newSession({
      cwd: process.cwd(),
      mcpServers: [],
    });

    // Send a prompt
    const promptResult = await connection.prompt({
      sessionId: sessionResult.sessionId,
      prompt: [
        {
          type: "text",
          text: "Hello, agent!",
        },
      ],
    });

    console.log(`Agent completed with: ${promptResult.stopReason}`);
    ```
  </Step>

  <Step title="Cleanup">
    Properly terminate the agent process:

    ```typescript theme={null}
    try {
      // ... client operations
    } catch (error) {
      console.error("[Client] Error:", error);
    } finally {
      agentProcess.kill();
      process.exit(0);
    }
    ```
  </Step>
</Steps>

## Running the Example

### Prerequisites

Ensure you have the SDK installed:

```bash theme={null}
npm install @agentclientprotocol/sdk
```

### Run the Complete Example

```bash theme={null}
npx tsx src/examples/client.ts
```

This will:

1. Spawn the example agent
2. Initialize the connection
3. Create a session
4. Send a test prompt
5. Display the agent's response
6. Handle any permission requests

### Expected Output

```
✅ Connected to agent (protocol v1.0.0)
📝 Created session: a1b2c3d4e5f6...
💬 User: Hello, agent!

I'll help you with that. Let me start by reading some files...

🔧 Reading project files (pending)

🔧 Tool call `call_1` updated: completed

Now I understand the project structure...

🔧 Modifying critical configuration file (pending)

🔐 Permission requested: Modifying critical configuration file

Options:
   1. Allow this change (allow_once)
   2. Skip this change (reject_once)

Choose an option: 1

🔧 Tool call `call_2` updated: completed

Perfect! I've successfully updated the configuration...

✅ Agent completed with: end_turn
```

## Key Concepts

### Process Management

The client is responsible for:

* Spawning the agent process
* Managing stdio streams
* Handling process lifecycle
* Cleaning up on exit

### Bidirectional Communication

The client and agent communicate through:

* **Client → Agent**: Requests (initialize, newSession, prompt)
* **Agent → Client**: Notifications (sessionUpdate) and requests (requestPermission)

### Capability Negotiation

During initialization, the client declares its capabilities:

```typescript theme={null}
clientCapabilities: {
  fs: {
    readTextFile: true,   // Client can read files
    writeTextFile: true,  // Client can write files
  },
}
```

Agents can query these capabilities to determine what operations are available.

### Permission Model

The client controls what the agent can do:

* **Auto-approved**: Read operations, low-risk actions
* **User approval**: File modifications, deletions, sensitive operations
* **Blocked**: Operations not supported by the client

### Session Lifecycle

1. **Initialize**: Establish connection and exchange capabilities
2. **New Session**: Create isolated conversation context
3. **Prompt**: Send user requests and receive responses
4. **Updates**: Handle real-time agent notifications
5. **Cleanup**: Close session and terminate agent

## Platform Considerations

<Note>
  This example handles Windows compatibility by detecting the platform and using the appropriate npx command:

  ```typescript theme={null}
  const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
  ```
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Simple Agent Example" icon="robot" href="/examples/simple-agent">
    Build an agent that works with this client
  </Card>

  <Card title="API Reference" icon="book" href="https://agentclientprotocol.github.io/typescript-sdk/classes/ClientSideConnection.html">
    Explore the full ClientSideConnection API
  </Card>

  <Card title="Production Examples" icon="rocket" href="/examples/production-examples">
    See how production clients integrate ACP
  </Card>

  <Card title="Client Capabilities" icon="list-check" href="/concepts/agents-and-clients">
    Learn about all available client capabilities
  </Card>
</CardGroup>
