Skip to main content
Glama
restforge

@restforge-dev/mcp-server

Official
by restforge

Generate RESTForge Launcher Script

runtime_generate_launcher
Destructive

Produces server-start and server-stop scripts (.bat/.sh) for RESTForge based on OS and runtime mode. The user must run the script manually to keep the server alive.

Instructions

Generate a launcher script for the RESTForge Server in the project root. This tool does NOT run the server — it produces a .bat or .sh script that the user must execute themselves. This is intentional: a server launched by the AI session would terminate when the session ends; a script executed by the user lives in the user's terminal/session and persists independently.

Files produced (depending on os + mode) — file names are FIXED, not user-customisable:

  • windows + host: server-start.bat, server-stop.bat

  • windows + pm2: server-start.bat (calls 'pm2 start ecosystem.config.js'), server-stop.bat, ecosystem.config.js

  • linux + host: server-start.sh, server-stop.sh

  • linux + pm2: server-start.sh (calls 'pm2 start ecosystem.config.js'), server-stop.sh, ecosystem.config.js

For host mode the start script runs 'npx restforge ...' in the foreground (or 'exec' on Linux). The stop script kills the process by port via netstat/taskkill on Windows or lsof/fuser+kill on Linux — no PID file is required. For PM2 mode the ecosystem points 'script' directly at './node_modules/restforgejs/server.js' to bypass the npx shim and avoid PM2's default node interpreter mis-parsing a .cmd file.

USE WHEN:

  • The user asks "run the server", "jalankan server", "start RESTForge"

  • After confirming OS, mode, project, config, and port with the user (typically via 'runtime_detect_project', 'runtime_detect_config', and direct questions)

  • After 'runtime_check_launcher_exists' returned no conflicts, OR the user confirmed overwrite

DO NOT USE FOR:

  • Actually starting or stopping the server -> the user runs the generated script themselves

  • Modifying database schema or payload files -> out of scope

  • Running one-off commands -> out of scope; this tool is launcher-scaffolding only

Preconditions:

  • The cwd must exist.

  • 'cluster' and 'workers' are mutually exclusive.

  • 'watch' is not allowed with mode=pm2.

PRESENTATION GUIDANCE:

  • Match the user's language.

  • Never mention internal tool names. Refer to the produced files by their visible names (e.g. 'server-start.bat').

  • Tell the user clearly that the AI does NOT execute the script — they must run it themselves so the server keeps running after the AI session ends.

  • For PM2 mode: warn the user that PM2 must be installed globally first (npm install -g pm2). Do not auto-install.

  • After generation, summarise: location, files produced, how to start, how to stop. Do not paste the JSON envelope unless explicitly asked.

  • If the user asks for a different mode (Windows Service, Docker, systemd) — those are out of scope V1; suggest using host or pm2 instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cwdYesAbsolute path of the project folder root (output goes here)
osYesTarget OS: windows produces .bat, linux produces .sh
modeYesRuntime mode: host runs npx restforge directly; pm2 uses ecosystem.config.js
projectYesProject name (filename in src/modules/ without .js). Passed as --project=<name>
configYesConfig file name in config/ folder (e.g. db-connection.env). Passed as --config=<filename>
portYesServer port
overwriteNoIf true, overwrite existing files. If false, skip existing.
clusterNoIf true, append --cluster flag. Mutually exclusive with workers.
workersNoIf set, append --workers=N flag. Mutually exclusive with cluster.
watchNoIf true, append --watch flag. Not allowed with mode=pm2 (PM2 has its own watch via ecosystem).

