Skip to content

Releases: katticot/dsh-mcp-apps

v0.2.5

Choose a tag to compare

@katticot katticot released this 05 Nov 05:32
9332ba2

dsh-mcp-apps 0.2.5 — DSH 0.2.1-rc.5 baseline + the app-view persistence fix.

Verified before release: typecheck, 194 unit tests, artifact tests, and the 3-policy packaged smoke test (allow / deny / approve) against the real @deepseek-ai/dsh@0.2.1-rc.5 runtime.

Provenance: signed and published to the sigstore transparency log (logIndex 3692636223).

[0.2.5] - 2026-11-05

Changed

  • DSH 0.2.1-rc.5 compatibility: all @deepseek-ai/dsh-* peer ranges bumped from ^0.1.7-rc.2 to ^0.2.1-rc.5, and the matching devDependencies from the exact 0.1.7-rc.2 to the exact 0.2.1-rc.5. DSH's evaluatePluginCompatibility checker rejects plugins whose dsh peers don't satisfy the running version, so 0.2.4 and earlier refuse to load on the 0.2.1-rc.5 runtime (the version now shipped inside the DeepSeek Harness desktop application).
  • Cordis devDependency repinned to the exact 4.0.5 the 0.2.1-rc.5 runtime ships; the ^4.0.2 peer range is unchanged (cordis is not covered by the DSH checker).
  • scripts/smoke-dsh.mjs now installs and runs @deepseek-ai/dsh@0.2.1-rc.5 for the packaged smoke test.
  • docs/dsh-compatibility.md and the README compatibility table updated to the new baseline.

Removed

  • @deepseek-ai/dsh-client-ui-slots peer: the package no longer exists in the DSH 0.2.1-rc.5 runtime; the browser slots service is now provided by @deepseek-ai/dsh-client-ui-renderer. This plugin never imported the package (the client code injects the slots service by name only), so the peer was dropped rather than retargeted. The tool.call.toolview and plugins.detail.section slot names this plugin registers against are unchanged.

Fixed

  • Client: app views no longer silently disappear when the turn folds. The McpAppToolView reveal loop bound its MutationObserver target — and the user-gesture listeners that make a manual collapse stick — eagerly, on the first React commit where the iframe exists. The chat host commits the turn-process disclosure around the tool block on a later commit, so that eager binding watched a node too deep to ever observe the host's fold mutations; only the one-shot 300–2500 ms reveal timers defended the view, and a fold landing after they expired was final. The binding is now self-healing: every reveal pass re-resolves the target from the live iframe and re-binds the observer and listeners when it changes, and the effect cleanup removes listeners from the currently-bound node. Adds tests/client-plugin-race.spec.ts, which reproduces the real host commit order (the view mounts outside the disclosure, which is committed around the block afterwards) and fails against the old eager binding.
  • tests/dsh-host-contract.spec.ts now passes an owner PeerScope to ConnectionFetchHandler.fetch: under DSH 0.2.1-rc.5 the shared-channel fetch handler takes a required peer argument and answers 403 for exact routes when the dispatching peer's principal is not owner (the plugin's own registered routes are unaffected — the dispatcher invokes them with the request only).

v0.2.4

Choose a tag to compare

@katticot katticot released this 30 Sep 07:32
34631bf

Compare v0.2.3...v0.2.4

