@auggieteo/dsh-mcp-adapter 0.4.1 → 0.4.2

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,16 @@ 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.2] - 2026-09-28
8
+
9
+ ### Fixed
10
+
11
+ - 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).
12
+
13
+ ### Changed
14
+
15
+ - 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).
16
+
7
17
  ## [v0.4.1] - 2026-09-28
8
18
 
9
19
  ### Fixed
package/README.md CHANGED
@@ -157,6 +157,10 @@ JSON import accepts the standard `{ "mcpServers": { ... } }` shape. Import repla
157
157
 
158
158
  Every page write carries the latest entry revision. Field edits use path mutations.
159
159
 
160
+ 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.
161
+
162
+ 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).
163
+
160
164
  ### Server lifecycle
161
165
 
162
166
  Each Server's `lifecycle` setting controls when it connects and whether it idles out. `lazy` remains the default.
@@ -253,6 +257,7 @@ src/host/ host half: plain ESM, no build step
253
257
  src/client/ client half: JSX, bundled to lib/client.js
254
258
  build.mjs esbuild wrapper producing the loader-compatible bundle
255
259
  lib/client.js build artifact (gitignored), required at runtime
260
+ scripts/ CLI over the Settings nav-icon patch core
256
261
  docs/adr/ architecture decisions
257
262
  docs/verification/ end-to-end verification records
258
263
  CONTEXT.md project vocabulary
@@ -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");