Skip to main content

Custom Tools

Build custom tools to connect agents to any API, database, or service.

Tool Types

TypeBest ForExecution
HTTPREST APIsServer-side HTTP call
JavaScriptCustom logicSandboxed JS execution
WebhookExternal processingCallback to your server
DatabaseSQL queriesDirect DB connection

Creating HTTP Tools

Connect to any REST API:

const tool = await client.tools.create({
name: 'weather_lookup',
slug: 'weather-lookup',
description: 'Get current weather for a location',
category: 'api',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name'
},
units: {
type: 'string',
enum: ['metric', 'imperial'],
default: 'metric'
}
},
required: ['city']
},
executionType: 'http',
executionConfig: {
url: 'https://api.weather.com/v1/current',
method: 'GET',
headers: {
'Authorization': 'Bearer ${secrets.WEATHER_API_KEY}',
'Content-Type': 'application/json'
},
queryParams: {
q: '${city}',
units: '${units}'
}
},
secretsRequired: ['WEATHER_API_KEY']
});

HTTP Configuration Options

executionConfig: {
// URL with variable substitution
url: 'https://api.example.com/users/${userId}',

// HTTP method
method: 'POST', // GET, POST, PUT, PATCH, DELETE

// Headers
headers: {
'Authorization': 'Bearer ${secrets.API_KEY}',
'X-Custom-Header': '${customValue}'
},

// Query parameters
queryParams: {
filter: '${filter}',
limit: '${limit}'
},

// Request body (for POST/PUT/PATCH)
body: {
name: '${name}',
email: '${email}',
data: '${data}'
},

// Body type
bodyType: 'json', // json, form, raw

// Timeout in ms
timeout: 30000,

// Follow redirects
followRedirects: true,

// Response handling
responseType: 'json', // json, text, binary

// Extract specific field from response
responseMapping: {
result: '$.data.result',
status: '$.meta.status'
}
}

Creating JavaScript Tools

For custom logic that doesn't fit HTTP calls:

const tool = await client.tools.create({
name: 'data_transformer',
description: 'Transform and validate data',
category: 'javascript',
inputSchema: {
type: 'object',
properties: {
data: {
type: 'array',
description: 'Array of items to transform'
},
operation: {
type: 'string',
enum: ['sum', 'average', 'filter', 'sort', 'group']
},
options: {
type: 'object',
description: 'Operation-specific options'
}
},
required: ['data', 'operation']
},
executionType: 'javascript',
executionConfig: {
code: `
const { data, operation, options = {} } = input;

switch (operation) {
case 'sum':
const field = options.field || 'value';
return {
result: data.reduce((sum, item) => sum + (item[field] || 0), 0)
};

case 'average':
const avgField = options.field || 'value';
const total = data.reduce((sum, item) => sum + (item[avgField] || 0), 0);
return {
result: data.length > 0 ? total / data.length : 0
};

case 'filter':
return {
result: data.filter(item => {
for (const [key, value] of Object.entries(options.conditions || {})) {
if (item[key] !== value) return false;
}
return true;
})
};

case 'sort':
const sortField = options.field || 'id';
const direction = options.direction || 'asc';
return {
result: [...data].sort((a, b) => {
const aVal = a[sortField];
const bVal = b[sortField];
return direction === 'asc' ? aVal - bVal : bVal - aVal;
})
};

case 'group':
const groupField = options.field || 'category';
return {
result: data.reduce((groups, item) => {
const key = item[groupField] || 'unknown';
groups[key] = groups[key] || [];
groups[key].push(item);
return groups;
}, {})
};

default:
throw new Error('Unknown operation: ' + operation);
}
`
}
});

JavaScript Context

Your code has access to:

// Input from tool call
const { param1, param2 } = input;

// Secrets (read-only)
const apiKey = secrets.API_KEY;

// Context information
const { agentId, conversationId, userId } = context;

// Built-in utilities
const uuid = utils.uuid();
const hash = utils.sha256('data');
const encoded = utils.base64Encode('data');
const decoded = utils.base64Decode(encoded);
const now = utils.now(); // ISO timestamp

// HTTP fetch (limited)
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Return result
return { result: data };

Sandbox Limitations

JavaScript tools run in a secure sandbox:

  • No filesystem access
  • No process/OS access
  • Limited network (fetch only)
  • 30 second timeout
  • 128MB memory limit
  • No eval/Function constructor

Creating Webhook Tools

Delegate execution to your own server:

const tool = await client.tools.create({
name: 'custom_processor',
description: 'Process data with custom backend logic',
category: 'webhook',
inputSchema: {
type: 'object',
properties: {
data: { type: 'object' },
action: { type: 'string' }
},
required: ['data', 'action']
},
executionType: 'webhook',
executionConfig: {
url: 'https://your-server.com/tools/process',
method: 'POST',
headers: {
'X-Webhook-Secret': '${secrets.WEBHOOK_SECRET}'
},
// Include signature for verification
signatureHeader: 'X-Deeployd-Signature',
signatureAlgorithm: 'sha256',
// Timeout
timeout: 60000,
// Async mode (webhook calls back with result)
async: false
},
secretsRequired: ['WEBHOOK_SECRET']
});

