Skip to main content
Glama

Cosmic MCP Server

by patgpt
server.ts12.2 kB
#!/usr/bin/env node /** * @fileoverview Main MCP Server - Migrated to use official MCP TypeScript SDK * * This is the main entry point for the Cosmic MCP Server. It sets up the MCP server * with all the necessary tools for interacting with Cosmic CMS, handles dependency * injection, and provides a clean interface for AI assistants to manage content. * * @author Cosmic MCP Team * @version 2.0.0 */ import { createBucketClient } from '@cosmicjs/sdk'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; // Configuration and Types import { config } from './config.js'; import { tools } from './manifest.js'; import type { CosmicClient } from './types/cosmic.types.js'; // Repositories import { MediaRepository } from './repositories/media.repository.js'; import { ObjectRepository } from './repositories/object.repository.js'; import { TypeRepository } from './repositories/type.repository.js'; // Services import { MediaService } from './services/media.service.js'; import { ObjectService } from './services/object.service.js'; import { TypeService } from './services/type.service.js'; // Utilities and Validation import { validateToolInput, type CreateObjectInput, type DeleteMediaInput, type DeleteObjectInput, type GetObjectInput, type ListMediaInput, type ListObjectsInput, type SearchObjectsInput, type UpdateObjectInput, type UploadMediaInput, } from './validation.js'; // Error handling and logging import { UnknownToolError } from './errors/validation.error.js'; import logger from './utils/logger.js'; import { defaultRateLimiter } from './utils/rate-limiter.js'; /** * Main MCP Server class that handles all Cosmic CMS operations * * @description This class sets up and manages the MCP server, handling tool registration, * request routing, dependency injection, and error handling. It provides a complete * interface for AI assistants to interact with Cosmic CMS. * * @example * ```typescript * const server = new CosmicMCPServer(); * await server.start(); * ``` */ class CosmicMCPServer { /** Logger instance for server operations */ private logger = logger; /** MCP server instance */ private server: Server; // Dependencies private cosmicClient!: CosmicClient; private objectRepository!: ObjectRepository; private mediaRepository!: MediaRepository; private typeRepository!: TypeRepository; private objectService!: ObjectService; private mediaService!: MediaService; private typeService!: TypeService; constructor() { // Initialize the MCP server this.server = new Server( { name: 'cosmic-mcp', version: '2.0.0', }, { capabilities: { tools: {}, }, }, ); this.initializeDependencies(); this.setupRequestHandlers(); } private initializeDependencies(): void { // Initialize Cosmic client this.cosmicClient = createBucketClient({ bucketSlug: config.bucketSlug, readKey: config.readKey, writeKey: config.writeKey, }) as unknown as CosmicClient; // Initialize repositories this.objectRepository = new ObjectRepository(this.cosmicClient); this.mediaRepository = new MediaRepository(this.cosmicClient); this.typeRepository = new TypeRepository(this.cosmicClient); // Initialize services with dependency injection this.objectService = new ObjectService( this.objectRepository, this.typeRepository, ); this.mediaService = new MediaService(this.mediaRepository); this.typeService = new TypeService( this.typeRepository, this.objectRepository, ); this.logger.info('Dependencies initialized successfully'); } private setupRequestHandlers(): void { // Handle tools/list requests this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools, }; }); // Handle tools/call requests this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; this.logger.info('Processing tool call', { tool: name, hasArgs: !!args, }); // Global rate limiting defaultRateLimiter.checkAndConsume(`tool_${name}`); try { // Validate input const validatedInput = validateToolInput(name, args); // Route to appropriate handler const result = await this.executeToolHandler(name, validatedInput); this.logger.info('Tool call completed successfully', { tool: name, resultType: typeof result, }); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { this.logger.error('Tool call failed', { tool: name, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } // Tool Handler Methods (unchanged from original implementation) private async handleListObjects(input: ListObjectsInput): Promise<unknown> { const query: any = { status: input.status, limit: input.limit, skip: input.skip, sort: input.sort, }; if (input.type_slug) query.typeSlug = input.type_slug; if (input.locale) query.locale = input.locale; return await this.objectService.listObjects(query); } private async handleGetObject(input: GetObjectInput): Promise<unknown> { if ('id' in input) { const query: any = { id: input.id }; if (input.locale) query.locale = input.locale; return await this.objectService.getObject(query); } else { const query: any = { slug: input.slug, typeSlug: input.type_slug }; if (input.locale) query.locale = input.locale; return await this.objectService.getObject(query); } } private async handleCreateObject(input: CreateObjectInput): Promise<unknown> { const data: any = { title: input.title, typeSlug: input.type_slug, status: input.status, }; if (input.slug) data.slug = input.slug; if (input.content) data.content = input.content; if (input.metadata) data.metadata = input.metadata; if (input.locale) data.locale = input.locale; return await this.objectService.createObject(data); } private async handleUpdateObject(input: UpdateObjectInput): Promise<unknown> { const query = 'id' in input ? { id: input.id } : { slug: input.slug, typeSlug: input.type_slug }; const data: any = {}; if (input.title) data.title = input.title; if (input.content !== undefined) data.content = input.content; if (input.status) data.status = input.status; if (input.metadata) data.metadata = input.metadata; if (input.locale) data.locale = input.locale; return await this.objectService.updateObject(query, data); } private async handleDeleteObject(input: DeleteObjectInput): Promise<unknown> { const query = 'id' in input ? { id: input.id } : { slug: input.slug, typeSlug: input.type_slug }; await this.objectService.deleteObject(query); return { message: 'Object deleted successfully' }; } private async handleListObjectTypes(): Promise<unknown> { return await this.typeService.listObjectTypes({}); } private async handleUploadMedia(input: UploadMediaInput): Promise<unknown> { const data: any = { fileData: input.file_data, filename: input.filename, }; if (input.folder) data.folder = input.folder; return await this.mediaService.uploadMedia(data); } private async handleListMedia(input: ListMediaInput): Promise<unknown> { const query: any = { limit: input.limit, skip: input.skip, }; if (input.folder) query.folder = input.folder; return await this.mediaService.listMedia(query); } private async handleDeleteMedia(input: DeleteMediaInput): Promise<unknown> { await this.mediaService.deleteMedia({ id: input.id }); return { message: 'Media deleted successfully' }; } private async handleSearchObjects( input: SearchObjectsInput, ): Promise<unknown> { const query: any = { query: input.query, limit: input.limit, }; if (input.type_slug) query.typeSlug = input.type_slug; if (input.locale) query.locale = input.locale; return await this.objectService.searchObjects(query); } // Execute tool handler logic (extracted for reuse) private async executeToolHandler( toolName: string, validatedInput: unknown, ): Promise<unknown> { switch (toolName) { case 'list_objects': return await this.handleListObjects(validatedInput as ListObjectsInput); case 'get_object': return await this.handleGetObject(validatedInput as GetObjectInput); case 'create_object': return await this.handleCreateObject( validatedInput as CreateObjectInput, ); case 'update_object': return await this.handleUpdateObject( validatedInput as UpdateObjectInput, ); case 'delete_object': return await this.handleDeleteObject( validatedInput as DeleteObjectInput, ); case 'list_object_types': return await this.handleListObjectTypes(); case 'upload_media': return await this.handleUploadMedia(validatedInput as UploadMediaInput); case 'list_media': return await this.handleListMedia(validatedInput as ListMediaInput); case 'delete_media': return await this.handleDeleteMedia(validatedInput as DeleteMediaInput); case 'search_objects': return await this.handleSearchObjects( validatedInput as SearchObjectsInput, ); default: throw new UnknownToolError(toolName); } } // Server lifecycle methods public async start(): Promise<void> { try { this.logger.info('Starting Cosmic MCP Server', { bucketSlug: config.bucketSlug, toolsCount: tools.length, version: '2.0.0', }); // Create transport and connect const transport = new StdioServerTransport(); await this.server.connect(transport); this.logger.info('MCP Server ready for requests'); } catch (error) { this.logger.error('Server failed to start', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); process.exit(1); } } public async stop(): Promise<void> { this.logger.info('Stopping Cosmic MCP Server'); // Cleanup rate limiter defaultRateLimiter.destroy(); this.logger.info('Server stopped gracefully'); } } // Global error handlers process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully'); process.exit(0); }); process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully'); process.exit(0); }); process.on('uncaughtException', (error) => { logger.error('Uncaught exception', { error: error.message, stack: error.stack, name: error.name, }); process.exit(1); }); process.on('unhandledRejection', (reason) => { logger.error('Unhandled rejection', { reason: reason instanceof Error ? reason.message : String(reason), stack: reason instanceof Error ? reason.stack : undefined, }); process.exit(1); }); // Start the server async function main() { const server = new CosmicMCPServer(); await server.start(); } if (require.main === module) { main().catch((error) => { logger.error('Fatal error starting server', error); process.exit(1); }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/patgpt/cosmic-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server