superjolt
Version:
AI-powered deployment platform with MCP support - Deploy JavaScript apps using natural language with Claude Desktop
1,179 lines • 52.5 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpService = void 0;
const common_1 = require("@nestjs/common");
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const api_service_1 = require("../services/api.service");
const auth_service_1 = require("../services/auth.service");
const zod_1 = require("zod");
const promises_1 = require("fs/promises");
const path_1 = require("path");
let McpService = class McpService {
apiService;
authService;
constructor(apiService, authService) {
this.apiService = apiService;
this.authService = authService;
}
async createServer() {
const server = new index_js_1.Server({
name: 'superjolt',
version: '0.1.0',
}, {
capabilities: {
tools: {},
},
});
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
tools: [
{
name: 'check_auth',
description: 'Check if user is authenticated with Superjolt',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_current_user',
description: 'Get information about the currently authenticated user',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'list_machines',
description: 'List all machines',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'create_machine',
description: 'Create a new machine',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Machine name (optional)',
},
},
required: [],
},
},
{
name: 'delete_machine',
description: 'Delete a machine',
inputSchema: {
type: 'object',
properties: {
machineId: {
type: 'string',
description: 'Machine ID',
},
},
required: ['machineId'],
},
},
{
name: 'rename_machine',
description: 'Rename a machine',
inputSchema: {
type: 'object',
properties: {
machineId: {
type: 'string',
description: 'Machine ID',
},
newName: {
type: 'string',
description: 'New name for the machine',
},
},
required: ['machineId', 'newName'],
},
},
{
name: 'set_default_machine',
description: 'Set the default machine for deployments',
inputSchema: {
type: 'object',
properties: {
machineId: {
type: 'string',
description: 'Machine ID',
},
},
required: ['machineId'],
},
},
{
name: 'list_services',
description: 'List all services',
inputSchema: {
type: 'object',
properties: {
machineId: {
type: 'string',
description: 'Machine ID to filter services (optional)',
},
},
required: [],
},
},
{
name: 'start_service',
description: 'Start a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'stop_service',
description: 'Stop a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'restart_service',
description: 'Restart a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'delete_service',
description: 'Delete a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'rename_service',
description: 'Rename a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
newName: {
type: 'string',
description: 'New name for the service',
},
},
required: ['serviceId', 'newName'],
},
},
{
name: 'list_env_vars',
description: 'List environment variables for a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'set_env_vars',
description: 'Set environment variables',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Environment variable key',
},
value: {
type: 'string',
description: 'Environment variable value',
},
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['key', 'value', 'serviceId'],
},
},
{
name: 'get_env_var',
description: 'Get an environment variable value',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Environment variable key',
},
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['key', 'serviceId'],
},
},
{
name: 'delete_env_var',
description: 'Remove an environment variable',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Environment variable key',
},
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['key', 'serviceId'],
},
},
{
name: 'push_env_file',
description: 'Push a .env file to a service',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to .env file (defaults to .env in current directory)',
},
serviceId: {
type: 'string',
description: 'Service ID',
},
},
required: ['serviceId'],
},
},
{
name: 'get_logs',
description: 'Get logs for a service',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID',
},
lines: {
type: 'number',
description: 'Number of recent log lines to fetch (default: 100)',
},
follow: {
type: 'boolean',
description: 'Follow logs in real-time (default: false)',
},
},
required: ['serviceId'],
},
},
{
name: 'add_custom_domain',
description: 'Add a custom domain to a service',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'The custom domain (e.g., app.example.com)',
},
serviceId: {
type: 'string',
description: 'Service ID',
},
primary: {
type: 'boolean',
description: 'Set as primary domain (default: false)',
},
},
required: ['domain', 'serviceId'],
},
},
{
name: 'list_custom_domains',
description: 'List custom domains for a service or all services',
inputSchema: {
type: 'object',
properties: {
serviceId: {
type: 'string',
description: 'Service ID (optional, lists all if not provided)',
},
},
required: [],
},
},
{
name: 'remove_custom_domain',
description: 'Remove a custom domain',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'The custom domain to remove',
},
},
required: ['domain'],
},
},
{
name: 'get_custom_domain_status',
description: 'Get the status of a custom domain',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'The custom domain to check',
},
},
required: ['domain'],
},
},
],
}));
const authHandler = this.createAuthToolsHandler();
const serviceHandler = this.createServiceToolsHandler();
const machineHandler = this.createMachineToolsHandler();
const envHandler = this.createEnvToolsHandler();
const logHandler = this.createLogToolsHandler();
const domainHandler = this.createDomainToolsHandler();
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const toolName = request.params.name;
if (toolName === 'check_auth' || toolName === 'get_current_user') {
return authHandler(request);
}
if ([
'list_services',
'start_service',
'stop_service',
'restart_service',
'delete_service',
'rename_service',
].includes(toolName)) {
return serviceHandler(request);
}
if ([
'list_machines',
'create_machine',
'delete_machine',
'rename_machine',
'set_default_machine',
].includes(toolName)) {
return machineHandler(request);
}
if ([
'list_env_vars',
'set_env_vars',
'get_env_var',
'delete_env_var',
'push_env_file',
].includes(toolName)) {
return envHandler(request);
}
if (toolName === 'get_logs') {
return logHandler(request);
}
if ([
'add_custom_domain',
'list_custom_domains',
'remove_custom_domain',
'get_custom_domain_status',
].includes(toolName)) {
return domainHandler(request);
}
return {
content: [
{
type: 'text',
text: `Unknown tool: ${toolName}`,
},
],
isError: true,
};
});
server.onerror = (error) => {
process.stderr.write(`[MCP Error] ${error.message || error}\n`);
};
return server;
}
createAuthToolsHandler() {
return async (request) => {
if (request.params.name === 'check_auth') {
try {
const token = await this.authService.getToken();
return {
content: [
{
type: 'text',
text: token
? 'Authenticated'
: 'Not authenticated. Run "superjolt login" to authenticate.',
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error checking authentication: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
if (request.params.name === 'get_current_user') {
try {
const token = await this.authService.getToken();
if (!token) {
return {
content: [
{
type: 'text',
text: 'Not authenticated. Run "superjolt login" to authenticate.',
},
],
};
}
try {
const user = await this.apiService.getCurrentUser();
return {
content: [
{
type: 'text',
text: `User: ${user.name} (${user.email})\nGitHub: ${user.githubUsername}`,
},
],
};
}
catch (apiError) {
if (apiError.message?.includes('401') ||
apiError.message?.includes('Authentication') ||
apiError.response?.status === 401) {
return {
content: [
{
type: 'text',
text: 'Authentication token is invalid or expired. Run "superjolt login" to re-authenticate.',
},
],
};
}
throw apiError;
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error getting user info: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
throw new Error(`Unexpected tool in auth handler: ${request.params.name}`);
};
}
createServiceToolsHandler() {
const ServiceActionSchema = zod_1.z.object({
serviceId: zod_1.z.string().describe('Service ID'),
});
const ListServicesSchema = zod_1.z.object({
machineId: zod_1.z
.string()
.optional()
.describe('Machine ID to filter services'),
});
const RenameServiceSchema = zod_1.z.object({
serviceId: zod_1.z.string().describe('Service ID'),
newName: zod_1.z.string().describe('New name for the service'),
});
return async (request) => {
const { name } = request.params;
switch (name) {
case 'list_services': {
try {
const args = ListServicesSchema.parse(request.params.arguments);
const response = await this.apiService.listServices(args.machineId);
const services = response.services || [];
if (services.length === 0) {
return {
content: [
{
type: 'text',
text: 'No services found',
},
],
};
}
const serviceList = services
.map((s) => `- ${s.name} (${s.id}): ${s.state || s.status || 'unknown'} - ${s.url || 'No URL'}`)
.join('\n');
return {
content: [
{
type: 'text',
text: serviceList,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error listing services: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'start_service': {
try {
const args = ServiceActionSchema.parse(request.params.arguments);
await this.apiService.startService(args.serviceId);
return {
content: [
{
type: 'text',
text: `Service ${args.serviceId} started successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error starting service: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'stop_service': {
try {
const args = ServiceActionSchema.parse(request.params.arguments);
await this.apiService.stopService(args.serviceId);
return {
content: [
{
type: 'text',
text: `Service ${args.serviceId} stopped successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error stopping service: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'restart_service': {
try {
const args = ServiceActionSchema.parse(request.params.arguments);
await this.apiService.restartService(args.serviceId);
return {
content: [
{
type: 'text',
text: `Service ${args.serviceId} restarted successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error restarting service: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'delete_service': {
try {
const args = ServiceActionSchema.parse(request.params.arguments);
await this.apiService.deleteService(args.serviceId);
return {
content: [
{
type: 'text',
text: `Service ${args.serviceId} deleted successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting service: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'rename_service': {
try {
const args = RenameServiceSchema.parse(request.params.arguments);
await this.apiService.renameService(args.serviceId, args.newName);
return {
content: [
{
type: 'text',
text: `Service ${args.serviceId} renamed to "${args.newName}"`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error renaming service: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
default:
throw new Error(`Unexpected tool in service handler: ${name}`);
}
};
}
createMachineToolsHandler() {
const CreateMachineSchema = zod_1.z.object({
name: zod_1.z.string().optional().describe('Machine name'),
});
const MachineActionSchema = zod_1.z.object({
machineId: zod_1.z.string().describe('Machine ID'),
});
const RenameMachineSchema = zod_1.z.object({
machineId: zod_1.z.string().describe('Machine ID'),
newName: zod_1.z.string().describe('New name for the machine'),
});
return async (request) => {
const { name } = request.params;
switch (name) {
case 'list_machines': {
try {
const response = await this.apiService.listMachines();
const machines = response.machines || [];
const machineList = machines
.map((m) => `- ${m.name} (${m.id}): ${m.status} - ${m.region || 'Unknown region'}`)
.join('\n');
return {
content: [
{
type: 'text',
text: machineList || 'No machines found',
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error listing machines: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'create_machine': {
try {
const args = CreateMachineSchema.parse(request.params.arguments);
const machine = await this.apiService.createMachine();
if (args.name) {
await this.apiService.renameMachine(machine.id, args.name);
machine.name = args.name;
}
return {
content: [
{
type: 'text',
text: `Machine created successfully!\nID: ${machine.id}\nName: ${machine.name}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error creating machine: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'delete_machine': {
try {
const args = MachineActionSchema.parse(request.params.arguments);
await this.apiService.deleteMachine(args.machineId);
return {
content: [
{
type: 'text',
text: `Machine ${args.machineId} deleted successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting machine: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'rename_machine': {
try {
const args = RenameMachineSchema.parse(request.params.arguments);
await this.apiService.renameMachine(args.machineId, args.newName);
return {
content: [
{
type: 'text',
text: `Machine ${args.machineId} renamed to "${args.newName}"`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error renaming machine: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'set_default_machine': {
try {
const args = MachineActionSchema.parse(request.params.arguments);
await this.apiService.setDefaultMachine(args.machineId);
return {
content: [
{
type: 'text',
text: `Default machine set to ${args.machineId}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error setting default machine: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
default:
throw new Error(`Unexpected tool in machine handler: ${name}`);
}
};
}
createEnvToolsHandler() {
const ListEnvSchema = zod_1.z.object({
serviceId: zod_1.z.string().describe('Service ID'),
});
const SetEnvSchema = zod_1.z.object({
key: zod_1.z.string().describe('Environment variable key'),
value: zod_1.z.string().describe('Environment variable value'),
serviceId: zod_1.z.string().describe('Service ID'),
});
const GetEnvSchema = zod_1.z.object({
key: zod_1.z.string().describe('Environment variable key'),
serviceId: zod_1.z.string().describe('Service ID'),
});
const DeleteEnvSchema = zod_1.z.object({
key: zod_1.z.string().describe('Environment variable key'),
serviceId: zod_1.z.string().describe('Service ID'),
});
const PushEnvFileSchema = zod_1.z.object({
path: zod_1.z.string().optional().describe('Path to .env file'),
serviceId: zod_1.z.string().describe('Service ID'),
});
return async (request) => {
const { name } = request.params;
switch (name) {
case 'list_env_vars': {
try {
const args = ListEnvSchema.parse(request.params.arguments);
const envVars = await this.apiService.listEnvVars(args.serviceId);
const envList = Object.entries(envVars)
.map(([key, value]) => `${key}=${value}`)
.join('\n');
return {
content: [
{
type: 'text',
text: envList || 'No environment variables set',
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error listing environment variables: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'set_env_vars': {
try {
const args = SetEnvSchema.parse(request.params.arguments);
await this.apiService.setEnvVars(args.serviceId, {
[args.key]: args.value,
});
return {
content: [
{
type: 'text',
text: `Environment variable ${args.key} set successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error setting environment variable: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'get_env_var': {
try {
const args = GetEnvSchema.parse(request.params.arguments);
const envVar = await this.apiService.getEnvVar(args.serviceId, args.key);
const value = envVar[args.key];
return {
content: [
{
type: 'text',
text: value
? `${args.key}=${value}`
: `Environment variable ${args.key} not found`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error getting environment variable: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'delete_env_var': {
try {
const args = DeleteEnvSchema.parse(request.params.arguments);
await this.apiService.deleteEnvVar(args.serviceId, args.key);
return {
content: [
{
type: 'text',
text: `Environment variable ${args.key} removed successfully`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error removing environment variable: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
case 'push_env_file': {
try {
const args = PushEnvFileSchema.parse(request.params.arguments);
const envPath = args.path || (0, path_1.join)(process.cwd(), '.env');
const envContent = await (0, promises_1.readFile)(envPath, 'utf-8');
const envVars = {};
envContent.split('\n').forEach((line) => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=');
if (key) {
envVars[key.trim()] = valueParts.join('=').trim();
}
}
});
await this.apiService.setEnvVars(args.serviceId, envVars);
return {
content: [
{
type: 'text',
text: `Successfully pushed ${Object.keys(envVars).length} environment variables`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error pushing environment variables: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
default:
throw new Error(`Unexpected tool in env handler: ${name}`);
}
};
}
createLogToolsHandler() {
const GetLogsSchema = zod_1.z.object({
serviceId: zod_1.z.string().describe('Service ID'),
lines: zod_1.z
.number()
.optional()
.describe('Number of recent log lines to fetch'),
follow: zod_1.z.boolean().optional().describe('Follow logs in real-time'),
});
return async (request) => {
if (request.params.name === 'get_logs') {
try {
const args = GetLogsSchema.parse(request.params.arguments);
const logsResponse = await this.apiService.getServiceLogs(args.serviceId, {
tail: args.lines || 100,
});
const logs = logsResponse.logs || '';
return {
content: [
{
type: 'text',
text: logs || 'No logs available',
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error fetching logs: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
throw new Error(`Unexpected tool in log handler: ${request.params.name}`);
};
}
createDomainToolsHandler() {
const AddCustomDomainSchema = zod_1.z.object({
domain: zod_1.z.string(),
serviceId: zod_1.z.string(),
primary: zod_1.z.boolean().optional(),
});
const ListCustomDomainsSchema = zod_1.z.object({
serviceId: zod_1.z.string().optional(),
});
const RemoveCustomDomainSchema = zod_1.z.object({
domain: zod_1.z.string(),
});
const GetCustomDomainStatusSchema = zod_1.z.object({
domain: zod_1.z.string(),
});
return async (request) => {
if (request.params.name === 'add_custom_domain') {
try {
const args = AddCustomDomainSchema.parse(request.params.arguments);
const response = await this.apiService.createCustomDomain({
domain: args.domain,
serviceId: args.serviceId,
isPrimary: args.primary,
});
return {
content: [
{
type: 'text',
text: `Custom domain ${response.domain} added to service ${response.serviceId}\nStatus: ${response.status}\nSSL Status: ${response.sslStatus}${response.validationTarget ? `\nValidation URL: ${response.validationTarget}` : ''}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error adding custom domain: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
if (request.params.name === 'list_custom_domains') {
try {
const args = ListCustomDomainsSchema.parse(request.params.arguments);
const response = await this.apiService.listCustomDomains(args.serviceId);
if (response.domains.length === 0) {
return {
content: [
{
type: 'text',
text: args.serviceId
? `No custom domains found for service ${args.serviceId}`
: 'No custom domains found',
},
],
};
}
const domainList = response.domains
.map((d) => `- ${d.domain} (${d.serviceId})${d.isPrimary ? ' [PRIMARY]' : ''} - Status: ${d.status}, SSL: ${d.sslStatus}`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Custom domains (${response.total}):\n${domainList}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Error listing custom domains: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
if (request.params.name === 'remove_custom_domain') {
try {
const args = RemoveCustomDomainSche