@auggieteo/dsh-mcp-adapter 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/CONTEXT.md +5 -1
- package/README.md +30 -28
- package/docs/adr/0002-config-in-dsh-settings-namespace.md +2 -0
- package/docs/adr/0009-dual-generation-settings-api.md +2 -0
- package/docs/adr/0010-loader-entry-config-model.md +15 -0
- package/lib/client.js +15 -9
- package/lib/client.js.map +3 -3
- package/package.json +8 -8
- package/src/host/index.js +70 -45
- package/src/host/legacy-import.js +121 -0
- package/src/host/settings.js +130 -18
- package/src/host/workspace-config.js +3 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [v0.4.1] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- The Host entry no longer dies on start with `Error: cannot get property "config" without inject`. v0.4.0 read the Adapter's Loader-entry Config from `ctx.config`, but cordis 4 has no `config` service or accessor — the context proxy refuses any property the plugin did not declare in `inject`, so every DSH start that mounted the `mcp-adapter` entry threw inside `createMcpConfigScope` and the Adapter never came up. The validated Config is the second `apply` argument (`apply(ctx, config)`, the shape the shipped DSH plugins use), and its volatile fields are references the Loader commits through in place, so the scope stays live for the whole activation. `createMcpConfigScope(ctx, config)` now takes it explicitly; `test/settings.test.js` pins the contract against a real cordis fiber and its fakes refuse a `config` context property the way the proxy does. [ADR 0010](docs/adr/0010-loader-entry-config-model.md) records the correction.
|
|
12
|
+
|
|
13
|
+
## [v0.4.0] - 2026-09-27
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- One-time legacy import: after the Loader settles on DSH 0.1.7, a still-untouched `mcp-adapter` entry adopts the legacy `mcp:` section of the profile's `settings.yaml` (validated, then written through the settings service once, with a `mcp-adapter.legacy-imported` marker in the profile home so clearing every Server never resurrects the old list). Failures warn with manual copy instructions and never throw. `test/legacy-import.test.js`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Breaking: DSH 0.1.7 only.** The settings model the Adapter was built on is gone in 0.1.7 — `ctx.settings.register(namespace, schema)` was replaced by profile Loader-entry Config — and carrying the old generations alongside it would have meant two parallel settings subsystems, so support drops to DSH `0.1.7-rc.1` and newer (peer `@deepseek-ai/dsh-settings` `^0.1.7-rc.1`, `@deepseek-ai/schemastery` `^3.18.4`; stay on v0.3.x for older harnesses). The host half now exports `Config` with `mcpServers` and `skillInstall` marked volatile, reads live values off `ctx.config` (committed without a fiber restart via `loader/volatile-update`), and writes through `ctx.settings`; `createMcpConfigScope` keeps the `{ get, watch, update, mutate }` surface all consumers use. The settings namespace identity is the Loader entry id: `mcp-adapter` (was `mcp`). `src/host/index.js`, [ADR 0010](docs/adr/0010-loader-entry-config-model.md).
|
|
22
|
+
- The client reads the entry through the `configForms` service (`configForms.get('mcp-adapter')` plus the shared `describe()` mirror) instead of the removed `settingsScope`; the `settings.section` page, the `remote.settings` positional wire, and its `{ ok, value | error }` envelope are unchanged. An entry whose mirrored value fails the schema now shows an explicit invalid state instead of a permanent spinner.
|
|
23
|
+
- Cross-field validation moved from before-persist (the removed settings provider hook) to Host consumption: a Config section that passes schemastery but violates the transport rules serves no Servers with one deduplicated warning, so hand-edited profile Config cannot brick the manager.
|
|
24
|
+
- `test/client-apply.test.js` collapses to a single 0.1.7 generation fake pinning the `mcp-adapter` entry id on the wire.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- The DSH 0.1.1-rc.2 `connection.api.settings` client fallback, the 0.1.2/0.1.5 generation fakes, and the dead `@deepseek-ai/dsh-client-runtime` entry (last published for 0.1.1) from `dsh.client.inject`.
|
|
29
|
+
- The `installMcpSettings` export (replaced by `createMcpConfigScope`); the nav-icon patch script now targets 0.1.7's renamed `*Medium` icon members with `IconLinkOutlineMedium` as the default.
|
|
30
|
+
|
|
7
31
|
## [v0.3.0] - 2026-09-13
|
|
8
32
|
|
|
9
33
|
### Changed
|
package/CONTEXT.md
CHANGED
|
@@ -29,9 +29,13 @@ A per-server toggle that lets that server's tool calls run without asking the us
|
|
|
29
29
|
_Avoid_: trust, bypass, whitelist
|
|
30
30
|
|
|
31
31
|
**Config**:
|
|
32
|
-
The standard `mcpServers`-shaped JSON stored
|
|
32
|
+
The standard `mcpServers`-shaped JSON stored as the Adapter's profile Loader-entry Config (entry id `mcp-adapter`), editable in Settings > MCP. Global: one list for every DSH session.
|
|
33
33
|
_Avoid_: manifest, registry file
|
|
34
34
|
|
|
35
|
+
**Entry Config**:
|
|
36
|
+
The DSH 0.1.7 configuration model: a plugin's `Config` schema validated by the cordis Loader and handed to the plugin's `apply` as its second argument — cordis has no `config` service, so `ctx.config` is not readable — persisted in the profile composition. Fields marked volatile are edited live through the settings forms and commit into the running plugin's references without a restart.
|
|
37
|
+
_Avoid_: settings namespace, settings document
|
|
38
|
+
|
|
35
39
|
**Lazy Lifecycle**:
|
|
36
40
|
The connection policy: a server connects on first use and disconnects after an idle timeout. Opposite of eager connect at startup.
|
|
37
41
|
_Avoid_: on-demand boot, cold start
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
DSH plugin connecting the agent to MCP servers through one mcp proxy tool and a Settings > MCP page.
|
|
8
8
|
|
|
9
|
-
A [Deepseek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that mirrors the major features of [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter): one token-efficient `mcp` proxy tool over every configured server, optional per-tool promotion to native DSH tools, and configurable Server lifecycles (`lazy` by default). Servers are added and configured in the DSH web UI under **Settings > MCP**; configuration persists
|
|
9
|
+
A [Deepseek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that mirrors the major features of [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter): one token-efficient `mcp` proxy tool over every configured server, optional per-tool promotion to native DSH tools, and configurable Server lifecycles (`lazy` by default). Servers are added and configured in the DSH web UI under **Settings > MCP**; configuration persists as the Adapter's DSH profile entry config (entry `mcp-adapter`).
|
|
10
10
|
|
|
11
11
|
Vocabulary lives in [CONTEXT.md](./CONTEXT.md). Design decisions live in [docs/adr/](./docs/adr/). Work is tracked as [GitHub issues](https://github.com/auggie246/dsh-mcp-adapter/issues), and released changes are listed in [CHANGELOG.md](./CHANGELOG.md).
|
|
12
12
|
|
|
@@ -50,7 +50,7 @@ Vocabulary lives in [CONTEXT.md](./CONTEXT.md). Design decisions live in [docs/a
|
|
|
50
50
|
|
|
51
51
|
[pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter) established a workflow for driving many MCP servers from one agent: a single token-efficient proxy tool, lazy connections, and per-tool promotion. This plugin brings that workflow to [Deepseek Harness](https://github.com/deepseek-ai/deepseek-harness) natively.
|
|
52
52
|
|
|
53
|
-
Server Config is stored
|
|
53
|
+
Server Config is stored as the Adapter's DSH Loader-entry Config (the `Config` schema the host module exports, persisted in the active profile composition, hot-reloaded through volatile updates, revision-fenced) rather than pi-mcp-adapter's layered `.mcp.json` files, while still adopting the standard `mcpServers`-shaped JSON so configs paste over from pi, Claude Desktop, Cursor, or VS Code. See [ADR 0010](./docs/adr/0010-loader-entry-config-model.md) for the reasoning. The v1 end-to-end verification lives in [docs/verification/v1-e2e.md](./docs/verification/v1-e2e.md).
|
|
54
54
|
|
|
55
55
|
## Install
|
|
56
56
|
|
|
@@ -66,11 +66,10 @@ Then restart DSH. The Settings panel (`⌘,` / sidebar foot) gains an **MCP** se
|
|
|
66
66
|
|
|
67
67
|
| DSH release | Support |
|
|
68
68
|
| --- | --- |
|
|
69
|
-
| 0.1.
|
|
70
|
-
| 0.1.
|
|
71
|
-
| Anything else | Unsupported. The client fails with an explicit "No DSH settings write API" error instead of a crash. |
|
|
69
|
+
| 0.1.7-rc.1 and newer 0.1.x | Supported. Configuration is the plugin's Loader-entry Config: the Host reads the validated Config cordis passes to `apply` (there is no `ctx.config`) plus its live-committed volatile updates, writes go through the settings service, and the page reads the `configForms` service and writes through the `remote.settings` face mounted by the `@deepseek-ai/dsh-api-remotes` client bundle. |
|
|
70
|
+
| Older 0.1.x | Unsupported. Those releases require the registered-namespace settings model this plugin used through v0.3.x; upgrade DSH or stay on that release line. |
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
Upgrading from v0.3.x on DSH 0.1.7: at the first start after the upgrade, the Adapter imports the legacy `mcp:` section of the profile's `settings.yaml` into the `mcp-adapter` entry once. A `mcp-adapter.legacy-imported` marker records the import; anything the import refuses to accept (for example an invalid section) stays in `settings.yaml.imported` and a Host warning names the manual copy path. See [ADR 0010](./docs/adr/0010-loader-entry-config-model.md).
|
|
74
73
|
|
|
75
74
|
### Local development
|
|
76
75
|
|
|
@@ -113,37 +112,40 @@ The Host registers three human commands. A bare `/mcp` or `/mcp status` prints o
|
|
|
113
112
|
|
|
114
113
|
### Config
|
|
115
114
|
|
|
116
|
-
The
|
|
115
|
+
The Adapter's global Config is its DSH Loader-entry Config: the `Config` schema the host module exports, persisted in the active profile composition and validated when the entry loads. The Settings > MCP page owns normal edits; for manual edits the entry looks like this in the profile's patch layer:
|
|
117
116
|
|
|
118
117
|
```yaml
|
|
119
|
-
mcp
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
# cordis.patch.yml — the mcp-adapter entry's config
|
|
119
|
+
- id: mcp-adapter
|
|
120
|
+
name: "@auggieteo/dsh-mcp-adapter"
|
|
121
|
+
config:
|
|
122
|
+
mcpServers:
|
|
123
|
+
filesystem:
|
|
124
|
+
command: npx
|
|
125
|
+
args: [-y, "@modelcontextprotocol/server-filesystem", /workspace]
|
|
126
|
+
autoAllow: false
|
|
127
|
+
hosted:
|
|
128
|
+
url: https://mcp.example.com/api
|
|
129
|
+
headers:
|
|
130
|
+
Authorization: Bearer example-token
|
|
131
|
+
github:
|
|
132
|
+
url: https://mcp.example.com/other
|
|
133
|
+
auth: oauth
|
|
134
|
+
scopes: [repo, read:org]
|
|
135
|
+
skillInstall: file
|
|
134
136
|
```
|
|
135
137
|
|
|
136
138
|
Each Server configures exactly one Transport: `command` for stdio, or `url` for streamable HTTP with SSE fallback. Adapter extension fields are `auth` (`headers` by default, or `oauth` for HTTP Servers), `scopes` (OAuth scopes, requires `auth: oauth`), `disabled`, `autoAllow`, `lifecycle` (`lazy`, `eager`, `keep-alive`, or `lazy-keep-alive`; default `lazy`), `idleTimeoutMinutes` (default `10`), and `promotedTools`.
|
|
137
139
|
|
|
138
140
|
The one top-level Adapter field is `skillInstall` (`file` by default, `runtime`, or `off`); see [Agent skill](#agent-skill).
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
Schemastery validates the shape when the entry loads, and the Adapter enforces the full rule set — unknown fields, transport exclusivity, scope and lifecycle sanity — every time it serves the Config: a section that fails validation serves no Servers with one Host warning, never a partially applied or bricked configuration. Each `env` and `headers` value has the DSH `secret` schema role. Wire views retain each key and redact its value.
|
|
141
143
|
|
|
142
144
|
#### Workspace Config layer
|
|
143
145
|
|
|
144
|
-
The Host also reads `.dsh/mcp.json` under its workspace root and merges it over the global
|
|
146
|
+
The Host also reads `.dsh/mcp.json` under its workspace root and merges it over the global entry Config at resolve time. A workspace Server entry with the same name replaces the global entry wholesale; workspace-only names are added; global-only names pass through. Disable or override a Server from the workspace by defining it there with `disabled: true`.
|
|
145
147
|
|
|
146
|
-
The workspace file uses the same `mcpServers` shape and validation as the global Config. Unknown top-level keys (only `mcpServers` is allowed) or an invalid Server entry reject the whole layer, which fails closed to the global-only Config with a Host warning; a missing file is an empty layer. Values are never environment-variable-interpolated. The Adapter never writes the workspace file: page edits, imports, and API writes all stay on the global
|
|
148
|
+
The workspace file uses the same `mcpServers` shape and validation as the global Config. Unknown top-level keys (only `mcpServers` is allowed) or an invalid Server entry reject the whole layer, which fails closed to the global-only Config with a Host warning; a missing file is an empty layer. Values are never environment-variable-interpolated. The Adapter never writes the workspace file: page edits, imports, and API writes all stay on the global entry Config, which the workspace file overrides. See [ADR 0005](./docs/adr/0005-per-workspace-config.md).
|
|
147
149
|
|
|
148
150
|
### Settings page
|
|
149
151
|
|
|
@@ -153,7 +155,7 @@ Servers whose Config comes from the workspace `.dsh/mcp.json` show a `workspace`
|
|
|
153
155
|
|
|
154
156
|
JSON import accepts the standard `{ "mcpServers": { ... } }` shape. Import replaces matching Server entries and preserves Servers absent from the import.
|
|
155
157
|
|
|
156
|
-
Every page write carries the latest
|
|
158
|
+
Every page write carries the latest entry revision. Field edits use path mutations.
|
|
157
159
|
|
|
158
160
|
### Server lifecycle
|
|
159
161
|
|
|
@@ -224,7 +226,7 @@ The installed file is never overwritten. If `$DSH_HOME/skills/mcp-adapter/SKILL.
|
|
|
224
226
|
|
|
225
227
|
## Architecture
|
|
226
228
|
|
|
227
|
-
The package ships two faces. The host half (`src/host/`) is plain ESM with no build step; it
|
|
229
|
+
The package ships two faces. The host half (`src/host/`) is plain ESM with no build step; it exports the Loader-entry `Config` schema and installs the manager, the proxy tool, promotions, commands, OAuth services, and the bundled skill. The client half (`src/client/`) is JSX bundled by esbuild (`build.mjs`) into `lib/client.js`, a loader-compatible bundle shaped for `window.__ModuleLoader__.load`. The client declares its service dependencies through `package.json`'s `dsh.client.inject`; `cordis.patch.yml` inserts the host composition row on install.
|
|
228
230
|
|
|
229
231
|
The host exposes one Connection RPC channel, `/mcp-adapter`, with endpoints `status`, `catalog`, `overview`, `layers`, `reconnect`, `oauth-status`, `oauth-login`, and `oauth-logout`. The Settings page polls `overview` and `layers` for detached snapshots (a `layers` refresh also re-reads the workspace layer); `reconnect` restarts one named Server; the OAuth endpoints drive and report the per-Server sign-in state.
|
|
230
232
|
|
|
@@ -269,7 +271,7 @@ CHANGELOG.md released changes
|
|
|
269
271
|
|
|
270
272
|
Feel free to dive in! [Open an issue](https://github.com/auggie246/dsh-mcp-adapter/issues/new) or submit PRs; questions go through the issue tracker.
|
|
271
273
|
|
|
272
|
-
- Run `pnpm test` before submitting. The suite covers the host, the client settings controller, and the
|
|
274
|
+
- Run `pnpm test` before submitting. The suite covers the host, the client settings controller, the 0.1.7 entry-config wire (`test/client-apply.test.js`), and the legacy `settings.yaml` import (`test/legacy-import.test.js`).
|
|
273
275
|
- New project vocabulary belongs in [CONTEXT.md](./CONTEXT.md); design decisions get an [ADR](./docs/adr/) before or with the change.
|
|
274
276
|
|
|
275
277
|
## License
|
|
@@ -5,3 +5,5 @@ Server Config is stored in the Adapter's `mcp` namespace of the DSH settings ser
|
|
|
5
5
|
Considered options: pi's multi-file merge chain (`~/.config/mcp/mcp.json` → agent dirs → workspace `.mcp.json`, plus cross-tool `imports`) was rejected because DSH users interact through the web UI, not dotfiles; a single global namespace with a Settings > MCP form matches how every other DSH settings surface works, and gives us validation, conflict detection, and live update events for free.
|
|
6
6
|
|
|
7
7
|
Consequences: per-workspace servers and config-file imports become a later feature layered on top of the namespace (a workspace source merged at resolve time), not a change to where the global Config lives. The settings page needs an explicit JSON import action, since there is no file to copy-paste into.
|
|
8
|
+
|
|
9
|
+
*Amended (0.1.7):* DSH 0.1.7 replaced the settings service's registered namespaces with profile Loader-entry Config, so global Server Config now persists in the active profile composition under the `mcp-adapter` entry rather than an `mcp:` section of `settings.yaml`. The decisions that outlive the seam — standard `mcpServers`-shaped JSON, one global list edited through Settings > MCP, workspace sources layered at resolve time — all hold; see [ADR 0010](./0010-loader-entry-config-model.md).
|
|
@@ -11,3 +11,5 @@ Considered options: shipping a 0.1.2-only release and telling 0.1.1-rc.2 users t
|
|
|
11
11
|
Consequences: the `apply`-level test suite (`test/client-apply.test.js`) compiles the real client entry and drives both fake generations, locking the fallback contract. When DSH drops 0.1.1-rc.2 support, the fallback branch, the legacy fake, and the README note are the removal surface — the controller and host need no changes.
|
|
12
12
|
|
|
13
13
|
*Widening (0.1.5 check):* the DSH 0.1.2-rc.1 → 0.1.5-rc.2 diff leaves every face this ADR pins on untouched — `packages/settings/settings` and `packages/api/settings-controller` change version strings only, the remotes mount list still includes the settings controller (two remotes added, none removed), and the `connection` service keeps its `rpc` face while refactoring reconnect internals. The one rename in range (`CommandInputDescriptor.images` → `attachments`) does not touch the Adapter's commands, which declare only `input.hint`. The peer dependency therefore widens to `^0.1.1-rc.2 || ^0.1.2-rc.1 || ^0.1.5-rc.1` with no code change; `test/client-apply.test.js` locks the 0.1.5-rc.1 shape with its own generation fake.
|
|
14
|
+
|
|
15
|
+
*Superseded (0.1.7):* DSH 0.1.7 is the third shape this ADR anticipated and hard-failed on: `ctx.settings.register` is gone, configuration is the plugin's Loader-entry Config, and the client reads through the `configForms` service while the `remote.settings` wire keeps the positional shape and flat envelope pinned here. The 0.1.1 fallback and its fake are deleted; the surviving `remote.settings` wrapper now addresses the `mcp-adapter` entry id. See [ADR 0010](./0010-loader-entry-config-model.md).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Configuration is the plugin's Loader-entry Config (DSH 0.1.7)
|
|
2
|
+
|
|
3
|
+
DSH 0.1.7 removed the settings model the Adapter was built on. `ctx.settings.register(namespace, schema)` — the `SettingsProvider` seam where plugins registered their own namespace — is gone: `@deepseek-ai/dsh-settings` now exports a single `SettingsForms` service that projects the cordis **Loader entries' Config schemas** into editable profile forms (verified against the 0.1.7-rc.2 packages). Host plugins `export const Config`; the Loader validates the entry's profile config and cordis hands the validated object to the plugin as the **second `apply` argument** (`new runtime.callback(ctx, fiber.config)`) — there is no `config` service or accessor, so reading `ctx.config` throws `cannot get property "config" without inject`; fields marked `.volatile()` (schemastery `~3.18.4`) resolve to cosmokit volatile references, and a config edit touching only volatile fields is committed into the running fiber's references by cordis-plugin-loader without a restart, emitting `loader/volatile-update` — everything else restarts the entry. `SettingsForms` reads and writes by **profile entry id** (ours is `mcp-adapter`, the id `cordis.patch.yml` registers), refuses writes to non-volatile paths, and `configure({ auto })` decides whether the Settings shell auto-generates a form page for the entry. On the client `settingsScope` is replaced by the `configForms` service — `get(entryId)` answers a per-entry form controller (`{ status, value, base, user, revision, writable, mode }` snapshots, `subscribe`, `mutate`) and `describe()` a shared document mirror — while the `remote.settings` wire face (positional args, flat `{ ok, value | error }` envelope, `settings/document-updated` invalidation) and the `settings.section` slot (now owned by `dsh-client-ui-settings-general`) survive. This supersedes [ADR 0002](./0002-config-in-dsh-settings-namespace.md) (where global Config lives) and completes [ADR 0009](./0009-dual-generation-settings-api.md) (whose predicted third shape is exactly this one).
|
|
4
|
+
|
|
5
|
+
The Adapter's Config becomes its entry Config: `src/host/index.js` exports `Config = z.object({ mcpServers: …volatile(), skillInstall: …volatile() })`. Both marks sit on the top-level fields because schemastery rejects volatile fields below a dict or beneath another volatile field, and SettingsForms only accepts writes on schema-declared volatile paths. `MCP_SETTINGS_NAMESPACE` changes from `'mcp'` to `'mcp-adapter'`: under 0.1.7 the namespace identity is the entry id, so the client's `configForms.get()`, the `remote.settings` writes, and the host's `settings.update()` all address the entry. `createMcpConfigScope(ctx, config)` wraps the entry Config in the same `{ get, watch, update, mutate }` surface the old scope provided — `get()` unwraps the volatile refs off the Config object cordis passed to `apply`, `watch` notifies `(next, previous)` on `loader/volatile-update` when the value actually changed, and the write methods forward to `ctx.get('settings')` — so the workspace layer, manager, promotions, OAuth, and commands consume the port unchanged. The scope holds the object for the whole activation, which is sound because a volatile commit writes through the references inside it (cosmokit freezes them; the shared `Symbol.for('cosmokit.volatile.write')` member is the only writer) and any ordinary edit replaces `fiber.config` and restarts the entry, so a fresh `apply` receives the new object.
|
|
6
|
+
|
|
7
|
+
Considered options: keeping any legacy generation alongside 0.1.7 was rejected because the model replaced both faces at once — supporting 0.1.2/0.1.5 too means two parallel settings subsystems in Host and client (register-scope plus volatile-Config, `settingsScope` plus `configForms`) and doubled generation fakes; the release is 0.1.7-only, deleting the `connection.api.settings` fallback, the legacy fakes, and the dead `@deepseek-ai/dsh-client-runtime` entry (last published for 0.1.1) from `dsh.client.inject`. Restarting the whole entry on every form edit (the platform default for non-volatile fields) was rejected for `mcpServers` because a single keystroke in a Server dialog would drop every live connection; live volatile commits plus `loader/volatile-update` keep the manager reconciling exactly as it watched the old scope. The alternative to the scope facade — rewriting every consumer against the entry Config refs — was rejected as a wide diff with no behavior benefit.
|
|
8
|
+
|
|
9
|
+
Cross-field validation had to move. The old `SettingsProvider` ran `validateMcpSettings` before every persist; the Config write path validates only the schema, so structure errors now surface as a failed entry start, and `validateMcpSettings` runs inside the scope's `get()` instead: a section that passes the schema but fails the cross-field rules serves an empty server list with one deduplicated Host warning, so a hand-edited profile can never brick the connection manager. The client writes keep the direct `remote.settings` wrapper rather than the form controller's queue so conflict messages still reach the page as text, and the controller reports a mirrored-but-schema-invalid entry (whose form stays `loading` by contract) as an `invalid` state instead of spinning.
|
|
10
|
+
|
|
11
|
+
Legacy data needs one explicit bridge. 0.1.7 renames the profile's `settings.yaml` to `settings.yaml.imported` and imports only sections whose name matches an entry id — the Adapter's legacy `mcp` section under its `mcp-adapter` entry would be silently stranded. After the Loader settles (the platform import runs first and skips the mismatched name), if the entry is untouched — no user section, no non-default servers — the section is validated against the Config schema and written once through `settings.update`, and an `mcp-adapter.legacy-imported` marker file in the profile home records the import so a later deliberate "remove every Server" never resurrects the old list. Every failure warns with manual copy instructions and never throws.
|
|
12
|
+
|
|
13
|
+
Consequences: peers widen to `@deepseek-ai/dsh-settings` `^0.1.7-rc.1` and `@deepseek-ai/schemastery` `^3.18.4`; `yaml` becomes a runtime dependency of the migration module. Global Servers persist in the active profile composition (through Settings > MCP or the profile document) instead of a settings document, and README/CONTEXT vocabulary follows. `test/client-apply.test.js` collapses to a single 0.1.7 generation fake pinning ns `mcp-adapter` on the wire; `test/legacy-import.test.js` locks the migration. The nav-icon patch script targets 0.1.7's renamed `*Medium` icon members: its `MODELS_BRANCH` pattern now pins only the `Icon` prefix and the default icon is `IconLinkOutlineMedium`.
|
|
14
|
+
|
|
15
|
+
Correction (v0.4.1): this ADR originally said the Loader validates the entry Config *into `ctx.config`*, and the shipped v0.4.0 entry acted on that — it died on start with `cannot get property "config" without inject`. cordis 4 resolves every context property through a proxy that refuses anything the plugin did not declare in `inject`, and there is no `config` service or accessor to declare, so `ctx.config` is unreachable by construction. The validated Config is the second `apply` argument (`apply(ctx, config)`), which is how the shipped DSH plugins take it. `createMcpConfigScope` now receives that argument and `src/host/index.js` threads it through; the test fakes refuse a `config` context property the way the cordis proxy does, and a probe mounted through a real cordis fiber pins the contract, so the assumption cannot come back unnoticed.
|
package/lib/client.js
CHANGED
|
@@ -43,8 +43,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
43
|
var import_react = __toESM(require("react"), 1);
|
|
44
44
|
|
|
45
45
|
// src/client/settings-controller.js
|
|
46
|
-
var MCP_SETTINGS_NAMESPACE = "mcp";
|
|
46
|
+
var MCP_SETTINGS_NAMESPACE = "mcp-adapter";
|
|
47
47
|
var MCP_RPC_CHANNEL = "/mcp-adapter";
|
|
48
|
+
var INVALID_SETTINGS_MESSAGE = "The saved MCP configuration failed validation. Fix or remove the invalid fields in the profile, then reopen Settings.";
|
|
48
49
|
var SERVER_FIELDS = /* @__PURE__ */ new Set([
|
|
49
50
|
"command",
|
|
50
51
|
"args",
|
|
@@ -236,6 +237,12 @@ function secretRowOps(serverName, field, rows, existingKeys) {
|
|
|
236
237
|
function overviewEmpty() {
|
|
237
238
|
return { status: { servers: [] }, catalog: { servers: [] } };
|
|
238
239
|
}
|
|
240
|
+
function projectSettingsStatus(settings, document2) {
|
|
241
|
+
if (settings.status !== "loading") return settings;
|
|
242
|
+
const row = document2.view?.namespaces?.find((entry) => entry.ns === MCP_SETTINGS_NAMESPACE);
|
|
243
|
+
if (row === void 0 || row.value === void 0) return settings;
|
|
244
|
+
return { ...settings, status: "invalid", error: { message: INVALID_SETTINGS_MESSAGE } };
|
|
245
|
+
}
|
|
239
246
|
function serverSource(layers, name) {
|
|
240
247
|
return layers?.source?.[name] === "workspace" ? "workspace" : "global";
|
|
241
248
|
}
|
|
@@ -275,9 +282,10 @@ var McpSettingsController = class {
|
|
|
275
282
|
this.getSnapshot = () => this.snapshot;
|
|
276
283
|
}
|
|
277
284
|
projection() {
|
|
285
|
+
const settingsDocument = this.describe.getSnapshot();
|
|
278
286
|
return {
|
|
279
|
-
settings: this.scope.getSnapshot(),
|
|
280
|
-
settingsDocument
|
|
287
|
+
settings: projectSettingsStatus(this.scope.getSnapshot(), settingsDocument),
|
|
288
|
+
settingsDocument,
|
|
281
289
|
overview: this.overview,
|
|
282
290
|
layers: this.layers,
|
|
283
291
|
oauthStatuses: this.oauthStatuses,
|
|
@@ -1392,10 +1400,8 @@ function McpSettingsPage({ controller }) {
|
|
|
1392
1400
|
function createSettingsApi(ctx) {
|
|
1393
1401
|
const remoteSettings = readRemoteSettings(ctx);
|
|
1394
1402
|
if (remoteSettings !== void 0) return remoteSettingsApi(remoteSettings);
|
|
1395
|
-
const connectionSettings = ctx.connection?.api?.settings;
|
|
1396
|
-
if (connectionSettings !== void 0) return connectionSettings;
|
|
1397
1403
|
throw new Error(
|
|
1398
|
-
"No DSH settings write API: expected ctx.remote.settings (DSH 0.1.
|
|
1404
|
+
"No DSH settings write API: expected the ctx.remote.settings namespace (DSH 0.1.7 with @deepseek-ai/dsh-api-remotes). Upgrade @auggieteo/dsh-mcp-adapter to a release supporting this harness."
|
|
1399
1405
|
);
|
|
1400
1406
|
}
|
|
1401
1407
|
function readRemoteSettings(ctx) {
|
|
@@ -1523,10 +1529,10 @@ function installMcpSettingsStyles() {
|
|
|
1523
1529
|
}
|
|
1524
1530
|
|
|
1525
1531
|
// src/client/index.jsx
|
|
1526
|
-
var inject = ["slots", "
|
|
1532
|
+
var inject = ["slots", "configForms", "connection", "remote"];
|
|
1527
1533
|
function apply(ctx) {
|
|
1528
|
-
const scope = ctx.
|
|
1529
|
-
const describe = ctx.
|
|
1534
|
+
const scope = ctx.configForms.get(MCP_SETTINGS_NAMESPACE);
|
|
1535
|
+
const describe = ctx.configForms.describe();
|
|
1530
1536
|
const controller = new McpSettingsController({
|
|
1531
1537
|
scope,
|
|
1532
1538
|
describe,
|