Skip to content

[BUG] MCP Settings UI removes JSON formatting on save #9862

@Michaelzag

Description

@Michaelzag

Problem (one or two sentences)

When modifying MCP server settings through the UI, the .roo/mcp.json file loses all formatting and becomes minified to a single line.

Context (who is affected and when)

Affects any user who edits MCP settings via the UI (toggling tool permissions, enabling/disabling servers) and expects to read or manually edit the config file later. Particularly impacts users with large MCP configurations.

Reproduction steps

  1. Create a well-formatted .roo/mcp.json file with proper indentation
  2. Open Roo Code and navigate to MCP settings
  3. Toggle any setting (e.g., disable a tool permission like "always allow")
  4. Check the .roo/mcp.json file

Expected result

The JSON file maintains human-readable formatting with proper indentation after saves.

Actual result

The file is saved as minified JSON on a single line, making it difficult to read and manually edit.

Variations tried (optional)

Tested with different MCP settings modifications (toggle server disabled, change tool permissions) - all result in minified output.

Before/After Example

Before (well-formatted):

{
    "mcpServers": {
        "my-server": {
            "command": "uvx",
            "args": ["some-package"],
            "alwaysAllow": ["tool_one", "tool_two"]
        }
    }
}

After (minified):

{"mcpServers":{"my-server":{"command":"uvx","args":["some-package"],"alwaysAllow":["tool_one"]}}}

Root Cause - src/utils/safeWriteJson.ts

The _streamDataToFile helper function uses stream-json's Stringer which only outputs compact JSON:

async function _streamDataToFile(targetPath: string, data: any): Promise<void> {
    const fileWriteStream = fsSync.createWriteStream(targetPath, { encoding: "utf8" })
    const disassembler = Disassembler.disassembler()
    // Output will be compact JSON as standard Stringer is used.
    const stringer = Stringer.stringer()
    
    // ...
    disassembler.pipe(stringer).pipe(fileWriteStream)
    // ...
}

Affected Call Sites in src/services/mcp/McpHub.ts

updateServerConfig():

try {
    await safeWriteJson(configPath, updatedConfig)
} finally {
    // ...
}

deleteServer():

await safeWriteJson(configPath, updatedConfig)

updateServerToolList():

try {
    await safeWriteJson(normalizedPath, config)
} finally {
    // ...
}

Suggested Fix

Replace stream-json with json-stream-stringify which supports a spaces parameter for pretty-printing while maintaining streaming benefits for large files:

import { JsonStreamStringify } from 'json-stream-stringify'

async function _streamDataToFile(targetPath: string, data: any, prettyPrint = false): Promise<void> {
    const fileWriteStream = fsSync.createWriteStream(targetPath, { encoding: "utf8" })
    
    // JsonStreamStringify supports spaces parameter like JSON.stringify
    const stringifyStream = new JsonStreamStringify(
        data,
        undefined,                      // replacer
        prettyPrint ? '\t' : undefined  // spaces for indentation
    )
    
    // ...
    stringifyStream.pipe(fileWriteStream)
}

Then update the function signature:

async function safeWriteJson(filePath: string, data: any, options?: { prettyPrint?: boolean }): Promise<void>

And call with { prettyPrint: true } for config files.

App Version

v3.36.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue/PR - TriageNew issue. Needs quick review to confirm validity and assign labels.bugSomething isn't working

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions