Skip to content

feat(mcp): fbash structured colored renderer#33

Merged
lliWcWill merged 2 commits intomasterfrom
feat/fbash-structured-mcp-renderer
Apr 10, 2026
Merged

feat(mcp): fbash structured colored renderer#33
lliWcWill merged 2 commits intomasterfrom
feat/fbash-structured-mcp-renderer

Conversation

@lliWcWill
Copy link
Copy Markdown
Owner

Summary

Closes fcase #359 (fbash-colored-structured-mcp-output).

Replaces the previous minimal renderFbashResult in mcp/index.js with a Monokai-themed structured layout that matches the rest of the fsuite MCP renderers (renderFmapResult, renderFreadResult, renderFcontentResult, ...). Before this PR, fbash surfaced only a bare exit=N line plus raw stdout/stderr, ignoring the command class, duration, background job metadata, warnings, routing suggestions, and next hints that fbash -o json already emits. Agents consuming fbash output had to either parse text blobs or fall through to the raw JSON envelope.

Rendered layout

  1. Header – bold gold fbash (256-color fg 220 / fg(255,215,0)) + dim class=<class> + cyan <Nms> duration + exit badge (green exit=0 / red exit=N). Header is emitted for every call except background starts, where the exit badge is hidden because exit_code is null (still running).
  2. Stdout block – 2-space indent, preserved whitespace, truncated at 30 visible lines with a dim ... N more lines trailer. Empty stdout is rendered as nothing (no phantom STDOUT: header).
  3. Stderr block – bold red stderr: label + muted-warm body (fg(170,160,140) — distinct from stdout without screaming), only shown when non-empty, truncated at 10 lines.
  4. Status markers(silent success) on exit=0 with empty streams, (no output) on non-zero exit with empty streams.
  5. Footerwarnings[] with orange warn glyph, routing_suggestion.tool / next_hint with cyan arrow + bold tool name. Only one hint is emitted, preferring routing_suggestion when both are present.
  6. Background callout – when metadata.background_job_id is set and exit_code is null, emits [bg job <id> started · <cmd> · poll: <next_hint>] in dim gray and skips the stdout/stderr blocks entirely (the job has not produced final output yet).

The renderer returns null on parse failure or when d.tool !== "fbash", letting cli() fall back to raw text — the graceful-fallback contract of the other renderers.

Design research

  • MCP TypeScript SDK (/modelcontextprotocol/typescript-sdk via Context7): confirmed the tool-result contract is {content: [{type: "text", text}], structuredContent?}. structuredContent is intentionally omitted because Claude Code's ''early return blender'' discards content[text] when both are set (existing comment at mcp/index.js:1029). All existing rendered fsuite tools already follow this text-only pattern; this PR matches.
  • Claude Code issue #18728 (closed as user error): confirmed that ANSI escape codes pass through Claude Code's bash/MCP stdout verbatim, so embedding 256-color truecolor escapes directly in content[text] is sound.
  • fbash JSON envelope (./fbash -o json): all 14 fields now consumed — tool, command, command_class, exit_code, duration_ms, stdout, stderr, truncated, truncation_reason, warnings[], routing_suggestion.{tool,reason}, next_hint, metadata.background_job_id, metadata.background_status.

Before / after

Before

exit=1
ls: cannot access '/nonexistent-path-xxxxx': No such file or directory

After

fbash  class=query  6ms  exit=2
stderr:
  ls: cannot access '/nonexistent-path-xxxxx': No such file or directory
⚠ Consider using fls instead: fls provides structured listing with recon mode and JSON output
→ hint: use fls — fls provides structured listing with recon mode and JSON output

Rendered test-case snapshots (ANSI stripped)

1. printf hello — simple success

fbash  class=query  5ms  exit=0
  hello
⚠ Use direct text output instead of echo/printf through bash

2. false — silent failure

fbash  class=general  5ms  exit=1
  (no output)

3. sh -c "echo OUT; echo ERR >&2; exit 2" — mixed streams

fbash  class=general  5ms  exit=2
  OUT
stderr:
  ERR

4. sleep 0.2 --background — background job

fbash  class=general  0ms
[bg job fbash_1775850769_1300069 started · sleep 0.2 · poll: fbash --command '__fbash_poll fbash_1775850769_1300069']

5. ls /nonexistent-path-xxxxx — stderr-only error (see before/after above)

