Skip to main content
The ACP TypeScript SDK allows you to extend the protocol with custom methods and notifications that aren’t part of the core specification. This is useful for:
  • Adding proprietary features to your agent or client
  • Implementing experimental capabilities
  • Integrating with custom systems

Overview

Both sides of the connection support extension methods:
  • Agent side: Implement extMethod and extNotification in your Agent implementation
  • Client side: Implement extMethod and extNotification in your Client implementation
Extension methods and notifications work just like standard protocol methods, using the same JSON-RPC 2.0 message format.

Agent Extension Methods

Agents can handle custom requests from clients by implementing the optional extMethod and extNotification methods:

Receiving Extension Methods

import * as acp from '@agentclientprotocol/acp';

class MyAgent implements acp.Agent {
  // ... standard methods ...
  
  /**
   * Handle custom requests from the client
   */
  async extMethod(
    method: string,
    params: Record<string, unknown>,
  ): Promise<Record<string, unknown>> {
    switch (method) {
      case 'example.com/get_agent_stats':
        return {
          uptime: process.uptime(),
          sessionsActive: this.sessions.size,
          version: '1.0.0',
        };
      
      case 'example.com/custom_command':
        const command = params.command as string;
        return { result: await this.runCustomCommand(command) };
      
      default:
        throw acp.RequestError.methodNotFound(method);
    }
  }
  
  /**
   * Handle custom notifications from the client
   */
  async extNotification(
    method: string,
    params: Record<string, unknown>,
  ): Promise<void> {
    switch (method) {
      case 'example.com/log_event':
        console.log('Client event:', params);
        break;
      
      case 'example.com/cache_invalidate':
        this.clearCache();
        break;
      
      default:
        // Silently ignore unknown notifications
        console.warn('Unknown notification:', method);
    }
  }
}

Sending Extension Methods (Agent to Client)

Agents can call custom methods on the client using AgentSideConnection:
class MyAgent implements acp.Agent {
  private connection: acp.AgentSideConnection;
  
  constructor(connection: acp.AgentSideConnection) {
    this.connection = connection;
  }
  
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Call a custom client method
    const response = await this.connection.extMethod(
      'example.com/get_workspace_info',
      { includeGitStatus: true }
    );
    
    console.log('Workspace info:', response);
    
    // Send a custom notification
    await this.connection.extNotification(
      'example.com/agent_thinking',
      { status: 'analyzing code' }
    );
    
    // ... rest of prompt handling ...
  }
}

Client Extension Methods

Clients can handle custom requests from agents similarly:

Receiving Extension Methods

import * as acp from '@agentclientprotocol/acp';

class MyClient implements acp.Client {
  // ... standard methods ...
  
  /**
   * Handle custom requests from the agent
   */
  async extMethod(
    method: string,
    params: Record<string, unknown>,
  ): Promise<Record<string, unknown>> {
    switch (method) {
      case 'example.com/get_workspace_info':
        return {
          path: process.cwd(),
          gitBranch: await this.getGitBranch(),
          openFiles: this.getOpenFiles(),
        };
      
      case 'example.com/show_notification':
        this.showNotification(params.message as string);
        return { shown: true };
      
      default:
        throw acp.RequestError.methodNotFound(method);
    }
  }
  
  /**
   * Handle custom notifications from the agent
   */
  async extNotification(
    method: string,
    params: Record<string, unknown>,
  ): Promise<void> {
    switch (method) {
      case 'example.com/agent_thinking':
        this.updateStatusBar(params.status as string);
        break;
      
      case 'example.com/telemetry':
        this.sendTelemetry(params);
        break;
      
      default:
        console.warn('Unknown notification:', method);
    }
  }
}

Sending Extension Methods (Client to Agent)

const connection = new acp.ClientSideConnection(
  (agent) => new MyClient(),
  stream
);

// Initialize and create session...

// Call a custom agent method
const stats = await connection.extMethod(
  'example.com/get_agent_stats',
  {}
);

console.log('Agent stats:', stats);

// Send a custom notification
await connection.extNotification(
  'example.com/log_event',
  { event: 'user_action', action: 'file_saved' }
);

Naming Conventions

To avoid conflicts with future protocol additions and other implementations, follow these naming conventions:

Use Reverse Domain Names

Prefix your extension methods with a reverse domain name:
// Good
'example.com/get_stats'
'mycompany.io/custom_feature'
'github.com/copilot/analyze'

// Bad (may conflict with future ACP methods)
'get_stats'
'custom_feature'
'analyze'

Use Descriptive Names

Method names should clearly indicate their purpose:
// Good
'example.com/workspace/get_git_status'
'example.com/editor/show_notification'
'example.com/telemetry/log_event'

// Less clear
'example.com/get'
'example.com/do_thing'
'example.com/update'

Type Safety with Extensions

For better type safety, define interfaces for your extension methods:
// Define request/response types
interface GetAgentStatsRequest {
  includeMemory?: boolean;
}

interface GetAgentStatsResponse {
  uptime: number;
  sessionsActive: number;
  version: string;
  memoryUsage?: {
    heapUsed: number;
    heapTotal: number;
  };
}

// Use them in your implementation
class MyAgent implements acp.Agent {
  async extMethod(
    method: string,
    params: Record<string, unknown>,
  ): Promise<Record<string, unknown>> {
    switch (method) {
      case 'example.com/get_agent_stats': {
        const req = params as GetAgentStatsRequest;
        const response: GetAgentStatsResponse = {
          uptime: process.uptime(),
          sessionsActive: this.sessions.size,
          version: '1.0.0',
        };
        
        if (req.includeMemory) {
          const mem = process.memoryUsage();
          response.memoryUsage = {
            heapUsed: mem.heapUsed,
            heapTotal: mem.heapTotal,
          };
        }
        
        return response;
      }
      
      default:
        throw acp.RequestError.methodNotFound(method);
    }
  }
}

Complete Example

Here’s a complete example showing bidirectional extension methods:
import * as acp from '@agentclientprotocol/acp';

class ExtendedAgent implements acp.Agent {
  private connection: acp.AgentSideConnection;
  
  constructor(connection: acp.AgentSideConnection) {
    this.connection = connection;
  }
  
  // Standard methods...
  async initialize(params: acp.InitializeRequest) { /* ... */ }
  async newSession(params: acp.NewSessionRequest) { /* ... */ }
  async authenticate(params: acp.AuthenticateRequest) { /* ... */ }
  
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Get workspace context using extension method
    const workspace = await this.connection.extMethod(
      'example.com/get_workspace',
      { includeGit: true }
    );
    
    // Process prompt with workspace context...
    
    return { stopReason: 'end_turn' };
  }
  
  // Extension methods
  async extMethod(
    method: string,
    params: Record<string, unknown>,
  ): Promise<Record<string, unknown>> {
    if (method === 'example.com/get_capabilities') {
      return {
        supportsStreaming: true,
        supportsMultiModal: true,
        customFeatures: ['workspace-analysis', 'git-integration'],
      };
    }
    throw acp.RequestError.methodNotFound(method);
  }
}

Best Practices

  1. Always use namespaced method names to avoid conflicts
  2. Document your extensions clearly for users of your implementation
  3. Handle unknown methods gracefully by throwing RequestError.methodNotFound
  4. Version your extension methods if you expect them to change
  5. Consider backward compatibility when modifying extension method behavior

Build docs developers (and LLMs) love