filesystem

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package filesystem implements the in-process built-in filesystem MCP.

The logic is extracted (not imported) from github.com/achetronic/filesystem-mcp internal/tools/*.go and adapted for direct use inside baifo:

  • removed the dependency on github.com/mark3labs/mcp-go (tools are plain Go funcs wrapped as ADK tool.Tool via functiontool.New);
  • removed RBAC / JWT plumbing (sandbox lives at the Builder level, and v1 grants every agent unrestricted access to its sandbox);
  • merged write_file + append_file into a single tool with `encoding` and `mode` parameters to dodge LLM JSON-serialisation issues with long content (backticks, newlines, embedded JSON).

All side effects (undo state, scratch values, background processes) live in per-instance stores; constructing two Tools yields two independent universes, which lets isolated workers have their own.

Index

Constants

View Source
const (
	ScratchSet    = "set"
	ScratchGet    = "get"
	ScratchDelete = "delete"
	ScratchList   = "list"
)

Supported actions for ScratchArgs.Action.

View Source
const (
	EncodingUTF8   = "utf-8"
	EncodingBase64 = "base64"
)

Supported values for WriteFileArgs.Encoding.

View Source
const (
	WriteModeOverwrite = "overwrite"
	WriteModeAppend    = "append"
)

Supported values for WriteFileArgs.Mode.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Logger is used for non-fatal errors (e.g. failure to record undo
	// state). Defaults to slog.Default() when nil.
	Logger *slog.Logger

	// MaxExecOutputChars caps stdout/stderr (each) returned by exec and
	// process_status. 0 means unlimited.
	MaxExecOutputChars int

	// MaxReadFileChars caps the total characters returned by one
	// read_file call. 0 means unlimited.
	MaxReadFileChars int

	// MaxSearchOutputChars caps the total characters (matched lines plus
	// their context) returned by one search call. 0 means unlimited.
	MaxSearchOutputChars int
}

Config bundles construction-time options.

type DiffArgs

type DiffArgs struct {
	PathA  string `json:"path_a"`
	PathB  string `json:"path_b"`
	StartA int    `json:"start_a,omitempty"`
	EndA   int    `json:"end_a,omitempty"`
	StartB int    `json:"start_b,omitempty"`
	EndB   int    `json:"end_b,omitempty"`
}

DiffArgs is the input of the Diff tool.

type DiffResult

type DiffResult struct {
	PathA     string `json:"path_a"`
	PathB     string `json:"path_b"`
	Identical bool   `json:"identical"`
	Unified   string `json:"unified,omitempty"`
}

DiffResult is what the LLM receives.

type EditFileArgs

type EditFileArgs struct {
	Path  string          `json:"path"`
	Edits []EditOperation `json:"edits"`
}

EditFileArgs is the input of the EditFile tool.

type EditFileResult

type EditFileResult struct {
	Path         string         `json:"path"`
	EditsApplied int            `json:"edits_applied"`
	EditsFailed  int            `json:"edits_failed"`
	Results      []EditOpResult `json:"results"`
}

EditFileResult is what the LLM receives.

type EditOpResult

type EditOpResult struct {
	Index   int    `json:"index"`
	Success bool   `json:"success"`
	Error   string `json:"error,omitempty"`
}

EditOpResult reports the outcome of one EditOperation.

type EditOperation

type EditOperation struct {
	OldText    string `json:"old_text"`
	NewText    string `json:"new_text"`
	ReplaceAll bool   `json:"replace_all,omitempty"`
}

EditOperation is one find-and-replace operation.

type ExecArgs

type ExecArgs struct {
	Command    string            `json:"command"`
	Workdir    string            `json:"workdir,omitempty"`
	Timeout    int               `json:"timeout,omitempty"`
	Env        map[string]string `json:"env,omitempty"`
	Background bool              `json:"background,omitempty"`
}

ExecArgs is the input of the Exec tool.

type ExecResult

type ExecResult struct {
	ProcessID       string `json:"process_id,omitempty"`
	ExitCode        int    `json:"exit_code"`
	Stdout          string `json:"stdout"`
	Stderr          string `json:"stderr"`
	StdoutTruncated bool   `json:"stdout_truncated,omitempty"`
	StderrTruncated bool   `json:"stderr_truncated,omitempty"`
}

ExecResult is what the LLM receives. ProcessID is set only when Background=true; the other fields apply to the synchronous case.

type LsArgs

type LsArgs struct {
	Path          string `json:"path"`
	Depth         int    `json:"depth,omitempty"`
	Pattern       string `json:"pattern,omitempty"`
	IncludeHidden bool   `json:"include_hidden,omitempty"`
}

LsArgs is the input of the Ls tool.

type LsEntry

type LsEntry struct {
	Name    string `json:"name"`
	Path    string `json:"path"`
	Type    string `json:"type"`
	Depth   int    `json:"depth"`
	Size    int64  `json:"size,omitempty"`
	Mode    string `json:"mode"`
	ModTime string `json:"mod_time"`
}

LsEntry mirrors a filesystem entry returned by Ls. Output is flat (no nested children) so the schema inference works and the LLM gets a simpler shape to reason about. Tree structure is conveyed by the Depth field plus the lexicographic order of paths.

type LsResult

type LsResult struct {
	Entries []LsEntry `json:"entries"`
}

LsResult is what the LLM receives.

type ProcessEntry

type ProcessEntry struct {
	ID              string `json:"id"`
	Command         string `json:"command"`
	WorkDir         string `json:"workdir,omitempty"`
	StartedAt       string `json:"started_at"`
	Done            bool   `json:"done"`
	ExitCode        int    `json:"exit_code"`
	Stdout          string `json:"stdout,omitempty"`
	Stderr          string `json:"stderr,omitempty"`
	StdoutTruncated bool   `json:"stdout_truncated,omitempty"`
	StderrTruncated bool   `json:"stderr_truncated,omitempty"`
}

ProcessEntry is a snapshot of one background process.

type ProcessKillArgs

type ProcessKillArgs struct {
	ID string `json:"id"`
}

ProcessKillArgs is the input of the ProcessKill tool.

type ProcessKillResult

type ProcessKillResult struct {
	ID string `json:"id"`
}

ProcessKillResult is what the LLM receives.

type ProcessStatusArgs

type ProcessStatusArgs struct {
	ID string `json:"id,omitempty"`
}

ProcessStatusArgs is the input of the ProcessStatus tool. An empty ID lists every background process.

type ProcessStatusResult

type ProcessStatusResult struct {
	Processes []ProcessEntry `json:"processes"`
	Total     int            `json:"total"`
}

ProcessStatusResult is what the LLM receives.

type ReadFileArgs

type ReadFileArgs struct {
	Path   string      `json:"path"`
	Ranges []ReadRange `json:"ranges,omitempty"`
}

ReadFileArgs is the input of the ReadFile tool.

type ReadFileResult

type ReadFileResult struct {
	Path           string         `json:"path"`
	TotalLines     int            `json:"total_lines"`
	Fragments      []ReadFragment `json:"fragments"`
	Truncated      bool           `json:"truncated,omitempty"`
	TruncationNote string         `json:"truncation_note,omitempty"`
}

ReadFileResult is what the LLM receives.

type ReadFragment

type ReadFragment struct {
	Offset     int      `json:"offset"`
	Limit      int      `json:"limit"`
	Lines      []string `json:"lines"`
	TotalLines int      `json:"total_lines"`
}

ReadFragment is a slice of lines, numbered for the LLM's benefit.

type ReadRange

type ReadRange struct {
	Offset int `json:"offset"`
	Limit  int `json:"limit"`
}

ReadRange is one slice of lines to read.

type ScratchArgs

type ScratchArgs struct {
	Action string `json:"action"`
	Key    string `json:"key,omitempty"`
	Value  string `json:"value,omitempty"`
}

ScratchArgs is the input of the Scratch tool. Key/Value semantics depend on Action; documented per case below.

type ScratchResult

type ScratchResult struct {
	Value  string            `json:"value,omitempty"`
	Items  map[string]string `json:"items,omitempty"`
	Action string            `json:"action"`
	Key    string            `json:"key,omitempty"`
}

ScratchResult is what the LLM receives. Different fields are filled depending on the action.

type SearchArgs

type SearchArgs struct {
	Pattern      string `json:"pattern"`
	Path         string `json:"path"`
	Include      string `json:"include,omitempty"`
	Exclude      string `json:"exclude,omitempty"`
	Literal      bool   `json:"literal,omitempty"`
	ContextLines int    `json:"context_lines,omitempty"`
	MaxResults   int    `json:"max_results,omitempty"`
}

SearchArgs is the input of the Search tool.

type SearchMatch

type SearchMatch struct {
	File          string   `json:"file"`
	Line          int      `json:"line"`
	Content       string   `json:"content"`
	ContextBefore []string `json:"context_before,omitempty"`
	ContextAfter  []string `json:"context_after,omitempty"`
}

SearchMatch is one hit returned by Search.

type SearchResult

type SearchResult struct {
	Matches        []SearchMatch `json:"matches"`
	TotalMatches   int           `json:"total_matches"`
	Truncated      bool          `json:"truncated"`
	TruncationNote string        `json:"truncation_note,omitempty"`
}

SearchResult is what the LLM receives.

type SystemInfoResult

type SystemInfoResult struct {
	OS           string    `json:"os"`
	Architecture string    `json:"arch"`
	Hostname     string    `json:"hostname"`
	User         string    `json:"user"`
	WorkingDir   string    `json:"workdir"`
	NumCPU       int       `json:"num_cpu"`
	Time         time.Time `json:"time"`
}

SystemInfoResult is what the LLM receives.

type Tools

type Tools struct {
	// contains filtered or unexported fields
}

Tools is the entry point of the built-in filesystem MCP. It owns the per-instance mutable state (undo, scratch, processes) and exposes methods that match the MCP tool surface one-to-one.

Methods are designed to be called via functiontool.New, but they are also usable directly from Go code (e.g. tests, internal scripting).

func New

func New(cfg Config) *Tools

New constructs a Tools instance with empty stores.

func (*Tools) ADKTools

func (t *Tools) ADKTools() ([]tool.Tool, error)

ADKTools returns every tool of the built-in filesystem MCP wrapped as a google.golang.org/adk tool.Tool. The names match the original filesystem-mcp tool names so existing prompts keep working.

Each tool description targets the LLM and documents quirks the model needs to know — especially the encoding/mode parameters of write_file that were added to dodge JSON tool-call corruption with long content.

func (*Tools) Diff

func (t *Tools) Diff(args DiffArgs) (DiffResult, error)

Diff returns a unified-style diff between path_a and path_b (or slices of them when start/end are given). Implementation is an LCS table, which is fine for files up to a few thousand lines.

func (*Tools) EditFile

func (t *Tools) EditFile(args EditFileArgs) (EditFileResult, error)

EditFile applies a list of find-and-replace operations to a file. Edits are applied sequentially against the running content; an operation fails if old_text is missing or matches multiple times without replace_all=true. All-or-nothing semantics would be safer but harder to debug, so we surface per-operation results instead.

func (*Tools) Exec

func (t *Tools) Exec(args ExecArgs) (ExecResult, error)

Exec runs a shell command in the foreground (with a timeout) or in the background (returning a process_id that subsequent tools can inspect). Workdir is sanitised through resolvePath if set.

func (*Tools) Ls

func (t *Tools) Ls(args LsArgs) (LsResult, error)

Ls lists directory contents up to args.Depth (default 1). Hidden entries are filtered out unless IncludeHidden is set. The Pattern is matched against file names (directories are always shown so the tree can be walked).

func (*Tools) ProcessKill

func (t *Tools) ProcessKill(args ProcessKillArgs) (ProcessKillResult, error)

ProcessKill terminates a background process. The OS Kill signal is sent; the process may take a moment to actually exit.

func (*Tools) ProcessStatus

func (t *Tools) ProcessStatus(args ProcessStatusArgs) (ProcessStatusResult, error)

ProcessStatus returns the status of one or every background process.

func (*Tools) ReadFile

func (t *Tools) ReadFile(args ReadFileArgs) (ReadFileResult, error)

ReadFile reads a file in full or in ranges. Ranges are 0-based and each fragment is returned with line numbers prefixed so the LLM can reference them precisely without re-counting.

When t.maxReadFileChars is non-zero, a character budget is maintained across all fragments. Once the budget is exhausted the current fragment's Limit is adjusted to the lines actually included, remaining ranges are dropped, and the result carries Truncated=true together with a TruncationNote that tells the model how to read the rest. Truncation happens at whole-line granularity, with one exception: when the very first line already exceeds the budget (single-huge-line files), its head is returned with an inline truncation marker so the result is never empty.

func (*Tools) Scratch

func (t *Tools) Scratch(args ScratchArgs) (ScratchResult, error)

Scratch is a multiplexed tool for the four actions of a small KV store. The original filesystem-mcp shipped it as one tool with an `action` discriminator; we keep that shape for compatibility.

func (*Tools) Search

func (t *Tools) Search(args SearchArgs) (SearchResult, error)

Search walks the path tree and returns every line matching the regex. Literal=true escapes the pattern so the LLM can search for raw strings without learning regex syntax.

Two independent limits bound the result. max_results caps the number of matches (default 100). When t.maxSearchOutputChars is non-zero, a character budget caps the total size of the matched lines and their context across all files: once a match would push the result past the budget it is dropped (at whole-match granularity, so a match is never split), remaining files are skipped, and the result carries Truncated=true with a TruncationNote telling the model how to narrow the query. The char budget is what keeps a broad pattern from flooding the context window even when it stays under max_results.

func (*Tools) SystemInfo

func (t *Tools) SystemInfo() (SystemInfoResult, error)

SystemInfo collects coarse environment info. The LLM is told via the tool description to call this ONLY on explicit user request; preemptive calls just spam the chat with system facts the user did not ask for.

func (*Tools) Undo

func (t *Tools) Undo(args UndoArgs) (UndoResult, error)

Undo reverts the last write_file or edit_file operation on path.

func (*Tools) WriteFile

func (t *Tools) WriteFile(args WriteFileArgs) (WriteFileResult, error)

WriteFile creates, overwrites or appends to a file. The previous content (if any) is snapshotted into the undo store before the write so the change can be reverted.

type UndoArgs

type UndoArgs struct {
	Path string `json:"path"`
}

UndoArgs is the input of the Undo tool.

type UndoResult

type UndoResult struct {
	Path string `json:"path"`
}

UndoResult is what the LLM receives.

type WriteFileArgs

type WriteFileArgs struct {
	Path     string `json:"path"`
	Content  string `json:"content"`
	Encoding string `json:"encoding,omitempty"`
	Mode     string `json:"mode,omitempty"`
}

WriteFileArgs is the input of the WriteFile tool.

Two extra knobs vs the original filesystem-mcp version:

  • Encoding lets the LLM submit content as base64 to dodge JSON serialisation issues with backticks, newlines and embedded JSON. The model seems to truncate / corrupt long literal strings in tool-call payloads more often than long base64 strings.
  • Mode lets the LLM grow a file incrementally without having to fetch the previous content (which would require an extra read_file turn).

type WriteFileResult

type WriteFileResult struct {
	Path         string `json:"path"`
	BytesWritten int    `json:"bytes_written"`
	Mode         string `json:"mode"`
}

WriteFileResult is what the LLM receives.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL