#!/usr/bin/env node
/**
* HeyHo MCP Server
* Entry point for the Model Context Protocol server
*/
import dotenv from 'dotenv';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
ErrorCode,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import * as toolRegistry from './tools/index.js';
import * as resourceRegistry from './resources/index.js';
import * as promptRegistry from './prompts/index.js';
// Load environment variables from .env file
// Note: This does NOT override existing environment variables
// Precedence: Client config > System env vars > .env file
// quiet: true suppresses stdout messages that break MCP stdio protocol
const dotenvResult = dotenv.config({ quiet: true });
// Server version from package.json
const SERVER_VERSION = '0.1.0';
/**
* Main server instance
*/
const server = new Server(
{
name: 'attio-simple-mcp-server',
version: SERVER_VERSION,
description: `HeyHo Ventures CRM (Attio). Manages: Companies (portfolio, prospects), People (founders, LPs, contacts), Funds (HeyHo I, Endor, etc.), Investment Opportunities (deal pipeline), LP Opportunities (fundraising pipeline). Key relationships: Investment Opps link to Company + Fund; LP Opps link to Person OR Company + Fund.`,
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
/**
* Initialize and start the MCP server
*/
async function main() {
const debugMode = process.env['MCP_DEBUG'] === 'true';
// Verify environment configuration
if (!process.env['ATTIO_API_KEY']) {
console.error(
'❌ ATTIO_API_KEY not found - server cannot connect to Attio'
);
console.error('Please configure ATTIO_API_KEY via:');
console.error(' - System environment variables (recommended)');
console.error(' - .env file in project root');
console.error(' - MCP client configuration');
process.exit(1);
}
// Detect configuration source
const apiKeySource = dotenvResult.parsed?.['ATTIO_API_KEY']
? '.env file'
: 'system/client environment variable';
// Show configuration info
if (debugMode) {
console.error('--- Configuration Diagnostic ---');
console.error('Environment Configuration:');
console.error(` ATTIO_API_KEY: ${apiKeySource}`);
console.error(
` ATTIO_SAFE_MODE: ${process.env['ATTIO_SAFE_MODE'] || 'not set (default: false)'}`
);
console.error(
` ATTIO_TEST_PREFIX: ${process.env['ATTIO_TEST_PREFIX'] || 'not set (default: [TEST])'}`
);
console.error(
` ATTIO_MAX_TEST_RECORDS: ${process.env['ATTIO_MAX_TEST_RECORDS'] || 'not set (default: 100)'}`
);
console.error(
` NODE_ENV: ${process.env['NODE_ENV'] || 'not set (default: development)'}`
);
console.error(` MCP_DEBUG: ${process.env['MCP_DEBUG'] || 'not set'}`);
console.error('--------------------------------');
} else {
console.error(`✓ ATTIO_API_KEY: ${apiKeySource}`);
}
console.error(`✓ HeyHo MCP Server v${SERVER_VERSION} starting...`);
// Set up tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolRegistry.getAllTools(),
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const handler = toolRegistry.getToolHandler(name);
if (!handler) {
throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${name}`);
}
try {
return await handler(args ?? {});
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${errorMessage}`
);
}
});
// Set up resource handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: resourceRegistry.getAllResources(),
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
const handler = resourceRegistry.getResourceHandler(uri);
if (!handler) {
throw new McpError(
ErrorCode.InvalidRequest,
`Resource not found: ${uri}`
);
}
try {
return await handler();
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new McpError(
ErrorCode.InternalError,
`Resource read failed: ${errorMessage}`
);
}
});
// Set up prompt handlers
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: promptRegistry.getAllPrompts(),
}));
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const handler = promptRegistry.getPromptHandler(name);
if (!handler) {
throw new McpError(ErrorCode.MethodNotFound, `Prompt not found: ${name}`);
}
try {
return await handler(args ?? {});
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
throw new McpError(
ErrorCode.InternalError,
`Prompt execution failed: ${errorMessage}`
);
}
});
console.error('✓ Request handlers registered');
// Connect to stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('✓ Server connected via stdio');
console.error('✓ Ready to accept MCP requests');
}
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.error('\n⚠ Received SIGINT, shutting down gracefully...');
await server.close();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.error('\n⚠ Received SIGTERM, shutting down gracefully...');
await server.close();
process.exit(0);
});
// Start the server
main().catch((error) => {
console.error('❌ Fatal error:', error);
process.exit(1);
});