Implementation Reference

  • Main handler: registerRuntimeGenerateLauncher registers the 'runtime_generate_launcher' tool via server.registerTool(), containing the full async handler that validates inputs, builds launcher scripts from templates, writes files to disk, and returns a structured JSON result.
    export function registerRuntimeGenerateLauncher(server: McpServer): void {
      server.registerTool(
        'runtime_generate_launcher',
        {
          title: 'Generate RESTForge Launcher Script',
          description: `Generate a launcher script for the RESTForge Server in the project root. This tool does NOT run the server — it produces a .bat or .sh script that the user must execute themselves. This is intentional: a server launched by the AI session would terminate when the session ends; a script executed by the user lives in the user's terminal/session and persists independently.
    
    Files produced (depending on os + mode) — file names are FIXED, not user-customisable:
    - windows + host: server-start.bat, server-stop.bat
    - windows + pm2:  server-start.bat (calls 'pm2 start ecosystem.config.js'), server-stop.bat, ecosystem.config.js
    - linux + host:   server-start.sh, server-stop.sh
    - linux + pm2:    server-start.sh (calls 'pm2 start ecosystem.config.js'), server-stop.sh, ecosystem.config.js
    
    For host mode the start script runs 'npx restforge ...' in the foreground (or 'exec' on Linux). The stop script kills the process by port via netstat/taskkill on Windows or lsof/fuser+kill on Linux — no PID file is required. For PM2 mode the ecosystem points 'script' directly at './node_modules/restforgejs/server.js' to bypass the npx shim and avoid PM2's default node interpreter mis-parsing a .cmd file.
    
    USE WHEN:
    - The user asks "run the server", "jalankan server", "start RESTForge"
    - After confirming OS, mode, project, config, and port with the user (typically via 'runtime_detect_project', 'runtime_detect_config', and direct questions)
    - After 'runtime_check_launcher_exists' returned no conflicts, OR the user confirmed overwrite
    
    DO NOT USE FOR:
    - Actually starting or stopping the server -> the user runs the generated script themselves
    - Modifying database schema or payload files -> out of scope
    - Running one-off commands -> out of scope; this tool is launcher-scaffolding only
    
    Preconditions:
    - The cwd must exist.
    - 'cluster' and 'workers' are mutually exclusive.
    - 'watch' is not allowed with mode=pm2.
    
    PRESENTATION GUIDANCE:
    - Match the user's language.
    - Never mention internal tool names. Refer to the produced files by their visible names (e.g. 'server-start.bat').
    - Tell the user clearly that the AI does NOT execute the script — they must run it themselves so the server keeps running after the AI session ends.
    - For PM2 mode: warn the user that PM2 must be installed globally first (npm install -g pm2). Do not auto-install.
    - After generation, summarise: location, files produced, how to start, how to stop. Do not paste the JSON envelope unless explicitly asked.
    - If the user asks for a different mode (Windows Service, Docker, systemd) — those are out of scope V1; suggest using host or pm2 instead.`,
          inputSchema: {
            cwd: z
              .string()
              .min(1)
              .describe('Absolute path of the project folder root (output goes here)'),
            os: z
              .enum(['windows', 'linux'])
              .describe('Target OS: windows produces .bat, linux produces .sh'),
            mode: z
              .enum(['host', 'pm2'])
              .describe('Runtime mode: host runs npx restforge directly; pm2 uses ecosystem.config.js'),
            project: z
              .string()
              .min(1)
              .describe(
                'Project name (filename in src/modules/ without .js). Passed as --project=<name>'
              ),
            config: z
              .string()
              .min(1)
              .describe(
                'Config file name in config/ folder (e.g. db-connection.env). Passed as --config=<filename>'
              ),
            port: z.number().int().min(1).max(65535).describe('Server port'),
            overwrite: z
              .boolean()
              .default(false)
              .describe('If true, overwrite existing files. If false, skip existing.'),
            cluster: z
              .boolean()
              .optional()
              .describe('If true, append --cluster flag. Mutually exclusive with workers.'),
            workers: z
              .number()
              .int()
              .min(1)
              .max(64)
              .optional()
              .describe('If set, append --workers=N flag. Mutually exclusive with cluster.'),
            watch: z
              .boolean()
              .optional()
              .describe(
                'If true, append --watch flag. Not allowed with mode=pm2 (PM2 has its own watch via ecosystem).'
              ),
          },
          annotations: {
            title: 'Generate Launcher Script',
            readOnlyHint: false,
            idempotentHint: false,
            destructiveHint: true,
          },
        },
        async (input) => {
          const { cwd, os, mode, project, config, port, overwrite, cluster, workers, watch } = input;
    
          if (cluster && workers !== undefined) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Conflicting options: 'cluster' and 'workers' cannot both be set.
    
    For the assistant:
    - Ask the user to pick one. Cluster auto-detects CPU count; workers=N is explicit.`,
                },
              ],
              isError: false,
            };
          }
          if (mode === 'pm2' && watch) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Conflicting options: 'watch' is not allowed when mode=pm2 (PM2 manages watch internally via ecosystem.config.js).
    
    For the assistant:
    - Suggest setting watch only with mode=host.`,
                },
              ],
              isError: false,
            };
          }
    
          const projectCwd = resolve(cwd);
          try {
            await access(projectCwd);
          } catch {
            return {
              content: [
                {
                  type: 'text',
                  text: `Precondition not met: cwd does not exist: ${projectCwd}
    
    For the assistant:
    - The target folder is missing. Suggest creating it first or verifying the path.
    - Match the user's language. Do not mention internal tool names.`,
                },
              ],
              isError: false,
            };
          }
    
          const extra = buildExtraArgs({ cluster, workers, watch });
    
          const vars: Record<string, string> = {
            PROJECT: project,
            CONFIG: config,
            PORT: String(port),
            EXTRA_ARGS_SH: extra,
          };
    
          const startFile = LAUNCHER_FILES[os].start;
          const stopFile = LAUNCHER_FILES[os].stop;
    
          type FileSpec = { fileName: string; content: string };
          const filesToWrite: FileSpec[] = [];
    
          if (os === 'windows' && mode === 'host') {
            filesToWrite.push(
              { fileName: startFile, content: applyTemplate(TEMPLATE_WINDOWS_HOST_START, vars) },
              { fileName: stopFile, content: applyTemplate(TEMPLATE_WINDOWS_HOST_STOP, vars) }
            );
          } else if (os === 'windows' && mode === 'pm2') {
            filesToWrite.push(
              { fileName: startFile, content: applyTemplate(TEMPLATE_WINDOWS_PM2_START, vars) },
              { fileName: stopFile, content: applyTemplate(TEMPLATE_WINDOWS_PM2_STOP, vars) },
              { fileName: ECOSYSTEM_FILE, content: applyTemplate(TEMPLATE_PM2_ECOSYSTEM, vars) }
            );
          } else if (os === 'linux' && mode === 'host') {
            filesToWrite.push(
              { fileName: startFile, content: applyTemplate(TEMPLATE_LINUX_HOST_START, vars) },
              { fileName: stopFile, content: applyTemplate(TEMPLATE_LINUX_HOST_STOP, vars) }
            );
          } else if (os === 'linux' && mode === 'pm2') {
            filesToWrite.push(
              { fileName: startFile, content: applyTemplate(TEMPLATE_LINUX_PM2_START, vars) },
              { fileName: stopFile, content: applyTemplate(TEMPLATE_LINUX_PM2_STOP, vars) },
              { fileName: ECOSYSTEM_FILE, content: applyTemplate(TEMPLATE_PM2_ECOSYSTEM, vars) }
            );
          }
    
          const generated: { path: string; size_bytes: number; overwritten: boolean }[] = [];
          const skipped: { path: string; reason: string }[] = [];
    
          for (const f of filesToWrite) {
            const fullPath = join(projectCwd, f.fileName);
            let existed = false;
            try {
              await stat(fullPath);
              existed = true;
            } catch {
              existed = false;
            }
    
            if (existed && !overwrite) {
              skipped.push({ path: fullPath, reason: 'already exists, overwrite=false' });
              continue;
            }
            try {
              await writeFile(fullPath, f.content, 'utf8');
              generated.push({
                path: fullPath,
                size_bytes: Buffer.byteLength(f.content, 'utf8'),
                overwritten: existed,
              });
            } catch (err) {
              const msg = err instanceof Error ? err.message : String(err);
              return {
                content: [
                  { type: 'text', text: `Failed to write ${fullPath}: ${msg}` },
                ],
                isError: true,
              };
            }
          }
    
          const pm2Warning =
            mode === 'pm2'
              ? 'PM2 must be installed globally on the user machine (npm install -g pm2). This tool does not auto-install. If PM2 is missing, the launcher will fail at runtime.'
              : null;
          const stopMethod =
            mode === 'pm2'
              ? `pm2 stop ecosystem.config.js (invoked by ${stopFile})`
              : `kill the process listening on port ${port} (invoked by ${stopFile})`;
    
          const envelope = {
            cwd: projectCwd,
            os,
            mode,
            project,
            config,
            port,
            generated_files: generated,
            skipped_files: skipped,
            stop_method: stopMethod,
            pm2_warning: pm2Warning,
          };
          const prettyJson = JSON.stringify(envelope, null, 2);
    
          const summary =
            generated.length === filesToWrite.length
              ? `Generated ${generated.length} launcher file${generated.length === 1 ? '' : 's'}.`
              : `Generated ${generated.length}; skipped ${skipped.length} (already exist, overwrite=false).`;
    
          const startCommand = os === 'windows' ? `.\\${startFile}` : `./${startFile}`;
          const stopCommand = os === 'windows' ? stopFile : `./${stopFile}`;
    
          return {
            content: [
              {
                type: 'text',
                text: `${summary}
    
    Project path: ${projectCwd}
    OS: ${os}
    Mode: ${mode}
    Project: ${project}
    Config: ${config}
    Port: ${port}
    Generated: ${generated.length}
    Skipped: ${skipped.length}
    PM2 warning: ${pm2Warning ?? 'n/a'}
    
    --- Generation Result (JSON) ---
    ${prettyJson}
    --- end Generation Result (JSON) ---
    
    For the assistant:
    - ${
                  generated.length > 0
                    ? `Tell the user the launcher was generated at the project root. To start: ${startCommand}. To stop: ${stopCommand}.`
                    : `Nothing was generated because all files already exist and overwrite=false. Ask the user whether to overwrite (call again with overwrite=true) or pick a different base name.`
                }
    - ${
                  pm2Warning
                    ? `Warn the user that PM2 must be installed globally on their machine before running the launcher.`
                    : ``
                }
    - The server will run in the user's terminal — when launched manually, the process belongs to the user's session, NOT to this AI session.
    - For host mode the stop script kills the process listening on port ${port} (cross-platform port-based lookup). For PM2 mode the stop script delegates to pm2.
    - Match the user's language. Do not mention internal tool names.`,
              },
            ],
            isError: false,
          };
        }
      );
    }
  • Registration: import and call of registerRuntimeGenerateLauncher inside registerRuntimeTools, which is called from src/server.ts line 280.
    import { registerRuntimeGenerateLauncher } from './generate-launcher.js';
    import { registerRuntimeCheckStatus } from './check-status.js';
    
    export function registerRuntimeTools(server: McpServer): void {
      registerRuntimeDetectProject(server);
      registerRuntimeDetectConfig(server);
      registerRuntimeValidatePreflight(server);
      registerRuntimeCheckLauncherExists(server);
      registerRuntimeGenerateLauncher(server);
      registerRuntimeCheckStatus(server);
    }
  • Registration: registerRuntimeTools function that aggregates all runtime tool registrations, including registerRuntimeGenerateLauncher at line 14.
    export function registerRuntimeTools(server: McpServer): void {
      registerRuntimeDetectProject(server);
      registerRuntimeDetectConfig(server);
      registerRuntimeValidatePreflight(server);
      registerRuntimeCheckLauncherExists(server);
      registerRuntimeGenerateLauncher(server);
      registerRuntimeCheckStatus(server);
    }
  • Helper functions: buildExtraArgs (constructs CLI flags like --cluster, --workers=N, --watch) and applyTemplate (replaces {{VAR}} placeholders in template strings with provided variables).
    interface ExtraArgsOptions {
      cluster?: boolean;
      workers?: number;
      watch?: boolean;
    }
    
    function buildExtraArgs(opts: ExtraArgsOptions): string {
      const parts: string[] = [];
      if (opts.cluster) {
        parts.push('--cluster');
      } else if (opts.workers !== undefined) {
        parts.push(`--workers=${opts.workers}`);
      }
      if (opts.watch) {
        parts.push('--watch');
      }
      return parts.length > 0 ? ' ' + parts.join(' ') : '';
    }
    
    function applyTemplate(template: string, vars: Record<string, string>): string {
      return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');
    }
  • Schema: Zod-based input schema for the tool defining parameters: cwd, os, mode, project, config, port, overwrite, cluster, workers, watch.
      cwd: z
        .string()
        .min(1)
        .describe('Absolute path of the project folder root (output goes here)'),
      os: z
        .enum(['windows', 'linux'])
        .describe('Target OS: windows produces .bat, linux produces .sh'),
      mode: z
        .enum(['host', 'pm2'])
        .describe('Runtime mode: host runs npx restforge directly; pm2 uses ecosystem.config.js'),
      project: z
        .string()
        .min(1)
        .describe(
          'Project name (filename in src/modules/ without .js). Passed as --project=<name>'
        ),
      config: z
        .string()
        .min(1)
        .describe(
          'Config file name in config/ folder (e.g. db-connection.env). Passed as --config=<filename>'
        ),
      port: z.number().int().min(1).max(65535).describe('Server port'),
      overwrite: z
        .boolean()
        .default(false)
        .describe('If true, overwrite existing files. If false, skip existing.'),
      cluster: z
        .boolean()
        .optional()
        .describe('If true, append --cluster flag. Mutually exclusive with workers.'),
      workers: z
        .number()
        .int()
        .min(1)
        .max(64)
        .optional()
        .describe('If set, append --workers=N flag. Mutually exclusive with cluster.'),
      watch: z
        .boolean()
        .optional()
        .describe(
          'If true, append --watch flag. Not allowed with mode=pm2 (PM2 has its own watch via ecosystem).'
        ),
    },
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true and readOnlyHint=false; the description adds details on file generation, modes, and the intentional design choice (AI session termination). No contradictions, and it enriches understanding beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is comprehensive yet well-organized with sections (purpose, file details, use cases, preconditions, presentation guidance). Every sentence adds value, and the most critical info (it generates, doesn't run) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (10 parameters, no output schema), the description covers all aspects: purpose, exact file outputs, usage contexts, parameter constraints, and post-generation instructions. It leaves no ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description significantly adds meaning: explains os/mode file naming, mutual exclusivity of cluster/workers, and watch disallowed with pm2. This goes well beyond the schema's basic descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states 'Generate a launcher script for the RESTForge Server' and clarifies that it does not run the server, distinguishing it from sibling tools like runtime_check_status. This provides a specific verb and resource.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides clear when-to-use conditions (user asks to run the server) and when-not-to-use (actual server start/stop, modifying DB schema). It also lists preconditions and alternatives, making it easy for agents to decide.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/restforge/restforge-mcp'

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