@auggieteo/dsh-mcp-adapter 0.4.1 → 0.5.0

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 CHANGED
@@ -4,6 +4,22 @@ 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.5.0] - 2026-09-30
8
+
9
+ ### Fixed
10
+
11
+ - DSH 0.2.0-rc.2 no longer skips the plugin. A web profile running DSH 0.2.0-rc.2 refused to load the Adapter at all, logging `skipping profile bundle "@auggieteo/dsh-mcp-adapter": Plugin @auggieteo/dsh-mcp-adapter@0.4.2 is incompatible with dsh 0.2.0-rc.2: peerDependencies {"@deepseek-ai/dsh-settings":"^0.1.7-rc.1"}` and leaving MCP unconfigured — the runtime gates every `@deepseek-ai/dsh*` peer range against **its own version**, so a range capped at `^0.1.7-rc.1` excludes every 0.2.x release. The break is declaration-only: checked against the installed 0.2.0-rc.2 packages, every surface the Adapter uses is unchanged — `@deepseek-ai/dsh-settings` still exports `SettingsForms` and `redactSecrets`, the host `connection.fetch.register` exact-route seam takes the same `{ path, methods, requestBody, fetch }` route, the client's `connection.rpc.call(channel, endpoint, …)` signature is unchanged, `configForms` keeps `get`/`describe`/`mutate` over the positional `remote.settings` wire, the `settings.section` slot survives, `loader/volatile-update` still commits volatile Config edits (cordis-plugin-loader 1.0.5), schemastery is the same 3.18.4, and the nav-icon patch's `MODELS_BRANCH` pattern still matches the 0.2.0-rc.2 settings shell. The peer range therefore widens to `@deepseek-ai/dsh-settings` `^0.1.7-rc.1 || ^0.2.0-rc.1` with no source change, and no legacy drop is needed — DSH 0.1.7-rc.x keeps working. Compatibility is pinned by execution, not by diff reading: `test/client-apply.test.js` adds a 0.2.0-rc.2 generation fake locking the identical positional write (`remote.update`, `mcp-adapter`, the Server patch, revision `7` → `8`), and `test/package.test.js` pins the range against both generations' runtime versions. No exact-version exemption (`dsh plugin allow-version`) is needed: update the plugin instead. [ADR 0009](docs/adr/0009-dual-generation-settings-api.md).
12
+
13
+ ## [v0.4.2] - 2026-09-28
14
+
15
+ ### Fixed
16
+
17
+ - Settings > MCP loads again on DSH 0.1.7 instead of showing `Could not load MCP status: transport failure for /mcp-adapter/overview: HTTP 405`. The status RPC was a dedicated `connection.rpc.handle('/mcp-adapter', …)` channel, but DSH 0.1.7 mounts such channels with `owner.webServer.register` on the connection provider's own fiber, and cordis 4 refuses that un-injected property read — the channel silently never registered and the SPA static fallback answered every POST with 405 (the `mcp-adapter` plugin row still reads `active`; only a Host console line shows the failure). The RPC surface now mounts each endpoint as an exact Fetch route on the shared `/api` channel through the documented `connection.fetch.register` seam — the platform still applies browser authentication and the origin fence before the handler runs — and the Client calls `connection.rpc.call('/api', 'mcp-adapter/<endpoint>')`; envelopes and the page are otherwise unchanged. `test/rpc-fetch-routes.test.js` pins the mount and the dispose/remount lifecycle through a real cordis fiber, which the old `rpc.handle` fakes could not see. [ADR 0011](docs/adr/0011-rpc-on-shared-api-fetch-routes.md).
18
+
19
+ ### Changed
20
+
21
+ - The Settings MCP nav icon fixes itself. The shell hard-codes nav icons by section id and the `settings.section` slot carries no icon option, so v0.4.1 patched the installed shell bundle by hand — and a DSH upgrade replaced the patched file, bringing the gear back. The patch core moved to `src/host/nav-icon.js` and the Adapter now applies it at every activation: after any install or DSH upgrade the icon returns on the next `dsh web` start. Only the install the process actually runs from is patched (located via the CLI entry, `DSH_ROOT` overrides), the insert stays idempotent behind the `// dsh-mcp-adapter` marker, and every failure warns instead of throwing. `scripts/patch-dsh-settings-nav-icon.mjs` remains for manual runs and `--revert` and now ships with the package. [ADR 0012](docs/adr/0012-automatic-nav-icon-patch.md).
22
+
7
23
  ## [v0.4.1] - 2026-09-28
8
24
 
9
25
  ### Fixed
package/README.md CHANGED
@@ -67,6 +67,7 @@ Then restart DSH. The Settings panel (`⌘,` / sidebar foot) gains an **MCP** se
67
67
  | DSH release | Support |
