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,
.gitignoretraversal, 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
.bakhandling, and no-replace creation. - Optional execution —
run_scriptand unrestrictedshellare 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.
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.
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.
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.
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 \
--prettyThe 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.
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 numbersread_multiple_files- Read files in deterministic order under one aggregate decoded-output budgetwrite_file- Write through the shared encoder with explicitauto/always/never/preserveBOM policyedit_file- Direct edits or bounded one-shot preview/apply, including optional required pre-state backup, exact/flexible/fuzzy operations, and strict unified patchespatch_package- Inspect, preview, apply, and verify bounded declared multi-file edits, with optional all-target required backups and explicit partial-state evidencecopy_file- Copy a file to a new locationdelete_file- Delete a filelist_directory- Browse directories with filtering and deterministic name/mtime/size sortingtree- Compact deterministic.gitignore-aware tree through the shared secure walkersearch_files-.gitignore-aware glob search with bounded globally correct sortingfingerprint_paths- Stream deterministic SHA-256 state fingerprints with optional bounded entry detailsverify_state- Run bounded typed JSON, text-format, fixed Git diff, and fingerprint checks without arbitrary shell executionbackup_store- Review, restore, and explicitly garbage-collect the optional persistent store through bounded one-shot workflowsgrep_text_files- Paged regex search with pattern/filter arrays and content/path/count modesdetect_encoding- Auto-detect file encoding with confidence scoreconvert_encoding- Single/batch conversion, dry-run previews, unsupported-rune locations, and durable writesdetect_line_endings- Stream CRLF/LF/mixed detection with bounded inconsistent-line outputchange_line_endings- Stream LF/CRLF conversion while preserving encoding, BOM, and unrelated bytesmanage_bom- Inspect a bounded prefix or stream BOM add/strip through durable staginglist_encodings- Show all supported encodingsget_file_info- Get file/directory metadatacreate_directory- Create directories recursively (mkdir -p)move_file- Move or rename files and directorieslist_allowed_directories- Show accessible directoriesrun_script- Execute a supported script or executable inside an allowed directory when explicitly enabledshell- Execute an unrestricted shell command when explicitly enabledcheck_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.
This repository has evolved from its original upstream codebase. Compared with that baseline, the current source branch adds:
- optional
run_scriptandshellMCP 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_endingsand byte-preservingchange_line_endingssupport 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, andpreserveBOM policies plus byte-identical conversion no-op suppression; - a deterministic, cancellation-aware secure walker shared by
tree,search_files,grep_text_files, andfingerprint_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-v1inspect/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_stateworkflow for JSON syntax, encoding/BOM/line-ending/trailing-whitespace expectations, fixed directgit 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-v1descriptor, verified SHA-256 object deduplication, strict checksummed manifests, conservative single/package quota reservations, bounded recovery, a rebuildable index, bounded review/audit, opt-in requirededit_fileandpatch_packagepre-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_filesandgrep_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
stdiotransport 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.
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.
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-clientexecutable; - 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.
git clone https://github.com/zoster81/scripthold.git
Set-Location .\scripthold
go test ./...
go build -o scripthold_windows_amd64.exe ./cmd/scriptholdThe 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.
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.
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 = $trueTo enable unrestricted shell commands, change:
$EnableShell = $truerun_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.
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.
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:\ProjectsThe 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.
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 /dataThe 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.
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:
- stop the OpenAI tunnel or other MCP client using the binary;
- download the latest fork release;
- replace the executable;
- 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.
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.
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: autoand 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_COMMANDare 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_scriptvalidates its script and working-directory paths, whileshellvalidates only its working directory and is otherwise unrestricted; - optional backup store:
MCP_BACKUP_STORE_DIRmust 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.
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"
}
}
}
}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.
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.
Prerequisites: Go 1.26+
# Run tests
go test ./...
# Build
go build -o scripthold ./cmd/scriptholdMCP 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:\ProjectsOpens a browser where you can view tools, call them with custom arguments, and inspect responses.
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/projectExample 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"}}}GPL-3.0 - see LICENSE