Skip to main content
Glama

MiniMax MCP JS

Official
by MiniMax-AI
mcp-rest-server.ts33.4 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { RestServerTransport } from '@chatmcp/sdk/server/rest.js'; import { Config } from './types/index.js'; import { DEFAULT_SERVER_ENDPOINT, DEFAULT_SERVER_PORT, ERROR_API_KEY_REQUIRED, ERROR_API_HOST_REQUIRED, RESOURCE_MODE_URL, TRANSPORT_MODE_REST, OUTPUT_DIRECTORY_DESCRIPTION, } from './const/index.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ListPromptsRequestSchema, GetPromptRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import { ServiceManager } from './services/index.js'; import { MediaService } from './services/media-service.js'; import { MiniMaxAPI } from './utils/api.js'; import { ConfigManager } from './config/ConfigManager.js'; import { COMMON_REST_ARGUMENTS, COMMON_REST_INPUT_SCHEMA_PROPERTIES } from './schema/index.js'; // Retry configuration const MAX_RETRY_ATTEMPTS = 3; const RETRY_DELAY = 1000; /** * MCP REST Server class, provides REST API implementation of MCPServer functionality */ export class MCPRestServer { private server!: Server; private mcpServer!: McpServer; private config!: Config; private api!: MiniMaxAPI; private mediaService!: MediaService; private transport: RestServerTransport | null = null; /** * Create a REST server instance * @param customConfig Configuration object */ constructor(customConfig?: Partial<Config>) { // Initialize configuration this.initializeConfig(customConfig); // Create API and service instances this.api = new MiniMaxAPI(this.config); this.mediaService = new MediaService(this.api); // Create MCP server instance - used to get tool definitions this.mcpServer = new McpServer({ name: 'minimax-mcp-js', version: '1.0.0', }); // Create low-level server instance this.server = new Server( { name: 'minimax-mcp-js', version: '1.0.0' }, { capabilities: { tools: {}, resources: {}, prompts: {} } } ); // Register request handlers this.registerRequestHandlers(); } /** * Initialize configuration * @param customConfig Custom configuration */ private initializeConfig(customConfig?: Partial<Config>): void { // Use ConfigManager to get configuration, automatically handling priorities this.config = ConfigManager.getConfig(customConfig); // Ensure REST transport mode is used if (this.config.server) { this.config.server.mode = TRANSPORT_MODE_REST; } else { this.config.server = { mode: TRANSPORT_MODE_REST, port: DEFAULT_SERVER_PORT, endpoint: DEFAULT_SERVER_ENDPOINT }; } // console.log(`[${new Date().toISOString()}] REST server configuration initialized`); } /** * Update configuration and recreate API instance * @param newConfig New configuration object */ public updateConfig(newConfig: Partial<Config>): void { // Use ConfigManager to merge configurations this.config = ConfigManager.getConfig(newConfig, this.config); // Ensure REST transport mode is used if (this.config.server) { this.config.server.mode = TRANSPORT_MODE_REST; } // Update API instance and services this.api = new MiniMaxAPI(this.config); this.mediaService = new MediaService(this.api); // console.log(`[${new Date().toISOString()}] REST server configuration updated`); } /** * Register all request handlers */ private registerRequestHandlers(): void { // Tool-related handlers this.registerToolHandlers(); // Resource-related handlers this.registerResourceHandlers(); // Prompt-related handlers this.registerPromptHandlers(); } /** * Extract API key from request * Only get API key from meta.auth */ private extractApiKeyFromRequest(request: any): string | undefined { // Only get from meta.auth const metaAuth = request.params?._meta?.auth || request.params?.meta?.auth; if (metaAuth?.api_key) return metaAuth.api_key; if (metaAuth?.apiKey) return metaAuth.apiKey; // API key not found return undefined; } /** * Extract configuration from request * Only supports getting configuration from params.meta.auth */ private extractRequestConfig(request: any): Partial<Config> { const config: Partial<Config> = {}; // Get configuration from params._meta.auth or meta.auth try { // First try to get the _meta.auth field from the MCP protocol let metaAuth = request.params?._meta?.auth; // If it doesn't exist, try to get the meta.auth field if (!metaAuth && request.params?.meta?.auth) { metaAuth = request.params.meta.auth; } if (metaAuth && typeof metaAuth === 'object') { // console.log(`[${new Date().toISOString()}] Getting configuration from request meta.auth`); // Use ConfigManager to extract configuration const metaAuthConfig = ConfigManager.extractConfigFromMetaAuth(metaAuth); if (metaAuthConfig) { Object.assign(config, metaAuthConfig); } } } catch (error) { // console.warn(`[${new Date().toISOString()}] Failed to extract configuration from meta.auth:`, error); } return config; } /** * Create configuration for request */ private getRequestConfig(request: any): Config { // Get request-specific configuration const requestConfig = this.extractRequestConfig(request); // Merge configurations, request configuration has highest priority return ConfigManager.getConfig(requestConfig, this.config); } /** * Register tool-related request handlers */ private registerToolHandlers(): void { // List tools handler this.server.setRequestHandler(ListToolsRequestSchema, async (request) => { try { return { tools: [ { name: 'text_to_audio', description: 'Convert text to audio', arguments: [ { name: 'text', description: 'Text to convert to audio', required: true }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'voiceId', description: 'Voice ID to use, e.g. "female-shaonv"', required: false }, { name: 'model', description: 'Model to use', required: false }, { name: 'speed', description: 'Speech speed (0.5-2.0)', required: false }, { name: 'vol', description: 'Speech volume (0.1-10.0)', required: false }, { name: 'pitch', description: 'Speech pitch (-12 to 12)', required: false }, { name: 'emotion', description: 'Speech emotion, values: ["happy", "sad", "angry", "fearful", "disgusted", "surprised", "neutral"]', required: false }, { name: 'format', description: 'Audio format, values: ["pcm", "mp3","flac", "wav"]', required: false }, { name: 'sampleRate', description: 'Sample rate (Hz), values: [8000, 16000, 22050, 24000, 32000, 44100]', required: false }, { name: 'bitrate', description: 'Bitrate (bps), values: [64000, 96000, 128000, 160000, 192000, 224000, 256000, 320000]', required: false }, { name: 'channel', description: 'Audio channels, values: [1, 2]', required: false }, { name: 'languageBoost', description: `Enhance the ability to recognize specified languages and dialects. Supported values include: 'Chinese', 'Chinese,Yue', 'English', 'Arabic', 'Russian', 'Spanish', 'French', 'Portuguese', 'German', 'Turkish', 'Dutch', 'Ukrainian', 'Vietnamese', 'Indonesian', 'Japanese', 'Italian', 'Korean', 'Thai', 'Polish', 'Romanian', 'Greek', 'Czech', 'Finnish', 'Hindi', 'auto', default is 'auto'`, required: false }, { name: 'subtitleEnable', description: `The parameter controls whether the subtitle service is enabled. The model must be 'speech-01-turbo' or 'speech-01-hd'. If this parameter is not provided, the default value is false`, required: false }, { name: 'outputFile', description: 'Output file path, auto-generated if not provided', required: false } ], inputSchema: { type: 'object', properties: { text: { type: 'string' }, outputDirectory: { type: 'string' }, voiceId: { type: 'string' }, model: { type: 'string' }, speed: { type: 'number' }, vol: { type: 'number' }, pitch: { type: 'number' }, emotion: { type: 'string' }, format: { type: 'string' }, sampleRate: { type: 'number' }, bitrate: { type: 'number' }, channel: { type: 'number' }, languageBoost: { type: 'string' }, subtitleEnable: { type: 'boolean' }, outputFile: { type: 'string' } }, required: ['text'] } }, { name: 'list_voices', description: 'List all available voices', arguments: [ { name: 'voiceType', description: 'Type of voices to list, values: ["all", "system", "voice_cloning"]', required: false } ], inputSchema: { type: 'object', properties: { voiceType: { type: 'string' } }, required: ['voiceType'] } }, { name: 'play_audio', description: 'Play audio file. Supports WAV and MP3 formats. Does not support video.', arguments: [ { name: 'inputFilePath', description: 'Path to audio file to play', required: true }, { name: 'isUrl', description: 'Whether audio file is a URL', required: false } ], inputSchema: { type: 'object', properties: { inputFilePath: { type: 'string' }, isUrl: { type: 'boolean' } }, required: ['inputFilePath'] } }, { name: 'text_to_image', description: 'Generate image based on text prompt', arguments: [ { name: 'prompt', description: 'Text prompt for image generation', required: true }, { name: 'model', description: 'Model to use', required: false }, { name: 'aspectRatio', description: 'Image aspect ratio, values: ["1:1", "16:9","4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]', required: false }, { name: 'n', description: 'Number of images to generate (1-9)', required: false }, { name: 'promptOptimizer', description: 'Whether to optimize prompt', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'outputFile', description: 'Output file path, auto-generated if not provided', required: false } ], inputSchema: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string' }, aspectRatio: { type: 'string' }, n: { type: 'number' }, promptOptimizer: { type: 'boolean' }, outputDirectory: { type: 'string' }, outputFile: { type: 'string' } }, required: ['prompt'] } }, { name: 'generate_video', description: 'Generate video based on text prompt', arguments: [ { name: 'prompt', description: 'Text prompt for video generation', required: true }, { name: 'model', description: 'Model to use, values: ["T2V-01", "T2V-01-Director", "I2V-01", "I2V-01-Director", "I2V-01-live", "MiniMax-Hailuo-02"]', required: false }, { name: 'firstFrameImage', description: 'First frame image', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'outputFile', description: 'Output file path, auto-generated if not provided', required: false }, { name: 'async_mode', description: 'Whether to use async mode. Defaults to False. If True, the video generation task will be submitted asynchronously and the response will return a task_id. Should use `query_video_generation` tool to check the status of the task and get the result', required: false }, { name: 'resolution', description: 'The resolution of the video. The model must be "MiniMax-Hailuo-02". Values range ["768P", "1080P"]', required: false }, { name: 'duration', description: 'The duration of the video. The model must be "MiniMax-Hailuo-02". Values can be 6 and 10.', required: false }, ], inputSchema: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string' }, firstFrameImage: { type: 'string' }, outputDirectory: { type: 'string' }, outputFile: { type: 'string' }, async_mode: { type: 'boolean' }, resolution: { type: 'string' }, duration: { type: 'number' } }, required: ['prompt'] } }, { name: 'voice_clone', description: 'Clone voice using provided audio file', arguments: [ { name: 'voiceId', description: 'Voice ID to use', required: true }, { name: 'audioFile', description: 'Audio file path', required: true }, { name: 'text', description: 'Text for demo audio', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'isUrl', description: 'Whether audio file is a URL', required: false } ], inputSchema: { type: 'object', properties: { voiceId: { type: 'string' }, audioFile: { type: 'string' }, text: { type: 'string' }, outputDirectory: { type: 'string' }, isUrl: { type: 'boolean' } }, required: ['voiceId', 'audioFile'] } }, { name: 'image_to_video', description: 'Generate video based on image', arguments: [ { name: 'prompt', description: 'Text prompt for video generation', required: true }, { name: 'firstFrameImage', description: 'Path to first frame image', required: true }, { name: 'model', description: 'Model to use, values: ["I2V-01", "I2V-01-Director", "I2V-01-live"]', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'outputFile', description: 'Output file path, auto-generated if not provided', required: false }, { name: 'async_mode', description: 'Whether to use async mode. Defaults to False. If True, the video generation task will be submitted asynchronously and the response will return a task_id. Should use `query_video_generation` tool to check the status of the task and get the result', required: false } ], inputSchema: { type: 'object', properties: { prompt: { type: 'string' }, firstFrameImage: { type: 'string' }, model: { type: 'string' }, outputDirectory: { type: 'string' }, outputFile: { type: 'string' }, async_mode: { type: 'boolean' } }, required: ['prompt', 'firstFrameImage'] } }, { name: 'music_generation', description: 'Generate music based on text prompt and lyrics', arguments: [ { name: 'prompt', description: 'Music creation inspiration describing style, mood, scene, etc.', required: true }, { name: 'lyrics', description: 'Song lyrics for music generation.\nUse newline (\\n) to separate each line of lyrics. Supports lyric structure tags [Intro][Verse][Chorus][Bridge][Outro]\nto enhance musicality. Character range: [10, 600] (each Chinese character, punctuation, and letter counts as 1 character)', required: true }, { name: 'sampleRate', description: 'Sample rate of generated music', required: false }, { name: 'bitrate', description: 'Bitrate of generated music', required: false }, { name: 'format', description: 'Format of generated music', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false } ], inputSchema: { type: 'object', properties: { prompt: { type: 'string' }, lyrics: { type: 'string' }, sampleRate: { type: 'number' }, bitrate: { type: 'number' }, format: { type: 'string' }, outputDirectory: { type: 'string' } }, required: ['prompt', 'lyrics'] } }, { name: 'voice_design', description: 'Generate a voice based on description prompts', arguments: [ { name: 'prompt', description: 'The prompt to generate the voice from', required: true }, { name: 'previewText', description: 'The text to preview the voice', required: true }, { name: 'voiceId', description: 'The id of the voice to use', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false } ], inputSchema: { type: 'object', properties: { prompt: { type: 'string' }, previewText: { type: 'string' }, voiceId: { type: 'string' }, outputDirectory: { type: 'string' } }, required: ['prompt', 'previewText'] } } ] }; } catch (error) { throw this.wrapError('Failed to get tool list', error); } }); // Call tool handler this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.tool; const toolParams = request.params.params || {}; try { // Create configuration and API instance for this request const requestConfig = this.getRequestConfig(request); const requestApi = new MiniMaxAPI(requestConfig); const mediaService = new MediaService(requestApi); // Log API key (partially hidden) const apiKey = this.extractApiKeyFromRequest(request); const maskedKey = apiKey ? `${apiKey.substring(0, 4)}****${apiKey.substring(apiKey.length - 4)}` : 'not provided'; // console.log(`[${new Date().toISOString()}] Using API key: ${maskedKey} to call tool: ${toolName}`); // Choose different handler function based on tool name switch (toolName) { case 'text_to_audio': return await this.handleTextToAudio(toolParams, requestApi, mediaService); case 'list_voices': return await this.handleListVoices(toolParams, requestApi, mediaService); case 'play_audio': return await this.handlePlayAudio(toolParams); case 'text_to_image': return await this.handleTextToImage(toolParams, requestApi, mediaService); case 'generate_video': return await this.handleGenerateVideo(toolParams, requestApi, mediaService); case 'voice_clone': return await this.handleVoiceClone(toolParams, requestApi, mediaService); case 'image_to_video': return await this.handleImageToVideo(toolParams, requestApi, mediaService); case 'query_video_generation': return await this.handleVideoGenerationQuery(toolParams, requestApi, mediaService); case 'music_generation': return await this.handleGenerateMusic(toolParams, requestApi, mediaService); case 'voice_design': return await this.handleVoiceDesign(toolParams, requestApi, mediaService); default: throw new Error(`Unknown tool: ${toolName}`); } } catch (error) { throw this.wrapError(`Failed to call tool ${toolName}`, error); } }); } /** * Handle text to speech request */ private async handleTextToAudio(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.generateSpeech(args); return result; } catch (error) { if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to generate speech, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleTextToAudio(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to generate speech', error); } } /** * Handle list voices request */ private async handleListVoices(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.listVoices(args); return result; } catch (error) { if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to list voices, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleListVoices(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to list voices', error); } } /** * Handle play audio request */ private async handlePlayAudio(args: any): Promise<any> { try { // This operation needs to use the current mediaService instance return { content: [ { type: 'text', text: `Playing audio: ${args.inputFilePath}`, }, ], }; } catch (error) { throw this.wrapError('Failed to play audio', error); } } /** * Handle text to image request */ private async handleTextToImage(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.generateImage(args); return result; } catch (error) { if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to generate image, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleTextToImage(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to generate image', error); } } /** * Handle generate video request */ private async handleGenerateVideo(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.generateVideo(args); return result; } catch (error) { if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to generate video, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleGenerateVideo(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to generate video', error); } } /** * Handle voice clone request */ private async handleVoiceClone(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.cloneVoice(args); return result; } catch (error) { // Check if this is a real-name verification error const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('voice clone user forbidden') || errorMessage.includes('should complete real-name verification')) { // Domestic platform verification URL const verificationUrl = 'https://platform.minimaxi.com/user-center/basic-information'; return { content: [ { type: 'text', text: `Voice cloning failed: Real-name verification required. To use voice cloning feature, please:\n\n1. Visit MiniMax platform (${verificationUrl})\n2. Complete the real-name verification process\n3. Try again after verification is complete\n\nThis requirement is for security and compliance purposes.`, }, ], }; } // Regular retry mechanism if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to clone voice, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleVoiceClone(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to clone voice', error); } } /** * Handle image to video request */ private async handleImageToVideo(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Ensure model is suitable for image to video conversion if (!args.model) { args.model = 'I2V-01'; } // Ensure firstFrameImage parameter exists if (!args.firstFrameImage) { throw new Error('Missing required parameter: firstFrameImage'); } // Auto-generate output filename if not provided if (!args.outputFile) { const promptPrefix = args.prompt.substring(0, 20).replace(/[^\w]/g, '_'); args.outputFile = `i2v_${promptPrefix}_${Date.now()}`; } // Call media service to handle request const result = await mediaService.generateVideo(args); return result; } catch (error) { if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to generate video, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleImageToVideo(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to generate video', error); } } /** * Handle video generation query request */ private async handleVideoGenerationQuery(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.queryVideoGeneration(args); return result; } catch (error) { throw this.wrapError('Failed to query video generation', error); } } /** * Handle generate music request */ private async handleGenerateMusic(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.generateMusic(args); return result; } catch (error) { throw this.wrapError('Failed to generate music', error); } } /** * Handle voice design request */ private async handleVoiceDesign(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.designVoice(args); return result; } catch (error) { throw this.wrapError('Failed to design voice', error); } } /** * Register resource-related request handlers */ private registerResourceHandlers(): void { // List resources handler this.server.setRequestHandler(ListResourcesRequestSchema, async (request) => { try { // Create configuration for this request const requestConfig = this.getRequestConfig(request); // Return empty list - our implementation doesn't support resource listing return { resources: [] }; } catch (error) { throw this.wrapError('Failed to get resource list', error); } }); // Read resource handler this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { try { // Create configuration for this request const requestConfig = this.getRequestConfig(request); // Our implementation doesn't support resource reading, so always return 404 throw new Error(`Resource does not exist: ${request.params.uri}`); } catch (error) { throw this.wrapError('Failed to read resource', error); } }); } /** * Register prompt-related request handlers */ private registerPromptHandlers(): void { // List prompts handler this.server.setRequestHandler(ListPromptsRequestSchema, async (request) => { try { // Create configuration for this request const requestConfig = this.getRequestConfig(request); // Return empty list - our implementation doesn't support prompt listing return { prompts: [] }; } catch (error) { throw this.wrapError('Failed to get prompt list', error); } }); // Get prompt handler this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { try { // Create configuration for this request const requestConfig = this.getRequestConfig(request); // Our implementation doesn't support prompt retrieval, so always return 404 throw new Error(`Prompt does not exist: ${request.params.name}`); } catch (error) { throw this.wrapError('Failed to get prompt', error); } }); } /** * Start REST server */ public async start(): Promise<void> { try { const port = this.config.server?.port || DEFAULT_SERVER_PORT; const endpoint = this.config.server?.endpoint || DEFAULT_SERVER_ENDPOINT; // Create transport instance this.transport = new RestServerTransport({ endpoint: endpoint, port: port }); // Connect server await this.server.connect(this.transport); // Start HTTP server await this.transport.startServer(); // console.log(`[${new Date().toISOString()}] MiniMax MCP REST server started at: http://localhost:${port}${endpoint}`); } catch (error) { // console.error(`[${new Date().toISOString()}] Failed to start REST server:`, error); throw error; } } /** * Stop REST server */ public async stop(): Promise<void> { try { if (this.transport) { await this.transport.close(); this.transport = null; // console.log(`[${new Date().toISOString()}] REST server stopped`); } } catch (error) { // console.error(`[${new Date().toISOString()}] Failed to stop REST server:`, error); throw error; } } /** * Get MCP server instance */ public getMCPServer(): McpServer { return this.mcpServer; } /** * Get server instance */ public getServer(): Server { return this.server; } /** * Get current configuration */ public getConfig(): Config { return this.config; } /** * Wrap error message * @param message Error message prefix * @param error Original error * @returns Wrapped error */ private wrapError(message: string, error: unknown): Error { if (error instanceof Error) { const wrappedError = new Error(`${message}: ${error.message}`); wrappedError.stack = error.stack; return wrappedError; } return new Error(`${message}: ${String(error)}`); } }

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/MiniMax-AI/MiniMax-MCP-JS'

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