Skip to main content
Glama
restforge

@restforge-dev/mcp-server

Official
by restforge

Validate RESTForge Runtime Preflight

runtime_validate_preflight
Read-only

Run a preflight check to validate project readiness before launching, covering license, database, optional services, PID file, and port availability.

Instructions

Run a runtime preflight check before generating a launcher. Wraps 'npx restforge validate --config=' (license + database + redis/kafka) and additionally inspects: (1) .restforge/server.pid to detect a possibly-running server, (2) optional local port availability via Node net binding.

USE WHEN:

  • Before invoking 'runtime_generate_launcher' — to verify the project is ready to launch

  • The user asks "is this ready to run?", "preflight check", "cek apakah server bisa dijalankan"

  • After changing config or installing a new license — to verify everything still works

  • The user reports the server fails to start — to identify the failing component

DO NOT USE FOR:

  • Validating ONLY the config (license/database) without runtime context -> use 'setup_validate_config'

  • Listing or reading config values -> use 'setup_read_env'

  • Generating launcher files -> use 'runtime_generate_launcher'

This tool runs: npx restforge validate --config= in the given cwd, plus filesystem checks (PID file, optional port).

Preconditions:

  • The project must have restforgejs installed in node_modules.

  • The config file must exist in the config/ folder.

PRESENTATION GUIDANCE:

  • Match the user's language.

  • Never mention internal tool names.

  • Summarise by component: license, database, optional redis/kafka, PID file presence, port availability.

  • Do not echo license keys, passwords, or full connection URIs from the CLI output.

  • Preflight failure is informational, not a blocker. The user can still proceed to generate the launcher (with a warning recorded).

  • Port check is best-effort: a "free" result on this machine does not guarantee the port is free for the eventual server bind address (which may differ).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cwdYesAbsolute path of the project folder root
configNoConfig file name in the config/ folder (default: db-connection.env)db-connection.env
portNoPort to check availability (optional). When set, the tool tries to bind locally to detect if the port is free.

Implementation Reference

  • The main handler function for the runtime_validate_preflight tool. Runs 'npx restforge validate --config=<config>', checks PID file, and optionally checks port availability, then returns a structured PreflightResult.
        async ({ cwd, config, port }) => {
          const projectCwd = resolve(cwd);
    
          try {
            await access(join(projectCwd, 'node_modules', 'restforgejs'));
          } catch {
            return {
              content: [
                {
                  type: 'text',
                  text: `Precondition not met: the RESTForge package is not installed in this project.
    
    Project path: ${projectCwd}
    Expected location: node_modules/restforgejs
    
    For the assistant:
    - The user needs to install the RESTForge package before runtime preflight can run.
    - Suggest installing the package first, then retry preflight.
    - Match the user's language. Do not mention internal tool names.`,
                },
              ],
              isError: false,
            };
          }
    
          const result: PreflightResult = {
            config_validation: {
              ran: false,
              success: false,
              cli_command: '',
              cli_exit_code: -1,
              cli_stdout: '',
              cli_stderr: '',
            },
            pid_file: { path: '', exists: false, pid: null, process_alive: null },
            port_check: {
              requested: port !== undefined,
              port: port ?? null,
              available: null,
            },
            warnings: [],
            errors: [],
          };
    
          const cliResult = await execProcess(
            'npx',
            ['restforge', 'validate', `--config=${config}`],
            { cwd: projectCwd, timeout: 30_000 }
          );
          result.config_validation = {
            ran: true,
            success: cliResult.success,
            cli_command: cliResult.command,
            cli_exit_code: cliResult.exitCode,
            cli_stdout: truncate(cliResult.stdout || '', 500),
            cli_stderr: truncate(cliResult.stderr || '', 500),
          };
          if (!cliResult.success) {
            result.warnings.push(
              'Config validation did not pass — see config_validation.cli_stderr for details.'
            );
          }
    
          const pidPath = join(projectCwd, '.restforge', 'server.pid');
          result.pid_file.path = pidPath;
          try {
            const raw = await readFile(pidPath, 'utf8');
            const pid = parseInt(raw.trim(), 10);
            result.pid_file.exists = true;
            if (Number.isFinite(pid) && pid > 0) {
              result.pid_file.pid = pid;
              result.pid_file.process_alive = isProcessAlive(pid);
              if (result.pid_file.process_alive === true) {
                result.warnings.push(
                  `A server appears to be already running with PID ${pid}. Stop it first or it may conflict on the port.`
                );
              }
            } else {
              result.warnings.push('PID file exists but contains an invalid value.');
            }
          } catch {
            result.pid_file.exists = false;
          }
    
          if (port !== undefined) {
            result.port_check.available = await checkPortAvailable(port);
            if (result.port_check.available === false) {
              result.warnings.push(`Port ${port} is already in use on this machine.`);
            }
          }
    
          const envelope = result;
          const prettyJson = JSON.stringify(envelope, null, 2);
          const allPassed = result.config_validation.success && result.warnings.length === 0;
          const summary = allPassed
            ? 'Runtime preflight passed: config valid, no PID file conflict, port available.'
            : 'Runtime preflight completed with warnings — see details below.';
    
          const pidFileSummary = result.pid_file.exists
            ? `exists (pid=${result.pid_file.pid}, alive=${result.pid_file.process_alive})`
            : 'absent';
          const portSummary = result.port_check.requested
            ? result.port_check.available
              ? 'yes'
              : 'no'
            : '(not checked)';
    
          return {
            content: [
              {
                type: 'text',
                text: `${summary}
    
    Project path: ${projectCwd}
    Config: ${config}
    Port checked: ${port ?? '(not requested)'}
    Config validation: ${result.config_validation.success ? 'OK' : 'FAILED'}
    PID file: ${pidFileSummary}
    Port available: ${portSummary}
    Warnings: ${result.warnings.length}
    
    --- Preflight Result (JSON) ---
    ${prettyJson}
    --- end Preflight Result (JSON) ---
    
    For the assistant:
    - ${
                  allPassed
                    ? 'All preflight checks passed. The user can proceed to generate the launcher.'
                    : 'Some checks reported warnings. Summarise them in plain language to the user. The user can still choose to proceed (the launcher will be generated regardless).'
                }
    - Config validation runs the same checks as the standalone validator: license, database connection, and any enabled feature dependencies (redis, kafka).
    - PID file check inspects .restforge/server.pid: if a process is alive, warn the user that another server may be running.
    - Port check is best-effort: it tries to bind locally to the port. A port may still be in use by another machine on the same address — this only checks the local machine.
    - Do not echo full CLI stdout/stderr unless explicitly asked. Do not echo license keys or credentials.
    - Match the user's language. Do not mention internal tool names.`,
              },
            ],
            isError: false,
          };
        }
      );
    }
  • Input schema defined using Zod for cwd (required), config (optional, default 'db-connection.env'), and port (optional integer).
      inputSchema: {
        cwd: z.string().min(1).describe('Absolute path of the project folder root'),
        config: z
          .string()
          .min(1)
          .default('db-connection.env')
          .describe('Config file name in the config/ folder (default: db-connection.env)'),
        port: z
          .number()
          .int()
          .min(1)
          .max(65535)
          .optional()
          .describe('Port to check availability (optional). When set, the tool tries to bind locally to detect if the port is free.'),
      },
      annotations: {
        title: 'Validate Runtime Preflight',
        readOnlyHint: true,
        idempotentHint: false,
      },
    },
  • Registration function that registers the tool on the McpServer with name 'runtime_validate_preflight', its metadata, input schema, and handler.
    export function registerRuntimeValidatePreflight(server: McpServer): void {
      server.registerTool(
        'runtime_validate_preflight',
        {
          title: 'Validate RESTForge Runtime Preflight',
          description: `Run a runtime preflight check before generating a launcher. Wraps 'npx restforge validate --config=<config>' (license + database + redis/kafka) and additionally inspects: (1) .restforge/server.pid to detect a possibly-running server, (2) optional local port availability via Node net binding.
    
    USE WHEN:
    - Before invoking 'runtime_generate_launcher' — to verify the project is ready to launch
    - The user asks "is this ready to run?", "preflight check", "cek apakah server bisa dijalankan"
    - After changing config or installing a new license — to verify everything still works
    - The user reports the server fails to start — to identify the failing component
    
    DO NOT USE FOR:
    - Validating ONLY the config (license/database) without runtime context -> use 'setup_validate_config'
    - Listing or reading config values -> use 'setup_read_env'
    - Generating launcher files -> use 'runtime_generate_launcher'
    
    This tool runs: npx restforge validate --config=<config> in the given cwd, plus filesystem checks (PID file, optional port).
    
    Preconditions:
    - The project must have restforgejs installed in node_modules.
    - The config file must exist in the config/ folder.
    
    PRESENTATION GUIDANCE:
    - Match the user's language.
    - Never mention internal tool names.
    - Summarise by component: license, database, optional redis/kafka, PID file presence, port availability.
    - Do not echo license keys, passwords, or full connection URIs from the CLI output.
    - Preflight failure is informational, not a blocker. The user can still proceed to generate the launcher (with a warning recorded).
    - Port check is best-effort: a "free" result on this machine does not guarantee the port is free for the eventual server bind address (which may differ).`,
          inputSchema: {
            cwd: z.string().min(1).describe('Absolute path of the project folder root'),
            config: z
              .string()
              .min(1)
              .default('db-connection.env')
              .describe('Config file name in the config/ folder (default: db-connection.env)'),
            port: z
              .number()
              .int()
              .min(1)
              .max(65535)
              .optional()
              .describe('Port to check availability (optional). When set, the tool tries to bind locally to detect if the port is free.'),
          },
          annotations: {
            title: 'Validate Runtime Preflight',
            readOnlyHint: true,
            idempotentHint: false,
          },
        },
        async ({ cwd, config, port }) => {
          const projectCwd = resolve(cwd);
    
          try {
            await access(join(projectCwd, 'node_modules', 'restforgejs'));
          } catch {
            return {
              content: [
                {
                  type: 'text',
                  text: `Precondition not met: the RESTForge package is not installed in this project.
    
    Project path: ${projectCwd}
    Expected location: node_modules/restforgejs
    
    For the assistant:
    - The user needs to install the RESTForge package before runtime preflight can run.
    - Suggest installing the package first, then retry preflight.
    - Match the user's language. Do not mention internal tool names.`,
                },
              ],
              isError: false,
            };
          }
    
          const result: PreflightResult = {
            config_validation: {
              ran: false,
              success: false,
              cli_command: '',
              cli_exit_code: -1,
              cli_stdout: '',
              cli_stderr: '',
            },
            pid_file: { path: '', exists: false, pid: null, process_alive: null },
            port_check: {
              requested: port !== undefined,
              port: port ?? null,
              available: null,
            },
            warnings: [],
            errors: [],
          };
    
          const cliResult = await execProcess(
            'npx',
            ['restforge', 'validate', `--config=${config}`],
            { cwd: projectCwd, timeout: 30_000 }
          );
          result.config_validation = {
            ran: true,
            success: cliResult.success,
            cli_command: cliResult.command,
            cli_exit_code: cliResult.exitCode,
            cli_stdout: truncate(cliResult.stdout || '', 500),
            cli_stderr: truncate(cliResult.stderr || '', 500),
          };
          if (!cliResult.success) {
            result.warnings.push(
              'Config validation did not pass — see config_validation.cli_stderr for details.'
            );
          }
    
          const pidPath = join(projectCwd, '.restforge', 'server.pid');
          result.pid_file.path = pidPath;
          try {
            const raw = await readFile(pidPath, 'utf8');
            const pid = parseInt(raw.trim(), 10);
            result.pid_file.exists = true;
            if (Number.isFinite(pid) && pid > 0) {
              result.pid_file.pid = pid;
              result.pid_file.process_alive = isProcessAlive(pid);
              if (result.pid_file.process_alive === true) {
                result.warnings.push(
                  `A server appears to be already running with PID ${pid}. Stop it first or it may conflict on the port.`
                );
              }
            } else {
              result.warnings.push('PID file exists but contains an invalid value.');
            }
          } catch {
            result.pid_file.exists = false;
          }
    
          if (port !== undefined) {
            result.port_check.available = await checkPortAvailable(port);
            if (result.port_check.available === false) {
              result.warnings.push(`Port ${port} is already in use on this machine.`);
            }
          }
    
          const envelope = result;
          const prettyJson = JSON.stringify(envelope, null, 2);
          const allPassed = result.config_validation.success && result.warnings.length === 0;
          const summary = allPassed
            ? 'Runtime preflight passed: config valid, no PID file conflict, port available.'
            : 'Runtime preflight completed with warnings — see details below.';
    
          const pidFileSummary = result.pid_file.exists
            ? `exists (pid=${result.pid_file.pid}, alive=${result.pid_file.process_alive})`
            : 'absent';
          const portSummary = result.port_check.requested
            ? result.port_check.available
              ? 'yes'
              : 'no'
            : '(not checked)';
    
          return {
            content: [
              {
                type: 'text',
                text: `${summary}
    
    Project path: ${projectCwd}
    Config: ${config}
    Port checked: ${port ?? '(not requested)'}
    Config validation: ${result.config_validation.success ? 'OK' : 'FAILED'}
    PID file: ${pidFileSummary}
    Port available: ${portSummary}
    Warnings: ${result.warnings.length}
    
    --- Preflight Result (JSON) ---
    ${prettyJson}
    --- end Preflight Result (JSON) ---
    
    For the assistant:
    - ${
                  allPassed
                    ? 'All preflight checks passed. The user can proceed to generate the launcher.'
                    : 'Some checks reported warnings. Summarise them in plain language to the user. The user can still choose to proceed (the launcher will be generated regardless).'
                }
    - Config validation runs the same checks as the standalone validator: license, database connection, and any enabled feature dependencies (redis, kafka).
    - PID file check inspects .restforge/server.pid: if a process is alive, warn the user that another server may be running.
    - Port check is best-effort: it tries to bind locally to the port. A port may still be in use by another machine on the same address — this only checks the local machine.
    - Do not echo full CLI stdout/stderr unless explicitly asked. Do not echo license keys or credentials.
    - Match the user's language. Do not mention internal tool names.`,
              },
            ],
            isError: false,
          };
        }
      );
    }
  • Helper function to truncate CLI output to a max length (500 chars) for display safety.
    function truncate(s: string, n: number): string {
      return s.length > n ? s.substring(0, n) + '...' : s;
    }
  • Helper function that creates a net server to check if a given TCP port is available on the local machine.
    async function checkPortAvailable(port: number): Promise<boolean> {
      return new Promise((resolveFn) => {
        const tester = createServer()
          .once('error', () => resolveFn(false))
          .once('listening', () => {
            tester.close(() => resolveFn(true));
          })
          .listen(port);
      });
    }