Webhook Server Implementation

// Your server
app.post('/tools/process', async (req, res) => {
// Verify signature
const signature = req.headers['x-deeployd-signature'];
const expectedSig = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');

if (signature !== `sha256=${expectedSig}`) {
return res.status(401).json({ error: 'Invalid signature' });
}

const { data, action } = req.body.input;

// Process the request
const result = await processData(data, action);

// Return result
res.json({ result });
});

Managing Secrets

Add Secrets

await client.tools.setSecrets('tool-123', {
API_KEY: 'sk-xxxx',
WEBHOOK_SECRET: 'whsec-yyyy'
});

List Required Secrets

const tool = await client.tools.get('tool-123');
console.log(tool.secretsRequired); // ['API_KEY', 'WEBHOOK_SECRET']

Secret Security

  • Secrets are encrypted at rest (AES-256)
  • Never logged or exposed in responses
  • Injected at runtime only
  • Per-tool isolation

Tool Permissions

Restrict Tool Access

const tool = await client.tools.create({
name: 'sensitive_operation',
// ... config ...
permissions: {
// Only these agents can use this tool
allowedAgents: ['agent-123', 'agent-456'],
// Require approval for each use
requireApproval: true,
// Only certain roles
allowedRoles: ['admin', 'operator']
}
});

Approval Workflow

When requireApproval: true:

Agent wants to use tool


Approval request created


Admin approves/rejects


Tool executes (if approved)

Testing Tools

Test Execution

const result = await client.tools.test('tool-123', {
input: {
city: 'New York',
units: 'metric'
},
secrets: {
WEATHER_API_KEY: 'test-key'
}
});

console.log({
success: result.success,
output: result.output,
duration: result.durationMs,
error: result.error
});

Dry Run

Test without actual execution:

const validation = await client.tools.validate('tool-123', {
input: {
city: 'New York',
units: 'metric'
}
});

console.log({
valid: validation.valid,
errors: validation.errors,
resolvedUrl: validation.resolvedUrl,
resolvedBody: validation.resolvedBody
});

Example: CRM Integration Tool

const crmTool = await client.tools.create({
name: 'crm_customer_lookup',
slug: 'crm-customer-lookup',
description: 'Look up customer information from CRM by email or ID',
category: 'api',
inputSchema: {
type: 'object',
properties: {
lookupType: {
type: 'string',
enum: ['email', 'id'],
description: 'Type of lookup'
},
value: {
type: 'string',
description: 'Email address or customer ID'
},
includeOrders: {
type: 'boolean',
default: false,
description: 'Include order history'
}
},
required: ['lookupType', 'value']
},
executionType: 'http',
executionConfig: {
url: 'https://api.crm.example.com/v2/customers',
method: 'GET',
headers: {
'Authorization': 'Bearer ${secrets.CRM_API_KEY}',
'X-API-Version': '2024-01'
},
queryParams: {
'${lookupType}': '${value}',
expand: '${includeOrders ? "orders" : ""}'
},
responseMapping: {
customer: '$.data[0]',
orders: '$.data[0].orders',
totalSpent: '$.data[0].totalSpent'
}
},
secretsRequired: ['CRM_API_KEY'],
permissions: {
allowedRoles: ['sales', 'support', 'admin']
}
});

Best Practices

1. Clear Descriptions

// ✅ Good
description: 'Look up customer by email and return profile with order history'

// ❌ Bad
description: 'Get customer'

2. Validate Inputs

inputSchema: {
properties: {
email: {
type: 'string',
format: 'email' // Built-in validation
},
amount: {
type: 'number',
minimum: 0,
maximum: 10000
}
},
required: ['email', 'amount']
}

3. Handle Errors Gracefully

// In JavaScript tools
try {
const result = await fetch(url);
if (!result.ok) {
return {
error: true,
message: `API returned ${result.status}`,
retryable: result.status >= 500
};
}
return { success: true, data: await result.json() };
} catch (error) {
return {
error: true,
message: error.message,
retryable: true
};
}

4. Use Response Mapping

// Extract only what's needed
responseMapping: {
id: '$.data.id',
name: '$.data.profile.displayName',
email: '$.data.contact.email'
}

// Instead of returning entire response

5. Document Tool Usage

// In system prompt
`
You have access to the crm_customer_lookup tool.

Use it when:
- User asks about a customer
- You need to verify customer information
- Looking up order history

Example:
- "What's the order history for john@example.com?"
→ Use crm_customer_lookup with email lookup
`

Next: Explore Security Features for authentication and access control.