68
68
  | --- | --- |
69
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
+ | 0.2.0-rc.1 and newer 0.2.x | Supported. Same Loader-entry Config model: every surface the Adapter uses — `configForms`, the `remote.settings` wire face, the `connection.fetch` exact-route seam, `tools`/`commands` registration, the `settings.section` slot, and the `loader/volatile-update` commit path — is unchanged from 0.1.7, so the support is declaration-only (peer `@deepseek-ai/dsh-settings` `^0.1.7-rc.1 \|\| ^0.2.0-rc.1`). `test/client-apply.test.js` pins the 0.2.0-rc.2 wire shape by execution. |
70
71
  | 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. |
71
72
 
72
73
  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).
@@ -157,6 +158,10 @@ JSON import accepts the standard `{ "mcpServers": { ... } }` shape. Import repla
157
158
 
158
159
  Every page write carries the latest entry revision. Field edits use path mutations.
159
160
 
161
+ The status surface talks to the Host through exact Fetch routes on the shared `/api` channel (`POST /api/mcp-adapter/<endpoint>`), authenticated by the platform like every other `/api` request ([ADR 0011](./docs/adr/0011-rpc-on-shared-api-fetch-routes.md)). If the page instead shows `Could not load MCP status: … HTTP 405`, the installed Adapter predates v0.4.2: its dedicated RPC channel cannot mount on DSH 0.1.7. Upgrade and restart DSH.
162
+
163
+ The sidebar's MCP row shows a link icon. The icon map lives in the DSH settings shell and the `settings.section` slot carries no icon option, so the Adapter patches one marked branch into the installed shell bundle at every start — after a DSH upgrade the icon returns by itself, and if it does not, restart `dsh web` and refresh the page. `scripts/patch-dsh-settings-nav-icon.mjs` wraps the same patch for manual runs (`--icon <Name>`, `--revert`). See [ADR 0012](./docs/adr/0012-automatic-nav-icon-patch.md).
164
+
160
165
  ### Server lifecycle
161
166
 
162
167
  Each Server's `lifecycle` setting controls when it connects and whether it idles out. `lazy` remains the default.
@@ -253,6 +258,7 @@ src/host/ host half: plain ESM, no build step
253
258
  src/client/ client half: JSX, bundled to lib/client.js
254
259
  build.mjs esbuild wrapper producing the loader-compatible bundle
255
260
  lib/client.js build artifact (gitignored), required at runtime
261
+ scripts/ CLI over the Settings nav-icon patch core
256
262
  docs/adr/ architecture decisions
257
263
  docs/verification/ end-to-end verification records
258
264
  CONTEXT.md project vocabulary
@@ -13,3 +13,5 @@ Consequences: the `apply`-level test suite (`test/client-apply.test.js`) compile
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
14
 
