HubSpot MCP Server
by SheffieldP
Verified
- hubspot_mcp
- api
// API handler for both Vercel serverless and Replit
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
// Detect if running on Vercel or Replit
const isVercel = process.env.VERCEL === '1';
// Express middleware style handler for Replit
const handleRequest = async (req, res) => {
// CORS headers
res.setHeader('Access-Control-Allow-Credentials', true);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,POST');
res.setHeader('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-HubSpot-Access-Token');
// Handle OPTIONS request (preflight)
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
// Health check endpoint
if (req.method === 'GET' && (req.url === '/health' || req.path === '/health')) {
return res.status(200).json({ status: 'ok' });
}
// Test endpoint
if (req.method === 'GET' && (req.url === '/ping' || req.path === '/ping')) {
return res.status(200).send('pong');
}
// Debug endpoint
if (req.method === 'GET' && (req.url === '/debug' || req.path === '/debug')) {
const env = process.env;
const pythonInfo = {};
try {
const pythonVersionCmd = spawn('python', ['--version']);
let pythonVersion = '';
pythonVersionCmd.stdout.on('data', (data) => {
pythonVersion += data.toString();
});
await new Promise((resolve) => {
pythonVersionCmd.on('close', (code) => {
pythonInfo.versionExitCode = code;
pythonInfo.version = pythonVersion.trim();
resolve();
});
});
} catch (e) {
pythonInfo.error = e.message;
}
return res.status(200).json({
nodeVersion: process.version,
cwd: process.cwd(),
environment: isVercel ? 'Vercel' : 'Replit/Other',
pythonInfo,
serverPath: path.join(process.cwd(), 'src', 'mcp_server_hubspot', 'server.py'),
serverExists: fs.existsSync(path.join(process.cwd(), 'src', 'mcp_server_hubspot', 'server.py'))
});
}
// Main endpoint for MCP requests
if (req.method === 'POST') {
try {
// Get the request body
const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
// Check for access token in header first, then in body
const accessToken = req.headers['x-hubspot-access-token'] || body.accessToken || body.hubspotAccessToken;
if (!accessToken) {
return res.status(400).json({
error: 'Missing access token',
message: 'HubSpot access token must be provided in either the X-HubSpot-Access-Token header or in the request body as accessToken or hubspotAccessToken'
});
}
// Simple echo for debugging
if (req.url === '/echo' || req.path === '/echo') {
return res.status(200).json({
receivedBody: body,
receivedToken: accessToken ? '[PRESENT]' : '[MISSING]'
});
}
// Create public directory if it doesn't exist
const publicDir = path.join(process.cwd(), 'public');
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
// Use mock data on Vercel, real data elsewhere
if (isVercel) {
// Mock responses for Vercel
if (body.action === 'get_contacts') {
return res.status(200).json({
status: 'success',
message: 'Mock contacts data',
data: [
{ id: '1', firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
{ id: '2', firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' }
]
});
} else if (body.action === 'get_companies') {
return res.status(200).json({
status: 'success',
message: 'Mock companies data',
data: [
{ id: '101', name: 'Acme Corp', domain: 'acme.com' },
{ id: '102', name: 'Globex', domain: 'globex.com' }
]
});
} else {
// For any other action, return a generic success response
return res.status(200).json({
status: 'success',
message: `Mock response for action: ${body.action}`,
tokenValid: true
});
}
} else {
// Real Python execution (works on Replit but not Vercel)
// Get the path to the MCP server script
const scriptPath = path.join(process.cwd(), 'src', 'mcp_server_hubspot', 'server.py');
// Spawn a Python process to run the MCP server, passing the access token as an argument
const pythonProcess = spawn('python', [scriptPath, accessToken]);
// Set up response buffers
let responseData = '';
let errorData = '';
// Send the input to the Python process
pythonProcess.stdin.write(JSON.stringify(body) + '\n');
pythonProcess.stdin.end();
// Listen for data from the Python process
pythonProcess.stdout.on('data', (data) => {
responseData += data.toString();
});
// Listen for errors
pythonProcess.stderr.on('data', (data) => {
errorData += data.toString();
});
// Wait for the process to exit
await new Promise((resolve) => {
pythonProcess.on('close', (code) => {
resolve(code);
});
});
// If there was an error, return it
if (errorData) {
console.error('Python error:', errorData);
return res.status(500).json({ error: 'Internal server error', details: errorData });
}
// Try to parse the response as JSON
try {
const jsonResponse = JSON.parse(responseData);
return res.status(200).json(jsonResponse);
} catch (e) {
// If the response isn't valid JSON, just return it as text
return res.status(200).send(responseData);
}
}
} catch (error) {
console.error('Error processing request:', error);
return res.status(500).json({ error: 'Internal server error', details: error.message });
}
}
// If we get here, the method is not supported
return res.status(405).json({ error: 'Method not allowed' });
};
// Export for both Express and Vercel
module.exports = handleRequest;