Added

  • Remote MCP Server OAuth 2.0 / PKCE Support:
    • Secure file-backed OAuth token store (~/.dsh/mcp-apps/oauth/<server>.json) with restricted filesystem permissions (0700 directory, 0600 token file) persisting access tokens, refresh tokens, and dynamic client registration records across DSH restarts.
    • Full OAuth 2.0 authorization code flow with PKCE (RFC 7636) and metadata discovery (RFC 8414 / RFC 9728).
    • Dynamic client registration (RFC 7591) support alongside pre-configured static client credentials.
    • Dedicated /api/mcp-apps/oauth/callback web route with popup authentication window coordination via window.opener.postMessage.
    • Disconnect / logout endpoint (/api/mcp-apps/oauth/disconnect) and "Disconnect" button in the Plugins UI status section to cleanly revoke tokens and unregister tools.
    • externalUrl configuration setting for deployments behind reverse proxies or external URLs to ensure accurate OAuth redirect URIs.
  • Resilient Remote Server Lifecycle & Error Handling:
    • Concise single-line network error reporting: unwraps nested Node.js/Undici causes (ConnectTimeoutError, UND_ERR_CONNECT_TIMEOUT, ECONNREFUSED) instead of printing raw multi-line stack traces to stdout/stderr.
    • Exponential backoff with jitter for automatic server reconnection on connection drop or startup failure.
    • Manual retry RPC endpoint (/api/mcp-apps/servers/retry) and interactive "Retry" button on disconnected server rows in the Plugins UI.
    • Immediate lifecycle shutdown: forwards lifecycleController.signal into remote transports so pressing ^C or shutting down DSH aborts in-flight requests immediately without waiting for HTTP socket timeouts.
  • UI & Client Status Enhancements:
    • Detailed server connection states: "Needs Authorization", "Authenticated", "Connected", and "Disconnected".
    • Interactive "Connect Account", "Disconnect", and "Retry" buttons on server cards with accessible ARIA semantics and responsive design.

v0.2.3

Choose a tag to compare

@katticot katticot released this 28 Sep 14:50
2c8edba

Compare v0.2.2...v0.2.3

Added

  • Config schema metadata: every field in Config now carries a human-readable .description(), timeout fields carry .role('ms'), and env/headers dict values carry .role('secret') — DSH's schema-driven settings forms now show documentation and redact secrets before they reach the browser, without changing any validation, default, or type.
  • A read-only servers/status endpoint and a matching status section on the plugin's page in DSH's Plugins UI: per-server connection state, tool counts, and an expandable, filterable list of individual tool names grouped by visibility (model-visible / app-only / both). Never exposes command, args, env, headers, or url.
  • The package now self-activates as a DSH bundle: dsh plugin add dsh-mcp-apps inserts its mcp-apps row automatically (via dsh/cordis.patch.yml), giving it a card and detail page — with the official MCP mark as its icon — in DSH's Plugins UI. Previously it had to be wired in by hand with an insert: patch entry and had no presence in that UI at all.

Changed

  • README's Install and Configure sections rewritten for the bundle flow: install via the DSH Harness UI or CLI, then configure by patching (not insert:-ing) the mcp-apps row in your profile's cordis.patch.yml. Added a "Migrating from 0.2.x" section for existing insert:-based configs.
  • Peer ranges bumped to ^0.1.7-rc.2 to match the latest DSH; added @deepseek-ai/dsh-client-ui-plugin-manager as an optional peer, deliberately left out of dsh.client.inject so a deployment without the plugin manager doesn't gate this plugin's own tool-view loading on it.
  • scripts/smoke-dsh.mjs now patches the bundle's own mcp-apps row instead of inserting a second one.
  • Common transitive dependencies routed through @socketregistry drop-in replacements via pnpm overrides.

Security

  • Connection-error messages surfaced through servers/status are sanitized before storage: URL userinfo/query/fragment are stripped, any configured secret value (plus Bearer <token>-style sub-tokens and percent-encoded variants of it) is redacted, and the result is truncated to 300 characters. Configured secrets under 3 characters are not redacted, to avoid collateral redaction of common short substrings — documented as a known limitation.

v0.2.2

Choose a tag to compare

@katticot katticot released this 27 Sep 20:40
fd58fa7

v0.2.2

Summary

Restores compatibility with DSH 0.1.7-rc.2 (0.2.1 was rejected as incompatible on that runtime), fixes a bug that prevented tools from registering on headless DSH hosts, and merges in a batch of previously-unpublished lifecycle, approval, and client-reliability fixes. This release also fixes two bugs in the release pipeline itself that blocked publishing 0.2.2 on the first two attempts.