15
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).
16
+
17
+ *Widening (0.2.0 check):* DSH 0.2.0-rc.2 will not load the Adapter at all: `dsh-app-boot`'s compatibility gate evaluates every `@deepseek-ai/dsh*` peer range **against the runtime version itself** — `semver.satisfies("0.2.0-rc.2", "^0.1.7-rc.1", { includePrerelease: true })` is false — so the profile logs `skipping profile bundle` and the plugin never applies. Checked surface by surface against the installed 0.2.0-rc.2 packages, the shape this ADR pins is untouched: `@deepseek-ai/dsh-settings` still exports `SettingsForms` and `redactSecrets(schema, value)`; the host mounts RPC on the shared `/api` channel through `connection.fetch.register` with the same `{ path, methods, requestBody, fetch }` route accepted by `registerFetchRoute`; the client's `rpc.call(channel, endpoint, payload, signal)` signature is unchanged; `configForms` keeps `get(entryId)`, `describe()`, and `mutate(ops, expectedRevision)` forwarding to `remote.settings.mutate(ns, ops, revision)`; the `settings.section` slot, the `webServer` provider inject, and `loader/volatile-update` (cordis-plugin-loader 1.0.5) all survive; schemastery is the same 3.18.4, so `.volatile()` needs nothing; and the nav-icon patch's `MODELS_BRANCH` pattern still matches `dsh-client-ui-settings-general`'s 0.2.0-rc.2 bundle. The break is declaration-level, so per this ADR's rule — source changes only on a runtime break — the peer dependency widens to `^0.1.7-rc.1 || ^0.2.0-rc.1` and 0.1.7 support is kept, not dropped: the shape proves out identical, and a range that abandoned 0.1.7 would strand users whose harness the Adapter still satisfies. `test/client-apply.test.js` locks the 0.2.0-rc.2 generation with its own fake pinning the positional write on `mcp-adapter`, so the widening is backed by execution. The cap at `<0.3.0-0` is deliberate: the gate is the mechanism that turns an unverified future generation into one loud startup log instead of an `undefined` read at runtime, so it is widened by evidence, release by release — `test/package.test.js` pins the range and both accepted bounds.
@@ -0,0 +1,9 @@
1
+ # Settings RPC mounts as exact Fetch routes on the shared /api channel
2
+
3
+ `Settings > MCP` died on every load with `Could not load MCP status: transport failure for /mcp-adapter/overview: HTTP 405`. The status RPC was mounted as a dedicated channel: `ctx.connection.rpc.handle('/mcp-adapter', handler)`. In DSH 0.1.7 — byte-identical in rc.1 and rc.2, so not an rc regression, broken since the v0.4.0 port — `HostConnectionService.rpc.handle` ends in `register()`, which runs `owner.effect(() => owner.webServer.register(route))` where `owner` is the connection service's **own provider fiber** (the `client-connection` plugin context, whose `inject` never declares `webServer`). cordis 4 resolves every context property through a proxy that refuses an undeclared read (`cannot get property "webServer" without inject`), so the effect throws, the inner fiber dies at `state=3` with the failure visible only as a Host `ctx.logger.error` (the plugin row still reads `active`), and the prefix route is never registered. DSH's `dsh-host-frontend-static` fallback then answers the browser's `POST /mcp-adapter/overview` with 405. DSH's own plugins only use `rpc.intercept('/api', …)` — a path that never reads `webServer` — which is why `rpc.handle` stayed broken upstream. Reproduced in-process against a real cordis 4 fiber: the handle-based mount registers zero routes.
4
+
5
+ The Adapter mounts each Settings RPC endpoint instead as an **exact Fetch route** through the documented public seam `connection.fetch.register({ path, methods, requestBody, fetch })` (`ConnectionFetchRoute`: "One exact Fetch route on the shared API channel … owned by a Host feature"). Paths are `/api/mcp-adapter/<endpoint>` for the eight endpoints (`status`, `catalog`, `overview`, `layers`, `oauth-login`, `oauth-logout`, `oauth-status`, `reconnect`), `methods: ['POST']`, `requestBody: 'buffered'`. The platform's shared `/api` handler applies admission — browser authentication and the Host/origin fence — before the fetch, and `registerFetchRoute` only mutates the provider's live route map: it never touches `webServer`, so nothing depends on the broken path. Each route speaks the connection envelope so the Client's unchanged `connection.rpc.call('/api', 'mcp-adapter/<endpoint>')` validates the reply exactly as for platform endpoints: decode the `client-request` (`type`, string `rpcId`, matching `method`; malformed JSON or a non-`client-request` body ⇒ 400), dispatch to the endpoint handler, and answer `server-response` with the same `rpcId` and the unchanged `{ ok, value | error: { code, message, details } }` result shape. Registration is one `ctx.effect` per route: a partial failure rolls the earlier routes back with the fiber, and an entry restart disposes every route before re-registering — the cordis test pins that dispose/remount leaves no stale registration and no duplicate-collision. `test/rpc-fetch-routes.test.js` mounts `installMcpManagerRpc` through a real cordis fiber and a faithful `HostConnectionService.fetch` model, because the older fakes called `rpc.handle` directly and could not see the platform defect.
6
+
7
+ Considered options: declaring `webServer` in the caller's `inject` was ruled out by experiment — the refused read happens on the provider's fiber, which the caller cannot annotate. `rpc.intercept('/api', …)` was ruled out because the shared channel admits exactly one interceptor and `dsh-api-gateway` owns it (`registerInterceptor` throws otherwise). Mounting our own `webServer` prefix route was rejected for re-implementing platform internals — the node→Fetch bridge, admission, and envelope framing — which drift on every DSH release. Patching `dsh-client-connection` in place (the nav-icon-script pattern) was rejected as patching platform RPC internals: riskier, and unnecessary once the designed seam works. Upstream should still fix `rpc.handle`; this mount keeps working either way, because `fetch.register` is the documented API.
8
+
9
+ Consequences: the Settings wire URL becomes `POST /api/mcp-adapter/overview`; unauthenticated probes now get the platform's 401 instead of 405, non-POST methods fall through to the gateway interceptor (404), `MCP_RPC_CHANNEL` is `'/api'` on the client with the `mcp-adapter/` endpoint prefix, and the host exports `MCP_RPC_ROUTE_PREFIX` / `MCP_RPC_ENDPOINTS` in place of the old channel constant. [ADR 0010](./0010-loader-entry-config-model.md) stays accurate; only the RPC mount moved. See also [ADR 0012](./0012-automatic-nav-icon-patch.md) for the second half of the same 405-and-gear report.
@@ -0,0 +1,9 @@
1
+ # The Settings nav icon patch applies itself at every activation
2
+
3
+ The Settings sidebar picks its icon from a hard-coded `navIcon(id)` map inside the shell bundle `@deepseek-ai/dsh-client-ui-settings-general`; an unknown section id — ours is `mcp` — gets the generic settings gear. The `settings.section` slot's register options are only `id`, `order`, and `label`: there is no icon option, so a plugin cannot express the icon through the slot contract. v0.4.1 shipped the workaround as a manual script (`scripts/patch-dsh-settings-nav-icon.mjs`) that inserts one marked branch into the installed shell bundle — but a DSH upgrade replaces that bundle, so after the 0.1.7-rc.2 upgrade the gear was back and fixing it was an unremembered manual step.
4
+
5
+ The patch core moved to `src/host/nav-icon.js` (shipped with the package) and `apply()` calls `installMcpNavIconPatch(ctx)` at every activation, so a fresh install and every DSH upgrade self-heal on the next start. The hook locates the **running** DSH install from `process.argv[1]`: a global `dsh` runs through a bin symlink whose argv path stays the symlink, so the lookup resolves the real path first, then walks up to the nearest `package.json` naming `@deepseek-ai/dsh`; the `DSH_ROOT` environment variable overrides. The insert is idempotent via the `// dsh-mcp-adapter` marker (the per-boot cost is one file read). Two safety properties: the activation hook patches only the install it is running in — it never falls back to the npm global root, so a headless run or test process whose entry is not a DSH CLI touches nothing on disk — and every failure (missing file, pattern drift in a future build, read-only install) is a `ctx.logger.warn`, never a throw: Settings chrome must not break the Adapter. `scripts/patch-dsh-settings-nav-icon.mjs` remains as the CLI over the same core, with the npm-global-root candidate and `--revert` for manual use, and `scripts/` joined the package `files` list so an installed copy can revert itself. A patched file only takes effect once the server serves fresh bundles, so the logged guidance — and the README — say to restart `dsh web` and refresh when the icon does not appear.
6
+
7
+ Considered options: npm `postinstall` was rejected because it runs only at plugin install — it cannot cover DSH upgrades, which is exactly the case that broke — and package managers gate build scripts, making it unreliable. Asking for an icon register option in the `settings.section` slot is the right upstream change but not something this release can ship. Patching the client-modules combo cache was rejected: combos are content-hashed at boot, so the file patch plus a restart is the simpler correct layering.
8
+
9
+ Consequences: the default icon stays `IconLinkOutlineMedium` (the `--icon <Name>` knob chooses another primitive); `test/nav-icon.test.js` pins insert idempotence, revert round trip, pattern-drift rejection, argv-root discovery through a symlink, and the warn-only failure paths; and the ADR 0010 note about the script targeting `*Medium` icon members carries over unchanged. See also [ADR 0011](./0011-rpc-on-shared-api-fetch-routes.md) for the other half of the same report.
package/lib/client.js CHANGED
@@ -44,7 +44,8 @@ var import_react = __toESM(require("react"), 1);
44
44
 
