Skip to content
 
 

Repository files navigation

Scripthold

Go Report Card Release License: GPL-3.0 MCP Registry

Code from the web. Work locally. Recover safely.

Scripthold is a secure, encoding-aware local workspace runtime for AI agents. It lets web-based and local LLM clients read, edit, convert, execute, test, back up, and restore work inside explicitly authorized directories through stdio, authenticated Streamable HTTP, or a secure tunnel.

Scripthold was built with Scripthold. The project has been developed and verified through the same web-to-local workflow it provides to its users.

Lineage: Scripthold originated from the original project, created by Dimitar Grigorov. The project retains its GPL-3.0 lineage and permanent attribution.

AI clients see Настройки — not ???? or Íàñòðîéêè.

Scripthold detects text encodings from bytes rather than filenames, presents UTF-8 to the client, and preserves or deliberately converts encoding, BOM, and line endings through bounded-memory and durable filesystem operations.

  • 27 tools and 3 guided prompts over both transports — one catalog, one process-wide root policy, one error model, and equivalent behavior through stdio and Streamable HTTP.
  • Agent-oriented repository workflows — optional read line numbers, paged/multi-mode grep, .gitignore traversal, bounded sorting, batch conversion previews, approval-bound one-shot edits, strict patches, and ambiguity-safe fuzzy matching.
  • 24 registered encodings — Cyrillic, Windows-125x, ISO-8859, KOI8, UTF-16 LE/BE, GBK/GB18030, and other legacy text formats.
  • Fail-closed HTTP service — bearer authentication on every MCP request, loopback defaults, exact Host/Origin checks, bounded sessions and request resources, no CORS, and explicit TLS/proxy requirements for non-loopback exposure.
  • Secure filesystem and mutation model — resolved-root containment, deterministic traversal, bounded streaming, staged writes, practical concurrent-change detection, operation-specific transactional .bak handling, and no-replace creation.
  • Optional executionrun_script and unrestricted shell are disabled by default; HTTP requires a second explicit execution opt-in.

Suitable for: persistent local or containerized MCP services, desktop and CLI clients, secure tunnel bridges such as the OpenAI Secure MCP Tunnel, and legacy codebases whose text encoding cannot be inferred reliably from a filename or extension.

Project Direction

Scripthold began as a deployment-oriented fork for ChatGPT Web, but version 2.0 established an independently versioned downstream project rather than a thin synchronization branch. It owns its Go module, MCP Registry identity, release pipeline, public API decisions, transport architecture, container contract, and security documentation.

Transport Typical deployment Security boundary Roots behavior
stdio Local MCP clients and secure tunnel bridges Client configuration and operating-system process boundary Startup directories are authoritative; dynamic roots are a compatibility fallback only when startup roots are empty
stateful Streamable HTTP Persistent localhost services, containers, trusted proxies, and explicitly secured remote services Bearer token on every MCP request; loopback by default; TLS or a trusted proxy boundary for non-loopback listeners Startup directories are immutable and shared by every session; HTTP client roots are disabled

Both transports use the same BuildServer path and expose the same 27 tools, 3 prompts, encoding behavior, limits, typed errors, and execution policy. The HTTP trust model is defined in docs/HTTP_SECURITY.md; the fork's independent scope and relationship to upstream are defined in docs/PROJECT_DIRECTION.md.

The OpenAI Secure MCP Tunnel remains a supported stdio deployment option, not the identity or only use case of the project. The fork does not require Claude Code, Codex, ChatGPT, or another specific MCP host. Version 2.0 removes the fork-owned Claude Code downloader plugin to avoid maintaining a second network installer and cache trust boundary; any compatible client can invoke the released binary directly or connect to its HTTP endpoint.

Process-wide directory and session model

Allowed directories are a process-wide authorization boundary. Every MCP connection or future HTTP session attached to one server process sees the same configured directory set and the same 27 tools, limits, execution flags, and error behavior. A session represents an independent protocol connection with its own requests, cancellation, and lifecycle; it is not a per-agent filesystem role or sandbox.

This deliberately supports deployments where several agents work on different projects under one allowed drive or workspace, read shared documentation or libraries, and follow prompt-level rules about where each agent may write. The server does not enforce those per-agent read/write conventions. When technical isolation is required, run separate server processes with narrower allowed directories and, for concurrent Git work, separate checkouts or worktrees.

Directories supplied when the process starts remain authoritative and cannot be changed by a session. For stdio compatibility only, a roots-capable client may provide dynamic MCP roots when the process starts with no directory arguments. Streamable HTTP disables client roots and every HTTP session shares the same process-wide configured directories.

The fork-specific architecture includes authoritative process roots, Windows drive-root handling, optional local execution tools, a shared encoding/BOM-aware streaming text core, deterministic secure traversal, durable atomic mutations, transport-independent typed operation errors, bounded ordered concurrency and aggregate output budgets, shared process preparation, a transport-independent server builder, a fail-closed native Streamable HTTP transport, and an authoritative tool-metadata catalog. The upstream project remains the source of the original encoding-aware file-tool implementation.

Current Release Status

Version 2.0.0 completes the planned 2.x API cleanup, bounded-memory text pipeline, transport-independent server architecture, fail-closed native Streamable HTTP transport, cross-platform CI, reproducible packaging, and migration documentation. The release exposes the same 23-tool catalog through stdio and native Streamable HTTP while preserving process-wide allowed-directory policy and disabled-by-default execution tools.

Release binaries and archives are produced for Windows, Linux, and macOS on amd64 and arm64. The published Windows build has also passed a live dual-transport deployment smoke: the stdio connector and an authenticated stateful HTTP session both exposed the complete 23-tool catalog from the same 2.0.0 binary. Credential rotation, service supervision, and rollback remain operator-controlled procedures documented in docs/ROADMAP.md and docs/PUBLISHING.md.

Encoding detection is content-based. File extensions are not used to select or bias an encoding. Unicode BOMs and valid UTF-8 are authoritative. BOMless UTF-16 LE/BE is auto-detected only when structural and decoded-text evidence agree. Empty files are treated as assumed UTF-8; non-empty ambiguous input is reported explicitly and requires an encoding override in text operations.

The semantic-tag release workflow validates each release tag against a dated changelog entry before generating binaries, archives, checksums, and Registry metadata.

Unreleased Development Features

R15 is implemented and verified but remains unreleased and undeployed. It adds backward-compatible optional fields to the existing 23-tool catalog plus three transport-independent prompts: audit_encodings, fix_mojibake, and migrate_to_utf8. Recursive read/search workflows respect nested .gitignore rules by default, while mutation additions retain the existing full-document size limits, encoding/BOM-aware pipeline, durable staging, and concurrent-change checks.

R16 is complete and verified in source. fingerprint_paths provides deterministic streamed SHA-256 state evidence, edit_file adds bounded one-shot preview/apply, patch_package exposes strict patch-package-v1 inspect, dryRun, apply, and verify, and verify_state runs ordered typed JSON, text-format, fixed git diff --check, and fingerprint checks. The verification tool is read-only, requires no execution feature flag, invokes Git directly without a shell, and distinguishes failed expectations from operational errors. At the completed R16 boundary, package operations created no persistent backup and did not claim multi-file atomicity or automatic rollback; the later approval-bound backup behavior is governed by R17 and R18 below.

R17 approved the persistent-backup lifecycle, and R18 is complete and verified in source. An operator may configure MCP_BACKUP_STORE_DIR to initialize a separate non-overlapping internal store with owner-only permissions, one lifetime writer lock, an immutable descriptor, bounded recovery, and a rebuildable index. Internal code captures exact bytes into verified SHA-256 objects and strict checksummed manifests under conservative quotas. backup_store exposes bounded read-only status/list/inspect/audit, approval-bound original-target restore, and explicit generation-bound gcDryRun/gcApply. Restore uses a 256-bit one-shot capability, exact source revalidation, a mandatory safety backup before replacing an existing target, and no-replace creation for a missing target. GC preserves immutable pins and at least one version per target, applies retention and unpinned version-limit policy, removes manifests before fully verified unreferenced objects, recovers only recognized typed trash, and never runs automatically or in the background. edit_file preview and patch_package manifests may bind backupPolicy: "required"; their mutation paths capture approved pre-states before commit. Policy omission, direct editing, and logical no-ops still create no persistent backup. Alternate restore destinations, mutable pinning, automatic rollback, and secure-deletion guarantees remain unavailable.

R19 is complete in source and implements the offline backup-store diagnose command. It is read-only and existing-store-only: it acquires the pre-existing exclusive lock without create flags, emits bounded deterministic path-free JSON, and never rebuilds the index, cleans trash, removes staging/orphan data, repairs permissions, or otherwise mutates the store. Quick mode checks metadata; full mode additionally hashes referenced object bytes under explicit bounds. Repair, quarantine, salvage, migration, and every MCP/server behavior remain unavailable.

Run offline diagnosis only after stopping every server that owns the store:

scripthold backup-store diagnose \
  --store /absolute/path/to/backup-store \
  --mode full \
  --max-objects 10000 \
  --max-bytes 1073741824 \
  --pretty

The command never reads MCP_BACKUP_STORE_DIR. It writes exactly one JSON report to stdout and uses exit code 0 for a complete clean report, 2 for diagnosed issues or maintenance such as a rebuildable index, and 1 when no trustworthy report can be emitted. The complete contract is docs/OFFLINE_BACKUP_DIAGNOSTICS.md.

R20 is an active design/readiness milestone for MCP 2026-07-28. The current runtime remains on the stable Go SDK v1.6.1 and the existing 2025-11-25 transport behavior. No pre-release SDK is permitted in the main dependency graph. The approved direction is same-endpoint dual-generation HTTP with one shared security pipeline, stateful legacy sessions, stateless new-protocol requests, and stdio version gating that never relies on deprecated client roots for 2026-07-28. No dependency or runtime behavior has changed yet; see docs/MCP_2026_07_28_ADOPTION.md.

The feature set and relevant implementation approaches were reviewed in the original project and are credited as reciprocal cross-project engineering exchange. This fork's code is reworked for its bounded-memory, secure-walker, durable-mutation, stable-schema, and dual-transport requirements rather than mechanically synchronized; see Project lineage and independence and docs/PROJECT_DIRECTION.md.

What It Does

Provides 27 tools for file operations, encoding conversion, state verification, update checks, and optional local execution, plus 3 guided prompts:

  • read_text_file - Stream decoded text with bounded line/output memory and optional absolute line numbers
  • read_multiple_files - Read files in deterministic order under one aggregate decoded-output budget
  • write_file - Write through the shared encoder with explicit auto/always/never/preserve BOM policy
  • edit_file - Direct edits or bounded one-shot preview/apply, including optional required pre-state backup, exact/flexible/fuzzy operations, and strict unified patches
  • patch_package - Inspect, preview, apply, and verify bounded declared multi-file edits, with optional all-target required backups and explicit partial-state evidence
  • copy_file - Copy a file to a new location
  • delete_file - Delete a file
  • list_directory - Browse directories with filtering and deterministic name/mtime/size sorting
  • tree - Compact deterministic .gitignore-aware tree through the shared secure walker
  • search_files - .gitignore-aware glob search with bounded globally correct sorting
  • fingerprint_paths - Stream deterministic SHA-256 state fingerprints with optional bounded entry details
  • verify_state - Run bounded typed JSON, text-format, fixed Git diff, and fingerprint checks without arbitrary shell execution
  • backup_store - Review, restore, and explicitly garbage-collect the optional persistent store through bounded one-shot workflows
  • grep_text_files - Paged regex search with pattern/filter arrays and content/path/count modes
  • detect_encoding - Auto-detect file encoding with confidence score
  • convert_encoding - Single/batch conversion, dry-run previews, unsupported-rune locations, and durable writes
  • detect_line_endings - Stream CRLF/LF/mixed detection with bounded inconsistent-line output
  • change_line_endings - Stream LF/CRLF conversion while preserving encoding, BOM, and unrelated bytes
  • manage_bom - Inspect a bounded prefix or stream BOM add/strip through durable staging
  • list_encodings - Show all supported encodings
  • get_file_info - Get file/directory metadata
  • create_directory - Create directories recursively (mkdir -p)
  • move_file - Move or rename files and directories
  • list_allowed_directories - Show accessible directories
  • run_script - Execute a supported script or executable inside an allowed directory when explicitly enabled
  • shell - Execute an unrestricted shell command when explicitly enabled
  • check_for_updates - Check the latest release of this fork with a cached GitHub request

Supported encodings (24 total):

  • Unicode: UTF-8, UTF-16 LE, UTF-16 BE
  • Cyrillic: Windows-1251, KOI8-R, KOI8-U, CP866, ISO-8859-5
  • Western European: Windows-1252, ISO-8859-1, ISO-8859-15
  • Central European: Windows-1250, ISO-8859-2
  • Greek: Windows-1253, ISO-8859-7
  • Turkish: Windows-1254, ISO-8859-9
  • Chinese: GBK, GB18030
  • Other: Hebrew (Windows-1255), Arabic (Windows-1256), Baltic (Windows-1257), Vietnamese (Windows-1258), Thai (Windows-874)

manage_bom additionally recognizes UTF-32 LE/BE BOM signatures, but UTF-32 is not one of the 24 registered read/write encodings.

See TOOLS.md for detailed parameters and examples.

Security: File operations and run_script paths are restricted to allowed directories. Recursive filesystem tools resolve every visited entry through a shared secure walker and skip symlinks, Windows junctions, and other reparse points that resolve outside those directories. Mutation handlers revalidate paths before commit and use optimistic snapshots plus atomic or no-replace platform operations. Before run_script starts, its script and working directory are revalidated and the script's metadata plus SHA-256 snapshot must still match; this reduces but cannot eliminate the final path-based TOCTOU window without handle-relative execution. The optional shell tool revalidates only its working directory; the command itself remains unrestricted and runs with the operating-system permissions of the MCP server process.

Fork Architecture and Changes

This repository has evolved from its original upstream codebase. Compared with that baseline, the current source branch adds:

  • optional run_script and shell MCP tools, disabled by default, with shared bounded process preparation but separate authorization policies;
  • an authoritative embedded tool catalog consumed by runtime registration and Registry manifest generation, with drift tests for runtime metadata and documentation coverage;
  • CLI-provided allowed directories as the authoritative fallback for tunnel clients that do not implement MCP roots requests;
  • correct validation of descendants when a Windows drive root such as D:\ is allowed;
  • encoding-aware detect_line_endings and byte-preserving change_line_endings support for all 24 registered encodings, including UTF-16 LE/BE;
  • real upstream encoding fixtures covering every registered encoding, including UTF-16 and GBK/GB18030 round-trip tests;
  • conservative, extension-independent BOMless UTF-16 LE/BE detection with malformed-Unicode rejection, binary false-positive protection, deterministic mode semantics, and surrogate-pair handling across chunk boundaries;
  • a shared document encoder used by edits, full writes, and encoding conversions, with public auto, always, never, and preserve BOM policies plus byte-identical conversion no-op suppression;
  • a deterministic, cancellation-aware secure walker shared by tree, search_files, grep_text_files, and fingerprint_paths, including native Windows junction/reparse-point resolution and protection for deeply nested missing paths behind escaping links;
  • a shared atomic mutation layer for write, edit, conversion, line-ending, BOM, copy, move, and delete operations, with synced staging, transactional backups, no-replace destination commits, cleanup, and practical concurrent-modification detection;
  • a bounded process-local edit preview cache with 256-bit one-shot capabilities, exact prepared bytes, target/result fingerprints, deterministic expiry/eviction, stable file-identity checks, replay prevention, and an optional retained backupPolicy: "required" that captures the exact approved pre-state before mutation;
  • a strict patch-package-v1 inspect/dry-run/apply/verify workflow for bounded ordered existing-file edits, with unknown-field rejection, alias and hard-link detection, one-shot capabilities, shared encoding-aware preparation, optional conservative all-target required-backup reservation and capture before the first commit, deterministic commits, explicit partial-state evidence, and no automatic rollback;
  • an ordered read-only verify_state workflow for JSON syntax, encoding/BOM/line-ending/trailing-whitespace expectations, fixed direct git diff --check, and shared fingerprints, with strict schemas, bounded diagnostics, filtered process environment, and no shell or execution opt-in;
  • a disabled-by-default persistent-backup subsystem with a dedicated non-overlapping internal root, owner-only Windows DACL or Unix mode/owner validation, a platform-native lifetime writer lock, immutable backup-store-v1 descriptor, verified SHA-256 object deduplication, strict checksummed manifests, conservative single/package quota reservations, bounded recovery, a rebuildable index, bounded review/audit, opt-in required edit_file and patch_package pre-state capture, one-shot original-target restore, and explicit generation-bound garbage collection;
  • transport-independent typed operation errors for path validation, access control, encoding, decoding, output encoding, permissions, conflicts, cancellation, limits, and filesystem failures, with centralized MCP and batch mapping that preserves public messages and schemas;
  • a shared bounded ordered worker coordinator used by read_multiple_files and grep_text_files, with deterministic commits, cancellation-aware dispatch, aggregate output/state budgets, and early stop for global match limits;
  • a bounded-memory text pipeline with incremental decoding for all 24 encodings, 16 MiB decoded-line limits, SHA-256 read sessions, reader-based mutation staging, and hard configured limits for full-document editing;
  • an explicit process configuration and shared server builder separated from transport startup, with a lifecycle-aware stdio runner, signal cancellation, explicit stdio transport selection, and equivalence tests across multiple connections to the same process-wide tool and root policy;
  • native stateful Streamable HTTP with mandatory bearer authentication, exact Host/Origin validation, loopback defaults, bounded sessions and request resources, redacted access logging, and a second execution opt-in;
  • release hardening with pinned cross-platform CI, reproducible GoReleaser archives, checksum-driven Registry publication, a non-root transport-neutral container, migration documentation, and sanitized public launch examples.

See CHANGELOG.md for the maintained list of fork-specific changes.

server.template.json contains only the fork-owned MCP Registry identity and release-neutral placeholders. On a fork release, the registry workflow downloads the published checksums.txt, generates a temporary server.json with the exact release URLs and SHA-256 values, and publishes only after every expected binary is represented.

Installation

Choose stdio when the MCP client should own the child-process lifecycle or when a secure bridge expects a local command. Choose Streamable HTTP when the server should run as a persistent authenticated service, including localhost, containers, or a TLS/trusted-proxy deployment. The recipes below are deployment options, not a priority order; both transports expose the same tools and policy.

Stdio through the OpenAI Secure MCP Tunnel

One validated stdio deployment is Windows plus the OpenAI tunnel client. The tunnel launches this fork as a local stdio MCP process and bridges it to the remote connector used by ChatGPT Web.

Requirements:

  • Windows PowerShell 5.1 or later;
  • the official OpenAI tunnel-client executable;
  • a Windows build of this fork;
  • an OpenAI Runtime API key with the tunnel permissions required by your OpenAI configuration;
  • a valid Tunnel ID;
  • one explicit local directory to expose to the MCP server.

This project uses OpenAI's official Secure MCP Tunnel client, not a third-party tunnel implementation. See the official OpenAI tunnel-client repository and the OpenAI Secure MCP Tunnel guide for tunnel installation, permissions, control-plane setup, and current product requirements.

The official client is the customer-run agent that connects a private or localhost MCP server to OpenAI-hosted products while keeping the MCP server off the public internet.

Build the fork locally

git clone https://github.com/zoster81/scripthold.git
Set-Location .\scripthold
go test ./...
go build -o scripthold_windows_amd64.exe ./cmd/scripthold

The Go module is github.com/zoster81/scripthold, and all internal imports resolve through the fork namespace. Build from source for development commits; use only fork-owned release tags with matching assets for packaged installations.

Until the first Scripthold-named release is tagged, go install github.com/zoster81/scripthold/cmd/scripthold@main installs the current rebranded command. Do not use @latest yet: Go resolves it to a historical pre-rebrand tag.

Download a fork release

Published fork releases provide a directly downloadable Windows binary. The currently published 2.0.0 release retains the historical remote asset filename; the command below saves it locally with the Scripthold name:

New-Item -ItemType Directory -Force "$env:LOCALAPPDATA\Programs\scripthold" | Out-Null
Invoke-WebRequest `
    "https://github.com/zoster81/scripthold/releases/latest/download/mcp-file-tools_windows_amd64.exe" `
    -OutFile "$env:LOCALAPPDATA\Programs\scripthold\scripthold_windows_amd64.exe"

For unreleased development commits, build from source as shown above.

OpenAI Tunnel quick start

A sanitized English example is provided at examples/start-openai-tunnel.ps1. It is intentionally a single-transport stdio reference; an operator may combine stdio and native HTTP startup in a private launcher outside the repository.

Place these files in the same private working directory:

tunnel-client.exe
scripthold_windows_amd64.exe
start-openai-tunnel.ps1

Copy the example outside the Git checkout before entering credentials:

$runDirectory = "$env:LOCALAPPDATA\OpenAI-Mcp-Tunnel"
New-Item -ItemType Directory -Force $runDirectory | Out-Null
Copy-Item .\examples\start-openai-tunnel.ps1 $runDirectory
Copy-Item .\scripthold_windows_amd64.exe $runDirectory
# Copy tunnel-client.exe from your OpenAI tunnel installation into the same directory.
notepad "$runDirectory\start-openai-tunnel.ps1"

Replace only the placeholders:

$RuntimeApiKey = "REPLACE_WITH_RUNTIME_API_KEY"
$TunnelId = "tunnel_REPLACE_WITH_ID"
$AllowedDirectory = "C:\Path\To\AllowedProject"

The tunnel identifier must be tunnel_ followed by exactly 32 lowercase hexadecimal characters. Never commit the edited script. The example selects stdio explicitly and keeps run_script and shell disabled by default.

To enable script execution for supported files located inside an allowed directory, change:

$EnableRunScript = $true

To enable unrestricted shell commands, change:

$EnableShell = $true

run_script validates the script path and working directory against the allowed roots, but the launched process is not sandboxed. shell validates only its working directory; the command itself can access anything permitted to the Windows identity running the tunnel. Enable these capabilities only for a trusted connector and after reviewing TOOLS.md.

Run the test from Windows PowerShell with the complete one-line command:

powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\OpenAI-Mcp-Tunnel\start-openai-tunnel.ps1"

From Command Prompt, use:

powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%LOCALAPPDATA%\OpenAI-Mcp-Tunnel\start-openai-tunnel.ps1"

The script validates paths and placeholders, runs tunnel-client doctor --explain, then starts the tunnel with the local operator UI at http://127.0.0.1:8080/ui. This validated tunnel workflow continues to use the server's stdio transport even though the same binary also supports native Streamable HTTP.

Other stdio MCP clients

The same binary can be used directly by clients that launch local stdio MCP servers. Supply every allowed directory as a command-line argument.

{
  "mcpServers": {
    "scripthold": {
      "type": "stdio",
      "command": "C:\\Tools\\scripthold_windows_amd64.exe",
      "args": ["D:\\Projects", "C:\\Users\\YOUR_NAME\\Documents"]
    }
  }
}

The transport can be selected explicitly with --transport=stdio or MCP_TRANSPORT=stdio. A roots-capable stdio client may provide workspace directories dynamically only when the process starts without directory arguments. Once directories are configured at startup, they remain the authoritative process-wide set.

Native Streamable HTTP

The native HTTP transport is stateful, bearer-authenticated, and bound to loopback by default. Every session shares the directory arguments supplied when the process starts; HTTP clients cannot add or change roots. The tracked HTTP launcher is a standalone reference even when a private deployment launcher starts both transports.

Create a private token file and start the endpoint from PowerShell:

$tokenPath = Join-Path $env:TEMP "scripthold.token"
$tokenBytes = New-Object byte[] 32
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try { $rng.GetBytes($tokenBytes) } finally { $rng.Dispose() }
[System.IO.File]::WriteAllText(
    $tokenPath,
    [Convert]::ToBase64String($tokenBytes),
    [System.Text.UTF8Encoding]::new($false)
)

$env:MCP_HTTP_TOKEN_FILE = $tokenPath
$env:MCP_HTTP_ADDR = "127.0.0.1:8765"
.\scripthold_windows_amd64.exe --transport=streamable-http D:\Projects

The MCP endpoint is http://127.0.0.1:8765/mcp. Clients must send the token as Authorization: Bearer <token> on every MCP POST, GET, and DELETE request. /healthz and /readyz expose only minimal liveness/readiness status. A complete sanitized Windows launcher with loopback defaults, optional TLS/proxy settings, environment restoration, and both execution gates disabled is available at examples/start-streamable-http.ps1.

MCP_HTTP_TOKEN and MCP_HTTP_TOKEN_FILE are cleared from the server process environment immediately after startup configuration is validated, preventing optional execution tools from inheriting the credential. The token itself remains fixed for the process lifetime; rotation requires a controlled restart.

Do not put tokens in command-line arguments, URLs, cookies, or query parameters. Browser CORS is disabled. Non-loopback listeners require explicit opt-in plus TLS or an explicitly trusted proxy boundary. See docs/HTTP_SECURITY.md for the complete deployment and threat model.

Container image

The repository Dockerfile uses the Go version declared by go.mod, a version-pinned Alpine runtime, a statically linked binary, and an unprivileged runtime identity (10001:10001). The container working directory is /data; cache and temporary files use /tmp/scripthold. The image remains transport-neutral, so its entry point is the server binary and callers select stdio or Streamable HTTP explicitly.

Build a development image with an explicit embedded version:

docker build --build-arg VERSION=dev -t scripthold:dev .

A hardened stdio invocation mounts exactly one allowed root and keeps the rest of the container filesystem read-only:

docker run --rm -i \
  --read-only \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --mount type=bind,source=/absolute/project,target=/data \
  scripthold:dev --transport=stdio /data

The mounted directory must be accessible to UID/GID 10001. For native HTTP, mount the workspace at /data, mount the bearer token and TLS files read-only under /run/secrets, publish port 8765, and supply the fail-closed non-loopback/TLS settings documented above. A direct-TLS deployment can use an orchestration health check equivalent to:

wget --no-check-certificate --spider -q https://127.0.0.1:8765/healthz

The Dockerfile intentionally does not bake in a health check because stdio has no HTTP endpoint. HTTP orchestrators should use /healthz for liveness and /readyz for readiness; stdio supervisors should monitor the process lifecycle. SIGTERM is the declared container stop signal and reaches the server's graceful-shutdown path.

Updating the fork

The update checker is notification-only and checks releases from zoster81/scripthold. It never downloads or replaces a binary.

To update a manual Windows installation:

  1. stop the OpenAI tunnel or other MCP client using the binary;
  2. download the latest fork release;
  3. replace the executable;
  4. restart the tunnel and run its diagnostics.
Invoke-WebRequest `
    "https://github.com/zoster81/scripthold/releases/latest/download/mcp-file-tools_windows_amd64.exe" `
    -OutFile "$env:LOCALAPPDATA\Programs\scripthold\scripthold_windows_amd64.exe"

Set MCP_NO_UPDATE_CHECK=1 before starting the server to disable release checks.

Project lineage and independence

This project originated from the original upstream repository and retains its GPL-3.0 lineage and attribution. The fork now owns its module path, release pipeline, update source, MCP Registry namespace, public API decisions, transport architecture, and security model. It is maintained as an independent downstream project rather than a branch expected to remain merge-compatible with later upstream releases.

Upstream continues to evolve separately and is explicitly credited as the source for the R15 agent-workflow feature set and relevant implementation approaches. The code in this fork is reworked for its own architecture rather than mechanically synchronized, and useful functionality, implementation techniques, tests, or security improvements may flow in either direction through normal discussion or GPL-3.0-compatible contributions. Several fork capabilities remain intentionally outside upstream's narrower product direction. See docs/PROJECT_DIRECTION.md for the reciprocal-exchange, maintenance, and contribution boundaries.

How to Use

Once the connector is active, ask ChatGPT Web or the connected MCP client:

  • "List all .pas files in the allowed project directory"
  • "Read config.ini and detect its encoding"
  • "Show all supported encodings"
  • "Read MainForm.dfm using CP1251 encoding"
  • "Detect this extensionless file's encoding and line endings"
  • "Convert data.legacy from mixed endings to CRLF without changing its encoding or BOM"
  • "Convert multilingual.data from UTF-8 to UTF-16 LE with bom: auto and create a backup"
  • "List the largest matching files while respecting .gitignore"
  • "Show only file paths containing either of these two regex patterns"
  • "Run the UTF-8 migration prompt for this project and preview every unsupported character before writing"

Security: File tools access only explicitly allowed directories:

  • OpenAI Tunnel: the directory arguments embedded in MCP_COMMAND are the authoritative process-wide set;
  • roots-capable stdio clients: client-provided roots are accepted only when the process starts without configured directories;
  • multiple sessions: every connection to one process shares the same allowed directories; prompt instructions may narrow an agent's intended write scope but are not server-enforced ACLs;
  • execution tools: run_script validates its script and working-directory paths, while shell validates only its working directory and is otherwise unrestricted;
  • optional backup store: MCP_BACKUP_STORE_DIR must be a separate canonical non-overlapping path and is denied to ordinary file tools; metadata actions never expose object bytes or internal paths, restore is restricted to a verified manifest's original authorized target, and GC is explicit, generation-bound, pin-aware, manifest-first, reference-counted, and never background-triggered.

Configuration

The server can be configured via environment variables:

Variable Description Default
MCP_TRANSPORT Process transport selection: stdio or streamable-http. The CLI --transport option takes precedence. stdio
MCP_HTTP_ADDR Native HTTP listen address. Only localhost or an IP literal is accepted; non-loopback requires explicit opt-in. 127.0.0.1:8765
MCP_HTTP_PATH Clean absolute MCP endpoint path, distinct from /healthz and /readyz. /mcp
MCP_HTTP_TOKEN Bearer token supplied through the environment. Exactly one token source is required for HTTP. unset
MCP_HTTP_TOKEN_FILE Preferred bearer-token source; must reference a regular readable file. Mutually exclusive with MCP_HTTP_TOKEN. unset
MCP_HTTP_ALLOWED_HOSTS Additional comma-separated exact Host values. Wildcards and suffix matching are rejected. listener-derived
MCP_HTTP_ALLOWED_ORIGINS Comma-separated exact browser origins. Empty rejects every request carrying Origin; no CORS allow headers are emitted. empty
MCP_HTTP_ALLOW_NON_LOOPBACK Explicit opt-in required for a non-loopback listener. disabled
MCP_HTTP_TLS_CERT_FILE TLS certificate for direct HTTPS. Must be configured with MCP_HTTP_TLS_KEY_FILE. unset
MCP_HTTP_TLS_KEY_FILE TLS private key for direct HTTPS. Must be configured with MCP_HTTP_TLS_CERT_FILE. unset
MCP_HTTP_TRUSTED_PROXY_CIDRS Comma-separated proxy networks permitted to supply a bounded X-Forwarded-For chain. empty
MCP_HTTP_MAX_BODY_BYTES Maximum body size of one HTTP POST. 16777216
MCP_HTTP_MAX_INFLIGHT_BODY_BYTES Aggregate reservation budget for concurrent HTTP POST bodies. 67108864
MCP_HTTP_MAX_CONCURRENT_REQUESTS Maximum simultaneous non-SSE HTTP handlers. SSE streams remain bounded by MCP_MAX_SESSIONS. 64
MCP_HTTP_SESSION_TIMEOUT Idle lifetime of a stateful HTTP session. 15m
MCP_HTTP_ENABLE_EXECUTION Additional HTTP-only gate required before run_script or shell can use their existing authorization flags. disabled
MCP_DEFAULT_ENCODING Default encoding for newly created files when write_file is called without encoding. Existing files keep a confidently detected encoding. Legacy encodings such as cp1251 remain available as explicit overrides. utf-8
MCP_MAX_FILE_BYTES Hard source-size limit for full-document operations such as edit_file. 67108864
MCP_MAX_DECODED_CHARACTERS Maximum decoded characters returned by read_text_file. 16777216
MCP_MAX_LINE_BYTES Maximum bytes in one decoded UTF-8 line. 16777216
MCP_MAX_BATCH_FILES Maximum items accepted by bounded batch operations, including read_multiple_files, fingerprint_paths, patch packages, and verify_state checks/path lists. 256
MCP_MAX_MATCHES Server maximum for grep_text_files.maxMatches. 10000
MCP_MAX_FINGERPRINT_ENTRIES Maximum files plus directories inspected by one fingerprint_paths request. 100000
MCP_MAX_FINGERPRINT_ENTRY_DETAILS Maximum optional per-entry fingerprint records returned by one request. 1000
MCP_MAX_EDIT_PREVIEWS Maximum live one-shot edit_file previews retained by one server process. 128
MCP_MAX_EDIT_PREVIEW_BYTES Aggregate dynamic bytes retained by live edit previews; independent of normal result output. 67108864
MCP_EDIT_PREVIEW_TTL_SECONDS Lifetime of one edit preview before lazy expiration and handle cleanup. 900
MCP_MAX_PATCH_PACKAGE_BYTES Maximum encoded semantic size of one patch-package-v1 manifest. 16777216
MCP_MAX_PATCH_PACKAGE_PREPARED_BYTES Aggregate retained prepared bytes, diffs, paths, and metadata during one package dry run. 67108864
MCP_MAX_PATCH_PACKAGE_PREVIEWS Maximum live one-shot package previews retained by one server process. 16
MCP_MAX_PATCH_PACKAGE_PREVIEW_BYTES Aggregate bytes retained by live package previews. 134217728
MCP_PATCH_PACKAGE_PREVIEW_TTL_SECONDS Lifetime of one package preview before lazy expiration and identity cleanup. 900
MCP_MAX_OUTPUT_BYTES Aggregate read output, retained grep state, fingerprint details, edit/package/restore/GC responses, verification diagnostics, and inconsistent-line output budget. 67108864
MCP_MAX_SESSIONS Maximum live native Streamable HTTP sessions. 128
MCP_BACKUP_STORE_DIR Unreleased R18 dedicated internal store. Must be absolute, canonical, non-overlapping with public roots, owner-only, and exclusively lockable. Enables required edit/package capture, bounded review/audit, original-target restore, and explicit generation-bound GC. unset
MCP_BACKUP_MAX_TOTAL_BYTES Maximum unique object bytes admitted by the internal capture primitive, including durable orphans and live conservative reservations. Hard maximum: 1 TiB. 1073741824
MCP_BACKUP_MAX_OBJECT_BYTES Maximum bytes in one internally captured object. Hard maximum: 1 GiB. 67108864
MCP_BACKUP_MAX_MANIFESTS Maximum live internal manifests and bounded recovery/audit scale. Hard maximum: 1,000,000. 10000
MCP_BACKUP_MAX_VERSIONS_PER_TARGET Maximum internally captured unpinned manifest versions for one target; pinned captures use the separate global pinned quota. Hard maximum: 10,000. 32
MCP_BACKUP_MAX_PINNED Maximum manifests created with immutable pinned state by internal capture. Hard maximum: 100,000. 256
MCP_BACKUP_RETENTION_DAYS Age threshold used only by explicit gcDryRun; never an automatic deletion timer. Hard maximum: 3,650 days. 30
MCP_BACKUP_PLAN_TTL_SECONDS Restore and GC one-shot capability lifetime. Hard maximum: 86,400 seconds. 900
MCP_MEMORY_THRESHOLD Deprecated fallback for MCP_MAX_FILE_BYTES and MCP_MAX_OUTPUT_BYTES; specific variables take precedence. unset
MCP_ENABLE_RUN_SCRIPT Enables only the run_script tool. Accepted true values: 1, true, yes, on, enabled. disabled
MCP_ENABLE_SHELL Enables only the unrestricted shell tool. Accepted true values: 1, true, yes, on, enabled. disabled
MCP_ENABLE_EXECUTION Enables both run_script and shell; use only in a trusted environment. disabled