DSH 0.1.7-rc.2 compatibility

DSH 0.1.7-rc.2 refused to load the plugin (Plugin dsh-mcp-apps@0.2.1 is incompatible with dsh 0.1.7-rc.2: peerDependencies {...}) because the @deepseek-ai/dsh-* peer ranges were pinned to an exact 0.1.5-rc.3 string. DSH's compatibility checker (evaluatePluginCompatibility in @deepseek-ai/dsh-app-boot) only inspects peerDependencies keys named @deepseek-ai/dsh or prefixed @deepseek-ai/dsh-, and calls semver.satisfies(runtimeVersion, requirement, { includePrerelease: true }); a bare version with no range operator is treated as exact-equality, which is why the old pin failed outright against 0.1.7-rc.2.

Every @deepseek-ai/dsh-* peer is now a caret range, ^0.1.7-rc.2, verified against the checker's own logic: 0.1.7-rc.3 and later 0.1.x prereleases keep satisfying it (because of includePrerelease: true), while a 0.2.0 DSH runtime correctly still trips it. Matching devDependencies (and @deepseek-ai/cordis, pinned to 4.0.4) are pinned exactly so CI builds/tests against the same runtime the peer range targets; the cordis peerDependency itself stays a permissive ^4.0.2 since it isn't covered by DSH's checker. Full writeup: docs/dsh-compatibility.md.

