Skip to content

Feat/filesystem content base64#4401

Open
Ev3lynx727 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Ev3lynx727:feat/filesystem-content-base64
Open

Feat/filesystem content base64#4401
Ev3lynx727 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Ev3lynx727:feat/filesystem-content-base64

Conversation

@Ev3lynx727

Copy link
Copy Markdown

Description

Add optional content_base64 / newText_base64 parameters to write_file and edit_file tools. These are decoded server-side with Buffer.from(x, 'base64').

Existing content and newText parameters remain unchanged — fully backward compatible. Callers provide one or the other; the .refine() Zod check enforces this.

Also closes #2033edit_file PowerShell $() interpolation failure, same root cause.

Closes #4394

Server Details

  • Server: filesystem
  • Changes to: tools (write_file, edit_file)

Motivation and Context

MCP tool parameters are serialized as JSON before transmission. File content containing special characters (quotes, backticks, newlines, ${} template syntax, PowerShell $()) can break JSON framing, causing JSON Parse error: Unterminated string or Cannot convert undefined or null to object failures on write operations.

Base64 charset (A-Za-z0-9+/=) is fully JSON-safe — zero escaping needed at any layer. The server already uses this pattern for read_media_file output (readFileAsBase64Stream), so zero new dependencies.

Bonus: content: string makes binary file writes impossible. content_base64 enables them.

How Has This Tested?

  • Stress-tested with 150-byte payload containing ALL special characters (", `, ${}, $(), \n, backslashes, unicode) — all pass with content_base64
  • npm run build --workspace=src/filesystem compiles cleanly
  • npm test --workspace=src/filesystem — all tests pass

Breaking Changes

None. Existing content and newText parameters unchanged. New *_base64 parameters are purely additive.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follows MCP security best practices
  • I have updated the server's README accordingly
  • I have tested this with an LLM client
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have documented all environment variables and configuration options

Additional context

Schema

const WriteFileArgsSchema = z.object({
  path: z.string(),
  content: z.string().optional(),
  content_base64: z.string().optional(),
}).refine(
  args => args.content !== undefined || args.content_base64 !== undefined,
  { message: "Must provide either content or content_base64" }
);

const EditOperation = z.object({
  oldText: z.string(),
  newText: z.string().optional(),
  newText_base64: z.string().optional()
}).refine(
  args => args.newText !== undefined || args.newText_base64 !== undefined,
  { message: "Must provide either newText or newText_base64" }
);

Prior Art

Our ecosystem — proven in production:

  • server-commands-rtk (Node.js MCP server) — write_file with path + content_b64. Used daily by OpenCode agent with zero JSON parse failures since implementation.
  • OpenCode (anomalyco/opencode) — AGENTS.md documents this pattern as standard workaround.
  • MemPalace MCP — uses content_base64 for filing large content into the knowledge palace.

Upstream in this repo:

  • read_media_file — already returns base64-encoded images/audio via readFileAsBase64Stream(). This completes the round-trip.

No new environment variables or configuration options. content_base64/newText_base64 are optional tool parameters decoded server-side — zero config surface.

Industry pattern:

  • AutoGPT, Dify, Tracecat — all use base64 content transport between agents and file tools.
  • MCP image-generation servers — return base64-encoded outputs. This change lets the filesystem server accept base64 content from any producer without intermediate file handling.

…ters

Add optional content_base64 parameter to write_file and newText_base64
parameter to edit_file (per-edit). Base64 charset (A-Za-z0-9+/=)
is fully JSON-safe, eliminating transport-level JSON serialization
failures when file content contains backticks, quotes, template
literals, or other special characters.

- WriteFileArgsSchema: content becomes optional, content_base64 added
- EditOperation: newText becomes optional, newText_base64 added
- Both schemas use .refine() to require at least one of the two
- Handlers decode base64 server-side via Buffer.from()
- Fully backward-compatible: existing callers using content/newText
  continue unchanged

Refs: modelcontextprotocol#4394
…d to MCP clients

The write_file and edit_file tool registrations used plain-object
inputSchema ({ path: z.string(), content: z.string() }) that excluded
content_base64/newText_base64. The MCP SDK validates against inputSchema,
so these parameters were silently stripped before the handler ran.

Fixing by passing the full Zod schemas (WriteFileArgsSchema,
EditFileArgsSchema) which include the optional base64 parameters.

E2E verified: server exposes content_base64 in schema, client sends it,
server decodes and writes correctly with all 147 existing tests passing.
…mands-rtk

Before writing, ensure parent directory exists via fs.mkdir(recursive:true).
Closes the gap between forked filesystem (validation + path security) and
server-commands-rtk (auto-mkdir).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant