#!/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, searchStyles } from './tools/styles.js';
import { listPalettes, getPaletteDetails, searchPalettes } from './tools/palettes.js';
import { recommendDesign } from './tools/recommend.js';
import { createProject, listProjects, getProject, updateProject, deleteProject } 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 as { business_type: string };
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 as { style_id: string };
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 as { palette_id: string };
const result = await getPaletteDetails(palette_id);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_create_project': {
const projectData = args as {
name: string;
business_type: string;
description?: string;
style_id?: string;
palette_id?: string;
domain?: string;
};
const result = await createProject(projectData);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_list_projects': {
const params = args as {
status?: 'draft' | 'published' | 'archived';
business_type?: string;
limit?: number;
offset?: number;
};
const result = await listProjects(params);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'webforge_get_project': {
const { project_id } = args as { project_id: string };
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 as {
project_id: string;
name?: string;
business_type?: string;
description?: string;
style_id?: string;
palette_id?: string;
domain?: string;
status?: 'draft' | 'published' | 'archived';
};
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);
});
}