This release tightens process and group isolation, adds explicit env plumbing for stdio servers, and includes one important behaviour change for users running in child_process mode.
Downstream stdio MCP servers are now spawned with env_clear() plus a small launch-allowlist (PATH, HOME, USERPROFILE, SYSTEMROOT, WINDIR, PATHEXT, TEMP, TMP, TMPDIR, SSL_CERT_FILE, SSL_CERT_DIR).
If any downstream server expected to read credentials or other settings from the parent process environment (for example, a GitHub server reading GITHUB_PERSONAL_ACCESS_TOKEN), declare them explicitly on [servers.*].env:
[servers.github]
command = "github-mcp"
transport = "stdio"
env = { GITHUB_PERSONAL_ACCESS_TOKEN = "${GITHUB_TOKEN}" }${VAR} expansion in config still references the parent process env at load time.
All config structs now have deny_unknown_fields. Any stale or misspelled keys that previously parsed silently will now produce a parse error at startup. Run forgemax doctor (or forgemax serve --check) once before upgrading and fix any errors reported.
The forge run script.js subcommand previously bypassed group isolation. If you used forge run with [groups] configured and relied on cross-group access, you will now see "cross-group" errors. The fix matches how forge serve already behaved.
If your config omits execution_mode entirely, sandbox executions now run in child_process (previously in_process). Set execution_mode = "in_process" explicitly to keep the old behaviour. An invalid value at runtime now logs a warning and falls back to child_process rather than silently to in_process.
The default IPC envelope size is now 65 MB, sized to fit a full-size 64 MB resource payload plus 1 MB overhead. Previously the IPC default was 8 MB while the resource default was 64 MB -- internally inconsistent for child_process mode. Tighten both for memory-constrained deployments via [sandbox]:
[sandbox]
max_resource_size_mb = 8
max_ipc_message_size_mb = 9If you construct TransportConfig::Stdio directly (rather than via to_transport_config), add the new field:
// Before
TransportConfig::Stdio { command, args }
// After
TransportConfig::Stdio { command, args, env: HashMap::new() }See CHANGELOG.md for the full list. Highlights: stash group isolation is now actually enforced cross-group; the worker pool now propagates known_servers/known_tools so structured-error fuzzy matching works in pooled mode; resource truncation returns a structured object instead of invalid raw JSON; the install.ps1 Windows installer now verifies SHA256.
No breaking changes. Drop-in upgrade from v0.5.0.
deno_core0.391 → 0.398,v8146.3 → 147.2oxc_parser/oxc_ast/oxc_span/oxc_allocator0.115 → 0.126sha20.10 → 0.11 (major bump in the upstream crate; Forgemax's hashing API is unchanged, so callers are unaffected)
ReconnectingClient no longer uses a fixed 100ms sleep while a concurrent reconnection is in flight — waiters now block on tokio::sync::Notify (bounded at 30s) and retry with the fresh client. Behaviour under light load is unchanged; under heavy concurrent load this prevents transport-dead errors from leaking past the reconnection layer and tripping the circuit breaker.
rmcp 1.2 marks all model structs #[non_exhaustive]. If you construct rmcp types directly (e.g., Implementation { ... }), you must migrate to the builder API:
// Before (rmcp 0.17)
Implementation {
name: "my-server".into(),
version: "1.0".into(),
}
// After (rmcp 1.2)
Implementation::new("my-server", Some("1.0"))Affected types: Implementation, ServerInfo, CallToolResult, ReadResourceResult, ListResourcesResult, CallToolRequestParams, ReadResourceRequestParams.
Two optional fields on [servers.*]:
[servers.my-server]
reconnect = true # Enable auto-reconnect on transport death (default: true for stdio)
max_reconnect_backoff_secs = 30 # Max backoff between reconnect attempts (default: 30)DispatchError::TransportDead is a new variant for permanent transport failures (broken pipe, channel closed). If you match exhaustively on DispatchError, add a branch for TransportDead. It is not retryable — the ReconnectingClient decorator handles reconnection automatically.
The client decorator chain is now: McpClient → ReconnectingClient → Timeout → CircuitBreaker → Router. The reconnecting layer sits below timeout/circuit-breaker so transport death is caught before circuit breaker probing.
v0.4.x configuration files work without modification. The new reconnect and max_reconnect_backoff_secs fields default to safe values when absent.
In v0.3.x, worker-pool, metrics, and config-watch were opt-in features (default off). In v0.4.0, all three are default-on.
If you were already using --features worker-pool,metrics: Remove the flag — features are now on by default. The redundant flag is harmless but unnecessary.
If you want a minimal build: Use --no-default-features to disable all optional features:
cargo build --release --no-default-featuresIf you want selective features: Combine --no-default-features with --features:
cargo build --release --no-default-features --features ast-validator,worker-poolforgemax now uses clap for argument parsing with subcommands:
| Command | Description |
|---|---|
forgemax (no args) |
Start the MCP gateway server (unchanged behavior) |
forgemax serve |
Explicit alias for the default server mode |
forgemax doctor |
Validate configuration and connectivity |
forgemax manifest |
Inspect the capability manifest |
forgemax run <file> |
Execute a JavaScript file against configured servers |
forgemax init |
Generate a starter configuration file |
Backward compatibility: Running forgemax with no arguments still starts the server, exactly as in v0.3.x.
v0.3.x configuration files work without modification. New optional sections ([sandbox.pool], [manifest], [groups.*]) default to safe values when absent.
A production configuration example is available at forge.toml.example.production.
SECURITY.md— Comprehensive security model documentationCONTRIBUTING.md— Developer setup and contribution guidelinesROADMAP.md— Project roadmap and non-goalsexamples/— JavaScript examples demonstrating all sandbox APIs
- Stash group isolation (H1): IPC stash messages now carry
group: Option<String>, ensuring stash data is scoped by server group in ChildProcess mode. Previously,_current_groupwas discarded, allowing cross-group access. - Worker stderr hardening (H3): Worker stderr is now
Stdio::piped()(debug, bounded to 4KB) orStdio::null()(production).Stdio::inherit()is never used, preventing unbounded stderr leakage. - URI scheme validation (M2):
validate_resource_uri()now blocks dangerous URI schemes (data:,javascript:,ftp:,gopher:,telnet:,ldap:,dict:). Custom MCP schemes (e.g.,postgres://) are allowed. - AST alias detection (AST-12): The AST validator now detects aliased dangerous identifiers (
const e = eval; e("code")), including multi-hop aliases and destructured eval fromglobalThis. - AST
require()blocking:requireadded toDANGEROUS_IDENTIFIERSandcheck_call_callee, preventingrequire('child_process')and alias evasion (const r = require; r('fs')). - Audit code_preview redaction:
code_previewin audit entries is now passed throughredact_error_message()to strip credentials before logging. - Stash operation limits: Per-execution rate limiting for stash operations via
max_stash_callsinStashOverrides.
- IPC error type preservation: Introduced
IpcDispatchErrorstruct to preserveDispatchErrorvariant (code, server, tool, timeout_ms) across the IPC boundary. Previously, typed errors were flattened to strings when crossing from host to worker, losing structured error information (fuzzy-match suggestions, error codes). - Pre-dispatch tool name validation:
RouterDispatchernow validates tool names against known tools before dispatching to upstream servers. Previously, misspelled tool names were sent upstream and returned as genericUpstreamerrors with no fuzzy-match suggestions. Now returnsToolNotFoundwith Levenshtein-based suggestions (e.g.,find_symbls→Did you mean 'find_symbols'?).
- Worker pool pre-warming:
WorkerPool::pre_warm()spawnsmin_workersworkers at startup (requiresworker-poolfeature). - Background reaping:
WorkerPool::start_reap_task()runs periodic idle worker cleanup while preservingmin_workers(requiresworker-poolfeature). - Prometheus metrics:
ForgeMetricsstruct with execution counters, duration histograms, error counters, and pool gauges (requiresmetricsfeature). - Structured timeout in IPC:
ExecutionCompletenow carriestimeout_ms: Option<u64>for structured timeout reporting, with backward-compatible string parsing fallback. - Raw IPC passthrough:
write_raw_message()/read_raw_message()functions for zero-copy message forwarding.
New optional feature flags (all default off):
worker-pool— gatespre_warm()andstart_reap_task()in forge-sandboxmetrics— gatesprometheus-clientdependency andForgeMetricsmoduleconfig-watch— gatesnotifycrate for config file watching (forge-config)
All new IPC fields use #[serde(default, skip_serializing_if = "Option::is_none")]. A v0.3.1 parent receiving a v0.3.0 worker message (missing new fields) deserializes them as None. A v0.3.0 parent receiving a v0.3.1 worker message (extra fields) ignores them. Rolling upgrades are safe in both directions.
ToolDispatcher::call_toolandResourceDispatcher::read_resourcenow returnResult<Value, DispatchError>instead ofResult<Value, anyhow::Error>. Update trait implementations to useforge_error::DispatchError.SandboxConfighas new required fields. Use struct update syntax (..Default::default()) when constructing.
- Replace string
.contains()assertions on errors with typedmatches!patterns onDispatchErrororSandboxErrorvariants. - Update
ToolDispatcher/ResourceDispatcherimplementations to returnDispatchError.
- AST validator: Pre-execution validation of JavaScript code for banned patterns, import/require, eval, and environment access.
- Structured errors:
DispatchErrorenum withServerNotFound,ToolNotFound,ExecutionFailed, andTimeoutvariants, plus fuzzy matching suggestions. - LiveManifest refresh:
arc-swap-based lock-free manifest with SIGHUP and periodic refresh. - Worker pool: Configurable worker pool with health checks, max-uses recycling, and idle reaping.
- Resource reading:
ResourceDispatchertrait andreadResource()sandbox API. - Stash API: Per-session key-value store with TTL, group isolation, and audit logging.