/**
* Agent Gateway Registration Client
*
* Handles registration of the MCP server with the Agent Gateway.
* Similar to how ag-ui-server-agent registers with the gateway.
*/
import { ILogger } from '../logging/logger.js';
/**
* Agent registration payload
*/
export interface AgentRegistration {
name: string;
type: 'mcp';
version: string;
description: string;
endpoint: string;
capabilities: string[];
metadata?: Record<string, any>;
healthCheckEndpoint?: string;
}
/**
* Agent Gateway client configuration
*/
export interface AgentGatewayConfig {
gatewayUrl: string;
agentName: string;
agentVersion: string;
agentDescription: string;
mcpEndpoint: string;
healthEndpoint: string;
capabilities: string[];
registrationInterval?: number; // in milliseconds
apiKey?: string;
}
/**
* Agent Gateway Client
*/
export class AgentGatewayClient {
private readonly config: AgentGatewayConfig;
private readonly logger: ILogger;
private registrationInterval: NodeJS.Timeout | null = null;
private isRegistered = false;
constructor(config: AgentGatewayConfig, logger: ILogger) {
this.config = {
registrationInterval: 60000, // Default: 60 seconds
...config,
};
this.logger = logger;
}
/**
* Register with the Agent Gateway
*/
async register(): Promise<boolean> {
try {
const registration: AgentRegistration = {
name: this.config.agentName,
type: 'mcp',
version: this.config.agentVersion,
description: this.config.agentDescription,
endpoint: this.config.mcpEndpoint,
capabilities: this.config.capabilities,
healthCheckEndpoint: this.config.healthEndpoint,
metadata: {
framework: 'typescript',
protocol: 'mcp',
apis: ['graphql', 'rest'],
entities: ['person', 'company', 'opportunity'],
registeredAt: new Date().toISOString(),
},
};
this.logger.info('Registering with Agent Gateway', {
gateway: this.config.gatewayUrl,
agent: this.config.agentName,
});
const response = await fetch(`${this.config.gatewayUrl}/api/v1/agents/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
},
body: JSON.stringify(registration),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Registration failed: ${response.status} ${errorText}`);
}
const result = await response.json();
this.isRegistered = true;
this.logger.info('Successfully registered with Agent Gateway', {
gateway: this.config.gatewayUrl,
agent: this.config.agentName,
response: result,
});
return true;
} catch (error) {
this.logger.error(
'Failed to register with Agent Gateway',
error instanceof Error ? error : new Error(String(error)),
{
gateway: this.config.gatewayUrl,
agent: this.config.agentName,
}
);
return false;
}
}
/**
* Unregister from the Agent Gateway
*/
async unregister(): Promise<boolean> {
try {
this.logger.info('Unregistering from Agent Gateway', {
gateway: this.config.gatewayUrl,
agent: this.config.agentName,
});
const response = await fetch(
`${this.config.gatewayUrl}/api/v1/agents/${encodeURIComponent(this.config.agentName)}`,
{
method: 'DELETE',
headers: {
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
},
}
);
if (!response.ok && response.status !== 404) {
const errorText = await response.text();
throw new Error(`Unregistration failed: ${response.status} ${errorText}`);
}
this.isRegistered = false;
this.logger.info('Successfully unregistered from Agent Gateway');
return true;
} catch (error) {
this.logger.error(
'Failed to unregister from Agent Gateway',
error instanceof Error ? error : new Error(String(error))
);
return false;
}
}
/**
* Send heartbeat to the Agent Gateway
*/
async sendHeartbeat(): Promise<boolean> {
try {
const response = await fetch(
`${this.config.gatewayUrl}/api/v1/agents/${encodeURIComponent(this.config.agentName)}/heartbeat`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
status: 'healthy',
}),
}
);
if (!response.ok) {
throw new Error(`Heartbeat failed: ${response.status}`);
}
this.logger.debug('Heartbeat sent successfully');
return true;
} catch (error) {
this.logger.error(
'Failed to send heartbeat',
error instanceof Error ? error : new Error(String(error))
);
return false;
}
}
/**
* Start automatic registration and heartbeat
*/
async start(): Promise<void> {
this.logger.info('Starting Agent Gateway client', {
gateway: this.config.gatewayUrl,
interval: this.config.registrationInterval,
});
// Initial registration
const registered = await this.register();
if (!registered) {
this.logger.warn('Initial registration failed, will retry on interval');
}
// Set up periodic heartbeat/re-registration
this.registrationInterval = setInterval(async () => {
if (this.isRegistered) {
await this.sendHeartbeat();
} else {
await this.register();
}
}, this.config.registrationInterval);
this.logger.info('Agent Gateway client started');
}
/**
* Stop automatic registration and heartbeat
*/
async stop(): Promise<void> {
this.logger.info('Stopping Agent Gateway client');
if (this.registrationInterval) {
clearInterval(this.registrationInterval);
this.registrationInterval = null;
}
if (this.isRegistered) {
await this.unregister();
}
this.logger.info('Agent Gateway client stopped');
}
/**
* Get registration status
*/
getStatus(): { isRegistered: boolean; gateway: string; agent: string } {
return {
isRegistered: this.isRegistered,
gateway: this.config.gatewayUrl,
agent: this.config.agentName,
};
}
}
/**
* Factory function to create Agent Gateway client
*/
export function createAgentGatewayClient(logger: ILogger): AgentGatewayClient | null {
const gatewayUrl = process.env.AGENT_GATEWAY_URL;
// Gateway registration is optional
if (!gatewayUrl) {
logger.info('Agent Gateway URL not configured, skipping registration');
return null;
}
const config: AgentGatewayConfig = {
gatewayUrl,
agentName: process.env.AGENT_NAME || 'twenty-crm-mcp-server',
agentVersion: process.env.AGENT_VERSION || '1.0.0',
agentDescription:
process.env.AGENT_DESCRIPTION ||
'Twenty CRM MCP Server with GraphQL and REST API support',
mcpEndpoint: process.env.MCP_ENDPOINT || 'http://localhost:5008/mcp',
healthEndpoint: process.env.HEALTH_ENDPOINT || 'http://localhost:5008/health',
capabilities: [
'person.create',
'person.read',
'person.update',
'person.delete',
'person.list',
'company.create',
'company.read',
'company.list',
'opportunity.create',
'opportunity.read',
'opportunity.list',
],
registrationInterval: process.env.AGENT_REGISTRATION_INTERVAL
? parseInt(process.env.AGENT_REGISTRATION_INTERVAL, 10)
: 60000,
apiKey: process.env.AGENT_GATEWAY_API_KEY,
};
logger.info('Creating Agent Gateway client', {
gateway: config.gatewayUrl,
agent: config.agentName,
});
return new AgentGatewayClient(config, logger);
}