#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; // MCP Forge License Validation async function validateLicense() { const apiKey = process.env.MCPFORGE_API_KEY; if (!apiKey) { console.error('\n❌ MCP Forge API key required\n'); console.error(' 1. Get your API key at: https://mcpforge.org/dashboard'); console.error(' 2. Add to your MCP configuration:'); console.error(' "env": {'); console.error(' "MCPFORGE_API_KEY": "your-key-here"'); console.error(' }\n'); process.exit(1); } try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const response = await fetch('https://www.mcpforge.org/api/validate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'x-server-type': 'payload' }, signal: controller.signal }); clearTimeout(timeout); const data = await response.json() as { valid: boolean }; if (!data.valid) { console.error('\n❌ Invalid or expired MCP Forge license\n'); console.error(' Visit https://mcpforge.org/dashboard to:'); console.error(' • Check your trial status'); console.error(' • Upgrade to a paid plan'); console.error(' • Get support\n'); process.exit(1); } console.log('✅ MCP Forge license validated'); } catch (error: any) { // STRICT MODE: Exit on any error (production requirement) console.error('\n❌ License validation failed:', error.message); console.error(' Please check your internet connection and try again.\n'); console.error(' Visit https://www.mcpforge.org/dashboard for support.\n'); process.exit(1); } } // Call validation before server starts await validateLicense(); // Tool implementations import { articlesListTool, articlesCreateTool, articlesGetTool, articlesUpdateTool, articlesDeleteTool } from './tools/articles.js'; import { sitesListTool, sitesCreateTool, sitesGetTool, sitesUpdateTool, sitesDeleteTool } from './tools/sites.js'; import { mediaListTool, mediaUploadTool, mediaGetTool, mediaDeleteTool } from './tools/media.js'; import { searchTool } from './tools/search.js'; import { serverInfoTool } from './tools/server-info.js'; const server = new Server( { name: 'payload-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Define tool schemas const tools = [ // Article Management { name: 'articles_list', description: 'List articles from Payload CMS with optional filtering, pagination, and search', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination (default: 1)' }, limit: { type: 'number', description: 'Number of items per page (default: 10, max: 100)' }, sort: { type: 'string', description: 'Sort field (e.g., "-createdAt", "title")' }, where: { type: 'object', description: 'Filter conditions (JSON object)' }, }, }, }, { name: 'articles_get', description: 'Get a specific article by ID from Payload CMS', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Article ID', required: true }, }, required: ['id'], }, }, { name: 'articles_create', description: 'Create a new article in Payload CMS', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Article title', required: true }, content: { type: 'string', description: 'Article content (rich text/markdown)' }, author: { type: 'string', description: 'Author ID' }, category: { type: 'string', description: 'Category ID' }, status: { type: 'string', enum: ['draft', 'published'], description: 'Publication status' }, meta: { type: 'object', description: 'Additional metadata' }, }, required: ['title'], }, }, { name: 'articles_update', description: 'Update an existing article in Payload CMS', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Article ID', required: true }, title: { type: 'string', description: 'Article title' }, content: { type: 'string', description: 'Article content' }, author: { type: 'string', description: 'Author ID' }, category: { type: 'string', description: 'Category ID' }, status: { type: 'string', enum: ['draft', 'published'], description: 'Publication status' }, }, required: ['id'], }, }, { name: 'articles_delete', description: 'Delete an article from Payload CMS', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Article ID', required: true }, }, required: ['id'], }, }, // Site Management { name: 'sites_list', description: 'List sites from Payload CMS', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number for pagination' }, limit: { type: 'number', description: 'Number of items per page' }, }, }, }, { name: 'sites_get', description: 'Get a specific site by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Site ID', required: true }, }, required: ['id'], }, }, { name: 'sites_create', description: 'Create a new site in Payload CMS', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Site name', required: true }, domain: { type: 'string', description: 'Site domain' }, settings: { type: 'object', description: 'Site settings/configuration' }, }, required: ['name'], }, }, // Media Management { name: 'media_list', description: 'List media files from Payload CMS', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number' }, limit: { type: 'number', description: 'Items per page' }, }, }, }, { name: 'media_upload', description: 'Upload a media file to Payload CMS', inputSchema: { type: 'object', properties: { file: { type: 'string', description: 'File path or base64 data', required: true }, alt: { type: 'string', description: 'Alt text for the media' }, caption: { type: 'string', description: 'Caption for the media' }, }, required: ['file'], }, }, { name: 'media_get', description: 'Get media file details by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Media ID', required: true }, }, required: ['id'], }, }, // Search { name: 'search', description: 'Search across all Payload CMS content', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query', required: true }, collections: { type: 'array', items: { type: 'string' }, description: 'Collections to search in (articles, sites, media, etc.)', }, limit: { type: 'number', description: 'Maximum results' }, }, required: ['query'], }, }, // Diagnostics { name: 'server_info', description: 'Get server status, connection health, and diagnostic information', inputSchema: { type: 'object', properties: {}, }, }, ]; // Register tools/list handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Register tools/call handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { // Articles case 'articles_list': result = await articlesListTool(args || {}); break; case 'articles_get': result = await articlesGetTool(args); break; case 'articles_create': result = await articlesCreateTool(args); break; case 'articles_update': result = await articlesUpdateTool(args); break; case 'articles_delete': result = await articlesDeleteTool(args); break; // Sites case 'sites_list': result = await sitesListTool(args || {}); break; case 'sites_get': result = await sitesGetTool(args); break; case 'sites_create': result = await sitesCreateTool(args); break; // Media case 'media_list': result = await mediaListTool(args || {}); break; case 'media_upload': result = await mediaUploadTool(args); break; case 'media_get': result = await mediaGetTool(args); break; // Search case 'search': result = await searchTool(args); break; // Diagnostics case 'server_info': result = await serverInfoTool(); break; default: throw new Error(`Unknown tool: ${name}`); } return { content: [ { type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2), }, ], }; } catch (error: any) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], isError: true, }; } }); // Start server with stdio transport async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // Keep the process alive process.on('SIGINT', async () => { await server.close(); process.exit(0); }); process.on('SIGTERM', async () => { await server.close(); process.exit(0); }); } main().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); });