@auggieteo/dsh-mcp-adapter 0.2.0 → 0.2.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 +26 -0
- package/README.md +10 -1
- package/docs/adr/0009-dual-generation-settings-api.md +2 -0
- package/lib/client.js +180 -172
- package/lib/client.js.map +2 -2
- package/package.json +1 -1
- package/src/host/index.js +7 -2
- package/src/host/settings.js +10 -1
- package/src/host/skill.js +125 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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.2.2] - 2026-09-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `skillInstall` Config field (`file` by default, `runtime`, or `off`) controlling how the bundled `mcp-adapter` agent skill is installed. `file` copies `skills/mcp-adapter/SKILL.md` verbatim to `$DSH_HOME/skills/mcp-adapter/SKILL.md` at startup (atomic temp-file-then-rename write, never a symlink), so the DSH filesystem skill provider lists it under Settings > Skills > DSH skills. `runtime` keeps the previous in-memory registration; `off` installs nothing. The mode is read once at Adapter startup.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- The bundled agent skill now installs as a real file by default instead of registering only in memory. The installed file is never overwritten: an identical file is left untouched, and a file edited by a human wins with one Host warning naming the installed path and the package version — delete `$DSH_HOME/skills/mcp-adapter/` and restart DSH to refresh it from the package. A failed file install (for example a read-only `$DSH_HOME`) falls back to the runtime registration with a warning; the skill stays a convenience and never throws.
|
|
16
|
+
|
|
17
|
+
## [v0.2.1] - 2026-09-06
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `scripts/patch-dsh-settings-nav-icon.mjs`: patches the installed DSH settings shell so the `mcp` nav row in Settings shows a connection icon (`IconLinkOutline16`) instead of the generic settings-gear fallback. The icon mapping is DSH shell chrome that this plugin cannot reach through the `settings.section` slot (it accepts only `id`, `order`, `label`). The insert is idempotent and marked `// dsh-mcp-adapter`; `--revert` restores the original bytes. A DSH upgrade replaces the patched file, so re-run the script afterwards, then restart `dsh web` and refresh the page.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Settings > MCP has a single Add Server entry point: the dialog now offers an Input fields mode for one Server and a Paste JSON mode for a standard `mcpServers` object (existing names are replaced), replacing the separate Import JSON button and dialog in both the toolbar and the empty state.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- The plugin failed to load on DSH 0.1.2-rc.1 with `failed to apply loader entry … cannot get property "remote.settings" without inject`: the runner mounts the settings namespace as the traced dotted service `remote.settings`, so the plain `ctx.remote?.settings` read is governed and throws for plugins that do not declare it — while declaring it would park the plugin forever on 0.1.1-rc.2, where the namespace is never mounted. The write face is now resolved through the inject-free `ctx.get('remote.settings')`, which works on both generations ([ADR 0009](docs/adr/0009-dual-generation-settings-api.md)).
|
|
30
|
+
- The Add Server and Import JSON dialogs follow the host dialog theme again: the modal card now surfaces on `bg-layer-2` with the shared `--dsw-elevation-prominent` treatment and the overlay mask blurs like the host Modal primitive, instead of the off-palette `bg-overlay` fill and a hardcoded drop shadow.
|
|
31
|
+
|
|
7
32
|
## [v0.2.0] - 2026-09-05
|
|
8
33
|
|
|
9
34
|
Everything from v0.1.2 through this release: the six deferred v1 features (#8–#12, #14), the dual-generation DSH compatibility fix, and the standard-readme documentation pass.
|
|
@@ -41,5 +66,6 @@ Everything from v0.1.2 through this release: the six deferred v1 features (#8–
|
|
|
41
66
|
|
|
42
67
|
Initial releases of the Adapter through v1 issues #1–#7: proxy-first tool surface, Config in the DSH settings namespace, promotion registry, lazy lifecycle, output guard, settings page, and the `/mcp-adapter` RPC channel. See [docs/verification/v1-e2e.md](docs/verification/v1-e2e.md).
|
|
43
68
|
|
|
69
|
+
[v0.2.2]: https://github.com/auggie246/dsh-mcp-adapter/compare/v0.2.1...v0.2.2
|
|
44
70
|
[v0.2.0]: https://github.com/auggie246/dsh-mcp-adapter/compare/v0.1.2...v0.2.0
|
|
45
71
|
[v0.1.2]: https://github.com/auggie246/dsh-mcp-adapter/releases/tag/v0.1.2
|
package/README.md
CHANGED
|
@@ -130,10 +130,13 @@ mcp:
|
|
|
130
130
|
url: https://mcp.example.com/other
|
|
131
131
|
auth: oauth
|
|
132
132
|
scopes: [repo, read:org]
|
|
133
|
+
skillInstall: file
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
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`.
|
|
136
137
|
|
|
138
|
+
The one top-level Adapter field is `skillInstall` (`file` by default, `runtime`, or `off`); see [Agent skill](#agent-skill).
|
|
139
|
+
|
|
137
140
|
The schema rejects unknown fields and invalid transport combinations before they reach `$DSH_HOME/settings.yaml`. Each `env` and `headers` value has the DSH `secret` schema role. Wire views retain each key and redact its value.
|
|
138
141
|
|
|
139
142
|
#### Workspace Config layer
|
|
@@ -211,7 +214,13 @@ The SDK transports refresh expired access tokens through the stored refresh toke
|
|
|
211
214
|
|
|
212
215
|
### Agent skill
|
|
213
216
|
|
|
214
|
-
The package bundles a model-facing skill in `skills/mcp-adapter/SKILL.md
|
|
217
|
+
The package bundles a model-facing skill in `skills/mcp-adapter/SKILL.md`, so agents learn the search → describe → call workflow, how to read failure messages (including the OAuth sign-in hint), and which commands belong to the human. The top-level `skillInstall` Config field controls how the Adapter installs it; the mode is read once at Adapter startup, so a change takes effect on the next DSH restart.
|
|
218
|
+
|
|
219
|
+
- `file` (default): on startup the Adapter copies the bundled `SKILL.md` verbatim to `$DSH_HOME/skills/mcp-adapter/SKILL.md` (an atomic temp-file-then-rename write, never a symlink). The DSH filesystem skill provider discovers the file, so the skill shows under **Settings > Skills > DSH skills** and joins the session catalog without a restart of an already-running session. When the file install fails, the Adapter falls back to the runtime registration below and logs one warning.
|
|
220
|
+
- `runtime`: the Adapter registers the skill in memory on the DSH `skills` service only. It reaches agents but never appears on the Settings > Skills page.
|
|
221
|
+
- `off`: the Adapter installs no skill.
|
|
222
|
+
|
|
223
|
+
The installed file is never overwritten. If `$DSH_HOME/skills/mcp-adapter/SKILL.md` already exists, your edit wins: an identical file is left untouched, and a differing file gets one Host warning naming the installed path and the package version. To refresh the installed copy from the package, delete `$DSH_HOME/skills/mcp-adapter/` and restart DSH. Editing the installed file changes what the session catalog serves; the edit survives plugin restarts.
|
|
215
224
|
|
|
216
225
|
## Architecture
|
|
217
226
|
|
|
@@ -4,6 +4,8 @@ The Adapter's client bundle once read `ctx.connection.api.settings` directly. DS
|
|
|
4
4
|
|
|
5
5
|
We resolve the write face at apply time (`src/client/settings-api.js`) and prefer `ctx.remote.settings` when it exists; otherwise we fall back to the 0.1.1-rc.2 `ctx.connection.api.settings` face. Both normalize to the controller's existing contract — `update`/`mutate` take one request object and answer `{ result: { ok, value | error } }` — so `McpSettingsController` and the host stay generation-agnostic. The client declares `remote` in its `inject` (the service exists in both generations; `remote.settings` does not exist on 0.1.1-rc.2 and stays undeclared, so the runner never parks waiting for it), and `package.json` adds `@deepseek-ai/dsh-api-remotes` to `dsh.client.inject` so the typed settings namespace mounts before consumers apply. On the host, `MCP_SETTINGS_NAMESPACE` becomes the plain `'mcp'` string: branding was type-only on 0.1.1-rc.2, and 0.1.2-rc.1 accepts the validated raw string. The peer dependency widens to `^0.1.1-rc.2 || ^0.1.2-rc.1`.
|
|
6
6
|
|
|
7
|
+
*Correction:* the original resolution read the rc.1 face as `ctx.remote?.settings`, assuming a plain property. The rc.1 runner instead mounts the namespace as the traced dotted service `remote.settings`: reading through `ctx.remote` composes that key, and cordis throws `cannot get property "remote.settings" without inject` for any plugin that did not declare the dotted service — which crashed this plugin at apply with a failed loader entry. Declaring it is not an option either: a declared-but-never-mounted service keeps the fiber from ever applying, which is exactly the 0.1.1-rc.2 case. The face is therefore resolved through cordis's inject-free `ctx.get('remote.settings')`, which answers the mounted namespace on 0.1.2-rc.1 via the root isolate and undefined on 0.1.1-rc.2, keeping the fallback reachable on both generations; the plain-property read remains as a legacy fallback for runners that expose the face as a plain property.
|
|
8
|
+
|
|
7
9
|
Considered options: shipping a 0.1.2-only release and telling 0.1.1-rc.2 users to upgrade was rejected because the harness is in flux and plugin releases should not force harness moves; probing `ctx.remote.$host` or version strings was rejected because face presence is the actual capability signal and survives future renames better. A hard failure with upgrade guidance is kept for a hypothetical third shape, so a future break surfaces as one clear message instead of an `undefined` read.
|
|
8
10
|
|
|
9
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.
|
package/lib/client.js
CHANGED
|
@@ -647,6 +647,7 @@ function useEscapeClose(onClose) {
|
|
|
647
647
|
}, [onClose]);
|
|
648
648
|
}
|
|
649
649
|
function AddServerDialog({ controller, busy, onClose }) {
|
|
650
|
+
const [mode, setMode] = (0, import_react.useState)("form");
|
|
650
651
|
const [name, setName] = (0, import_react.useState)("");
|
|
651
652
|
const [transport, setTransport] = (0, import_react.useState)("stdio");
|
|
652
653
|
const [command, setCommand] = (0, import_react.useState)("");
|
|
@@ -654,11 +655,28 @@ function AddServerDialog({ controller, busy, onClose }) {
|
|
|
654
655
|
const [argsText, setArgsText] = (0, import_react.useState)("[]");
|
|
655
656
|
const [auth, setAuth] = (0, import_react.useState)("headers");
|
|
656
657
|
const [scopesText, setScopesText] = (0, import_react.useState)("");
|
|
658
|
+
const [jsonText, setJsonText] = (0, import_react.useState)('{\n "mcpServers": {\n \n }\n}');
|
|
657
659
|
const [error, setError] = (0, import_react.useState)();
|
|
658
660
|
useEscapeClose(onClose);
|
|
661
|
+
const switchMode = (next) => {
|
|
662
|
+
setMode(next);
|
|
663
|
+
setError(void 0);
|
|
664
|
+
};
|
|
659
665
|
const submit = async (event) => {
|
|
660
666
|
event.preventDefault();
|
|
661
667
|
setError(void 0);
|
|
668
|
+
if (mode === "json") {
|
|
669
|
+
try {
|
|
670
|
+
parseMcpImport(jsonText);
|
|
671
|
+
} catch (nextError) {
|
|
672
|
+
setError(nextError instanceof Error ? nextError.message : String(nextError));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const result = await controller.importJson(jsonText);
|
|
676
|
+
if (result) onClose();
|
|
677
|
+
else setError("The Host rejected this import. Review the page error and retry.");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
662
680
|
let config;
|
|
663
681
|
try {
|
|
664
682
|
config = normalizeServerConfig(
|
|
@@ -677,170 +695,165 @@ function AddServerDialog({ controller, busy, onClose }) {
|
|
|
677
695
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-card-head", children: [
|
|
678
696
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
679
697
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "mcp-modal-title", children: "Add Server" }),
|
|
680
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-muted", children: "
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-muted", children: "Fill in the fields for one Server, or paste an mcpServers JSON." })
|
|
681
699
|
] }),
|
|
682
700
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "mcp-button", onClick: onClose, children: "Close" })
|
|
683
701
|
] }),
|
|
684
|
-
|
|
685
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
686
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "Name" }),
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-dialog-tabs", role: "tablist", "aria-label": "Add Server input mode", children: [
|
|
687
703
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
688
|
-
"
|
|
704
|
+
"button",
|
|
689
705
|
{
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
706
|
+
type: "button",
|
|
707
|
+
role: "tab",
|
|
708
|
+
"aria-selected": mode === "form",
|
|
709
|
+
className: mode === "form" ? "mcp-dialog-tab mcp-dialog-tab-active" : "mcp-dialog-tab",
|
|
693
710
|
disabled: busy,
|
|
694
|
-
|
|
695
|
-
|
|
711
|
+
onClick: () => switchMode("form"),
|
|
712
|
+
children: "Input fields"
|
|
713
|
+
}
|
|
714
|
+
),
|
|
715
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
716
|
+
"button",
|
|
717
|
+
{
|
|
718
|
+
type: "button",
|
|
719
|
+
role: "tab",
|
|
720
|
+
"aria-selected": mode === "json",
|
|
721
|
+
className: mode === "json" ? "mcp-dialog-tab mcp-dialog-tab-active" : "mcp-dialog-tab",
|
|
722
|
+
disabled: busy,
|
|
723
|
+
onClick: () => switchMode("json"),
|
|
724
|
+
children: "Paste JSON"
|
|
696
725
|
}
|
|
697
726
|
)
|
|
698
727
|
] }),
|
|
699
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
700
|
-
|
|
701
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
702
|
-
|
|
728
|
+
error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-error", role: "alert", children: error }),
|
|
729
|
+
mode === "json" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
730
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "MCP Config JSON" }),
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
732
|
+
"textarea",
|
|
703
733
|
{
|
|
704
|
-
|
|
705
|
-
|
|
734
|
+
autoFocus: true,
|
|
735
|
+
className: "mcp-textarea",
|
|
736
|
+
style: { minHeight: 280 },
|
|
737
|
+
value: jsonText,
|
|
706
738
|
disabled: busy,
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "stdio", children: "stdio" }),
|
|
710
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "http", children: "Streamable HTTP" })
|
|
711
|
-
]
|
|
739
|
+
spellCheck: false,
|
|
740
|
+
onChange: (event) => setJsonText(event.target.value)
|
|
712
741
|
}
|
|
713
|
-
)
|
|
742
|
+
),
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-muted", children: "One or more Servers. A name that already exists is replaced." })
|
|
714
744
|
] }),
|
|
715
|
-
|
|
745
|
+
mode === "form" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
716
746
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
717
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "Name" }),
|
|
718
748
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
719
749
|
"input",
|
|
720
750
|
{
|
|
751
|
+
autoFocus: true,
|
|
721
752
|
className: "mcp-input",
|
|
722
|
-
value:
|
|
753
|
+
value: name,
|
|
723
754
|
disabled: busy,
|
|
724
|
-
onChange: (event) =>
|
|
725
|
-
placeholder: "
|
|
755
|
+
onChange: (event) => setName(event.target.value),
|
|
756
|
+
placeholder: "github"
|
|
726
757
|
}
|
|
727
758
|
)
|
|
728
759
|
] }),
|
|
729
760
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
730
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "
|
|
731
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
732
|
-
"
|
|
761
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "Transport" }),
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
763
|
+
"select",
|
|
733
764
|
{
|
|
734
|
-
className: "mcp-
|
|
735
|
-
value:
|
|
765
|
+
className: "mcp-select",
|
|
766
|
+
value: transport,
|
|
736
767
|
disabled: busy,
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
] })
|
|
743
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
744
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
745
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "URL" }),
|
|
746
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
747
|
-
"input",
|
|
748
|
-
{
|
|
749
|
-
className: "mcp-input",
|
|
750
|
-
value: url,
|
|
751
|
-
disabled: busy,
|
|
752
|
-
onChange: (event) => setUrl(event.target.value),
|
|
753
|
-
placeholder: "https://example.com/mcp"
|
|
768
|
+
onChange: (event) => setTransport(event.target.value),
|
|
769
|
+
children: [
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "stdio", children: "stdio" }),
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "http", children: "Streamable HTTP" })
|
|
772
|
+
]
|
|
754
773
|
}
|
|
755
774
|
)
|
|
756
775
|
] }),
|
|
757
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
776
|
+
transport === "stdio" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
758
777
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
759
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
761
|
-
"
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "Command" }),
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
780
|
+
"input",
|
|
762
781
|
{
|
|
763
|
-
className: "mcp-
|
|
764
|
-
value:
|
|
782
|
+
className: "mcp-input",
|
|
783
|
+
value: command,
|
|
765
784
|
disabled: busy,
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
children: [
|
|
769
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "headers", children: "Static headers" }),
|
|
770
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oauth", children: "OAuth 2.0 with PKCE" })
|
|
771
|
-
]
|
|
785
|
+
onChange: (event) => setCommand(event.target.value),
|
|
786
|
+
placeholder: "npx"
|
|
772
787
|
}
|
|
773
788
|
)
|
|
774
789
|
] }),
|
|
775
|
-
|
|
776
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "Args as a JSON array" }),
|
|
792
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
793
|
+
"textarea",
|
|
794
|
+
{
|
|
795
|
+
className: "mcp-textarea",
|
|
796
|
+
value: argsText,
|
|
797
|
+
disabled: busy,
|
|
798
|
+
placeholder: '["-y", "@modelcontextprotocol/server-github"]',
|
|
799
|
+
onChange: (event) => setArgsText(event.target.value),
|
|
800
|
+
spellCheck: false
|
|
801
|
+
}
|
|
802
|
+
)
|
|
803
|
+
] })
|
|
804
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "URL" }),
|
|
777
807
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
778
808
|
"input",
|
|
779
809
|
{
|
|
780
810
|
className: "mcp-input",
|
|
781
|
-
value:
|
|
811
|
+
value: url,
|
|
782
812
|
disabled: busy,
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
onChange: (event) => setScopesText(event.target.value)
|
|
813
|
+
onChange: (event) => setUrl(event.target.value),
|
|
814
|
+
placeholder: "https://example.com/mcp"
|
|
786
815
|
}
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
] }),
|
|
826
|
-
error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-error", role: "alert", children: error }),
|
|
827
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
828
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "MCP Config JSON" }),
|
|
829
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
830
|
-
"textarea",
|
|
831
|
-
{
|
|
832
|
-
autoFocus: true,
|
|
833
|
-
className: "mcp-textarea",
|
|
834
|
-
style: { minHeight: 280 },
|
|
835
|
-
value: text,
|
|
836
|
-
disabled: busy,
|
|
837
|
-
spellCheck: false,
|
|
838
|
-
onChange: (event) => setText(event.target.value)
|
|
839
|
-
}
|
|
840
|
-
)
|
|
816
|
+
)
|
|
817
|
+
] }),
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-field-row", children: [
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "HTTP authentication" }),
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
822
|
+
"select",
|
|
823
|
+
{
|
|
824
|
+
className: "mcp-select",
|
|
825
|
+
value: auth,
|
|
826
|
+
disabled: busy,
|
|
827
|
+
title: "Static headers send fixed values on every request. OAuth 2.0 with PKCE signs in through the provider in your browser.",
|
|
828
|
+
onChange: (event) => setAuth(event.target.value),
|
|
829
|
+
children: [
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "headers", children: "Static headers" }),
|
|
831
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oauth", children: "OAuth 2.0 with PKCE" })
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
] }),
|
|
836
|
+
auth === "oauth" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "mcp-field", children: [
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-label", children: "OAuth scopes (optional, comma-separated)" }),
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
839
|
+
"input",
|
|
840
|
+
{
|
|
841
|
+
className: "mcp-input",
|
|
842
|
+
value: scopesText,
|
|
843
|
+
disabled: busy,
|
|
844
|
+
placeholder: "read write",
|
|
845
|
+
title: "Optional. Leave empty when the provider defines no scopes \u2014 for example Context7. Request only scopes the provider grants.",
|
|
846
|
+
onChange: (event) => setScopesText(event.target.value)
|
|
847
|
+
}
|
|
848
|
+
),
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "mcp-muted", children: "Leave empty unless the provider requires scopes." })
|
|
850
|
+
] })
|
|
851
|
+
] }),
|
|
852
|
+
auth === "oauth" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-muted", children: "OAuth signs in through your browser. The Sign in button appears on the Server panel after you save." })
|
|
853
|
+
] })
|
|
841
854
|
] }),
|
|
842
855
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-actions", children: [
|
|
843
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "mcp-button mcp-button-primary", disabled: busy, children: busy ? "Importing\u2026" : "Import Servers" }),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "mcp-button mcp-button-primary", disabled: busy, children: busy ? mode === "json" ? "Importing\u2026" : "Adding\u2026" : mode === "json" ? "Import Servers" : "Add Server" }),
|
|
844
857
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "mcp-button", disabled: busy, onClick: onClose, children: "Cancel" })
|
|
845
858
|
] })
|
|
846
859
|
] }) });
|
|
@@ -1303,56 +1316,32 @@ function McpSettingsPage({ controller }) {
|
|
|
1303
1316
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: "mcp-title", children: "MCP" }),
|
|
1304
1317
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-intro", children: "Connect MCP Servers and choose how their tools reach the Agent." })
|
|
1305
1318
|
] }),
|
|
1306
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
),
|
|
1317
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1318
|
-
"button",
|
|
1319
|
-
{
|
|
1320
|
-
type: "button",
|
|
1321
|
-
className: "mcp-button mcp-button-primary",
|
|
1322
|
-
disabled: snapshot.busy || !settings.writable,
|
|
1323
|
-
onClick: () => setDialog("add"),
|
|
1324
|
-
children: "Add Server"
|
|
1325
|
-
}
|
|
1326
|
-
)
|
|
1327
|
-
] })
|
|
1319
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1320
|
+
"button",
|
|
1321
|
+
{
|
|
1322
|
+
type: "button",
|
|
1323
|
+
className: "mcp-button mcp-button-primary",
|
|
1324
|
+
disabled: snapshot.busy || !settings.writable,
|
|
1325
|
+
onClick: () => setDialog("add"),
|
|
1326
|
+
children: "Add Server"
|
|
1327
|
+
}
|
|
1328
|
+
) })
|
|
1328
1329
|
] }),
|
|
1329
1330
|
!settings.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-warning", children: "The active Settings Provider is read-only." }),
|
|
1330
1331
|
snapshot.error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-error", role: "alert", children: snapshot.error }),
|
|
1331
1332
|
names.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-empty", children: [
|
|
1332
1333
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "mcp-card-title", children: "No MCP Servers" }),
|
|
1333
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-muted", children: "Add a Server or
|
|
1334
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
),
|
|
1345
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1346
|
-
"button",
|
|
1347
|
-
{
|
|
1348
|
-
type: "button",
|
|
1349
|
-
className: "mcp-button",
|
|
1350
|
-
disabled: snapshot.busy || !settings.writable,
|
|
1351
|
-
onClick: () => setDialog("import"),
|
|
1352
|
-
children: "Import JSON"
|
|
1353
|
-
}
|
|
1354
|
-
)
|
|
1355
|
-
] })
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mcp-muted", children: "Add a Server through its input fields, or paste an existing mcpServers JSON." }),
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mcp-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1336
|
+
"button",
|
|
1337
|
+
{
|
|
1338
|
+
type: "button",
|
|
1339
|
+
className: "mcp-button mcp-button-primary",
|
|
1340
|
+
disabled: snapshot.busy || !settings.writable,
|
|
1341
|
+
onClick: () => setDialog("add"),
|
|
1342
|
+
children: "Add Server"
|
|
1343
|
+
}
|
|
1344
|
+
) })
|
|
1356
1345
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mcp-layout", children: [
|
|
1357
1346
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { className: "mcp-sidebar", "aria-label": "MCP Servers", children: names.map((name) => {
|
|
1358
1347
|
const status = statusFor(snapshot.overview, name);
|
|
@@ -1395,14 +1384,13 @@ function McpSettingsPage({ controller }) {
|
|
|
1395
1384
|
selected
|
|
1396
1385
|
)
|
|
1397
1386
|
] }),
|
|
1398
|
-
dialog === "add" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddServerDialog, { controller, busy: snapshot.busy, onClose: () => setDialog() })
|
|
1399
|
-
dialog === "import" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImportDialog, { controller, busy: snapshot.busy, onClose: () => setDialog() })
|
|
1387
|
+
dialog === "add" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddServerDialog, { controller, busy: snapshot.busy, onClose: () => setDialog() })
|
|
1400
1388
|
] });
|
|
1401
1389
|
}
|
|
1402
1390
|
|
|
1403
1391
|
// src/client/settings-api.js
|
|
1404
1392
|
function createSettingsApi(ctx) {
|
|
1405
|
-
const remoteSettings = ctx
|
|
1393
|
+
const remoteSettings = readRemoteSettings(ctx);
|
|
1406
1394
|
if (remoteSettings !== void 0) return remoteSettingsApi(remoteSettings);
|
|
1407
1395
|
const connectionSettings = ctx.connection?.api?.settings;
|
|
1408
1396
|
if (connectionSettings !== void 0) return connectionSettings;
|
|
@@ -1410,6 +1398,20 @@ function createSettingsApi(ctx) {
|
|
|
1410
1398
|
"No DSH settings write API: expected ctx.remote.settings (DSH 0.1.2+) or ctx.connection.api.settings (DSH 0.1.1-rc.2). Upgrade @auggieteo/dsh-mcp-adapter to a release supporting this harness."
|
|
1411
1399
|
);
|
|
1412
1400
|
}
|
|
1401
|
+
function readRemoteSettings(ctx) {
|
|
1402
|
+
if (typeof ctx.get === "function") {
|
|
1403
|
+
try {
|
|
1404
|
+
const mounted = ctx.get("remote.settings");
|
|
1405
|
+
if (mounted !== void 0) return mounted;
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
try {
|
|
1410
|
+
return ctx.remote?.settings;
|
|
1411
|
+
} catch {
|
|
1412
|
+
}
|
|
1413
|
+
return void 0;
|
|
1414
|
+
}
|
|
1413
1415
|
function remoteSettingsApi(remote) {
|
|
1414
1416
|
return {
|
|
1415
1417
|
async update({ ns, patch, expectedRevision }) {
|
|
@@ -1493,11 +1495,17 @@ var MCP_SETTINGS_CSS = `
|
|
|
1493
1495
|
.mcp-promotion-name{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}
|
|
1494
1496
|
.mcp-promotion-description{display:block;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}
|
|
1495
1497
|
.mcp-empty{padding:30px 20px;text-align:center;display:flex;align-items:center;flex-direction:column;gap:10px}
|
|
1496
|
-
.mcp-overlay{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-1);display:flex;align-items:center;justify-content:center;padding:20px;z-index:1200;animation:mcp-fade .12s ease}
|
|
1497
|
-
.mcp-modal-card{width:min(560px,100%);max-height:min(760px,90vh);overflow:auto;padding:18px;display:flex;flex-direction:column;gap:13px;border:
|
|
1498
|
+
.mcp-overlay{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);display:flex;align-items:center;justify-content:center;padding:20px;z-index:1200;animation:mcp-fade .12s ease}
|
|
1499
|
+
.mcp-modal-card{width:min(560px,100%);max-height:min(760px,90vh);overflow:auto;padding:18px;display:flex;flex-direction:column;gap:13px;border:0;border-radius:14px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-elevation-prominent);animation:mcp-pop .16s ease}
|
|
1498
1500
|
@keyframes mcp-fade{from{opacity:0}to{opacity:1}}
|
|
1499
1501
|
@keyframes mcp-pop{from{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:none}}
|
|
1500
1502
|
.mcp-modal-title{margin:0;font-size:15px;line-height:22px;font-weight:600}
|
|
1503
|
+
.mcp-dialog-tabs{display:inline-flex;gap:2px;align-self:flex-start;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;padding:2px;background:var(--dsw-alias-bg-layer-3)}
|
|
1504
|
+
.mcp-dialog-tab{height:26px;border:0;border-radius:14px;background:transparent;color:var(--dsw-alias-label-secondary);padding:0 12px;font:inherit;font-size:12px;cursor:pointer;transition:background .12s ease,color .12s ease}
|
|
1505
|
+
.mcp-dialog-tab:hover:not(:disabled):not(.mcp-dialog-tab-active){color:var(--dsw-alias-label-primary)}
|
|
1506
|
+
.mcp-dialog-tab:disabled{cursor:not-allowed;opacity:.5}
|
|
1507
|
+
.mcp-dialog-tab-active{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}
|
|
1508
|
+
.mcp-dialog-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
1501
1509
|
.mcp-confirm{border-radius:8px;padding:10px;display:flex;flex-direction:column;gap:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent)}
|
|
1502
1510
|
@container (max-width:660px){.mcp-layout{grid-template-columns:minmax(0,1fr)}.mcp-sidebar{position:static;flex-direction:row;overflow-x:auto;padding-bottom:2px}.mcp-server-button{width:auto;flex:none;white-space:nowrap}}
|
|
1503
1511
|
@container (max-width:480px){.mcp-field-row{grid-template-columns:minmax(0,1fr)}.mcp-secret-row{grid-template-columns:minmax(0,1fr) auto}.mcp-secret-row .mcp-input:first-child{grid-column:1 / -1}.mcp-card-head .mcp-actions{margin-left:0;width:100%}.mcp-toolbar{align-items:flex-start}}
|