Creating Your First Client | Agent Tool Protocol
Skip to main content

Creating Your First Client

Learn how to create and configure an Agent Tool Protocol client to connect to servers and execute code.

Basic Client Setup

Minimal Client

The simplest client requires just the server URL:

import { AgentToolProtocolClient } from '@mondaydotcomorg/atp-client';

const client = new AgentToolProtocolClient({
baseUrl: 'http://localhost:3333',
});

// Initialize the client session
await client.init();

// Connect and discover APIs
await client.connect();

// Execute code
const result = await client.execute(`
return "Hello from ATP!";
`);

console.log(result.result); // "Hello from ATP!"

Client with Configuration

For production use with authentication and custom services:

import { AgentToolProtocolClient } from '@mondaydotcomorg/atp-client';

const client = new AgentToolProtocolClient({
baseUrl: process.env.ATP_SERVER_URL,
headers: {
'x-api-key': process.env.ATP_API_KEY,
'Authorization': `Bearer ${process.env.ATP_TOKEN}`,
},
hooks: {
preRequest: async (context) => {
// Refresh token before each request if needed
const token = await refreshTokenIfNeeded();
return {
headers: {
...context.currentHeaders,
Authorization: `Bearer ${token}`,
},
};
},
},
});

Initialization

Client Initialization

// Initialize with client info
const initResult = await client.init({
name: 'my-agent',
version: '1.0.0',
environment: 'production',
});

console.log('Client ID:', initResult.clientId);
console.log('Token:', initResult.token);
console.log('Expires at:', new Date(initResult.expiresAt));

Connecting to the Server

// Connect and discover available APIs
const connectResult = await client.connect({
apiGroups: ['users', 'products'], // Optional: filter API groups
});

console.log('Server version:', connectResult.serverVersion);
console.log('Available API groups:', connectResult.apiGroups);

Executing Code

Basic Execution

const result = await client.execute(`
// Your TypeScript code here
const data = await users.getUser({ userId: '123' });
return data;
`);

if (result.status === 'completed') {
console.log('Result:', result.result);
} else {
console.error('Error:', result.error);
}

Execution with Configuration

const result = await client.execute(
`
const user = await users.getUser({ userId: '123' });
const orders = await orders.getUserOrders({ userId: user.id });
return { user, orders };
`,
{
timeout: 60000, // 60 seconds
maxMemory: 256 * 1024 * 1024, // 256 MB
maxLLMCalls: 20,
allowedAPIs: ['users', 'orders'], // Restrict to specific APIs
}
);

Handling Results

const result = await client.execute(code);

switch (result.status) {
case 'completed':
console.log('Success:', result.result);
break;

case 'failed':
console.error('Execution failed:', result.error?.message);
if (result.error?.retryable) {
console.log('Error is retryable');
}
break;

case 'timeout':
console.error('Execution timed out');
break;

case 'memory_exceeded':
console.error('Memory limit exceeded');
break;

case 'paused':
console.log('Execution paused, needs callback');
// Handle pause (see Pause/Resume section)
break;
}

// Check execution statistics
console.log('Duration:', result.stats.duration, 'ms');
console.log('Memory used:', result.stats.memoryUsed, 'bytes');
console.log('LLM calls:', result.stats.llmCallsCount);

Providing Services

LLM Integration

Provide your own LLM for the server to use during execution:

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});

// Provide LLM to the client
client.provideLLM({
async call(prompt, options) {
const response = await anthropic.messages.create({
model: options?.model || 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
temperature: options?.temperature || 0.7,
system: options?.systemPrompt,
messages: [
{ role: 'user', content: prompt },
],
});

return response.content[0].text;
},

async extract(prompt, schema, options) {
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: [
{ role: 'user', content: prompt },
],
tools: [{
name: 'extract',
description: 'Extract structured data',
input_schema: schema,
}],
});

return response.content[0].input;
},

async classify(text, categories, options) {
const prompt = `Classify the following text into one of these categories: ${categories.join(', ')}\n\nText: ${text}`;
const result = await this.call(prompt, options);
return result.trim();
},
});

Approval Handler

Implement human-in-the-loop approval:

client.provideApproval({
async request(message, context) {
console.log('Approval requested:', message);
console.log('Context:', context);

// In a real app, show UI to user and wait for approval
const approved = await showApprovalDialog(message, context);

return {
approved,
response: approved ? 'Approved by user' : 'Rejected by user',
timestamp: Date.now(),
};
},
});

Embedding Provider

Provide embeddings for semantic search:

import { OpenAI } from 'openai';

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

client.provideEmbedding({
async embed(text) {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text,
});

return response.data[0].embedding;
},

async similarity(text1, text2) {
const [embedding1, embedding2] = await Promise.all([
this.embed(text1),
this.embed(text2),
]);

// Cosine similarity
return cosineSimilarity(embedding1, embedding2);
},
});

Client Tools

Register tools that execute on the client side:

import { ToolOperationType, ToolSensitivityLevel } from '@mondaydotcomorg/atp-client';

// Define client tools
const clientTools = [
{
name: 'readLocalFile',
namespace: 'filesystem',
description: 'Read a file from the local filesystem',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path' },
},
required: ['path'],
},
handler: async ({ path }) => {
const fs = await import('fs/promises');
return await fs.readFile(path, 'utf-8');
},
metadata: {
operationType: ToolOperationType.READ,
sensitivityLevel: ToolSensitivityLevel.INTERNAL,
},
},
{
name: 'writeLocalFile',
namespace: 'filesystem',
description: 'Write content to a local file',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string' },
},
required: ['path', 'content'],
},
handler: async ({ path, content }) => {
const fs = await import('fs/promises');
await fs.writeFile(path, content, 'utf-8');
return { success: true };
},
metadata: {
operationType: ToolOperationType.WRITE,
sensitivityLevel: ToolSensitivityLevel.INTERNAL,
requiresApproval: true,
},
},
];

// Register tools with client
client.provideTools(clientTools);

// Re-initialize to register tools with server
await client.init();

Now the server code can call these client tools:

const result = await client.execute(`
// Read from local filesystem (runs on client)
const config = await filesystem.readLocalFile({ path: './config.json' });

// Process with server API
const processed = await api.processConfig({ config });

// Write result back (runs on client)
await filesystem.writeLocalFile({
path: './output.json',
content: JSON.stringify(processed)
});

return processed;
`);

API Discovery

Searching APIs

// Search for user-related APIs
const results = await client.searchAPI('find user by email', {
apiGroups: ['users'],
maxResults: 10,
useEmbeddings: true,
});

results.forEach(result => {
console.log(`${result.functionName}: ${result.description}`);
console.log(`Relevance: ${result.relevanceScore}`);
});

Exploring APIs

// Explore available APIs like a file system
const root = await client.exploreAPI('/');
console.log('Available API groups:', root.items);

// Explore a specific API group
const usersAPI = await client.exploreAPI('/users');
console.log('User API functions:', usersAPI.items);

// Get details about a specific function
const getUser = await client.exploreAPI('/users/getUser');
console.log('Function definition:', getUser.definition);

Type Definitions

Generate TypeScript types for available APIs:

const typeDefinitions = client.getTypeDefinitions();

// Write to file for IDE support
import fs from 'fs/promises';
await fs.writeFile('./atp-types.d.ts', typeDefinitions);

Example generated types:

declare namespace users {
function getUser(params: { userId: string }): Promise<User>;
function createUser(params: { name: string; email: string }): Promise<User>;
}

declare namespace products {
function getProduct(params: { productId: string }): Promise<Product>;
function listProducts(params: { limit?: number }): Promise<Product[]>;
}

Pause and Resume

Handling Paused Execution

let result = await client.execute(`
// This will pause execution
const response = await llm.call({ prompt: "What is 2+2?" });
return response;
`);

// Check if execution paused
if (result.status === 'paused' && result.needsCallback) {
console.log('Execution paused for:', result.needsCallback.type);

// Provide the callback result and resume
const resumeResult = await client.resume(
result.executionId,
{ result: "4" } // LLM response
);

console.log('Final result:', resumeResult.result);
}

Batch Callbacks

Handle multiple callbacks in parallel:

let result = await client.execute(`
// Multiple LLM calls that need to be batched
const [response1, response2, response3] = await Promise.all([
llm.call({ prompt: "What is 2+2?" }),
llm.call({ prompt: "What is 3+3?" }),
llm.call({ prompt: "What is 4+4?" }),
]);

return { response1, response2, response3 };
`);

if (result.needsCallbacks) {
// Process all callbacks in parallel
const results = await Promise.all(
result.needsCallbacks.map(async (callback) => {
const llmResult = await myLLM.call(callback.payload.prompt);
return {
id: callback.id,
result: llmResult,
};
})
);

// Resume with batch results
const finalResult = await client.resumeBatch(
result.executionId,
results
);

console.log('Final result:', finalResult.result);
}

Error Handling

Retry Logic

async function executeWithRetry(code: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const result = await client.execute(code);

if (result.status === 'completed') {
return result;
}

if (result.error?.retryable) {
console.log(`Retry ${i + 1}/${maxRetries}`);
await sleep(1000 * (i + 1)); // Exponential backoff
continue;
}

throw new Error(`Execution failed: ${result.error?.message}`);
}

throw new Error('Max retries exceeded');
}

Error Recovery

try {
const result = await client.execute(code);

if (result.status === 'failed') {
if (result.error?.code === 'TIMEOUT_ERROR') {
// Increase timeout and retry
return await client.execute(code, { timeout: 120000 });
}

if (result.error?.code === 'MEMORY_LIMIT_EXCEEDED') {
// Increase memory and retry
return await client.execute(code, { maxMemory: 512 * 1024 * 1024 });
}

throw new Error(result.error.message);
}

return result;
} catch (error) {
console.error('Execution error:', error);
throw error;
}

Token Management

Automatic Token Rotation

// Tokens are automatically rotated based on server config
const client = new AgentToolProtocolClient({
baseUrl: 'http://localhost:3333',
});

await client.init();

// Token will be automatically refreshed when needed
setInterval(async () => {
await client.execute(`return Date.now();`);
}, 60000); // Execute every minute

Manual Token Refresh

// Get current client ID
const clientId = client.getClientId();

// Re-initialize to get a new token
await client.init({ name: 'my-agent' });

Next Steps

Best Practices

Connection Management

// Create client once and reuse
const client = new AgentToolProtocolClient({
baseUrl: process.env.ATP_SERVER_URL,
});

// Initialize on startup
await client.init();
await client.connect();

// Reuse for all executions
async function executeCode(code: string) {
return await client.execute(code);
}

Error Logging

client.on('error', (error) => {
console.error('Client error:', error);

// Log to external service
logToSentry(error);
});

Graceful Shutdown

process.on('SIGTERM', async () => {
console.log('Shutting down client...');

// Wait for pending executions
await client.waitForPendingExecutions();

// Close connection
await client.disconnect();

process.exit(0);
});

Troubleshooting

Connection Issues

// Test connection
try {
await client.connect();
console.log('Connected successfully');
} catch (error) {
console.error('Connection failed:', error.message);

// Check if server is running
// Verify URL and credentials
// Check network connectivity
}

Execution Timeouts

// Increase timeout for long-running operations
const result = await client.execute(code, {
timeout: 300000, // 5 minutes
});

Memory Issues

// Monitor memory usage
const result = await client.execute(code);
console.log('Memory used:', result.stats.memoryUsed / 1024 / 1024, 'MB');

// Increase if needed
const result2 = await client.execute(code, {
maxMemory: 512 * 1024 * 1024, // 512 MB
});
Agent Tool Protocol | ATP - Code Execution for AI Agents