Behavior5/5

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

Annotations already declare readOnlyHint=true and idempotentHint=false. The description reinforces this by stating preflight failure is informational and not a blocker, and explains the best-effort nature of the port check. No contradictions are present.

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 well-structured with clear sections (USE WHEN, DO NOT USE FOR, Preconditions, PRESENTATION GUIDANCE). It is front-loaded with the purpose and every sentence adds value without redundancy.

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

Completeness4/5

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

Although no output schema exists, the description provides presentation guidance and notes the informational nature of failures. However, it lacks explicit details on the return format (e.g., structured object vs. text). This minor gap prevents a perfect score.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3. The description adds context for the 'port' parameter ('tries to bind locally to detect if the port is free'), which is beyond the schema's description. For 'cwd' and 'config', the description aligns with the schema but does not add significant new meaning.

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 the tool runs a runtime preflight check before generating a launcher, detailing the components inspected (license, database, redis/kafka, PID file, port). This clearly distinguishes from sibling tools like 'setup_validate_config' and 'runtime_generate_launcher'.

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?

The description includes dedicated 'USE WHEN' and 'DO NOT USE FOR' sections that specify conditions (e.g., before invoking 'runtime_generate_launcher', after config changes) and explicitly name alternative tools (e.g., 'setup_validate_config', 'setup_read_env'). This provides excellent guidance for tool selection.

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