/**
* BRIDGE MCP Server
*
* "You don't need another automation tool. You just need a bridge."
*
* This server connects AI (Claude, Gemini, etc.) to your business tools
* via the Model Context Protocol (MCP).
*
* Architecture:
* AI (Claude/Gemini) → Bridge MCP Server → Your Tools (ClickUp, HubSpot, etc.)
*
* Each connector is modular - add new integrations by creating files in /connectors
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express, { Request, Response } from "express";
// Import connectors
import { registerClickUpTools } from "./connectors/clickup.js";
import { registerSkillTools } from "./connectors/skills.js";
// Import auth middleware
import { authenticateToken } from "./middleware/auth.js";
// Import webhook handlers
import { handleHubSpotWebhook } from "./webhooks/hubspot.js";
// Future connectors (uncomment as you build them):
// import { registerHubSpotTools } from "./connectors/hubspot.js";
// import { registerGuruTools } from "./connectors/guru.js";
// import { registerAnalyticsTools } from "./connectors/google-analytics.js";
const app = express();
app.use(express.json());
// Health check endpoint for Google Cloud Run
app.get("/health", (_req: Request, res: Response) => {
res.json({
status: "healthy",
service: "bridge-mcp",
version: "1.0.0",
connectors: ["clickup", "skills"]
});
});
// HubSpot webhook endpoint (no auth required - HubSpot sends webhooks)
app.post("/webhooks/hubspot", handleHubSpotWebhook);
// Main MCP endpoint (requires authentication)
app.all("/mcp", authenticateToken, async (req: Request, res: Response) => {
// Create a new server instance for each request (stateless)
const server = new McpServer({
name: "bridge-mcp",
version: "1.0.0",
});
// Register all connector tools
registerClickUpTools(server);
registerSkillTools(server);
// Future connectors:
// registerHubSpotTools(server);
// registerGuruTools(server);
// registerAnalyticsTools(server);
// Create transport and handle request
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // Stateless mode
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP request error:", error);
if (!res.headersSent) {
res.status(500).json({ error: "Internal server error" });
}
}
});
// Handle DELETE for session cleanup (required by MCP spec)
app.delete("/mcp", async (_req: Request, res: Response) => {
res.status(200).json({ message: "Session terminated" });
});
// PUBLIC TEST ENDPOINT - NO AUTH (temporary for testing)
// TODO: Remove this before production launch!
app.all("/mcp-test", async (req: Request, res: Response) => {
// Create a new server instance for each request (stateless)
const server = new McpServer({
name: "bridge-mcp-test",
version: "1.0.0",
});
// Register all connector tools
registerClickUpTools(server);
registerSkillTools(server);
// Create transport and handle request
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // Stateless mode
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP request error:", error);
if (!res.headersSent) {
res.status(500).json({ error: "Internal server error" });
}
}
});
app.delete("/mcp-test", async (_req: Request, res: Response) => {
res.status(200).json({ message: "Session terminated" });
});
const PORT = process.env.PORT || 8080;
app.listen(Number(PORT), "0.0.0.0", () => {
console.log(`
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ 🌉 BRIDGE MCP Server ║
║ ║
║ "You don't need another automation tool. ║
║ You just need a bridge." ║
║ ║
║ Running on port ${PORT} ║
║ Health: http://localhost:${PORT}/health ║
║ MCP: http://localhost:${PORT}/mcp ║
║ ║
║ Active Connectors: ║
║ • ClickUp ║
║ • Skills Library ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
`);
});