Skip to content
archived Visibility internal Owner erik@uvilo.com Approver _ Created 2026-04-26 Updated 2026-04-26

Bash Refactor Spec

forge-bash is a TypeScript MCP server that replaces uvilo-shell, removing the blocking permission system in favor of a non-blocking audit log, blocking rm to enforce uvilo-trash, and establishing the TypeScript standard for all future Forge MCP tool development.

Requirements: Bash Refactor Requirements Research: Bash Refactor Research


1. TypeScript Implementation Standard

forge-bash is written in TypeScript using these conventions (captured in the Write_Typescript skill):

  • Runtime & build: esbuild bundles src/index.ts to dist/index.js — a single-file ESM bundle executed with node dist/index.js. No node_modules at runtime.
  • Module system: ESM only ("type": "module" in package.json).
  • MCP SDK: McpServer from @modelcontextprotocol/sdk/server/mcp.js, StdioServerTransport from @modelcontextprotocol/sdk/server/stdio.js.
  • Schema validation: Zod via zod/v4 import.
  • Project structure: src/index.ts entry, dist/ output (gitignored), package.json, tsconfig.json.
  • Build command: esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js
  • Type checking: tsc --noEmit (separate from build; esbuild doesn’t type-check).
  • Error handling: Tools return { content: [{ type: 'text', text: error }], isError: true }.
  • Logging: console.error() for debug/diagnostic output (stdout is reserved for MCP protocol).

2. Project Layout

Forge/Configs-debian/mcp-servers/
├── forge-bash/
│   ├── src/
│   │   └── index.ts
│   ├── dist/
│   │   └── index.js          # gitignored
│   ├── package.json
│   ├── tsconfig.json
│   └── README.md
├── uvilo-shell.py            # Existing — remains during validation
├── uvilo-trash.py
├── typesense-mcp.py
└── suprsend-mcp-proxy.py

3. Write_Typescript Skill

A new skill at Forge/Skills/Write_Typescript/SKILL.md captures the TypeScript conventions from Section 1. The skill follows the standard skill format: procedure outline and essential rules in SKILL.md, extended examples in references/ if needed.

4. Tool Identity

The MCP server is named forge-bash. It registers a single tool called run that accepts one parameter: command (string). There is no grant parameter. Both the MCP server name and the tool name are forge-bash and run respectively, matching the pattern established by uvilo-shell.

The tool description states: execute a shell command or pipeline in /workspace, supporting operators |, &&, ||, ;. rm is blocked — use uvilo-trash instead. All commands are audit-logged.

forge-bash coexists with uvilo-shell during validation. Both are registered in librechat.yaml. Agent instructions reference forge-bash immediately (not uvilo-shell).

5. Two-Layer Architecture

Layer 1 — Unix Execution

Commands are passed verbatim to the shell via child_process.execFile with the shell binary (bash or fallback sh) and -c flag. Working directory is /workspace. A 5-minute (300,000 ms) hard timeout is enforced. The maxBuffer is set to 50 MB to handle large output before Layer 2 truncation. Output is captured as Buffer (encoding: 'buffer') for binary detection.

Railway environment variables are injected at startup by reading /proc/1/environ, splitting on null bytes, and setting any variables not already present in process.env.

Layer 2 — LLM Presentation

After execution, the raw output is processed through these transformations in order:

  1. Binary guard: If stdout contains null bytes, or the first 1024 bytes have >10% control characters (excluding TAB, LF, CR), the output is rejected with an error message suggesting file <path> or xxd <path> | head -20.
  2. Overflow truncation: If output exceeds 200 lines or 50 KB, the full output is written to /tmp/forge-bash/cmd-N.txt (incrementing N), and the response is truncated to the first 200 lines with a navigation hint appended.
  3. Stderr attachment: If the exit code is non-zero and stderr is non-empty, stderr is appended to the output.
  4. Metadata footer: [exit:N | Xms] is appended to every response, where N is the exit code and X is the execution time in milliseconds.

Error cases produce the same footer format: timeout returns ERROR: Command timed out after 300 s. with [exit:124 | Xms], and execution failures return ERROR: Execution failed: {message} with [exit:1 | Xms].

6. Audit Log

Every executed command (including blocked commands) is logged to a daily TSV file at /tmp/forge-bash/audit-YYYY-MM-DD.tsv. Each line has four tab-separated columns:

ColumnFormatExample
tsISO 8601 UTC2026-04-26T14:32:01.234Z
commandFull string (tabs → \\t, newlines → \\n)git pull origin dev
exitCodeInteger0
durationMsInteger536

For blocked commands, exitCode is -1 and durationMs is 0. The directory /tmp/forge-bash/ is created on first write. Daily rotation is automatic — each calendar day (UTC) gets its own file.

7. rm Blocking

Before execution, the command chain is parsed into segments (splitting on |, ||, &&, ; while respecting quotes). For each segment, leading VAR=value assignments are stripped, and the first remaining token is checked. If the first token is rm or ends with /rm, the command is blocked with a message directing the agent to use uvilo-trash instead.

This is a targeted guard, not a security boundary. Aliases (alias del=rm), find -delete, and other indirect deletion methods are not blocked — this is acceptable per the trusted-environment model.

8. LibreChat Registration

forge-bash is registered in librechat.yaml with:

forge-bash:
  title: "Forge Bash"
  description: "Bash execution with two-layer architecture and audit logging"
  command: node
  args:
    - /workspace/erik/uvilo-os/Forge/Configs-debian/mcp-servers/forge-bash/dist/index.js
  timeout: 330000
  initTimeout: 30000
  serverInstructions: |
    Bash execution in /workspace. Supports chains (| && || ;).
    rm is blocked — use uvilo-trash instead. All commands are audit-logged.

The uvilo-shell entry remains during validation. Agent instructions (FORGE.md, MCP server instructions) are updated to reference forge-bash instead of uvilo-shell.

9. FORGE.md & Reference Cleanup

All references to the removed permission system are cleaned up:

  • PERMISSION RULE removed from FORGE.md.
  • Project session workflow step 2 (check for uncommitted shell-permissions.yaml) removed.
  • uvilo-shell MCP server instructions in FORGE.md replaced with forge-bash instructions (no mention of grant, permissions, or ⚠️ PERMISSION REQUIRED).
  • /end-session command updated to remove the shell-permissions commit step.

After cleanup, grep for PERMISSION REQUIRED, grant (in the permission context), and shell-permissions returns no hits outside project documentation.

Note: shell-permissions.yaml files are NOT trashed — uvilo-shell still requires them to function during the coexistence period. They will be removed when uvilo-shell is retired (out of scope for this project).

10. Activation

After all implementation and reference cleanup is complete, run /update-agents to sync the master agent config to all LibreChat variants. This makes forge-bash the active shell tool in the running instance. Without this step, forge-bash exists in librechat.yaml but the running agents still carry the old uvilo-shell instructions in their system prompts.