To override, set environment variables in the tunnel launcher or another stdio client configuration:

{
  "mcpServers": {
    "scripthold": {
      "command": "C:\\Tools\\scripthold_windows_amd64.exe",
      "args": ["D:\\Projects"],
      "env": {
        "MCP_DEFAULT_ENCODING": "utf-8"
      }
    }
  }
}

Use Cases

Legacy Codebases

Many legacy projects use non-UTF-8 encodings that AI assistants can't handle natively:

  • Delphi/Pascal (Windows-1251): Source files with Cyrillic UI text
  • Extensionless or custom-format text (UTF-16, Windows code pages, ISO-8859, or UTF-8): detect from content and use an explicit encoding when evidence is ambiguous
  • Visual Basic 6 (Windows-1252): Forms and config files with Western European characters
  • Legacy PHP/HTML (CP1251, ISO-8859-1): Web apps with localized content
  • Old config files (Various): INI, properties, registry files with legacy encodings

How it works:

User: Read config.ini and change the title to "Настройки"
Assistant: [read_text_file with cp1251] → [modify UTF-8] → [write_file with cp1251]

The original encoding can be preserved while the public bom policy controls BOM output explicitly. The default auto policy writes UTF-8 and legacy encodings without BOM and UTF-16 LE/BE with their canonical BOM; use preserve when BOM presence must match an existing file.

Contributing

Contributor workflow is documented in CONTRIBUTING.md. The intentional 1.8-to-2.0 API changes are listed in docs/MIGRATION_2.0.md. Coding agents should read the root AGENTS.md and the nearest scoped AGENTS.md before editing a subtree. Public planning and verification gates remain in docs/ROADMAP.md and docs/DEVELOPMENT_CHECKLIST.md.

Development

Prerequisites: Go 1.26+

# Run tests
go test ./...

# Build
go build -o scripthold ./cmd/scripthold

Debugging with MCP Inspector

MCP Inspector provides a web UI for testing MCP servers.

Prerequisites: Node.js v18+

# Run with allowed directory (required)
npx @modelcontextprotocol/inspector go run ./cmd/scripthold -- /path/to/allowed/dir

# Or with built binary
npx @modelcontextprotocol/inspector ./scripthold.exe C:\Projects

Opens a browser where you can view tools, call them with custom arguments, and inspect responses.

Manual Debugging

Run the server with an allowed directory and send JSON-RPC commands via stdin:

# Specify transport and allowed directory
go run ./cmd/scripthold --transport=stdio /path/to/project

Example commands (paste into terminal):

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_directory","arguments":{"path":"/path/to/project","pattern":"*.go"}}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_text_file","arguments":{"path":"/path/to/project/main.pas","encoding":"cp1251"}}}
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"detect_encoding","arguments":{"path":"/path/to/project/file.txt"}}}

License

GPL-3.0 - see LICENSE

About

Secure encoding-aware local workspace runtime for AI agents. Code from the web. Work locally. Recover safely.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages