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.tstodist/index.js— a single-file ESM bundle executed withnode dist/index.js. Nonode_modulesat runtime. - Module system: ESM only (
"type": "module"inpackage.json). - MCP SDK:
McpServerfrom@modelcontextprotocol/sdk/server/mcp.js,StdioServerTransportfrom@modelcontextprotocol/sdk/server/stdio.js. - Schema validation: Zod via
zod/v4import. - Project structure:
src/index.tsentry,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
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:
- 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>orxxd <path> | head -20. - 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. - Stderr attachment: If the exit code is non-zero and stderr is non-empty, stderr is appended to the output.
- 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:
| Column | Format | Example |
|---|---|---|
ts | ISO 8601 UTC | 2026-04-26T14:32:01.234Z |
command | Full string (tabs → \\t, newlines → \\n) | git pull origin dev |
exitCode | Integer | 0 |
durationMs | Integer | 536 |
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:
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-sessioncommand 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.