Creating Tools
Learn the patterns and best practices for building CTP-compliant tools.Tool Structure
Every CTP tool consists of two parts:- Definition - Metadata describing the tool
- Function - Implementation that processes inputs
Copy
import type { ToolDefinition, ToolFunction } from '@conveniencepro/ctp-core';
// 1. Define result type
interface MyToolResult {
// ... result properties
}
// 2. Create definition
export const myToolDefinition: ToolDefinition = {
// ... metadata
};
// 3. Implement function
export const myToolFn: ToolFunction<MyToolResult> = (params) => {
// ... implementation
};
// 4. Export together
export default { definition: myToolDefinition, fn: myToolFn };
Definition Patterns
Minimal Definition
Copy
export const minimalDefinition: ToolDefinition = {
id: 'minimal-tool',
name: 'Minimal Tool',
description: 'A minimal example tool.',
category: 'utilities',
tags: ['example'],
method: 'POST',
parameters: [
{
name: 'input',
type: 'text',
label: 'Input',
description: 'Input value',
required: true,
},
],
outputDescription: 'Processed result',
example: {
input: { input: 'test' },
output: { result: 'processed' },
},
};
Full-Featured Definition
Copy
export const fullDefinition: ToolDefinition = {
// Required
id: 'advanced-tool',
name: 'Advanced Tool',
description: 'A fully-featured example with all options.',
category: 'formatters',
tags: ['advanced', 'example', 'featured'],
method: 'POST',
parameters: [/* ... */],
outputDescription: 'Formatted output with metadata',
example: {
input: { /* ... */ },
output: { /* ... */ },
},
// Optional metadata
version: '1.0.0',
icon: '🔧',
keywords: ['full', 'complete'],
relatedTools: ['simple-tool', 'other-tool'],
// AI guidance
aiInstructions: 'Use default settings unless user specifies otherwise.',
// Execution
executionMode: 'client',
requiresAuth: false,
// Rate limiting
rateLimit: {
requests: 100,
window: 60,
},
};
Implementation Patterns
Synchronous Tool
Copy
export const syncFn: ToolFunction<Result> = (params) => {
const input = params.input as string;
// Validation
if (!input) {
return {
success: false,
error: 'Input is required',
errorCode: 'MISSING_REQUIRED',
};
}
// Processing
const result = processSync(input);
// Return success
return {
success: true,
data: result,
};
};
Asynchronous Tool
Copy
export const asyncFn: ToolFunction<Result> = async (params) => {
const input = params.input as string;
try {
// Async operation (e.g., Web Crypto)
const hash = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(input)
);
return {
success: true,
data: { hash: arrayToHex(hash) },
};
} catch (error) {
return {
success: false,
error: (error as Error).message,
errorCode: 'EXECUTION_ERROR',
};
}
};
With Metadata
Copy
export const metadataFn: ToolFunction<Result> = (params) => {
const startTime = performance.now();
const input = params.input as string;
const result = process(input);
return {
success: true,
data: result,
metadata: {
executionTime: performance.now() - startTime,
inputSize: input.length,
outputSize: JSON.stringify(result).length,
},
};
};
With Warnings
Copy
export const warningFn: ToolFunction<Result> = (params) => {
const algorithm = params.algorithm as string;
const warnings: string[] = [];
if (algorithm === 'SHA-1') {
warnings.push('SHA-1 is deprecated for security purposes');
}
if (algorithm === 'MD5') {
warnings.push('MD5 is cryptographically broken');
}
const result = compute(params);
return {
success: true,
data: result,
metadata: {
warnings: warnings.length > 0 ? warnings : undefined,
},
};
};
Parameter Patterns
Select with Descriptions
Copy
{
name: 'format',
type: 'select',
label: 'Output Format',
description: 'Choose the output format',
required: false,
defaultValue: 'json',
options: [
{ value: 'json', label: 'JSON', description: 'JavaScript Object Notation' },
{ value: 'yaml', label: 'YAML', description: 'Human-readable format' },
{ value: 'xml', label: 'XML', description: 'Extensible Markup Language' },
],
}
Conditional Parameters
Copy
parameters: [
{
name: 'mode',
type: 'select',
label: 'Mode',
required: true,
options: [
{ value: 'simple', label: 'Simple' },
{ value: 'advanced', label: 'Advanced' },
],
},
{
name: 'advancedOption',
type: 'text',
label: 'Advanced Option',
description: 'Only shown in advanced mode',
required: false,
dependsOn: [{ field: 'mode', condition: 'equals', value: 'advanced' }],
},
]
Grouped Parameters
Copy
parameters: [
// Input group
{ name: 'input', type: 'textarea', group: 'input', order: 1, /* ... */ },
// Options group
{ name: 'format', type: 'select', group: 'options', order: 1, /* ... */ },
{ name: 'indent', type: 'number', group: 'options', order: 2, /* ... */ },
// Advanced group
{ name: 'strict', type: 'boolean', group: 'advanced', order: 1, /* ... */ },
]
Bidirectional Tools
Handle encode/decode or similar operations:Copy
export const bidirectionalDefinition: ToolDefinition = {
id: 'base64-encoder',
name: 'Base64 Encoder/Decoder',
// ...
parameters: [
{
name: 'input',
type: 'textarea',
label: 'Input',
description: 'Text to encode or Base64 to decode',
required: true,
},
{
name: 'mode',
type: 'select',
label: 'Mode',
required: false,
defaultValue: 'encode',
options: [
{ value: 'encode', label: 'Encode' },
{ value: 'decode', label: 'Decode' },
],
},
],
};
export const bidirectionalFn: ToolFunction<Result> = (params) => {
const input = params.input as string;
const mode = (params.mode as 'encode' | 'decode') || 'encode';
const output = mode === 'encode'
? btoa(input)
: atob(input);
return {
success: true,
data: { output, mode },
};
};
Error Handling
Validation Errors
Copy
if (!input) {
return {
success: false,
error: 'Input is required',
errorCode: 'MISSING_REQUIRED',
};
}
if (typeof input !== 'string') {
return {
success: false,
error: 'Input must be a string',
errorCode: 'TYPE_ERROR',
};
}
if (input.length > 100000) {
return {
success: false,
error: 'Input exceeds maximum length',
errorCode: 'CONSTRAINT_VIOLATION',
};
}
Execution Errors
Copy
try {
const parsed = JSON.parse(input);
return { success: true, data: { parsed } };
} catch (e) {
return {
success: false,
error: `Invalid JSON: ${(e as Error).message}`,
errorCode: 'INVALID_INPUT',
suggestion: 'Check for missing quotes or trailing commas',
};
}
Testing Tools
Copy
import { describe, it, expect } from 'vitest';
import myTool from './my-tool';
describe('my-tool', () => {
it('returns success for valid input', () => {
const result = myTool.fn({ input: 'test' });
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
});
it('returns error for missing input', () => {
const result = myTool.fn({});
expect(result.success).toBe(false);
expect(result.errorCode).toBe('MISSING_REQUIRED');
});
});