6. seq 1 500 — truncation

fbash  class=general  5ms  exit=0
  1
  2
  3
  ... (28 more lines)
  30
  ... 470 more lines

Test plan

  • Add mcp/fbash-render.test.mjs — 7 end-to-end tests via StdioClientTransport covering all 6 brief cases + the ''header is always bold gold'' invariant.
  • Update structured-parity.test.mjs — the ''fbash pretty output does not color exit=null as an error'' test previously asserted on the literal string background_job_id; rewritten to match the new [bg job <id> started …] callout while preserving the original intent (exit=null must never appear in plain or colored text).
  • cd mcp && node --test35 / 35 pass, zero regressions.
  • Verified all ANSI escapes are emitted (gold fg, bold, red, cyan, dim) via explicit regex asserts.
  • Verified result.structuredContent is undefined (contract with Claude Code's early-return blender).
  • Graceful fallback verified — unparseable / non-fbash JSON returns null so cli() falls through to the raw text path.

Commits

  1. feat(mcp): add structured colored renderFbashResult — renderer rewrite + updated structured-parity assertion
  2. test(mcp): add fbash renderer end-to-end coverage — new dedicated test suite

Closes fcase #359.

Replace the minimal fbash MCP renderer with a Monokai-themed structured
layout that matches the other fsuite renderers (renderFmapResult,
renderFreadResult, etc.). The new renderer surfaces per-stream coloring,
the command classification, duration, exit status, background job
callouts, truncation warnings, and routing hints in visually distinct
zones so agents can parse fbash output at a glance instead of reading
a raw text blob.

Rendered layout (closes fcase #359):
  1. Header  — bold gold "fbash" + dim class + cyan duration + exit badge
               (green exit=0 / red exit=N; header is hidden for bg starts).
  2. Stdout  — 2-space indent, preserved whitespace, truncated at 30
               visible lines with a dim "... N more lines" trailer.
  3. Stderr  — bold red "stderr:" label + muted-warm body, only shown
               when non-empty, truncated at 10 visible lines.
  4. Status  — (silent success) / (no output) markers for the empty
               stdout + stderr edge cases, keyed off exit_code.
  5. Footer  — truncation reason, warnings[] (orange warn glyph), and
               routing_suggestion / next_hint (cyan arrow, bold tool
               name). Only one hint is emitted, preferring routing_suggestion
               when present.
  6. Background — when metadata.background_job_id is set and
               exit_code is null, emits a "[bg job <id> started · <cmd>
               · poll: <next_hint>]" callout and skips the stdout/stderr
               blocks (the job hasn't produced final output yet).

The renderer returns null on parse failure or when d.tool !== "fbash",
letting cli() fall back to the raw text path — matches the existing
renderer contract in mcp/index.js.

Also update the pre-existing "fbash pretty output does not color
exit=null as an error" test to assert on the new "[bg job <id>
started...]" callout format instead of the old literal
"background_job_id" string, while preserving the original intent
(exit=null must never render in red and the job id must still be
surfaced for the caller).

All 35 MCP tests pass.
Exercise renderFbashResult through the real MCP stdio client across
all six test cases from fcase #359:

  1. printf hello  — simple success: gold header, exit=0, stdout block
  2. false         — silent failure: red exit=1, (no output) marker
  3. sh -c OUT/ERR — mixed streams: all three zones rendered distinctly
  4. sleep --bg    — background job: [bg job ...] callout, no streams
  5. ls /nope      — stderr-only error: red exit, stderr block, hint
  6. seq 1 500     — long stdout: truncated with "... N more lines"

Also pins invariants that the renderer contract relies on:
  - structuredContent is never returned (Claude Code "early return
    blender" discards content[text] when structuredContent is set)
  - the fbash header is always bold + 256-color gold (fg 255,215,0)
  - exit=null never appears in plain or colored text
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

Caution

Review failed

Pull request was closed or merged during review

Summary by CodeRabbit

Release Notes

  • Improvements

    • Enhanced fbash output formatting with color-coded status indicators, structured headers, and duration displays.
    • Improved background job rendering with distinct callout format and job identifiers.
    • Added truncation indicators for large outputs with line limits.
    • Better visual presentation of successful operations, errors, and incomplete executions.
  • Tests

    • Added comprehensive end-to-end test suite for fbash output rendering validation.

Walkthrough

The PR refactors the renderFbashResult function to restructure output formatting with a new out array approach, adds explicit background job handling with early exit, implements output truncation with indicators, and updates routing hint display. A comprehensive test suite validates the rendering across six functional cases (success, silent failure, mixed streams, background jobs, stderr-only errors, truncation) plus ANSI color verification.

Changes

Cohort / File(s) Summary
Core Rendering Logic
mcp/index.js
Restructured renderFbashResult to replace incremental lines.push() with structured out array. Added background job detection with early return and callout emission. Reworked stdout/stderr rendering with separate truncation caps (30 lines for stdout, 10 for stderr), trailing newline stripping, and dim truncation indicators. Updated empty-output behavior to emit silent-success marker for exit_code === 0 or "(no output)" for non-zero exits. Added truncation/warnings rendering and routing suggestion hint preference over next_hint.
Test Coverage
mcp/fbash-render.test.mjs
New end-to-end test file validating renderFbashResult via real MCP roundtrip with StdioClientTransport. Tests six functional cases: successful execution with exit=0 header and duration; silent failure with exit=1 and "(no output)" marker; mixed stdout/stderr streams with exit=2; background jobs with [bg job ...] callout and no exit badge; stderr-only errors; long stdout truncation with "... N more lines" trailer. Includes bonus test verifying header bold gold ANSI styling (\x1b[1m + gold foreground).
Test Assertion Updates
mcp/structured-parity.test.mjs
Updated background job test assertions to expect new callout format regex (bg job fbash_<id>_<id>) instead of raw background_job_id in plain text. Added explicit assertion that exit=null never appears in plain text output.

Sequence Diagram

sequenceDiagram
    participant Test as Test Harness
    participant Server as MCP Server<br/>(index.js)
    participant Fbash as fbash Tool<br/>(exec)
    participant Renderer as renderFbashResult

    Test->>Server: Spawn: node ./index.js
    Test->>Server: MCP call: fbash tool<br/>(command, args)
    
    alt Background Job Start
        Fbash-->>Server: {background_job_id, exit_code: null}
        Server->>Renderer: Parse result
        Renderer-->>Server: [bg job fbash_<id>_<id>]<br/>(early return)
    else Normal Execution
        Fbash-->>Server: {stdout, stderr, exit_code}
        Server->>Renderer: Parse result
        Renderer->>Renderer: Build header<br/>(fbash, duration_ms, exit=N)
        Renderer->>Renderer: Render stdout<br/>(cap 30 lines)
        Renderer->>Renderer: Render stderr<br/>(cap 10 lines, muted)
        Renderer->>Renderer: Add truncation<br/>indicators if needed
        Renderer-->>Server: out.join("\n") + "\n"
    end
    
    Server-->>Test: MCP response:<br/>rendered text
    Test->>Test: Assert ANSI<br/>sequences & labels
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(mcp): fbash structured colored renderer' accurately describes the primary change: replacing the minimal fbash renderer with a structured, colored version that matches other fsuite MCP renderers.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the renderer rewrite, layout elements, design decisions, and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fbash-structured-mcp-renderer

Comment @coderabbitai help to get the list of available commands and usage tips.

@lliWcWill lliWcWill merged commit 9baca07 into master Apr 10, 2026
1 of 2 checks passed
@lliWcWill lliWcWill deleted the feat/fbash-structured-mcp-renderer branch April 10, 2026 19:56
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af5ab2d357

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mcp/index.js
const isBackgroundStart = bgId && d.exit_code == null && bgStatus !== "completed";
if (isBackgroundStart) {
const pollHint = d.next_hint ? ` ${DIM}· poll:${UNDIM} ${INFO}${d.next_hint}${RESET}` : "";
out.push(`${GRAY}[bg job ${bgId} started${d.command ? ` · ${d.command}` : ""}${pollHint}${GRAY}]${RESET}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape command text before embedding in bg callout

The background callout interpolates d.command verbatim, so multi-line commands or ANSI/control sequences break the renderer layout and can inject spoofed lines into the MCP text output. For example, a background command containing \n renders as extra lines inside the [bg job ...] block, which makes the status text ambiguous and can mislead downstream agent parsing. Please sanitize/escape control characters (or omit the raw command) before formatting this line.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant