#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import { supabase } from './db/client.js';
import { listStyles, getStyleDetails } from './tools/styles.js';
import { listPalettes, getPaletteDetails } from './tools/palettes.js';
import { recommendDesign } from './tools/recommend.js';
import { createProject, listProjects, getProject, updateProject } from './tools/projects.js';
/**
* WebForge MCP Server
* Provides tools for creating and managing websites for local businesses
*/
const server = new Server({
name: 'webforge-mcp',
version: '1.0.0',
description: 'WebForge MCP Server - Create and manage websites for local businesses',
}, {
capabilities: {
tools: {},
},
});
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'webforge_list_styles',
description: 'List all available design styles with basic information',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
},
{
name: 'webforge_list_palettes',
description: 'List all available color palettes with basic information',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
},
{
name: 'webforge_recommend_design',
description: 'Get top 5 design recommendations (style + palette combinations) for a business type',
inputSchema: {
type: 'object',
properties: {
business_type: {
type: 'string',
description: 'Type of business (e.g., "restaurant", "cafe", "dental clinic", "law firm")',
},
},
required: ['business_type'],
additionalProperties: false,
},
},
{
name: 'webforge_get_style_details',
description: 'Get complete details for a specific design style including CSS tokens',
inputSchema: {
type: 'object',
properties: {
style_id: {
type: 'string',
description: 'Style ID (e.g., "S01", "S02")',
},
},
required: ['style_id'],
additionalProperties: false,
},
},
{
name: 'webforge_get_palette_details',
description: 'Get complete details for a specific color palette including all colors',
inputSchema: {
type: 'object',
properties: {
palette_id: {
type: 'string',
description: 'Palette ID (e.g., "P01", "P02")',
},
},
required: ['palette_id'],
additionalProperties: false,
},
},
{
name: 'webforge_create_project',
description: 'Create a new website project in WebForge',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Project name',
},
business_type: {
type: 'string',
description: 'Type of business',
},
description: {
type: 'string',
description: 'Optional project description',
},
style_id: {
type: 'string',
description: 'Optional style ID to assign',
},
palette_id: {
type: 'string',
description: 'Optional palette ID to assign',
},
domain: {
type: 'string',
description: 'Optional custom domain',
},
},
required: ['name', 'business_type'],
additionalProperties: false,
},
},
{
name: 'webforge_list_projects',
description: 'List website projects with optional filtering',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['draft', 'published', 'archived'],
description: 'Filter by project status',
},
business_type: {
type: 'string',
description: 'Filter by business type',
},
limit: {
type: 'number',
description: 'Maximum number of projects to return (default: 50)',
minimum: 1,
maximum: 100,
},
offset: {
type: 'number',
description: 'Number of projects to skip (default: 0)',
minimum: 0,
},
},
additionalProperties: false,
},
},
{
name: 'webforge_get_project',
description: 'Get detailed information about a specific project',
inputSchema: {
type: 'object',
properties: {
project_id: {
type: 'string',
description: 'Project ID',
},
},
required: ['project_id'],
additionalProperties: false,
},
},
{
name: 'webforge_update_project',
description: 'Update an existing website project',
inputSchema: {
type: 'object',
properties: {
project_id: {
type: 'string',
description: 'Project ID to update',
},
name: {
type: 'string',
description: 'New project name',
},
business_type: {
type: 'string',
description: 'New business type',
},
description: {
type: 'string',
description: 'New project description',
},
style_id: {
type: 'string',
description: 'New style ID to assign',
},
palette_id: {
type: 'string',
description: 'New palette ID to assign',
},
domain: {
type: 'string',
description: 'New custom domain',
},
status: {
type: 'string',
enum: ['draft', 'published', 'archived'],
description: 'New project status',
},
},
required: ['project_id'],
additionalProperties: false,
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'webforge_list_styles': {
const result = await listStyles();
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_list_palettes': {
const result = await listPalettes();
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_recommend_design': {
const { business_type } = args;
const result = await recommendDesign(business_type);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_get_style_details': {
const { style_id } = args;
const result = await getStyleDetails(style_id);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_get_palette_details': {
const { palette_id } = args;
const result = await getPaletteDetails(palette_id);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_create_project': {
const projectData = args;
const result = await createProject(projectData);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_list_projects': {
const params = args;
const result = await listProjects(params);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_get_project': {
const { project_id } = args;
const result = await getProject(project_id);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_update_project': {
const { project_id, ...updates } = args;
const result = await updateProject(project_id, updates);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Log the error
supabase.log(`Tool error (${name}): ${errorMessage}`, 'error');
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${errorMessage}`);
}
});
/**
* Start the MCP server
*/
async function main() {
console.error('π Starting WebForge MCP Server...');
try {
// Test database connection on startup
const isConnected = await supabase.testConnection();
if (!isConnected) {
console.error('β Failed to connect to Supabase database');
process.exit(1);
}
// Check if required tables exist
const { exists, missing } = await supabase.checkTables();
if (missing.length > 0) {
console.error(`β οΈ Missing database tables: ${missing.join(', ')}`);
console.error('Please run the seeding script: npm run seed');
}
else {
console.error('β
All required tables exist');
}
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('β
WebForge MCP Server is running');
}
catch (error) {
console.error('π₯ Failed to start server:', error);
process.exit(1);
}
}
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.error('π Shutting down WebForge MCP Server...');
process.exit(0);
});
process.on('SIGTERM', async () => {
console.error('π Shutting down WebForge MCP Server...');
process.exit(0);
});
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error('π₯ Server startup failed:', error);
process.exit(1);
});
}
//# sourceMappingURL=index.js.map