Skip to content

Commit ed42d46

Browse files
Dumbrisclaude
andauthored
feat(update): update_check config block + dismissible Web UI update banner (Spec 079 US1) (#805)
* feat(update): update_check config block with hot-reload + env precedence (Spec 079 US1) Add the update_check.{enabled,channel} config group (FR-012/FR-013): - enabled (default true) gates BOTH the background poll and the manual CheckNow (/api/v1/info?refresh=true) path; when disabled no network check runs and GetVersionInfo returns nil so /api/v1/info omits the update object entirely — every surface (banner, badge, status/doctor annotation) goes quiet (FR-015). - channel: "stable" (default; prereleases never offered) or "rc" (prereleases included), validated; the config-file equivalent of MCPPROXY_ALLOW_PRERELEASE_UPDATES. - Precedence (FR-014): the existing env switches WIN over config — MCPPROXY_DISABLE_AUTO_UPDATE=true force-disables and MCPPROXY_ALLOW_PRERELEASE_UPDATES=true force-includes prereleases. The spec leaves precedence open; env-wins is the operator-override reading, documented in code and docs. - Hot-reload: DetectConfigChanges reports "update_check"; ApplyConfig (API path) and ReloadConfiguration (disk path) both re-gate the running checker. The background loop now stays alive while config-disabled, and a re-enable/channel switch triggers a prompt re-check instead of waiting up to the 4h interval. - make swagger: config.UpdateCheckConfig surfaced on config.Config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): dismissible per-version update banner on the dashboard (Spec 079 US1) FR-005: non-modal alert banner on the dashboard when the checker reports an update — "Update available: vX — you are running vY" with a release-notes link and a dismiss (X) button. Dismissal persists the dismissed latest_version in localStorage, so the same version never re-nags across reloads while a newer release shows the banner again. The existing sidebar badge + manual-check toast are unchanged. When update_check.enabled=false the daemon omits the update object from /api/v1/info, so the banner (and badge) are naturally absent; the manual "check for updates" action now says checks are disabled instead of the misleading "You are running the latest version". Tests: 6 vitest cases (render, no-update, absent update object, dismiss persists, stays dismissed on remount, newer version reappears). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(update): document update_check config block, env-var precedence, and the Web UI banner (Spec 079 US1) - features/version-updates.md: update_check.{enabled,channel} reference, hot-reload behavior, explicit env-vs-config precedence (env wins, one direction only), config-based examples, dismissible per-version banner. - configuration.md + configuration/config-file.md (served reference): new Update Check sections + complete-reference key. - configuration/environment-variables.md: auto-update table is core+tray (not tray-only) and points at the config-file equivalent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(update): review fixes — tray honors update_check.enabled, no stale nudge/cache Address Codex review findings on the Spec 079 US1 branch: - Go tray self-update (major): the tray's independent daily release check (checkForUpdates) now reads update_check.enabled from the shared config file and skips the network check when disabled (FR-015: no check on any surface). Fails open on missing/unreadable config, matching the pre-079 default; MCPPROXY_DISABLE_AUTO_UPDATE still wins (FR-014). Full FR-001a convergence (tray consuming the shared checker, incl. channel) remains a separate 079 work item — docs updated to say exactly that. - Tray stale nudge (minor): checkUpdateFromAPI treats an absent update object in /api/v1/info (checker disabled via hot-reload) as "no update", clearing state and hiding the menu item instead of returning early and leaving a stale "New version available" entry until restart. - Checker races (minor): SetConfig now bumps a config generation and drops the cached VersionInfo on any effective change; check() captures the generation and updateVersionInfo discards results from a stale generation or while disabled. An in-flight check can no longer publish/announce after disable, and a channel switch or re-enable never briefly serves wrong-channel cached info (FR-013/FR-015). - UpdateBanner (minor): localStorage reads/writes wrapped in try/catch, degrading to session-only dismissal when storage is blocked (precedent: stores/system.ts), so blocked storage cannot break Dashboard setup. Tests: new unit tests for the tray config gate, the stale-nudge clear (httptest core stub), the checker generation/disable races, and a blocked-localStorage banner spec. go test -race, vitest (253 pass), golangci-lint v2, and make build all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(update): sync REST/CLI/prerelease docs with update_check gating - rest-api.md: /api/v1/info omits the update object when checking is disabled (update_check.enabled=false or MCPPROXY_DISABLE_AUTO_UPDATE); note refresh=true no-ops while disabled and point at the update_check config block. - cli/status-command.md: document the disabled case (no update object, version shown without annotation). - prerelease-builds.md: RC opt-in now also possible via update_check.channel=rc for the core checker (Go tray self-update check stays env-only until FR-001a). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(update): tray gate honors --config override; check() re-reads enabled Codex final-review findings on PR #805 (Spec 079 US1): - Major: ServerAdapter.GetConfigPath() hardcoded ~/.mcpproxy/mcp_config.json, ignoring MCPPROXY_TRAY_CONFIG_PATH — the very path the tray launches core with as --config (buildCoreArgs). So the update_check gate read the wrong file under a custom config path and failed open, letting the tray-owned daily GitHub check run despite update_check.enabled=false. Resolve the env override first, matching what core actually uses. - Minor: check() captured cfgGen but not the enabled flag, so a disable racing after the caller's outer Enabled() gate (loop tick / re-enable goroutine) still issued a GitHub request. The generation guard dropped the result, but the request fired — violating FR-015 "no network check on any surface". Re-read enabled under the same lock as gen and bail before checkFunc. Both covered by new tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(update): tray gates self-update via core API, not config file The tray must interact with the core only over the socket/REST API and hold no state (CLAUDE.md). The previous update-check gate violated this by calling config.LoadFromFile on the core's mcp_config.json. Replace updateCheckEnabledByConfig with fetchCoreUpdateInfo, which asks the core via GET /api/v1/info (the same endpoint checkUpdateFromAPI uses). The core omits the update object when update_check.enabled=false, making its config the single source of truth: - core reports an update object -> run the legacy GitHub self-update flow - core omits the update object -> skip (checking disabled / no update) - core unreachable -> skip this tick (do not fall open to a network check the operator may have disabled); the 24h ticker retries checkUpdateFromAPI now shares fetchCoreUpdateInfo. File-based gate tests are replaced with API-based ones asserting the network path (injected selfUpdateFunc) runs only when the core reports an update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c39e9b7 commit ed42d46