Fixes

  • Tools failed to register on headless hosts: src/index.ts previously injected 'webServer' alongside 'tools' and 'connection', but this plugin's routes go through the public ctx.connection.fetch.register contract (HostConnectionFetch), which never touches ctx.webServer. Since every Cordis 4.x inject entry is a hard requirement, requiring webServer left the plugin's fiber permanently PENDING on a headless DSH host, so none of its host-side MCP tools would register. webServer is no longer in inject (now just ['tools', 'connection']); a regression test builds a real Cordis tree with tools + connection but no webServer provider and asserts the fiber still activates.
  • Public DSH host connection lifecycle: the plugin registers its /api/mcp-apps/* endpoints through the public HostConnectionFetch.register(route) contract instead of a fabricated Context.connection shape and runtime prototype-probing, returning the public ConnectionRpcResult response envelope on each route. Shutdown now awaits every route's unregister call (via each route's own disposer) before independently tearing down the tool pool and session store.
  • Scoped optional approval services with a stale-service check: approval and agents are only injected when a server config actually sets allowAppToolCalls: 'approve' (Object.values(config.servers).some(server => server.allowAppToolCalls === 'approve')), instead of being unconditional hard requirements. The approval/agent service references are captured via ctx.inject's disposer pattern and cleared if the service is swapped or torn down — an in-flight approval call re-checks the captured reference after the request resolves and fails with unavailable instead of using a stale reference if the service changed underneath it.
  • Reconnect-aware client tool discovery: the client (src/client/index.tsx, McpAppToolView.tsx) re-discovers MCP app tools after a host reconnect instead of only on initial load.
  • Browser-safe client artifact: the ./client bundle is now built and verified to run without Node-only globals, with a dedicated tests/client-bundle.spec.ts and vitest.artifact.config.ts checking the packaged bundle.
  • Headless DSH smoke test in CI: scripts/smoke-dsh.mjs installs a real DSH runtime into a throwaway DSH_HOME, packs and loads the plugin, and drives a live tools/call round trip end-to-end across allow/deny/approve policies — now wired into both ci.yml and release.yml against @deepseek-ai/dsh@0.1.7-rc.2, headless.
  • Architecture diagram docs: added docs/diagrams/plugin-architecture.html/.json documenting the plugin's Cordis service/lifecycle wiring.

Release pipeline fixes

Two bugs in .github/workflows/release.yml blocked the first two attempts to publish this version (both fixed before this release went out, see #5 and #6):

  • Tarball path misparse (#5): npm publish "$PACKAGE_TARBALL" with PACKAGE_TARBALL="dist/dsh-mcp-apps-0.2.2.tgz" (no ./ prefix) made npm-package-arg treat the bare relative path as a hosted-git shorthand (owner/repo) instead of a local file, so npm tried git ls-remote ssh://git@github.com/dist/dsh-mcp-apps-0.2.2.tgz.git and failed with a permission-denied error before ever reaching the registry. Fixed by defining PACKAGE_TARBALL with a leading ./ so every consumer gets an unambiguous local path, plus a no-auth npm publish --dry-run guard added to ci.yml to catch this class of regression on every PR.
  • npm version too old for OIDC trusted publishing (#6): once the path bug was fixed, npm publish --provenance failed with ENEEDAUTH because the npm CLI bundled with actions/setup-node's Node 22 (10.9.8) doesn't support OIDC-based trusted publishing, which requires npm >= 11.5.1. Fixed by pinning npm@11.20.0 immediately before the publish step (and before the CI dry-run guard).

Upgrade notes

No source-level API break for consumers: pnpm typecheck passed against the bumped devDependencies with no usage changes needed (HostConnectionFetch, SubprocessRuntime, AgentRegistry, ApprovalService/ApprovalOutcome all held across the version bump). webServer is no longer a required inject, so tools now register correctly on headless DSH hosts (no webServer service) — 0.2.1 silently failed to register any tools there.

This release requires DSH >= 0.1.7-rc.2. A host running DSH 0.1.5-rc.x will now refuse to load this plugin (its runtime version doesn't satisfy the ^0.1.7-rc.2 peer range). Hosts on 0.1.7-rc.2 or a later 0.1.x prerelease can load it as before; a future DSH 0.2.x runtime will need a new plugin release. No configuration changes are required for existing installs.

Links

v0.2.1

Choose a tag to compare

@katticot katticot released this 27 Sep 19:16

v0.2.1

Summary

Security fixes for two unbounded-memory paths (oversized stdio messages, unchecked raw resource reads) plus CI hardening. No breaking changes.

Fixes (security)

  • Stdio memory exhaustion: a stdio MCP server could send an arbitrarily large unterminated JSON-RPC line, and the SDK's ReadBuffer would buffer it without limit while waiting for a newline. maxMessageBytes is now passed through as the SDK's own maxBufferSize option on StdioClientTransport, so an oversized line now throws and closes the connection (handled by the existing reconnect/backoff logic) instead of growing memory unbounded.
  • Unchecked raw resource reads: readResourceRaw returned client.readResource() without any size check, while readResource enforced MAX_RESOURCE_SIZE_BYTES but measured length in UTF-16 code units and fully base64-decoded blobs just to measure them. Both paths now sum content sizes in UTF-8 bytes for text, and in bytes derived algebraically from the base64 length for blobs (no decoding needed), summed across all content items.

Changes

  • maxMessageBytes now also applies to stdio servers (previously only remote transports), sharing the same DEFAULT_MAX_MESSAGE_BYTES default of 16MB with remote transports.
  • Internal cleanup: removed the unreachable no-serverName fallback branches in ServerPool.listResources/readResourceRaw/readResource, making serverName a required parameter. ServerPool is internal and not exported, so this has no user-facing effect.

CI

  • Pinned actions/checkout, pnpm/action-setup, and actions/setup-node in ci.yml to the exact commit SHAs release.yml already used.

Upgrade notes

No breaking changes. A stdio server that sends a single message larger than 16MB will now fail where it previously could grow memory unbounded (or hit the SDK's undocumented 10MB default) — raise maxMessageBytes for that server if you expect larger single messages.

Links

v0.2.0

Choose a tag to compare

@katticot katticot released this 27 Sep 17:20

v0.2.0 Release Notes

Highlights

  • Security hardening: Session tokens now required for reverse tool calls and RPC operations
  • Transport compliance: Only MCP-spec transports (stdio, streamable-http, sse) are now supported; custom ipc and websocket removed
  • Improved security defaults: allowAppToolCalls now defaults to deny for safer by-default configuration
  • Session isolation: Bounded session store with least-recently-used eviction prevents unbounded memory growth
  • Release infrastructure: Published via npm trusted publishing with provenance verification

Breaking changes

  • Transport removal: The ipc and websocket transports have been removed. To migrate an ipc server, run it as stdio or put it behind an HTTP endpoint (streamable-http or sse). Replace websocket with streamable-http or sse. Remote urls must now be http(s):// (a config with transport: 'ipc' or 'websocket' now fails validation at startup).

  • App tool call authorization: allowAppToolCalls defaults to deny, so reverse calls from apps are blocked until explicitly configured. Each server must now declare one of: 'deny' (blocked), 'approve' (routed through approval handler), or 'allow' (permitted).

  • Environment variable allowlisting: ${VAR} expansion (in env and headers only) no longer reads DSH_*, secret-like names (*KEY*, *TOKEN*, *SECRET*, *PASSWORD*), SSH_AUTH_SOCK or GPG_AGENT_INFO unless the server lists them in allowedVars, e.g. allowedVars: ['MCP_TOKEN']. Otherwise they expand to an empty string (or the :-default).

  • Node engine version: engines.node is now >=22. Older Node versions are no longer supported.

  • Client types export: The ./client export no longer declares types; it is loaded only by the DSH module loader.

  • Configuration validation: Null servers (servers.<id>: null), non-positive timeouts, and malformed remote URLs are now rejected at startup.

Security

  • Session-bound RPC: tools/call, resources/read, resources/read-raw, and resources/list now require a valid session token bound to the server that issued it. The unauthenticated fallback has been removed.

  • CSP hardening: CSP meta tag is placed first using DOMParser, with a safe fallback. Private IP ranges, link-local addresses, and the blanket img-src https: rule have been removed for stronger isolation.

  • Loopback-only HTTP: Remote plain http:// is allowed only to loopback addresses (127.0.0.1, [::1]). Incoming messages are capped per server with maxMessageBytes to prevent memory exhaustion.

Packaging/Release

  • Published via npm trusted publishing using OIDC, eliminating the need for static tokens and providing cryptographic provenance verification.
  • Release workflow enforces minimal permissions, runs on a dedicated release environment, and pins all actions to commit SHAs.
  • Package contents are verified: no dangling source maps, .npmignore removed, packageManager field added.

Upgrade notes

servers:
  # Before (0.1.0): ipc
  # daemon:
  #   transport: ipc
  #   socketPath: /tmp/mcp-daemon.sock

  # After (0.2.0): spawn it as stdio (or expose it over streamable-http / sse)
  daemon:
    transport: stdio
    command: my-mcp-server
    allowAppToolCalls: approve        # default is deny; 'approve' asks the user during a turn

  remote:
    transport: streamable-http
    url: https://mcp.example.com/mcp
    headers:
      Authorization: 'Bearer ${MCP_TOKEN}'
    allowedVars: [MCP_TOKEN]          # secret-like vars are blocked unless listed

Requires Node.js 22 or later. Full details: #1.

v0.1.0 - Initial Release

Choose a tag to compare

@katticot katticot released this 23 Sep 21:04
e871c1a

Universal MCP Apps (SEP-1865) Host & UI Plugin for DeepSeek Harness

  • Multi-transport server pool (stdio, unix domain sockets/ipc, remote sse & streamable-http)
  • Dynamic tool synchronization and LLM API schema sanitization (^[a-zA-Z0-9_-]+$)
  • Sandboxed iframe container with dynamically synthesized Content Security Policy (CSP)
  • Resilient PostMessage transport with early handshake buffering
  • Bidirectional AppBridge reverse tool calling and live data refresh fallback
  • Auto-reveal lifecycle coordinator preventing premature DSH turn process collapse
  • Full unit test suite (17 vitest tests) and automated CI/CD workflows