Scripthold — Secure MCP Server for Local Workspaces
Code from the web. Work locally. Recover safely.
Scripthold is a Model Context Protocol (MCP) server that gives web, desktop, and CLI agents controlled access to explicitly authorized local workspaces. It reads and writes legacy text safely, exposes deterministic repository-oriented workflows, supports authenticated Streamable HTTP as well as stdio, and can optionally run durable asynchronous local tasks.
AI clients see Настройки — not ???? or Íàñòðîéêè.
Scripthold detects encodings from bytes and decoded-text evidence rather than filenames, presents text to the MCP client as UTF-8, and preserves or deliberately converts encoding, BOM, and line endings through bounded-memory and durable filesystem operations.
- 36 tools and 3 guided prompts over one authoritative catalog in Scripthold
3.0.0. - 168 registered encodings, including UTF-32 LE/BE and broad portable legacy coverage; automatic detection remains intentionally more conservative than explicit codec support.
- 101 active source-intelligence providers across programming languages, dialects, DSLs, document/config formats, and composites, with capability-specific evidence and fail-closed ambiguity.
- Secure filesystem boundaries with resolved-root containment, deterministic traversal, Windows reparse/junction handling, staged mutation, conflict detection, and no-replace creation.
- Verified change workflows with deterministic fingerprints, one-shot edit approval, strict patch packages, persistent backup integration, and typed verification.
- Offline backup recovery with deterministic persisted review plans, immutable source evidence, fully verified reconstruction into a separate staged destination, mandatory full audit, no-replace promotion, and path-free provenance.
- Durable asynchronous execution with idempotent admission, an owner-only task store, bounded queue/logs, independent supervisor/worker/executor lifecycle, recovery, logical locks, and cancellation.
- Fail-closed Streamable HTTP with bearer authentication, loopback defaults, exact Host/Origin checks, bounded resources, no CORS, and explicit TLS/proxy requirements for non-loopback exposure.
Scripthold was built with Scripthold.
Lineage: Scripthold originated from the original
mcp-file-toolsproject, created by Dimitar Grigorov, and retains its GPL-3.0 lineage and permanent attribution. See Project Direction.
Current release and development state
Scripthold 3.0.0 is the current public release. It exposes 36 tools, 3 guided prompts, 168 registered encodings, and 101 active source-intelligence providers. The GitHub Release publishes six raw binaries, six platform archives, and checksums.txt; GitHub-only workflows add MCPB bundles and the MCP Registry publication for io.github.zoster81/scripthold.
The 3.0.0 release line contains the completed R23-R27 work and exposes 36 tools. R27 completed on 2026-08-16 with 101 active source-analysis providers across 103 registry rows; Dockerfile and Make remain auxiliary inactive metadata. source_symbols provides bounded declaration/navigation workflows, while source_query adds structural search, supported project relations, bounded fingerprint-verified context, and coherent process-local incremental generations. Capability claims are provider-specific and mechanically projected in docs/LANGUAGE_CAPABILITIES.md; ambiguous formats fail closed, and no external parser/compiler/LSP runtime or persistent on-disk code index is required.
No milestone after R27 is active by default; Scripthold 3.0.0 publishes the completed R23-R27 release scope. Current milestone state belongs in docs/ROADMAP.md, concise completed history in docs/ROADMAP_HISTORY.md, and the R23-R27 subsystem contracts in docs/MCP_MUTATION_SURFACE.md, docs/SAFE_FILESYSTEM_OPERATIONS.md, docs/SOURCE_INTELLIGENCE.md, docs/BACKUP_RECOVERY.md, and docs/MULTILANGUAGE_CODE_INTELLIGENCE.md.
Quality and security
The push-event Test Suite is the exact-commit release-candidate gate. It covers Windows/Linux/macOS native regression and race testing, Go vet, standalone Staticcheck, the repository's focused golangci-lint policy, govulncheck, deterministic fuzz checks, six supported-target cross-builds, workflow/shell validation, and native/container smoke before the aggregate Release candidate job can pass.
CodeQL adds Go code scanning on main pushes, a weekly schedule, and manual runs. Vulnerability reporting and responsible-disclosure guidance are in SECURITY.md. Secret scanning, allowed-root confinement, durable mutation/backup invariants, encoding safety, and transport security remain part of the repository's normal verification model rather than badge-only claims.
Transport and authorization model
| Transport | Typical use | Security boundary | Roots behavior |
|---|---|---|---|
| stdio | Local MCP clients and secure tunnel bridges | Client configuration plus operating-system process boundary | Startup directories are authoritative; dynamic client roots are accepted only when startup roots are empty |
| Streamable HTTP | Persistent localhost services, containers, trusted proxies, explicitly secured remote services | Bearer token on every MCP request; loopback by default; TLS or trusted proxy boundary for non-loopback | Startup directories are immutable and shared by all requests; HTTP clients cannot mutate roots |
Both transports use the same BuildServer path and expose the same tools, prompts, limits, encoding behavior, error model, and execution policy.
Allowed directories are a process-wide authorization boundary. Sessions separate protocol lifecycle and cancellation; they are not per-agent filesystem ACLs. If two agents require technical isolation, run separate Scripthold processes with narrower roots and, for concurrent Git writes, separate checkouts or worktrees.
MCP 2026-07-28 is supported through the stable Go SDK. Native HTTP serves stateless modern requests beside retained stateful legacy sessions under the same outer authentication, Host/Origin, resource, logging, and execution controls. See docs/MCP_2026_07_28_ADOPTION.md and docs/HTTP_SECURITY.md.
Tool catalog
File and directory operations
read_text_file— stream decoded text with bounded output and optional line numbers.read_multiple_files— deterministic bounded batch reads with per-file status.write_whole_file— replace complete file contents through the shared encoder.edit_file— read-only exact edit preview with approval fingerprints and a one-shot capability.edit_file_apply— apply only the exact prepared edit identified bypreviewId.patch_package— read-only inspect/dry-run/verify for declared multi-file edits.patch_package_apply— apply only a prepared patch-package capability.list_directory— list directory entries with filtering and deterministic sorting.tree— compact.gitignore-aware deterministic tree output.get_file_info— read file or directory metadata.filesystem_package— read-only bounded preparation for coordinated no-replace create/copy/move/delete filesystem changes.filesystem_package_apply— apply one prepared filesystem package by one-shotpreviewId.search_files— bounded.gitignore-aware glob search.source_symbols— bounded read-only sourceoutline,digest,find, and fingerprint-boundshownavigation.source_query— bounded R27 read-only structural search, supported project relations, and fingerprint-verified task-context assembly.fingerprint_paths— deterministic SHA-256 state fingerprints.verify_state— bounded typed JSON/text/Git-diff/fingerprint checks.backup_store— read-only status/history/compare/audit plus restore/GC preparation for the optional persistent store.backup_restore_apply— apply one prepared original-target restore.backup_gc_apply— apply one prepared generation-bound backup GC plan.grep_text_files— paged regex search with deterministic partial-coverage reporting.
Encoding and service tools
detect_encoding— conservative encoding detection with confidence or explicit ambiguity.convert_encoding— read-only exact single/batch conversion preview.convert_encoding_apply— apply a prepared exact conversion bypreviewId.detect_line_endings— bounded LF/CRLF/mixed analysis.change_line_endings— line-ending conversion while preserving encoding/BOM semantics.manage_bom— detect BOM state or prepare an exact add/strip change.manage_bom_apply— apply one prepared BOM mutation bypreviewId.list_encodings— authoritative runtime encoding inventory.list_allowed_directories— report process-authorized roots.check_for_updates— notification-only fork release check.
Durable task execution
task_run— durably enqueue idempotent shell or script work.task_list— page/filter persistent task metadata.task_get— inspect current/terminal task state and bounded lifecycle history.task_logs— read bounded stdout/stderr with absolute cursors.task_cancel— cancel queued work or terminate a running process tree.
The detailed schemas, outputs, limits, and examples are authoritative in TOOLS.md. internal/toolcatalog/catalog.json is the source of truth for runtime tool metadata.
Encoding support
list_encodings is authoritative for canonical names, aliases, and capability metadata. Scripthold 3.0.0 exposes 168 canonical read/write encodings across Unicode, IBM/DOS/EBCDIC, ISO-8859, Windows, classic Mac/KOI8/other single-byte families, and East Asian/stateful multibyte families.
The production runtime remains pure Go. Additional mappings and state machines derived from pinned GNU libiconv evidence are checked in and require no libiconv/GCC dependency during ordinary build or execution. UTF-32 LE/BE are full text encodings with strict scalar validation; generic byte-order-unspecified utf-32 remains intentionally rejected. See docs/GLOBAL_ENCODING_COVERAGE.md for the completed R22 contract.
Installation
Choose stdio when the MCP client should own the child process or a secure bridge expects a local command. Choose Streamable HTTP for a persistent authenticated service. Both expose the same public behavior.
Use a published release
Scripthold-named releases use raw binary names of the form scripthold_<os>_<arch> (with .exe on Windows) and matching platform archives. Historical 2.0.0 predates the rename and retains its original asset names.
For reproducible installations, use a specific semantic release rather than @main or an assumed historical asset name. Verify the published asset against checksums.txt before installation.
Build from source
git clone https://github.com/zoster81/scripthold.git
cd scripthold
go test ./...
go build -o scripthold ./cmd/scripthold
The module path is github.com/zoster81/scripthold.
Local stdio clients
Pass every startup-authorized directory as an argument:
{
"mcpServers": {
"scripthold": {
"type": "stdio",
"command": "C:\\Tools\\scripthold_windows_amd64.exe",
"args": ["D:\\Projects"]
}
}
}
A roots-capable stdio client may provide dynamic roots only when the process starts without directory arguments. MCP_STDIO_LEGACY_HANDSHAKE=1 exists only for legacy bridges that probe discovery and repeat an equivalent legacy initialization on one persistent child; leave it disabled for normal modern clients.
Native Streamable HTTP
HTTP requires exactly one bearer-token source. A minimal loopback PowerShell start is:
$tokenPath = Join-Path $env:TEMP "scripthold.token"
$bytes = New-Object byte[] 32
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
try { $rng.GetBytes($bytes) } finally { $rng.Dispose() }
[System.IO.File]::WriteAllText($tokenPath, [Convert]::ToBase64String($bytes), [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; /healthz and /readyz expose minimal liveness/readiness status. The token must be sent as Authorization: Bearer <token> on every MCP request. Do not put tokens in command-line arguments, URLs, cookies, or query parameters.
Non-loopback listeners require explicit opt-in plus TLS or an explicitly trusted proxy boundary. Browser CORS is not enabled. See docs/HTTP_SECURITY.md before exposing HTTP beyond loopback.
OpenAI Secure MCP Tunnel
The repository includes sanitized PowerShell examples for tunnel and local topologies:
| Example | Topology |
|---|---|
start-local-stdio.ps1 | One foreground local stdio server. |
start-local-http.ps1 | One authenticated HTTP server; loopback by default. |
start-openai-tunnel-stdio-plus-local-http.ps1 | Tunnel to a dedicated stdio child plus an independent local HTTP process. |
start-openai-tunnel-http-plus-local-stdio.ps1 | Tunnel to authenticated HTTP plus an independent local stdio child. |
Copy an example outside the Git checkout before replacing placeholders. Never commit Runtime API keys, Tunnel IDs, bearer tokens, or private state paths. The tunnel setup uses OpenAI's official tunnel-client; consult the official client documentation for current OpenAI control-plane requirements.
The example launchers keep task_run execution disabled by default. Script and shell execution remain separate authorizations, and HTTP additionally requires MCP_HTTP_ENABLE_EXECUTION=1.
Container image
The repository Dockerfile builds a statically linked binary and runs as unprivileged UID/GID 10001. The image is transport-neutral.
docker build --build-arg VERSION=dev -t scripthold:dev .
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. HTTP containers should mount token/TLS files read-only, publish only the intended port, and preserve the security contract in docs/HTTP_SECURITY.md.
Security model
- File tools access only explicitly authorized roots after canonical path resolution.
- Recursive operations do not follow escaping symlinks, junctions, or other reparse points.
- Mutations stage and revalidate before commit; initially missing destinations use no-replace creation. Single-file mutators classify the bounded actual target state after failures that may occur beyond the commit boundary instead of reporting preview-predicted changes as fact.
- Failed MCP tool calls preserve stable error metadata/text; tools with structured output also expose
errorCodeand human-readablemessagethere, retaining any existing partial-state evidence. - The optional backup store must be a separate non-overlapping owner-only authority and is inaccessible to ordinary file tools.
task_runis disabled by default. Script tasks validate/fingerprint the script and execute an owner-only matching snapshot; shell tasks validate the logical shell name before durable admission, confine only the working directory, and otherwise run with the executor identity's operating-system permissions.- HTTP adds authentication, Host/Origin, proxy/TLS, resource, logging, and execution boundaries; it is not a replacement for operating-system isolation.
Detailed contracts: HTTP security, verified changes, persistent backups, offline backup diagnostics, R23 mutation surface, R24 safe filesystem operations, R25 source intelligence, R26 backup recovery, and durable tasks.
Configuration
The most important process-wide variables are summarized below. Subsystem documents contain the precise security and lifecycle semantics.
| Variable | Purpose | Default |
|---|---|---|
MCP_TRANSPORT | stdio or streamable-http; CLI --transport takes precedence. | stdio |
MCP_DEFAULT_ENCODING | Encoding for newly created files when no encoding is supplied. | utf-8 |
MCP_MAX_FILE_BYTES | Full-document source-size limit. | 67108864 |
MCP_MAX_DECODED_CHARACTERS | Maximum decoded characters returned by read_text_file. | 16777216 |
MCP_MAX_LINE_BYTES | Maximum decoded UTF-8 bytes in one line. | 16777216 |
MCP_MAX_BATCH_FILES | Maximum items in bounded batch/path-list operations. | 256 |
MCP_MAX_MATCHES | Server maximum for grep matches. | 10000 |
MCP_MAX_OUTPUT_BYTES | Aggregate structured/text output budget. | 67108864 |
MCP_SOURCE_MAX_FILES | R25 source files considered per request before stricter global ceilings. | 256 |
MCP_SOURCE_MAX_AGGREGATE_BYTES | Aggregate raw source bytes selected by one source-intelligence request. | 67108864 |
MCP_SOURCE_MAX_FILE_BYTES | Per-file source-intelligence byte ceiling. | 8388608 |
MCP_SOURCE_MAX_SYMBOLS | Retained source-symbol ceiling per request/analyzer budget. | 10000 |
MCP_SOURCE_MAX_CONCURRENCY | Bounded source-analysis worker count. | 4 |
MCP_SOURCE_MAX_REQUEST_SECONDS | Source-intelligence request deadline. | 30 |
MCP_SOURCE_MAX_OUTPUT_BYTES | Source-intelligence structured output budget before the global output ceiling. | 16777216 |
MCP_SOURCE_MAX_RESULTS | R27 retained search/relation result ceiling. | 10000 |
MCP_SOURCE_MAX_GRAPH_NODES | R27 graph-node ceiling. | 5000 |
MCP_SOURCE_MAX_GRAPH_EDGES | R27 graph-edge ceiling. | 20000 |
MCP_SOURCE_MAX_GRAPH_DEPTH | R27 graph traversal depth ceiling. | 8 |
MCP_SOURCE_MAX_CONTEXT_BYTES | R27 task-context byte budget. | 1048576 |
MCP_SOURCE_MAX_CONTEXT_ITEMS | R27 retained context-item ceiling. | 256 |
MCP_SOURCE_MAX_INDEX_PROJECTS | R27 retained process-local index-scope ceiling. | 4 |
MCP_SOURCE_MAX_INDEX_GENERATIONS | R27 retained generations per index scope. | 2 |
MCP_MAX_FILESYSTEM_PACKAGE_OPERATIONS | Maximum operations in one filesystem-package-v1 manifest. | 256 |
MCP_MAX_FILESYSTEM_PACKAGE_BYTES | Maximum prepared filesystem-package manifest size. | 16777216 |
MCP_MAX_FILESYSTEM_RECURSIVE_ENTRIES | Maximum entries in one exact recursive copy/delete scope. | 100000 |
MCP_MAX_FILESYSTEM_RECURSIVE_DEPTH | Maximum exact recursive copy/delete depth. | 128 |
MCP_MAX_FILESYSTEM_AGGREGATE_BYTES | Maximum aggregate source bytes in one filesystem package. | 1073741824 |
MCP_MAX_FILESYSTEM_STAGING_BYTES | Maximum aggregate bytes staged before filesystem-package commit. | 1073741824 |
MCP_MAX_FILESYSTEM_PACKAGE_PREVIEWS | Maximum retained filesystem-package preview capabilities. | 16 |
MCP_MAX_FILESYSTEM_PACKAGE_PREVIEW_BYTES | Maximum aggregate retained preview state. | 134217728 |
MCP_FILESYSTEM_PACKAGE_PREVIEW_TTL_SECONDS | Filesystem-package preview lifetime. | 900 |
MCP_MEMORY_THRESHOLD | Deprecated fallback for file/output byte limits. | unset |
MCP_HTTP_ADDR | HTTP listen address. | 127.0.0.1:8765 |
MCP_HTTP_PATH | MCP endpoint path. | /mcp |
MCP_HTTP_TOKEN_FILE / MCP_HTTP_TOKEN | Mutually exclusive HTTP bearer-token sources. | unset |
MCP_HTTP_ALLOWED_HOSTS | Additional exact Host values. | listener-derived |
MCP_HTTP_ALLOWED_ORIGINS | Exact accepted Origin values; no CORS headers are emitted. | empty |
MCP_HTTP_ALLOW_NON_LOOPBACK | Required opt-in for non-loopback binding. | disabled |
MCP_HTTP_TLS_CERT_FILE / MCP_HTTP_TLS_KEY_FILE | Direct HTTPS certificate/key pair. | unset |
MCP_HTTP_TRUSTED_PROXY_CIDRS | Immediate trusted proxy networks. | empty |
MCP_HTTP_MAX_BODY_BYTES | Per-POST body limit. | 16777216 |
MCP_HTTP_MAX_INFLIGHT_BODY_BYTES | Aggregate concurrent POST-body reservation. | 67108864 |
MCP_HTTP_MAX_CONCURRENT_REQUESTS | Concurrent non-SSE HTTP handlers. | 64 |
MCP_HTTP_SESSION_TIMEOUT | Legacy stateful session idle timeout. | 15m |
MCP_HTTP_ENABLE_EXECUTION | Additional HTTP-only execution gate. | disabled |
MCP_BACKUP_STORE_DIR | Enables the dedicated persistent backup store. | unset |
MCP_BACKUP_DEFAULT_POLICY | Default persistent pre-state policy for approval-bound edit/package/BOM/encoding mutations: disabled or required. | disabled |
MCP_TASK_STORE_DIR | Enables the owner-only durable task registry. | unset |
MCP_ENABLE_RUN_SCRIPT | Authorizes task_run kind=script. | disabled |
MCP_ENABLE_SHELL | Authorizes unrestricted task_run kind=shell. | disabled |
MCP_ENABLE_EXECUTION | Authorizes both task kinds. | disabled |
Backup limits, task-store limits, edit/package preview limits, and the full HTTP configuration contract are documented in docs/PERSISTENT_BACKUP_LIFECYCLE.md, docs/DURABLE_TASKS.md, TOOLS.md, and docs/HTTP_SECURITY.md.
Typical uses
- Read and safely modify legacy source/configuration files without changing their encoding accidentally.
- Search mixed-encoding repositories with explicit partial-coverage evidence.
- Navigate heterogeneous repositories across 101 active source providers, query supported project relations, and assemble bounded source context without loading complete projects into the model.
- Preview and approve edits or multi-file patch packages against deterministic fingerprints.
- Keep approval-bound persistent backups and restore a selected original target safely.
- Recover trustworthy records from a damaged backup store offline into a separate audited destination without modifying the source evidence.
- Run long builds/tests through durable tasks without tying process lifetime to one MCP request.
- Serve the same workspace tools through local stdio, authenticated HTTP, containers, or a secure tunnel bridge.
Example:
User: Read config.ini and change the title to "Настройки".
Assistant: read_text_file (cp1251) -> edit_file preview preserving cp1251 -> explicit approval -> edit_file_apply(previewId)
Development and contribution
Prerequisite Go version is declared by go.mod. The full local quality gate also uses the repository-pinned golangci-lint policy.
go mod verify
go test ./...
golangci-lint run ./...
go build -o scripthold ./cmd/scripthold
Contributor workflow is in CONTRIBUTING.md. Coding agents should read the root AGENTS.md and the nearest scoped guide. Reusable verification is in docs/DEVELOPMENT_CHECKLIST.md, current planning in docs/ROADMAP.md, and publication in docs/PUBLISHING.md.
The intentional 1.8-to-2.0 breaking changes remain documented in docs/MIGRATION_2.0.md. The Scripthold 3.0.0 R23-R27 surface evolution is documented in docs/MIGRATION_3.0.md and the completed subsystem contracts.
License
GPL-3.0 — see LICENSE.