25 files changed

Lines changed: 1324 additions & 47 deletions

cmd/mcpproxy-tray/internal/api/adapter.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path/filepath"
88
"runtime"
9+
"strings"
910

1011
internalRuntime "github.com/smart-mcp-proxy/mcpproxy-go/internal/runtime"
1112
"github.com/smart-mcp-proxy/mcpproxy-go/internal/tray"
@@ -307,8 +308,15 @@ func (a *ServerAdapter) ReloadConfiguration() error {
307308
return fmt.Errorf("ReloadConfiguration not yet supported via API")
308309
}
309310

310-
// GetConfigPath returns the configuration file path
311+
// GetConfigPath returns the configuration file path core is running with.
312+
// The tray passes MCPPROXY_TRAY_CONFIG_PATH to core as --config (see
313+
// buildCoreArgs in main.go), so tray-side config consumers — e.g. the Spec 079
314+
// update_check gate — must resolve to that same override, not a hardcoded
315+
// default. Falls back to the default ~/.mcpproxy path when unset.
311316
func (a *ServerAdapter) GetConfigPath() string {
317+
if cfg := strings.TrimSpace(os.Getenv("MCPPROXY_TRAY_CONFIG_PATH")); cfg != "" {
318+
return cfg
319+
}
312320
homeDir, err := os.UserHomeDir()
313321
if err != nil {
314322
return "~/.mcpproxy/mcp_config.json" // fallback

cmd/mcpproxy-tray/internal/api/adapter_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,3 +645,30 @@ func TestHealthDataFlow_EndToEnd(t *testing.T) {
645645
status := adapter.GetStatus().(map[string]interface{})
646646
assert.Equal(t, 1, status["connected_servers"], "Status should use health.level for connected count")
647647
}
648+
649+
// =============================================================================
650+
// ServerAdapter.GetConfigPath Tests
651+
// =============================================================================
652+
653+
// The tray launches core with --config <MCPPROXY_TRAY_CONFIG_PATH> (see
654+
// buildCoreArgs in main.go). GetConfigPath must resolve to that SAME path so
655+
// tray-side config consumers (e.g. the Spec 079 update_check gate) read the
656+
// config core is actually using — not a hardcoded default.
657+
func TestServerAdapter_GetConfigPath_HonorsTrayConfigPathEnv(t *testing.T) {
658+
const custom = "/tmp/custom-tray-config/mcp_config.json"
659+
t.Setenv("MCPPROXY_TRAY_CONFIG_PATH", custom)
660+
661+
adapter := NewServerAdapter(NewMockClient())
662+
663+
assert.Equal(t, custom, adapter.GetConfigPath())
664+
}
665+
666+
func TestServerAdapter_GetConfigPath_DefaultWhenEnvUnset(t *testing.T) {
667+
t.Setenv("MCPPROXY_TRAY_CONFIG_PATH", "")
668+
669+
adapter := NewServerAdapter(NewMockClient())
670+
671+
got := adapter.GetConfigPath()
672+
assert.Contains(t, got, "mcp_config.json")
673+
assert.NotEqual(t, "", got)
674+
}

docs/api/rest-api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ Get application info, version, and update availability.
793793
| `listen_addr` | string | Server listen address |
794794
| `endpoints.http` | string | HTTP API endpoint address |
795795
| `endpoints.socket` | string | Unix socket path (empty if disabled) |
796-
| `update` | object | Update information (may be null if not checked yet) |
796+
| `update` | object | Update information (may be null if not checked yet; omitted entirely when update checking is disabled via `update_check.enabled: false` or `MCPPROXY_DISABLE_AUTO_UPDATE=true`) |
797797
| `update.available` | boolean | Whether a newer version is available |
798798
| `update.latest_version` | string | Latest version available on GitHub |
799799
| `update.release_url` | string | URL to the GitHub release page |
@@ -802,7 +802,7 @@ Get application info, version, and update availability.
802802
| `update.check_error` | string | Error message if update check failed |
803803

804804
:::tip Update Checking
805-
MCPProxy automatically checks for updates every 4 hours. The update information is exposed via this endpoint and used by the tray application and web UI to show update notifications.
805+
MCPProxy automatically checks for updates every 4 hours. The update information is exposed via this endpoint and used by the tray application and web UI to show update notifications. Use `?refresh=true` to force an immediate re-check. Checking is controlled by the `update_check` config block (`enabled`, `channel`) — see [Version Updates](/features/version-updates); when disabled, `?refresh=true` performs no check and the `update` object is omitted.
806806
:::
807807

808808
### Docker

docs/cli/status-command.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ When the daemon is running, `status` surfaces the result of the background updat
151151
- **Update available**: `Version: v1.2.0 (update available: v1.3.0 — <release URL>)`
152152
- **Up to date**: `Version: v1.3.0 (latest)`
153153
- **Check failed or not yet completed** (offline, rate-limited): the version is shown without any annotation. In JSON output the `update.check_error` field retains the failure reason for diagnostics.
154+
- **Update checking disabled** (`update_check.enabled: false` in the config, or `MCPPROXY_DISABLE_AUTO_UPDATE=true`): the daemon performs no check and omits the `update` object entirely, so the version is shown without any annotation.
154155

155156
In machine-readable output (`-o json`/`-o yaml`) the `update` object also carries `checked_at` (when the last successful check ran, so consumers can judge staleness) and `is_prerelease` (whether the offered version is a prerelease), matching the `/api/v1/info` contract.
156157

docs/configuration.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Complete reference for MCPProxy configuration file (`mcp_config.json`). This doc
1717
11. [Code Execution](#code-execution)
1818
12. [Feature Flags](#feature-flags)
1919
13. [Registries](#registries)
20-
14. [Complete Example](#complete-example)
20+
14. [Update Check](#update-check)
21+
15. [Complete Example](#complete-example)
2122

2223
---
2324

@@ -1033,6 +1034,49 @@ and are hot-reloadable. Non-positive values fall back to the defaults.
10331034

10341035
---
10351036

1037+
## Update Check
1038+
1039+
Controls the background upgrade-awareness checker (Spec 079). MCPProxy
1040+
periodically queries GitHub Releases and surfaces "update available" on
1041+
`mcpproxy status` / `doctor`, a startup log line, the Web UI (sidebar badge +
1042+
dismissible banner), and the trays. Checks never block and fail silently when
1043+
offline.
1044+
1045+
```json
1046+
{
1047+
"update_check": {
1048+
"enabled": true,
1049+
"channel": "stable"
1050+
}
1051+
}
1052+
```
1053+
1054+
| Field | Type | Default | Description |
1055+
|-------|------|---------|-------------|
1056+
| `enabled` | boolean | `true` | Master switch for update checking. When `false`, no network check is performed (background poll *and* the manual `/api/v1/info?refresh=true` re-check) and no upgrade nudge appears on any surface — the `update` object is omitted from `/api/v1/info`. |
1057+
| `channel` | string | `"stable"` | Release channel: `"stable"` (GitHub `releases/latest`; prereleases never offered) or `"rc"` (prerelease tags such as `v0.47.0-rc.1` included). |
1058+
1059+
Both keys are optional and hot-reloadable: editing them (config file or
1060+
`POST /api/v1/config/apply`) takes effect without a restart, and re-enabling
1061+
triggers a prompt re-check.
1062+
1063+
**Environment-variable precedence** — the existing switches keep working and
1064+
**win over** the config keys (operator override):
1065+
1066+
| Variable | Effect |
1067+
|----------|--------|
1068+
| `MCPPROXY_DISABLE_AUTO_UPDATE=true` | Force-disables update checking even when `update_check.enabled` is `true`. |
1069+
| `MCPPROXY_ALLOW_PRERELEASE_UPDATES=true` | Force-selects the prerelease (`rc`) channel even when `update_check.channel` is `stable`. |
1070+
1071+
The env vars only widen in one direction (disable checks / enable
1072+
prereleases); they cannot force-enable checking that config disabled — with
1073+
`update_check.enabled: false`, checks stay off regardless of environment.
1074+
1075+
See [Version Updates](features/version-updates.md) for where updates are
1076+
surfaced.
1077+
1078+
---
1079+
10361080
## Complete Example
10371081

10381082
Here's a complete configuration example with all major sections:

docs/configuration/config-file.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ MCPProxy uses a JSON configuration file located at `~/.mcpproxy/mcp_config.json`
3838
"features": {
3939
"enable_web_ui": true
4040
},
41+
"update_check": {
42+
"enabled": true,
43+
"channel": "stable"
44+
},
4145
"mcpServers": []
4246
}
4347
```
@@ -112,6 +116,23 @@ Both cadences are configurable globally, and can be overridden per server (see [
112116
| `code_execution_max_tool_calls` | integer | `0` | Maximum tool calls (0 = unlimited) |
113117
| `code_execution_pool_size` | integer | `10` | VM pool size for code execution |
114118

119+
### Update Check Settings
120+
121+
Controls the background upgrade-awareness checker. Both keys are optional and
122+
hot-reloadable (no restart needed).
123+
124+
| Option | Type | Default | Description |
125+
|--------|------|---------|-------------|
126+
| `update_check.enabled` | boolean | `true` | Master switch. When `false`, no network check runs (background poll and manual re-check) and no upgrade nudge appears on any surface — the `update` object is omitted from `/api/v1/info`. |
127+
| `update_check.channel` | string | `"stable"` | Release channel: `"stable"` (prereleases never offered) or `"rc"` (prerelease tags like `v0.47.0-rc.1` included). |
128+
129+
The existing environment switches keep working and **win over** these keys:
130+
`MCPPROXY_DISABLE_AUTO_UPDATE=true` force-disables checking, and
131+
`MCPPROXY_ALLOW_PRERELEASE_UPDATES=true` force-selects the prerelease channel.
132+
They only widen in one direction — they cannot re-enable checking that the
133+
config disabled. See [Version Updates](/features/version-updates) for where
134+
updates are surfaced.
135+
115136
### MCP Servers
116137

117138
See [Upstream Servers](/configuration/upstream-servers) for detailed server configuration.

docs/configuration/environment-variables.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,19 @@ The tray application doesn't read the config file directly. It launches the core
111111
| `MCPPROXY_TRAY_ENDPOINT` | Override tray-core communication endpoint (unix:///path/socket.sock or npipe:////./pipe/name) | Auto-detect |
112112
| `MCPPROXY_TRAY_INSPECT_ADDR` | Address for tray instrumentation/debug server | - |
113113

114-
### Auto-Update Settings (Tray)
114+
### Auto-Update Settings
115115

116116
| Variable | Description | Default |
117117
|----------|-------------|---------|
118-
| `MCPPROXY_DISABLE_AUTO_UPDATE` | Disable automatic update checks | `false` |
119-
| `MCPPROXY_UPDATE_NOTIFY_ONLY` | Only notify about updates, don't auto-install | `false` |
120-
| `MCPPROXY_ALLOW_PRERELEASE_UPDATES` | Allow prerelease/beta version updates | `false` |
121-
| `MCPPROXY_UPDATE_APP_BUNDLE` | Enable app bundle updates (macOS) | `false` |
118+
| `MCPPROXY_DISABLE_AUTO_UPDATE` | Disable automatic update checks (core + tray) | `false` |
119+
| `MCPPROXY_UPDATE_NOTIFY_ONLY` | Only notify about updates, don't auto-install (tray) | `false` |
120+
| `MCPPROXY_ALLOW_PRERELEASE_UPDATES` | Allow prerelease/beta version updates (core + tray) | `false` |
121+
| `MCPPROXY_UPDATE_APP_BUNDLE` | Enable app bundle updates (macOS tray) | `false` |
122+
123+
Update checking can also be controlled from the config file via the
124+
`update_check` block (`enabled`, `channel`) — see
125+
[Version Updates](/features/version-updates). When both are set, the
126+
environment variables **win** over the config keys.
122127

123128
### Setting Tray Variables on macOS
124129

docs/features/version-updates.md

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ The sidebar displays the current version at the bottom. When an update is availa
3737
- A small "update available" badge appears next to the version
3838
- Click to view the release notes
3939

40+
The dashboard additionally shows a **dismissible update banner** ("Update
41+
available: vX — you are running vY") with a release-notes link. Dismissal is
42+
**per version**: dismissing the banner for v1.3.0 keeps it hidden for v1.3.0
43+
(persisted in the browser), but the banner reappears when a newer release
44+
becomes available. The banner is non-modal and never blocks the UI.
45+
4046
### CLI Doctor Command
4147

4248
The `mcpproxy doctor` command shows version information:
@@ -55,20 +61,75 @@ Download: https://github.com/smart-mcp-proxy/mcpproxy-go/releases/tag/v1.3.0
5561

5662
## Configuration
5763

64+
### Config File (`update_check`)
65+
66+
Update checking is controlled from `mcp_config.json` via the `update_check`
67+
block:
68+
69+
```json
70+
{
71+
"update_check": {
72+
"enabled": true,
73+
"channel": "stable"
74+
}
75+
}
76+
```
77+
78+
| Option | Type | Default | Description |
79+
|--------|------|---------|-------------|
80+
| `enabled` | boolean | `true` | Master switch. When `false`, no network check is performed (background poll and the manual re-check) and no upgrade nudge appears on any surface — the `update` object is omitted from `/api/v1/info`. |
81+
| `channel` | string | `"stable"` | Release channel: `"stable"` (GitHub `releases/latest`; prereleases never offered) or `"rc"` (prerelease tags such as `v0.47.0-rc.1` included). |
82+
83+
Both keys are **hot-reloadable**: editing the config file or applying it via
84+
`POST /api/v1/config/apply` takes effect without a restart. Re-enabling (or
85+
switching channels) triggers a prompt re-check instead of waiting for the next
86+
4-hour tick.
87+
88+
`enabled: false` also gates the Go tray's built-in daily self-update check, so
89+
no surface performs a network check while disabled. The tray does **not** read
90+
`mcp_config.json` itself (it holds no state); instead it asks the core via
91+
`GET /api/v1/info` before checking — the core omits the `update` object when
92+
update checking is disabled, and the tray then skips its own network check. If
93+
the core is unreachable the tray skips that tick and retries, rather than
94+
falling open to a check the operator may have disabled. The tray's own check
95+
still selects prereleases via `MCPPROXY_ALLOW_PRERELEASE_UPDATES` only —
96+
converging it fully onto the shared checker (including `channel`) is a separate
97+
Spec 079 work item (FR-001a).
98+
5899
### Environment Variables
59100

60101
| Variable | Description | Default |
61102
|----------|-------------|---------|
62103
| `MCPPROXY_DISABLE_AUTO_UPDATE` | Disable background update checks entirely | `false` |
63104
| `MCPPROXY_ALLOW_PRERELEASE_UPDATES` | Include prerelease/beta versions in update checks | `false` |
64105

106+
### Precedence (env vs config)
107+
108+
The environment switches **win over** the `update_check` config keys — they
109+
are the operator override:
110+
111+
- `MCPPROXY_DISABLE_AUTO_UPDATE=true` disables checking even when
112+
`update_check.enabled` is `true`.
113+
- `MCPPROXY_ALLOW_PRERELEASE_UPDATES=true` selects the prerelease channel even
114+
when `update_check.channel` is `"stable"`.
115+
116+
The env vars only widen in one direction (disable checks / include
117+
prereleases). They cannot re-enable checking that the config disabled: with
118+
`update_check.enabled: false`, no check runs regardless of environment.
119+
65120
### Examples
66121

67122
```bash
68-
# Disable update checking
123+
# Disable update checking (config file — persistent, hot-reloads)
124+
# "update_check": { "enabled": false }
125+
126+
# Disable update checking (environment — wins over config)
69127
MCPPROXY_DISABLE_AUTO_UPDATE=true mcpproxy serve
70128

71-
# Enable prerelease updates (for beta testers)
129+
# Opt in to prerelease (RC) updates via config
130+
# "update_check": { "channel": "rc" }
131+
132+
# Enable prerelease updates via environment (for beta testers)
72133
MCPPROXY_ALLOW_PRERELEASE_UPDATES=true mcpproxy serve
73134
```
74135

@@ -127,7 +188,7 @@ When running a development build (version shows as "development"), update checki
127188
### Update check not working
128189

129190
1. Ensure you have internet connectivity
130-
2. Check if `MCPPROXY_DISABLE_AUTO_UPDATE` is set
191+
2. Check if `MCPPROXY_DISABLE_AUTO_UPDATE` is set, or `update_check.enabled` is `false` in `mcp_config.json`
131192
3. Run `mcpproxy doctor` to see current version status
132193
4. Check logs for any GitHub API errors:
133194
```bash
@@ -136,7 +197,8 @@ When running a development build (version shows as "development"), update checki
136197

137198
### Prerelease not showing
138199

139-
By default, prerelease versions are excluded. To enable:
200+
By default, prerelease versions are excluded. To enable, set
201+
`"update_check": { "channel": "rc" }` in `mcp_config.json`, or:
140202

141203
```bash
142204
export MCPPROXY_ALLOW_PRERELEASE_UPDATES=true

docs/prerelease-builds.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ The GitHub release is created with `prerelease: true`, so it does **not** become
6969
- Does not deploy docs or trigger marketing automation (`deploy-docs`, `trigger-marketing-update` guarded).
7070
- Not offered as an update on **stable channels**:
7171
- The macOS tray uses GitHub `releases/latest`, which excludes prereleases (`native/macos/MCPProxy/MCPProxy/Services/UpdateService.swift`), plus a semver downgrade guard so an `-rc` is never treated as "newer" than the matching stable.
72-
- The backend/tray update check is stable-only by default (`internal/tray/tray.go``releases/latest`). Set `MCPPROXY_ALLOW_PRERELEASE_UPDATES=true` to opt in to RC update offers.
72+
- The backend/tray update check is stable-only by default (`internal/tray/tray.go``releases/latest`). Set `MCPPROXY_ALLOW_PRERELEASE_UPDATES=true` to opt in to RC update offers; the core checker can also opt in via `"update_check": { "channel": "rc" }` in `mcp_config.json` (Spec 079 — the Go tray's own self-update check gates on the core's decision by querying `GET /api/v1/info` rather than reading the config file, and selects prereleases via `MCPPROXY_ALLOW_PRERELEASE_UPDATES`; converging it fully onto the shared checker is FR-001a).
7373

7474
### Installing an RC
7575

0 commit comments

Comments
 (0)