DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

re-ITRT /

re-ITRT/dsh-skill-mcp-panel

Verified

A Skill list and MCP server manager for DeepSeek Harness, mounted as a sidebar panel and themed entirely from the host's own design tokens.

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@654f6d7b

dsh-skill-mcp-panel

A Skill list and MCP server manager for DeepSeek Harness, mounted as a sidebar panel and themed entirely from the host's own design tokens.

License: MIT Tests Dependencies

中文文档

One sidebar entry — 技能与 MCP — opens a full page with two dense lists: every Agent skill this deployment serves, and every configured MCP server. Each row is a single line: name, a short description, a switch, and a 详情 toggle that expands in place. MCP rows can also be edited, created, and deleted, and both lists can reveal the directory the thing lives in.

The page carries no theme code. Every colour, radius, font, and focus ring comes from the host's own --dsw-* tokens, and its type follows the host's own font size, so it tracks the deployment theme (light, dark, or a third-party theme) and the Settings → Appearance → Font size setting automatically.

Typography

dsh-client-ui-layout projects the content font size onto body as an inline variable and derives a delta from it:

--dsh-content-font-size: 14px;                      /* 12–17, the user's setting */
--dsh-content-font-delta: calc(var(--dsh-content-font-size, 14px) - 14px);

Every size on this page is expressed the way the conversation surfaces express theirs — <base>px + var(--smp-delta) — where --smp-delta aliases --dsh-content-font-delta. Nothing is a fixed pixel size, and a test asserts that, so the panel grows and shrinks with the setting exactly as the transcript beside it does.

The two lists

Skills

The catalog visible to the current session composition, so project- and preset-scoped skills appear beside bundled ones.

Control Behaviour
Name the /name identifier, plus its discovery source, plus a policy tag only when the policy is not the default
Description clamped to one line; the full text stays in the tooltip
Switch enables or disables the skill
详情 provider, source, path, the invocation policy spelled out, the full body, and 打开目录 / 删除

A skill has two independent invocation flags, both on by default:

Flag Meaning
modelInvocable the skill is in the model's catalog, so the Agent may load it on its own
userInvocable the skill is in the composer's / menu, so you can invoke it by typing /name

Because both are on for almost every skill, tagging them everywhere says nothing. The row tags only the surprising cases, and the detail panel spells the policy out in a sentence instead of leaving two words to interpret.

How the switch works

Switching a skill off is maintained by this plugin, not by editing files. DSH has no per-skill switch: a skill exists because a provider lists it, and each provider decides its own invocation policy — the shipped Office provider hard-codes both flags and ignores frontmatter, so no file edit could ever disable those skills.

The lever that works for every provider is the registry's own duplicate rule: within a layer, the candidate with the lowest rank wins the name. This plugin therefore registers one provider that re-declares each switched-off skill with rank: 0 and invocation: { modelInvocable: false, userInvocable: false }. That removes it from the model catalog and the human / menu while keeping it visible here — with its original description, provider, and body — so it can be switched back on.

The set lives in one JSON file, <DSH home>/storages/skill-mcp-panel.json, and every change invalidates the registry's catalog cache.

Removing a skill

删除 really removes the file — it is not another way of switching off. A directory bundle is the whole <root>/<name>/ directory, so its references and scripts go with it; anything else is the single file. The confirmation says which of the two removal modes applies before anything happens.

The skill comes from Mode What happens
the user — project-dsh, project-agents, user-dsh, user-agents, custom permanent the file is deleted outright; nothing is kept
the deployment — bundled and anything else parked the file leaves its root but is moved to the trash and listed under 已删除, where 恢复 puts it back

The split follows the registry's own source bucket, which is what says who owns the file. Your own skills are yours to delete, so they are deleted. A shipped skill is different: removing one destroys a file an upgrade owns and an install would silently restore, and the switch cannot disable the Office skills at all. Parking those keeps the one lever that does work reversible.

A parked skill goes to <DSH home>/storages/skill-mcp-panel.trash/<id>/ with a manifest.json naming its original path. Restoring refuses to overwrite a path that has since reappeared, and a trash id is validated as a single path segment, so nothing outside the trash can be addressed.

Removal is always two steps: the action row is replaced by a confirmation naming the skill, and only the confirmation's 删除 calls the host.

MCP servers

One row per configured server.

Control Behaviour
Namespace mcp__<serverName>__*, with tags for status, transport, and tool count
Target the command …args line, or the endpoint URL
Switch writes the row's disabled flag in the profile patch, exactly as the deployment's own plugin list does
详情 entry id, plugin, transport, redacted configuration, every registered tool, then 打开目录, 编辑, and 删除
新建服务器 replaces the list with a create form, so the empty state and the form never share the view
删除 removes the row from the profile patch, then waits for the Loader to drop it

The create/edit form

A fixed two-column grid: server name and transport share the first row, then the transport's own fields — command and working directory, or a URL spanning both columns — and arguments and environment variables side by side at three rows each. Everything not needed for the common case (timeout, fail-on-startup) sits behind one 更多选项 disclosure.

Format guidance lives in the controls' placeholders, never as prose under them, so no field grows a second line and the columns stay aligned. The form replaces the list while it is open, so the header cannot offer a button the form already is. Editing loads the deployment's own form projection on demand, which is the only per-item read the page performs besides a skill's full body.

Opening a directory

打开目录 reveals a skill's own folder, or the profile directory holding cordis.patch.yml for an MCP row. It reuses the deployment's own capability rather than re-implementing platform launching: the page asks GET /open-in-app/apps which file-manager entry this host resolved (explorer, finder, or filemanager) and posts {app, path} to POST /open-in-app/open. When the catalog resolves no file manager — an SSH launch, for example — the control is not offered at all.

The directory always comes from the host payload, and the host derives it with dirname from a path it already resolved. The page never invents the path.

Freshness

Nothing polls, and there is no refresh button. The list is read on mount — which happens every time the panel is opened — again when the window regains focus (throttled to one read per four seconds), and after every write the page performs. The controls that mutate anything re-read the list themselves, so the page never shows a state the deployment has already left.

Install

dsh plugin --profile desktop add <path-to-this-directory>

or, from an Agent session, plugin_manager with action: install_bundle and the absolute path of this directory as the target.

A host-code change needs a process restart. The deployment composes the browser half from dsh.client and hot-reloads its bundle, so a page refresh is enough for UI changes; the Node half is imported once into the process's ESM cache, so a new lib/index.js takes effect only after the application restarts.

Publishing to npm

This repository is the plugin's home; npm is an optional second channel. npm run publish:npm handles it, and it exists because two things about this name are easy to get wrong.

The bare name is taken. dsh-skill-mcp-panel on npm belongs to an unrelated DSH plugin — Fishquito7/dsh-skill-mcp-panel, which covers the same ground with more features and is actively maintained. An unscoped publish fails with a 403, and adding a confusingly similar neighbour would be worse than not publishing at all. So the script publishes under the scope of whoever is logged in, which cannot collide with anyone.

A mirror cannot accept a publish. A registry= line in ~/.npmrc is often a read-only mirror, so the script always targets registry.npmjs.org explicitly.

npm login --registry=https://registry.npmjs.org/   # once; a website login does not count
npm run publish:npm

Signing in at npmjs.com is not the same as signing in the CLI: npm keeps the browser session and the command line separate, and a token has to land in ~/.npmrc. The script says exactly this if it finds no token.

The package name is swapped in for the duration of the publish and restored afterwards — even when the publish fails — so the repository keeps its own name. prepublishOnly runs the test suite as a gate, and the version has to be bumped (npm version patch) before publishing again.

Layout

Path Role
package.json Declares dsh.bundle.patch and the dsh.client browser half
cordis.patch.yml The bundle patch that inserts the Node half into a profile
lib/index.js Node half: the /skill-mcp read/write surface
lib/client.js Browser half: the sidebar.panellist entry and the main page
test/host.test.mjs Node-half tests over a fake host, validating writes as real YAML
test/client.test.mjs Browser-half render tests over a minimal hook-aware renderer

There is no build step. lib/ is the authored source and the shipped artifact, and the browser half is hand-authored in the module-table format (window.__ModuleLoader__.load({ id, factory })), so no bundler is required and pnpm never has to run a dependency build script. There are no runtime dependencies.

Tests

npm test

Node's built-in test runner, 83 tests, no dependencies. The host suite drives the real route handler over a fake host context whose config editor parses the profile patch with a real YAML reader, so every emitted document is validated by a parser other than the writer that produced it. The client suite renders the browser half through a minimal hook-aware renderer and asserts on the resulting tree.

If no YAML reader is installed anywhere, the host suite still runs; it just stops checking emitted documents with an independent parser. Point DSH_YAML_MODULE at one to restore that layer.

Host API

Everything lives under one prefix route. Every request first passes the deployment's own trust fence, ctx.connection.requestRejection() — the same Host/Origin and browser-authentication check every shipped host route uses (/open-in-app included). A composition without a connection service falls back to a Sec-Fetch-Site/Origin check.

Route Method Purpose
/skill-mcp/state GET the merged snapshot; ?bodies=0 skips body previews, ?session=<id> scopes the skill read
/skill-mcp/skill GET one skill's full definition, used only to enrich its body
/skill-mcp/skill/enabled POST { name, enabled } — writes the enablement overlay
/skill-mcp/skill/delete POST { name } — deletes a skill the user owns, or parks a shipped one
/skill-mcp/skill/restore POST { trashId } — puts a parked skill back
/skill-mcp/mcp/server GET one server's form projection, loaded on demand by the edit form
/skill-mcp/mcp/save POST { entryId, config } — persists through ctx.configEditor.edit()
/skill-mcp/mcp/create POST { config } — appends a row to the profile patch
/skill-mcp/mcp/enabled POST { entryId, enabled } — writes the row's disabled flag
/skill-mcp/mcp/delete POST { entryId } — removes the row from the profile patch

Only the detail view's body enrichment and the edit form use a per-item route; the list payload already carries everything else, so expanding a row can never show an empty panel.

The Node half requires webServer, connection, skills, and tools, and reads the optional agents, agentPresets, configEditor, and hmr services through ctx.get(...). The skill read resolves its scope in this order: a session named by ?session=, the newest live root Agent (with its workspace cwd), the default Agent preset's standing scope, then the global layer alone; the resolved source is reported as skills.scopeSource.

How writes stay safe

  • Configuration edits go through ctx.configEditor.edit(), which validates the next value against the running plugin, reconciles the Loader, and rolls back on failure.
  • Row creation, the disabled flag, and row removal are text patches of the profile's own patch file, serialized through ctx.hmr.runExclusive and written atomically. The Loader is then polled: if the row does not mount, the flag does not apply, or a removed row stays mounted, within six seconds the previous file bytes are restored and the call fails with the reason.
  • Skill enablement writes only this plugin's own overlay document and invalidates the registry's catalog cache. No skill file is edited.
  • Skill removal never follows a path the page supplied; the host resolves the skill, derives the target with its own rules, and validates a trash id as a single path segment.
  • Credentials never reach the browser. The list and detail payloads replace every credential-shaped value with a keep marker; the form shows those fields blank and saving a blank marker restores the stored value. A brand-new server has nothing to restore, so a blank credential field is simply dropped.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 654f6d7b5b1c

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout