import express, { Request, Response } from 'express';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
interface GreenhouseConfig {
apiKey: string;
baseUrl: string;
}
interface Tool {
name: string;
description: string;
parameters: {
type: string;
properties: Record<string, any>;
required?: string[];
};
}
class GreenhouseServer {
private config: GreenhouseConfig;
private axiosInstance: any;
private app: express.Application;
private tools: Tool[];
constructor() {
this.config = {
apiKey: process.env.GREENHOUSE_API_KEY || '',
baseUrl: 'https://harvest.greenhouse.io/v1'
};
this.axiosInstance = axios.create({
baseURL: this.config.baseUrl,
auth: {
username: this.config.apiKey,
password: ''
}
});
this.app = express();
this.app.use(express.json());
this.tools = [
this.listJobs(),
this.listCandidates(),
this.listApplications(),
this.moveApplication()
];
this.setupRoutes();
}
private setupRoutes() {
// Get available tools
this.app.get('/tools', (req: Request, res: Response) => {
res.json(this.tools);
});
// Execute tool
this.app.post('/execute/:tool', async (req: Request, res: Response) => {
const toolName = req.params.tool;
const tool = this.tools.find(t => t.name === toolName);
if (!tool) {
return res.status(404).json({ error: 'Tool not found' });
}
try {
const result = await this.executeTool(toolName, req.body);
res.json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
}
private async executeTool(toolName: string, parameters: any) {
switch (toolName) {
case 'list_jobs':
return await this.executeListJobs(parameters);
case 'list_candidates':
return await this.executeListCandidates(parameters);
case 'list_applications':
return await this.executeListApplications(parameters);
case 'move_application':
return await this.executeMoveApplication(parameters);
default:
throw new Error('Unknown tool');
}
}
private listJobs(): Tool {
return {
name: 'list_jobs',
description: 'List all jobs in Greenhouse',
parameters: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['open', 'closed', 'draft'],
description: 'Filter jobs by status'
}
}
}
};
}
private async executeListJobs(parameters: any) {
const response = await this.axiosInstance.get('/jobs', {
params: parameters
});
return response.data;
}
private listCandidates(): Tool {
return {
name: 'list_candidates',
description: 'List candidates in Greenhouse',
parameters: {
type: 'object',
properties: {
per_page: {
type: 'number',
description: 'Number of candidates per page'
},
page: {
type: 'number',
description: 'Page number'
}
}
}
};
}
private async executeListCandidates(parameters: any) {
const response = await this.axiosInstance.get('/candidates', {
params: parameters
});
return response.data;
}
private listApplications(): Tool {
return {
name: 'list_applications',
description: 'List applications in Greenhouse',
parameters: {
type: 'object',
properties: {
job_id: {
type: 'number',
description: 'Filter by job ID'
},
status: {
type: 'string',
description: 'Filter by application status'
}
}
}
};
}
private async executeListApplications(parameters: any) {
const response = await this.axiosInstance.get('/applications', {
params: parameters
});
return response.data;
}
private moveApplication(): Tool {
return {
name: 'move_application',
description: 'Move an application to a different stage',
parameters: {
type: 'object',
required: ['application_id', 'stage_id'],
properties: {
application_id: {
type: 'number',
description: 'ID of the application to move'
},
stage_id: {
type: 'number',
description: 'ID of the target stage'
}
}
}
};
}
private async executeMoveApplication(parameters: any) {
const { application_id, stage_id } = parameters;
const response = await this.axiosInstance.patch(
`/applications/${application_id}`,
{
current_stage_id: stage_id
}
);
return response.data;
}
public start(port: number = 3001) {
this.app.listen(port, () => {
console.log(`Greenhouse server listening on port ${port}`);
});
}
}
// Start the server
const server = new GreenhouseServer();
server.start();