#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const validate_manifest_1 = require("./tools/validate-manifest");
const validate_template_1 = require("./tools/validate-template");
const validate_package_1 = require("./tools/validate-package");
const get_example_1 = require("./tools/get-example");
const get_conversion_guide_1 = require("./tools/get-conversion-guide");
const get_tenant_schema_1 = require("./tools/get-tenant-schema");
const list_projects_1 = require("./tools/list-projects");
const create_site_1 = require("./tools/create-site");
const deploy_package_1 = require("./tools/deploy-package");
const get_field_types_1 = require("./tools/get-field-types");
const sync_schema_1 = require("./tools/sync-schema");
const generate_samples_1 = require("./tools/generate-samples");
const get_started_1 = require("./tools/get-started");
const cms_items_1 = require("./tools/cms-items");
// Server instructions - embedded in tool descriptions since MCP SDK doesn't have a dedicated field
// The get_started tool description acts as the primary entry point guidance for agents
const server = new index_js_1.Server({
name: 'multisite-cms',
version: '1.0.0',
}, {
capabilities: {
tools: {},
resources: {},
},
});
// Define available tools
const TOOLS = [
// get_started is the FIRST tool - agents should call this first
{
name: 'get_started',
description: 'CALL THIS FIRST. Intelligent entry point that checks authentication, lists your projects, and returns the exact workflow for your task. Provides schema details, field types, and example tool calls with real values.',
inputSchema: {
type: 'object',
properties: {
intent: {
type: 'string',
enum: ['explore', 'add_content', 'update_schema', 'convert', 'deploy'],
description: 'What you want to do: explore (see projects), add_content (create/edit items), update_schema (add collections/fields), convert (new website), deploy (push changes)',
},
projectId: {
type: 'string',
description: 'Project ID or name to get detailed schema and workflow for',
},
},
required: [],
},
},
{
name: 'validate_manifest',
description: 'Validate a manifest.json file for the CMS package. Returns errors if the structure is incorrect or required fields are missing.',
inputSchema: {
type: 'object',
properties: {
manifest: {
type: 'string',
description: 'The manifest.json content as a string',
},
},
required: ['manifest'],
},
},
{
name: 'validate_template',
description: 'Validate an HTML template for correct CMS token usage. Checks {{token}} syntax, field names, and triple braces for rich text. IMPORTANT: When projectId is provided, validates tokens against the project schema and reports missing fields/collections that MUST be created with sync_schema before deploying.',
inputSchema: {
type: 'object',
properties: {
html: {
type: 'string',
description: 'The HTML template content',
},
templateType: {
type: 'string',
enum: ['custom_index', 'custom_detail', 'static_page'],
description: 'The type of template being validated',
},
collectionSlug: {
type: 'string',
description: 'For collection templates, the collection slug',
},
projectId: {
type: 'string',
description: 'Optional: Project ID or name to validate tokens against actual schema. Requires authentication. If missing fields are found, provides instructions for creating them with sync_schema.',
},
},
required: ['html', 'templateType'],
},
},
{
name: 'validate_package',
description: 'Validate the complete structure of a CMS website package. Checks folder structure, manifest, all templates, and asset references.',
inputSchema: {
type: 'object',
properties: {
fileList: {
type: 'array',
items: { type: 'string' },
description: 'List of all file paths in the package',
},
manifestContent: {
type: 'string',
description: 'Content of manifest.json',
},
templateContentsJson: {
type: 'string',
description: 'JSON string mapping template file paths to their contents',
},
},
required: ['fileList', 'manifestContent'],
},
},
{
name: 'get_example',
description: 'Get example code for a specific pattern or use case. Use this when you need to see how something should be implemented.',
inputSchema: {
type: 'object',
properties: {
exampleType: {
type: 'string',
enum: [
'manifest_basic',
'manifest_custom_paths',
'manifest_minimal_with_ui',
'blog_index_template',
'blog_post_template',
'team_template',
'downloads_template',
'authors_template',
'author_detail_template',
'custom_collection_template',
'form_handling',
'asset_paths',
'data_edit_keys',
'each_loop',
'conditional_if',
'nested_fields',
'featured_posts',
'parent_context',
'equality_comparison',
'comparison_helpers',
'youtube_embed',
'nested_collection_loop',
'loop_variables',
'common_mistakes',
],
description: 'The type of example to retrieve',
},
},
required: ['exampleType'],
},
},
{
name: 'get_conversion_guide',
description: 'Get the website conversion guide. CRITICAL: You MUST follow the steps in order. Start by calling list_projects to get a projectId BEFORE building any templates. Skipping this step will cause deployment failures.',
inputSchema: {
type: 'object',
properties: {
section: {
type: 'string',
enum: ['full', 'first_steps', 'analysis', 'structure', 'seo', 'manifest', 'templates', 'tokens', 'forms', 'assets', 'checklist', 'common_mistakes'],
description: 'Specific section to retrieve, or "full" for everything. Use "first_steps" to get the required initial steps for establishing the target project. Use "common_mistakes" to see common AI mistakes and how to avoid them.',
},
},
required: [],
},
},
{
name: 'list_projects',
description: 'List all FastMode projects you have access to. Use this to discover project IDs and names for use with get_tenant_schema. Requires FASTMODE_AUTH_TOKEN environment variable to be set.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_tenant_schema',
description: 'Fetch the complete schema for a specific project including all collections and their fields. Use this when converting a website for an existing project to see their specific CMS configuration. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The project ID (UUID from list_projects) or project name',
},
},
required: ['projectId'],
},
},
{
name: 'create_site',
description: 'Create a new Fast Mode site/project. Checks for similar existing project names first. Opens browser for authentication if not already logged in. After creation, use deploy_package to upload your website.',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'The name for your new project',
},
subdomain: {
type: 'string',
description: 'Optional custom subdomain (auto-generated from name if not provided)',
},
confirmCreate: {
type: 'boolean',
description: 'Set to true to skip similar-name check (use only after user confirms they want a new project)',
},
},
required: ['name'],
},
},
{
name: 'deploy_package',
description: 'FIRST: Call get_started(intent: "deploy") to check project state. Validates and deploys a website package (.zip file). BLOCKS deployment if validation errors exist. Requires a projectId. Will check for GitHub sync and block if connected. Use force:true to override GitHub check.',
inputSchema: {
type: 'object',
properties: {
packagePath: {
type: 'string',
description: 'Path to the .zip file containing the website package',
},
projectId: {
type: 'string',
description: 'Project ID to deploy to. Use list_projects to find existing projects, or create_site to create a new one.',
},
force: {
type: 'boolean',
description: 'Optional: Skip GitHub connection check and deploy anyway. Use with caution - may cause conflicts if GitHub auto-deploy is enabled.',
},
},
required: ['packagePath'],
},
},
{
name: 'get_field_types',
description: 'Get the list of available field types for creating new fields in collections. Use this to see what field types you can use with sync_schema. This is NOT the list of fields in your schema - use get_tenant_schema for that. No authentication required.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'sync_schema',
description: 'FIRST: Call get_started(intent: "update_schema") to see current schema. Creates collections and fields in Fast Mode. Uses two-phase creation for relation fields. Skips duplicates. Provides field type suggestions. Call this BEFORE deploying. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name. Use list_projects to see available projects.',
},
collections: {
type: 'array',
description: 'New collections to create, each with optional fields',
items: {
type: 'object',
properties: {
slug: { type: 'string', description: 'URL-friendly identifier (lowercase, no spaces)' },
name: { type: 'string', description: 'Display name (plural)' },
nameSingular: { type: 'string', description: 'Singular form of name' },
description: { type: 'string', description: 'Optional description' },
hasSlug: { type: 'boolean', description: 'Whether items have URL slugs (default: true)' },
fields: {
type: 'array',
description: 'Fields to add to this collection',
items: {
type: 'object',
properties: {
slug: { type: 'string', description: 'Field identifier' },
name: { type: 'string', description: 'Field display name' },
type: { type: 'string', description: 'Field type (REQUIRED) - use get_field_types to see options' },
description: { type: 'string', description: 'Optional field description' },
isRequired: { type: 'boolean', description: 'Whether field is required' },
options: { type: 'string', description: 'For select/multiSelect: comma-separated options' },
referenceCollection: { type: 'string', description: 'For relation type: slug of referenced collection' },
},
required: ['slug', 'name', 'type'],
},
},
},
required: ['slug', 'name', 'nameSingular'],
},
},
fieldsToAdd: {
type: 'array',
description: 'Fields to add to existing collections',
items: {
type: 'object',
properties: {
collectionSlug: { type: 'string', description: 'Collection slug to add fields to' },
fields: {
type: 'array',
items: {
type: 'object',
properties: {
slug: { type: 'string', description: 'Field identifier' },
name: { type: 'string', description: 'Field display name' },
type: { type: 'string', description: 'Field type (REQUIRED) - use get_field_types to see options' },
description: { type: 'string', description: 'Optional field description' },
isRequired: { type: 'boolean', description: 'Whether field is required' },
options: { type: 'string', description: 'For select/multiSelect: comma-separated options' },
referenceCollection: { type: 'string', description: 'For relation type: slug of referenced collection' },
},
required: ['slug', 'name', 'type'],
},
},
},
required: ['collectionSlug', 'fields'],
},
},
},
required: ['projectId'],
},
},
{
name: 'generate_sample_items',
description: 'Generate sample items for collections with placeholder content. Automatically handles dependency ordering - collections with relation fields are populated after their parent collections. Use this after creating new collections with sync_schema to preview how they will look. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name. Use list_projects to see available projects.',
},
collectionSlugs: {
type: 'array',
items: { type: 'string' },
description: 'Optional: Specific collection slugs to generate samples for. If not provided, generates samples for all empty collections.',
},
},
required: ['projectId'],
},
},
{
name: 'create_cms_item',
description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Creates a new CMS item. For relation fields, pass the related item ID (not name). For richText fields, use HTML content. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name.',
},
collectionSlug: {
type: 'string',
description: 'The collection slug to add the item to (e.g., "blog", "team").',
},
name: {
type: 'string',
description: 'The name/title of the item.',
},
slug: {
type: 'string',
description: 'Optional URL slug. Auto-generated from name if not provided.',
},
data: {
type: 'object',
description: 'The field values for this item. Keys should match the collection field slugs.',
},
publishedAt: {
type: 'string',
description: 'Optional ISO date string. Defaults to now. Set to null for draft.',
},
},
required: ['projectId', 'collectionSlug', 'name', 'data'],
},
},
{
name: 'list_cms_items',
description: 'List items in a CMS collection. Use this to see what content exists in a collection. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name.',
},
collectionSlug: {
type: 'string',
description: 'The collection slug to list items from (e.g., "blog", "team").',
},
limit: {
type: 'number',
description: 'Optional: Maximum number of items to return.',
},
sort: {
type: 'string',
description: 'Optional: Field to sort by (e.g., "publishedAt", "name").',
},
order: {
type: 'string',
enum: ['asc', 'desc'],
description: 'Optional: Sort order. Defaults to desc.',
},
},
required: ['projectId', 'collectionSlug'],
},
},
{
name: 'get_cms_item',
description: 'Get a single CMS item by its slug. Use this to see the full details of a specific item. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name.',
},
collectionSlug: {
type: 'string',
description: 'The collection slug (e.g., "blog", "team").',
},
itemSlug: {
type: 'string',
description: 'The slug of the item to retrieve.',
},
},
required: ['projectId', 'collectionSlug', 'itemSlug'],
},
},
{
name: 'update_cms_item',
description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Updates an existing CMS item. Only provided fields are changed. Requires authentication.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name.',
},
collectionSlug: {
type: 'string',
description: 'The collection slug (e.g., "blog", "team").',
},
itemSlug: {
type: 'string',
description: 'The slug of the item to update.',
},
name: {
type: 'string',
description: 'Optional: New name for the item.',
},
data: {
type: 'object',
description: 'Optional: Updated field values. Only provided fields will be changed.',
},
publishedAt: {
type: 'string',
description: 'Optional: New publish date (ISO string), or null to unpublish.',
},
},
required: ['projectId', 'collectionSlug', 'itemSlug'],
},
},
{
name: 'delete_cms_item',
description: 'FIRST: Call get_started(intent: "add_content"). REQUIRES explicit user confirmation before calling. Ask the user to confirm deletion first. Never delete without user confirmation. This action cannot be undone.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Project ID (UUID) or project name.',
},
collectionSlug: {
type: 'string',
description: 'The collection slug (e.g., "blog", "team").',
},
itemSlug: {
type: 'string',
description: 'The slug of the item to delete.',
},
confirmDelete: {
type: 'boolean',
description: 'Must be true. Only set this after the user has explicitly confirmed they want to delete the item.',
},
},
required: ['projectId', 'collectionSlug', 'itemSlug', 'confirmDelete'],
},
},
];
// Handle list tools request
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool calls
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const params = args;
try {
let result;
switch (name) {
case 'get_started':
result = await (0, get_started_1.getStarted)({
intent: params.intent,
projectId: params.projectId,
});
break;
case 'validate_manifest':
result = await (0, validate_manifest_1.validateManifest)(params.manifest);
break;
case 'validate_template':
result = await (0, validate_template_1.validateTemplate)(params.html, params.templateType, params.collectionSlug, params.projectId);
break;
case 'validate_package': {
let templateContents;
if (params.templateContentsJson) {
try {
templateContents = JSON.parse(params.templateContentsJson);
}
catch {
templateContents = undefined;
}
}
result = await (0, validate_package_1.validatePackage)(params.fileList, params.manifestContent, templateContents);
break;
}
case 'get_example':
result = await (0, get_example_1.getExample)(params.exampleType);
break;
case 'get_conversion_guide':
result = await (0, get_conversion_guide_1.getConversionGuide)(params.section || 'full');
break;
case 'list_projects':
result = await (0, list_projects_1.listProjects)();
break;
case 'get_tenant_schema':
result = await (0, get_tenant_schema_1.getTenantSchema)(params.projectId);
break;
case 'create_site':
result = await (0, create_site_1.createSite)(params.name, params.subdomain, params.confirmCreate);
break;
case 'deploy_package':
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.force);
break;
case 'get_field_types':
result = await (0, get_field_types_1.getFieldTypes)();
break;
case 'sync_schema':
result = await (0, sync_schema_1.syncSchema)({
projectId: params.projectId,
collections: params.collections,
fieldsToAdd: params.fieldsToAdd,
});
break;
case 'generate_sample_items':
result = await (0, generate_samples_1.generateSampleItems)({
projectId: params.projectId,
collectionSlugs: params.collectionSlugs,
});
break;
case 'create_cms_item':
result = await (0, cms_items_1.createCmsItem)({
projectId: params.projectId,
collectionSlug: params.collectionSlug,
name: params.name,
slug: params.slug,
data: params.data,
publishedAt: params.publishedAt,
});
break;
case 'list_cms_items':
result = await (0, cms_items_1.listCmsItems)({
projectId: params.projectId,
collectionSlug: params.collectionSlug,
limit: params.limit,
sort: params.sort,
order: params.order,
});
break;
case 'get_cms_item':
result = await (0, cms_items_1.getCmsItem)({
projectId: params.projectId,
collectionSlug: params.collectionSlug,
itemSlug: params.itemSlug,
});
break;
case 'update_cms_item':
result = await (0, cms_items_1.updateCmsItem)({
projectId: params.projectId,
collectionSlug: params.collectionSlug,
itemSlug: params.itemSlug,
name: params.name,
data: params.data,
publishedAt: params.publishedAt,
});
break;
case 'delete_cms_item':
result = await (0, cms_items_1.deleteCmsItem)({
projectId: params.projectId,
collectionSlug: params.collectionSlug,
itemSlug: params.itemSlug,
confirmDelete: params.confirmDelete,
});
break;
default:
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true,
};
}
return {
content: [{ type: 'text', text: result }],
};
}
catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
});
// Example types for resources
const EXAMPLE_TYPES = [
'manifest_basic', 'manifest_custom_paths', 'manifest_minimal_with_ui',
'blog_index_template', 'blog_post_template', 'team_template', 'downloads_template',
'authors_template', 'author_detail_template', 'custom_collection_template',
'form_handling', 'asset_paths', 'image_handling', 'relation_fields', 'data_edit_keys',
'each_loop', 'conditional_if', 'nested_fields', 'featured_posts', 'parent_context',
'equality_comparison', 'comparison_helpers', 'youtube_embed', 'nested_collection_loop',
'loop_variables', 'common_mistakes'
];
// Guide sections for resources
const GUIDE_SECTIONS = [
'full', 'first_steps', 'analysis', 'structure', 'seo', 'manifest',
'templates', 'tokens', 'forms', 'assets', 'checklist', 'common_mistakes'
];
// Handle list resources request
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
const resources = [
// Help resource
{
uri: 'fastmode://help',
name: 'How to Use FastMode MCP',
description: 'Quick start guide for using FastMode tools and resources',
mimeType: 'text/plain',
},
// Field types reference
{
uri: 'fastmode://reference/field-types',
name: 'Available Field Types',
description: 'List of field types for creating collections (text, richText, image, etc.)',
mimeType: 'text/plain',
},
// Guide sections
...GUIDE_SECTIONS.map(section => ({
uri: `fastmode://guide/${section}`,
name: `Conversion Guide: ${section.replace(/_/g, ' ')}`,
description: `Website conversion guide - ${section} section`,
mimeType: 'text/plain',
})),
// Code examples
...EXAMPLE_TYPES.map(type => ({
uri: `fastmode://examples/${type}`,
name: `Example: ${type.replace(/_/g, ' ')}`,
description: `Code example for ${type.replace(/_/g, ' ')}`,
mimeType: 'text/plain',
})),
];
return { resources };
});
// Handle read resource request
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
// Help resource
if (uri === 'fastmode://help') {
return {
contents: [{
uri,
mimeType: 'text/plain',
text: `# FastMode MCP Server
## Quick Start
Call the **get_started** tool for guided help based on your task.
## Available Tools
- **list_projects** - List your FastMode projects
- **get_tenant_schema** - Get collections/fields for a project
- **create_cms_item** / **update_cms_item** / **delete_cms_item** - Manage content
- **deploy_package** - Deploy a website
- **sync_schema** - Create collections and fields
- **validate_manifest** / **validate_template** - Validate your code
## Available Resources
- **fastmode://reference/field-types** - Field types for creating collections
- **fastmode://guide/{section}** - Website conversion guide sections
- **fastmode://examples/{type}** - Code examples for common patterns
## Authentication
Authentication happens automatically via browser login on first use.`,
}],
};
}
// Field types reference
if (uri === 'fastmode://reference/field-types') {
const content = await (0, get_field_types_1.getFieldTypes)();
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content,
}],
};
}
// Guide sections
const guideMatch = uri.match(/^fastmode:\/\/guide\/(.+)$/);
if (guideMatch) {
const section = guideMatch[1];
const content = await (0, get_conversion_guide_1.getConversionGuide)(section);
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content,
}],
};
}
// Code examples
const exampleMatch = uri.match(/^fastmode:\/\/examples\/(.+)$/);
if (exampleMatch) {
const exampleType = exampleMatch[1];
const content = await (0, get_example_1.getExample)(exampleType);
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content,
}],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
async function main() {
const transport = new stdio_js_1.StdioServerTransport();
// Check authentication status (non-blocking)
const { getValidCredentials } = await Promise.resolve().then(() => __importStar(require('./lib/credentials')));
const { setAuthInProgress } = await Promise.resolve().then(() => __importStar(require('./lib/auth-state')));
const credentials = await getValidCredentials();
if (!credentials) {
// No valid credentials - start device flow in background
const { startDeviceFlow } = await Promise.resolve().then(() => __importStar(require('./lib/device-flow')));
console.error('FastMode MCP Server - Authentication Required');
console.error('Starting authentication flow...');
// Start auth in background but track it so tools can wait
const authPromise = startDeviceFlow();
setAuthInProgress(authPromise);
authPromise.then(result => {
console.error(result);
setAuthInProgress(null);
}).catch(err => {
console.error('Authentication error:', err);
setAuthInProgress(null);
});
}
else {
console.error(`FastMode MCP Server - Authenticated as ${credentials.email}`);
}
// Connect to transport immediately so Cursor doesn't timeout
await server.connect(transport);
console.error('FastMode MCP Server running on stdio');
}
main().catch(console.error);