45
45
  // src/client/settings-controller.js
46
46
  var MCP_SETTINGS_NAMESPACE = "mcp-adapter";
47
- var MCP_RPC_CHANNEL = "/mcp-adapter";
47
+ var MCP_RPC_CHANNEL = "/api";
48
+ var MCP_RPC_ENDPOINT_PREFIX = "mcp-adapter/";
48
49
  var INVALID_SETTINGS_MESSAGE = "The saved MCP configuration failed validation. Fix or remove the invalid fields in the profile, then reopen Settings.";
49
50
  var SERVER_FIELDS = /* @__PURE__ */ new Set([
50
51
  "command",
@@ -1537,7 +1538,12 @@ function apply(ctx) {
1537
1538
  scope,
1538
1539
  describe,
1539
1540
  settingsApi: createSettingsApi(ctx),
1540
- rpc: (endpoint, payload, signal) => ctx.connection.rpc.call(MCP_RPC_CHANNEL, endpoint, payload, signal)
1541
+ rpc: (endpoint, payload, signal) => ctx.connection.rpc.call(
1542
+ MCP_RPC_CHANNEL,
1543
+ `${MCP_RPC_ENDPOINT_PREFIX}${endpoint}`,
1544
+ payload,
1545
+ signal
1546
+ )
1541
1547
  });
1542
1548
  ctx.effect(() => installMcpSettingsStyles(), "mcp-adapter: Settings styles");
1543
1549
  ctx.effect(() => () => controller.dispose(), "mcp-adapter: Settings controller");