Skip to main content
Glama

Superjolt MCP Server

by scoritz
api.service.ts•9.5 kB
import { Injectable } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { ConfigService } from './config.service'; import { AuthService } from './auth.service'; import { LoggerService } from './logger.service'; import { IApiError, ICreateMachineResponse, IListMachinesResponse, IGetLogsResponse, ICurrentUser, ICreateCustomDomainRequest, ICreateCustomDomainResponse, IListCustomDomainsResponse, ICustomDomainStatus, IDeleteCustomDomainResponse, } from '../interfaces/api.interface'; @Injectable() export class ApiService { constructor( private readonly httpService: HttpService, private readonly configService: ConfigService, private readonly authService: AuthService, private readonly logger: LoggerService, ) {} async createMachine(): Promise<ICreateMachineResponse> { return this.request<ICreateMachineResponse>('POST', '/machine', {}); } async listMachines(): Promise<IListMachinesResponse> { return this.request<IListMachinesResponse>('GET', '/machines'); } async deleteMachine(machineId: string): Promise<void> { await this.request<void>('DELETE', `/machine/${machineId}`); } async renameMachine( machineId: string, newName: string, ): Promise<{ message: string; machineId: string; name: string }> { return this.request<{ message: string; machineId: string; name: string }>( 'PUT', `/machine/${encodeURIComponent(machineId)}/rename`, { name: newName }, ); } async listServices(machineId?: string): Promise<any> { const params = new URLSearchParams(); if (machineId) { params.append('machineId', machineId); } const query = params.toString(); return this.request<any>('GET', `/services${query ? `?${query}` : ''}`); } async getServiceLogs( serviceId: string, options: { tail?: number } = {}, ): Promise<IGetLogsResponse> { const params = new URLSearchParams(); if (options.tail) { params.append('tail', options.tail.toString()); } const queryString = params.toString(); const path = `/service/${encodeURIComponent(serviceId)}/logs${queryString ? `?${queryString}` : ''}`; return this.request<IGetLogsResponse>('GET', path); } async startService( serviceId: string, ): Promise<{ message: string; serviceId: string }> { return this.request<{ message: string; serviceId: string }>( 'POST', `/service/${encodeURIComponent(serviceId)}/start`, ); } async stopService( serviceId: string, ): Promise<{ message: string; serviceId: string }> { return this.request<{ message: string; serviceId: string }>( 'POST', `/service/${encodeURIComponent(serviceId)}/stop`, ); } async restartService( serviceId: string, ): Promise<{ message: string; serviceId: string }> { return this.request<{ message: string; serviceId: string }>( 'POST', `/service/${encodeURIComponent(serviceId)}/restart`, ); } async renameService( serviceId: string, newName: string, ): Promise<{ message: string; serviceId: string; name: string }> { return this.request<{ message: string; serviceId: string; name: string }>( 'PUT', `/service/${encodeURIComponent(serviceId)}/rename`, { name: newName }, ); } async deleteService(serviceId: string): Promise<{ message: string }> { return this.request<{ message: string }>( 'DELETE', `/service/${encodeURIComponent(serviceId)}`, ); } async resetAllResources(): Promise<{ deletedServices: number; deletedMachines: number; }> { return this.request<{ deletedServices: number; deletedMachines: number }>( 'DELETE', '/reset', ); } async getCurrentUser(): Promise<ICurrentUser> { return this.request<ICurrentUser>('GET', '/me'); } async setDefaultMachine(machineId: string): Promise<{ message: string }> { return this.request<{ message: string }>('PUT', '/machine/use', { machineId, }); } async setEnvVars( serviceId: string, envVars: Record<string, string>, ): Promise<{ message: string; count: number }> { return this.request<{ message: string; count: number }>( 'POST', `/service/${encodeURIComponent(serviceId)}/env`, envVars, ); } async getEnvVar( serviceId: string, key: string, ): Promise<Record<string, string>> { return this.request<Record<string, string>>( 'GET', `/service/${encodeURIComponent(serviceId)}/env/${encodeURIComponent(key)}`, ); } async listEnvVars(serviceId: string): Promise<Record<string, string>> { return this.request<Record<string, string>>( 'GET', `/service/${encodeURIComponent(serviceId)}/env`, ); } async deleteEnvVar( serviceId: string, key: string, ): Promise<{ message: string }> { return this.request<{ message: string }>( 'DELETE', `/service/${encodeURIComponent(serviceId)}/env/${encodeURIComponent(key)}`, ); } // Custom Domain methods async createCustomDomain( data: ICreateCustomDomainRequest, ): Promise<ICreateCustomDomainResponse> { return this.request<ICreateCustomDomainResponse>( 'POST', '/custom-domains', data, ); } async listCustomDomains( serviceId?: string, ): Promise<IListCustomDomainsResponse> { const params = new URLSearchParams(); if (serviceId) { params.append('serviceId', serviceId); } const query = params.toString(); return this.request<IListCustomDomainsResponse>( 'GET', `/custom-domains${query ? `?${query}` : ''}`, ); } async getCustomDomainStatus(domain: string): Promise<ICustomDomainStatus> { return this.request<ICustomDomainStatus>( 'GET', `/custom-domains/${encodeURIComponent(domain)}/status`, ); } async deleteCustomDomain( domain: string, ): Promise<IDeleteCustomDomainResponse> { return this.request<IDeleteCustomDomainResponse>( 'DELETE', `/custom-domains/${encodeURIComponent(domain)}`, ); } async request<T>( method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, data?: any, retry = true, ): Promise<T> { try { const apiUrl = this.configService.getApiUrl(); const url = `${apiUrl}${path}`; const headers = await this.getHeaders(); const config = { headers }; let response: any; switch (method) { case 'GET': response = await firstValueFrom(this.httpService.get<T>(url, config)); break; case 'POST': response = await firstValueFrom( this.httpService.post<T>(url, data, config), ); break; case 'PUT': response = await firstValueFrom( this.httpService.put<T>(url, data, config), ); break; case 'DELETE': response = await firstValueFrom( this.httpService.delete<T>(url, config), ); break; } return response.data; } catch (error: any) { // If unauthorized and we haven't retried yet, trigger login // But skip this in MCP mode (when logger is silent) const loggerOptions = (this.logger as any).silent; if (error.response?.status === 401 && retry && !loggerOptions) { await this.handleUnauthorized(); // Retry the request once after login return this.request<T>(method, path, data, false); } return this.handleError(error); } } private async getHeaders(): Promise<Record<string, string>> { const headers: Record<string, string> = { 'Content-Type': 'application/json', }; // Get auth token const token = await this.authService.getToken(); if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } private async handleUnauthorized(): Promise<void> { this.logger.log( '\nšŸ” Authentication required. Please log in to continue.\n', ); // Delete any existing invalid token await this.authService.deleteToken(); // Run the OAuth flow await this.authService.performOAuthFlow(); } private async handleError(error: any): Promise<never> { if (error.response) { const errorData: IApiError = { message: error.response.data?.message || 'Unknown error', statusCode: error.response.status, error: error.response.data?.error, }; switch (error.response.status) { case 400: throw new Error(`Bad Request: ${errorData.message}`); case 401: throw new Error( 'Authentication failed. Please run "superjolt login" first.', ); case 403: throw new Error( 'Forbidden: You do not have permission to perform this action', ); case 404: throw new Error(`Not Found: ${errorData.message}`); case 503: throw new Error( 'Service Unavailable: The API service is temporarily unavailable', ); default: throw new Error( `API Error (${errorData.statusCode}): ${errorData.message}`, ); } } else if (error.request) { throw new Error( 'Network Error: Unable to connect to the API. Please check your connection and API URL.', ); } else { throw new Error(`Error: ${error.message}`); } } }

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/scoritz/superjolt'

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