Creating Your First Server
Learn how to create and configure a production-ready Agent Tool Protocol server.
Basic Server Setup
Minimal Server
The simplest server requires just a few lines:
import { createServer } from '@mondaydotcomorg/atp-server';
const server = createServer();
server.listen(3333, () => {
console.log('Server running on http://localhost:3333');
});
This creates a server with default settings:
- 30-second execution timeout
- 128MB memory limit
- 10 LLM calls per execution
- No provenance tracking
- Memory-based caching
Server with Configuration
For production use, you'll want to customize the configuration:
import { createServer, MB, HOUR, MINUTE } from '@mondaydotcomorg/atp-server';
const server = createServer({
execution: {
timeout: 60000, // 60 seconds
memory: 256 * MB, // 256 MB
llmCalls: 20, // Max 20 LLM calls per execution
provenanceMode: 'track', // Enable provenance tracking
},
clientInit: {
tokenTTL: 24 * HOUR, // Tokens valid for 24 hours
tokenRotation: 30 * MINUTE, // Rotate tokens every 30 minutes
},
executionState: {
ttl: 3600, // Execution state kept for 1 hour
maxPauseDuration: 3600, // Max pause duration: 1 hour
},
discovery: {
embeddings: true, // Enable embedding-based search
},
audit: {
enabled: true, // Enable audit logging
},
logger: 'info', // Log level: debug, info, warn, error
});
server.listen(3333);
Registering APIs
Custom TypeScript APIs
Register custom functions with full TypeScript support:
server.registerAPI('users', {
// Get a user by ID
getUser: {
description: 'Fetch a user by their ID',
inputSchema: {
type: 'object',
properties: {
userId: { type: 'string', description: 'User ID' },
},
required: ['userId'],
},
outputSchema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
},
},
handler: async ({ userId }) => {
// Your implementation
return {
id: userId,
name: 'John Doe',
email: 'john@example.com',
};
},
keywords: ['user', 'profile', 'account'],
metadata: {
operationType: 'read',
sensitivityLevel: 'internal',
},
},
// Create a new user
createUser: {
description: 'Create a new user',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
},
required: ['name', 'email'],
},
handler: async ({ name, email }) => {
// Your implementation
const newUser = {
id: generateId(),
name,
email,
};
await database.users.insert(newUser);
return newUser;
},
metadata: {
operationType: 'write',
sensitivityLevel: 'internal',
requiresApproval: true, // Requires approval before execution
},
},
});
OpenAPI/Swagger Integration
Load and serve OpenAPI specifications:
import { loadOpenAPI } from '@mondaydotcomorg/atp-server';
// Load from URL
const petstoreAPI = await loadOpenAPI({
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
name: 'petstore',
});
server.addAPIGroup(petstoreAPI);
// Load from file
const myAPI = await loadOpenAPI({
path: './specs/my-api.yaml',
name: 'myapi',
baseURL: 'https://api.example.com',
});
server.addAPIGroup(myAPI);
Multiple API Groups
Organize APIs into logical groups:
// User management APIs
server.registerAPI('users', {
getUser: { /* ... */ },
createUser: { /* ... */ },
updateUser: { /* ... */ },
deleteUser: { /* ... */ },
});
// Product APIs
server.registerAPI('products', {
listProducts: { /* ... */ },
getProduct: { /* ... */ },
searchProducts: { /* ... */ },
});
// Order APIs
server.registerAPI('orders', {
createOrder: { /* ... */ },
getOrder: { /* ... */ },
listOrders: { /* ... */ },
});
Authentication & Authorization
Environment-based Authentication
The simplest approach uses environment variables:
// .env file
ATP_API_KEY=your-secret-key-here
Clients must provide this key:
const client = new AgentToolProtocolClient({
baseUrl: 'http://localhost:3333',
headers: {
'x-api-key': process.env.ATP_API_KEY,
},
});
Custom Authentication Provider
Implement custom authentication logic:
import { createServer } from '@mondaydotcomorg/atp-server';
class CustomAuthProvider {
name = 'custom-auth';
async validateToken(token: string): Promise<boolean> {
// Your validation logic
return token === 'valid-token';
}
async getUserInfo(token: string): Promise<any> {
// Return user information
return { userId: '123', role: 'admin' };
}
}
const server = createServer({
providers: {
auth: new CustomAuthProvider(),
},
});
OAuth Integration
For OAuth-based APIs:
server.registerAPI('github', {
getRepos: {
description: 'Get user repositories',
inputSchema: { type: 'object', properties: {} },
handler: async (params, context) => {
// Access OAuth token from context
const token = context.userAuth?.token;
// Call GitHub API with user's token
const response = await fetch('https://api.github.com/user/repos', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return await response.json();
},
metadata: {
requiredScopes: ['repo'],
},
},
});
Caching Configuration
Memory Cache (Default)
import { MemoryCache } from '@mondaydotcomorg/atp-providers';
const server = createServer({
providers: {
cache: new MemoryCache({
maxKeys: 1000,
defaultTTL: 3600, // 1 hour
}),
},
});
Redis Cache (Production)
import { RedisCache } from '@mondaydotcomorg/atp-providers';
const server = createServer({
providers: {
cache: new RedisCache({
url: process.env.REDIS_URL || 'redis://localhost:6379',
keyPrefix: 'atp:',
defaultTTL: 3600,
}),
},
});
Custom Cache Provider
class CustomCacheProvider {
name = 'custom-cache';
async get(key: string): Promise<any> {
// Your implementation
}
async set(key: string, value: any, ttl?: number): Promise<void> {
// Your implementation
}
async delete(key: string): Promise<void> {
// Your implementation
}
async clear(): Promise<void> {
// Your implementation
}
}
const server = createServer({
providers: {
cache: new CustomCacheProvider(),
},
});
Middleware
Add custom middleware to intercept requests:
server.use(async (context, next) => {
console.log(`Request: ${context.method}`);
// Add custom headers
context.setHeader('X-Server-Version', '1.0.0');
// Proceed with request
const result = await next();
console.log(`Response: ${context.method} completed`);
return result;
});
// Authentication middleware
server.use(async (context, next) => {
const apiKey = context.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.API_KEY) {
throw new Error('Unauthorized');
}
return next();
});
Error Handling
Global Error Handler
server.on('error', (error, context) => {
console.error('Server error:', error);
// Log to external service
logToSentry(error, {
method: context.method,
clientId: context.clientId,
});
});
Execution Error Handling
server.registerAPI('risky', {
dangerousOperation: {
description: 'An operation that might fail',
inputSchema: { type: 'object', properties: {} },
handler: async (params) => {
try {
return await performDangerousOperation();
} catch (error) {
// Transform error for client
throw new Error(`Operation failed: ${error.message}`);
}
},
},
});
Lifecycle Hooks
Startup and Shutdown
// Before server starts
server.on('beforeStart', async () => {
console.log('Initializing database...');
await database.connect();
});
// After server starts
server.on('ready', () => {
console.log('Server is ready to accept connections');
});
// Before server stops
server.on('beforeStop', async () => {
console.log('Closing database connections...');
await database.disconnect();
});
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received, shutting down gracefully...');
await server.stop();
process.exit(0);
});
Production Configuration
Complete Production Setup
import { createServer, MB, HOUR, MINUTE } from '@mondaydotcomorg/atp-server';
import { RedisCache } from '@mondaydotcomorg/atp-providers';
const server = createServer({
execution: {
timeout: 60000,
memory: 512 * MB,
llmCalls: 50,
provenanceMode: 'track',
securityPolicies: [
preventDataExfiltration(),
requireUserOrigin(['sensitive-api']),
],
},
clientInit: {
tokenTTL: 24 * HOUR,
tokenRotation: 30 * MINUTE,
},
executionState: {
ttl: 3600,
maxPauseDuration: 3600,
},
discovery: {
embeddings: true,
},
audit: {
enabled: true,
},
otel: {
enabled: true,
serviceName: 'atp-server',
traceEndpoint: process.env.OTEL_TRACE_ENDPOINT,
metricsEndpoint: process.env.OTEL_METRICS_ENDPOINT,
},
providers: {
cache: new RedisCache({
url: process.env.REDIS_URL,
}),
},
logger: process.env.NODE_ENV === 'production' ? 'warn' : 'info',
});
// Health check endpoint
server.use(async (context, next) => {
if (context.path === '/health') {
return { status: 'ok' };
}
return next();
});
// Start server
const PORT = parseInt(process.env.PORT || '3333');
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Next Steps
- Creating Your First Client: Build a client to connect to your server
- Architecture Overview: Understand how ATP works
- API Discovery: Learn about API search and discovery
- Security & Provenance: Implement security policies
Common Patterns
Rate Limiting
import { RateLimiterMemory } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterMemory({
points: 100, // Number of requests
duration: 60, // Per 60 seconds
});
server.use(async (context, next) => {
try {
await rateLimiter.consume(context.clientId);
return next();
} catch (error) {
throw new Error('Rate limit exceeded');
}
});
Request Logging
server.use(async (context, next) => {
const startTime = Date.now();
try {
const result = await next();
const duration = Date.now() - startTime;
console.log({
method: context.method,
clientId: context.clientId,
duration,
success: true,
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error({
method: context.method,
clientId: context.clientId,
duration,
success: false,
error: error.message,
});
throw error;
}
});
Troubleshooting
Server Won't Start
- Check if the port is already in use
- Verify Node.js version (18+ required)
- Check for syntax errors in your configuration
Memory Issues
- Increase the memory limit in execution config
- Monitor memory usage in production
- Consider implementing cleanup for long-running operations
Performance Issues
- Enable Redis caching
- Implement rate limiting
- Monitor with OpenTelemetry
- Optimize API handler implementations