How to Create a DeepSeek Harness Plugin

Nine screenshot-verified steps from a hello-world Context function to a dual-runtime MCP Apps plugin — with the exact contract, schema, and install commands.

Last updated: 2026-09-14

Every capability in DeepSeek Harness — from a single hello tool to a full interactive UI — ships as a plugin, and the barrier to writing your own is lower than it looks: a function that receives a Context, a few exports, and a Schema. This walkthrough follows a real build from empty folder to a working MCP Apps plugin running inside a live chat.

Nine steps cover the whole path: what a plugin is, the minimal contract, Host and Client runtimes, the load–run–dispose lifecycle, a real repository layout, the host entry and Config Schema, and the exact commands to install, declare, and verify a plugin in your own profile.

Create a DeepSeek Harness plugin in nine steps

  1. 1

    See where a plugin plugs in

    DeepSeek Harness is built on Cordis, and a plugin is a function that receives a Context. It declares the services it depends on, registers new capabilities on that Context, and leaves cleanup to the framework when it is uninstalled. Model-session tools and web interfaces both enter through this boundary — the slides map the surfaces your code can extend: the model, tools, MCP, and the web client.

    DeepSeek Harness architecture slide mapping the MODEL, TOOLS, MCP, and WEB CLIENT blocks that plug into the configurable core of DSH
    Four extension surfaces feed the harness core — plugins are how you add to any of them.Watch at 0:13
    Cordis Context diagram from a DSH plugin tutorial, with callouts for declaring dependencies, registering tools and UI, Config + Schema, and uninstall cleanup
    A plugin is a function receiving this Context: declare, register, and let the framework clean up.Watch at 0:25
  2. 2

    Start from the smallest working plugin

    The tutorial's hello example needs exactly three things: a loadable module entry (export const name = 'hello'), an apply(ctx) function that registers capabilities — here ctx.tools.add adds a hello tool that echoes your name back — and, only if the plugin takes options, an exported Config built with Schema.object. Harness validates that schema and renders the matching fields in the management UI. Everything else is optional.

    Minimal DeepSeek Harness plugin source with export const name, apply(ctx) calling ctx.tools.add, and a Schema.object Config beside a three-item checklist
    This whole file is a working plugin: an entry, an apply function, and an optional Config Schema.Watch at 1:00
  3. 3

    Choose Host, Client, or both — the lifecycle is the same

    Two runtimes matter. The Host is the backend process running DeepSeek Harness: it owns network connections and the keys your tools use. The Harness Web UI is the Client, responsible for components and interactive interfaces. The hello plugin is Host-only — it registers a tool and lets the chat display the result — while a UI plugin adds a Client entry with its own inject and apply. Either way, each entry runs apply in its own runtime, and the Context disposes everything it registered on uninstall.

    DSH plugin lifecycle slide with HOST ENTRY and CLIENT ENTRY · OPTIONAL chips below the LOAD, RUN, and DISPOSE stages
    Host entry is required, the Client entry is optional — both run the same three-stage lifecycle.Watch at 1:45
    RUN stage highlighted in green on a DSH plugin lifecycle diagram, the moment apply(ctx) executes and registers the plugin's capabilities
    RUN is your apply function at work — it registers capabilities where both runtimes can see them.Watch at 1:20
  4. 4

    Study the folder layout of a shipped plugin

    Before writing code, look at how a published plugin is organized. The sugarforever/dsh-mcp-apps repository keeps source, examples, tests, docs and package files at the root, and under src/ it separates config, connection, protocol, rpc, tools and client. You do not need to read every line — recognizing the three anchors (src/index.ts, src/config.ts, src/client/) is enough to navigate any DSH plugin.

    dsh plugin structure on GitHub: the sugarforever/dsh-mcp-apps src folder listing client, config.ts, connection.ts, index.ts, protocol.ts, rpc.ts, and tools.ts
    A shipped plugin keeps its contract in index.ts, validation in config.ts, and the browser side in client/.Watch at 2:05
  5. 5

    Write the host entry: name, inject, Config, apply

    Open src/index.ts and declare the public contract. export const name identifies the plugin; export const inject lists the services you depend on — the MCP Apps plugin asks for ['tools', 'connection']; export const Config hands validation to Harness. The apply(ctx, config) function receives the Context plus the validated configuration, and anything long-lived — RPC connections, tool registration — is wrapped in ctx.effect so Harness releases it uniformly when the plugin is uninstalled. These lines are the mandatory interface between a host plugin and the framework.

    src/index.ts of a DeepSeek Harness plugin open in VS Code with host-side imports and the highlighted name, inject, and Config contract exports
    The host file starts with imports from @deepseek-ai/cordis and the three contract exports before apply.Watch at 2:34
    Plugin contract callout over DSH plugin code labeling name as the plugin identity, inject as declared dependencies, and apply as registration and cleanup
    Four exports are the whole public contract — name, inject, Config, and apply.Watch at 2:55
  6. 6

    Give users a Config Schema they can fill in

    In src/config.ts the tutorial plugin describes two transport branches with one Schema union: stdio (command, args, env) and streamable-http (a required url, optional headers), plus shared fields like serverName (required), toolCallTimeoutMs (defaults to 60 000) and failOnStartupError (defaults to false). Developers implement the interface and the Schema; users only fill in the values — the examples/streamable-http.cordis.yml file in the repo is exactly that fill-in-the-blanks instance.

    config.ts of a DSH MCP plugin showing a Schema union with stdio and streamable-http branches, a required url, and default timeout fields
    One Schema union covers both transports; required and default fields drive the management UI.Watch at 3:05
    streamable-http.cordis.yml example from the dsh-mcp-apps repo, an insert entry pointing the plugin at an MCP server url with failOnStartupError
    Ship an examples file like this — it is the fill-in-the-blanks instance users copy.Watch at 3:35
  7. 7

    Install the plugin into a web profile

    Clone the plugin repo, then point DSH_HOME at the Harness home you want to extend and add the package into the web profile. The demo runs npx @deepseek-ai/dsh plugin --profile web add <path>, which links the local folder into the profile and confirms with added 1 package. Installed the plugin into a different profile? Run the same add command against that profile.

    $git clone https://github.com/sugarforever/dsh-mcp-apps.git
    $cd dsh-mcp-apps
    $export DSH_HOME=/tmp/dsh-video-home
    $npx @deepseek-ai/dsh plugin --profile web add /path/to/dsh-mcp-apps
    Terminal capturing git clone https://github.com/sugarforever/dsh-mcp-apps.git during a DeepSeek Harness plugin install walkthrough
    Start from the reference repo — clone it, then install its dependencies.Watch at 5:37
    zsh session registering a DSH plugin with npx @deepseek-ai/dsh plugin --profile web add after exporting DSH_HOME
    The add command links the plugin into the web profile — nothing else to configure yet.Watch at 5:40
  8. 8

    Declare the instance in cordis.patch.yaml

    Adding the package is not enough — a patch entry tells Harness to instantiate it. In the profile's cordis.patch.yaml (here $DSH_HOME/profiles/web/cordis.patch.yaml), insert an entry with a unique id, the plugin's package name ('@sugarforever/dsh-mcp-apps') and its config: serverName, transport: streamable-http, the MCP server url, and failOnStartupError. Harness reads this patch at startup and loads the plugin automatically.

    cordis.patch.yaml of a DSH web profile edited in vi, inserting @sugarforever/dsh-mcp-apps with serverName, streamable-http transport, and failOnStartupError
    This patch entry is what makes Harness instantiate the plugin on startup.Watch at 5:49
  9. 9

    Start Harness Web and watch the tool run

    Launch Harness Web with the same DSH_HOME and open 127.0.0.1:3080. Ask the agent to play a round of 2048: the model sees the remote tool exposed by the plugin and calls it, the Host pushes the result to the sandboxed UI the plugin preloaded, and a playable 2048 board renders right in the dialog. Plugin installed, configured, and verified end to end.

    DeepSeek Harness web UI at 127.0.0.1:3080 handling a play-2048 request with dsh-system-prompt context injections and an MCP app tool call
    The model sees the plugin's remote tool and decides to call it — no manual wiring.Watch at 6:10
    Playable 2048 board with a New Game button rendered inside a DeepSeek Harness chat after the model called the remote MCP tool
    The plugin's client entry turned a tool result into an interactive UI right in the dialog.Watch at 6:15

Creating a DeepSeek Harness plugin: FAQ

Real questions from developers writing their first plugin.

What is a DeepSeek Harness plugin, in one sentence?

A plugin is a function that receives a Cordis Context: it declares which services it depends on, registers new capabilities — model tools, services, or UI — on that Context, and relies on the framework to release everything it registered when the plugin is uninstalled.

What is the minimum a DSH plugin needs?

Three things: a loadable module entry point, an apply(ctx) function that receives the Context, and at least one capability registered inside apply — the tutorial's hello plugin adds a single tool with ctx.tools.add. An exported Config Schema is optional and only needed when the plugin takes configuration.

Do I need to write TypeScript to create a DeepSeek Harness plugin?

The tutorial's reference plugin is written in TypeScript — export const name = 'mcp-apps', inject arrays, and Schema.object(...) are all TypeScript idioms — and cloning that repo is the fastest way to start. The contract itself is just module exports, so the concepts transfer, but every example in this walkthrough is TypeScript.

How does Harness load my plugin at startup?

Three moves: point DSH_HOME at your Harness home, add the package into a profile with npx @deepseek-ai/dsh plugin --profile web add <path>, and declare an insert entry in that profile's cordis.patch.yaml with the plugin name and its config. Harness reads the patch when it starts and loads the plugin automatically — no changes needed on the Harness side.

Can a DSH plugin wrap a remote MCP server?

Yes — that is exactly the case study. The host entry connects to a stdio or Streamable HTTP MCP server, then registers the remote tools as Harness model tools inside ctx.effect. The Config Schema exposes the transport branches, so users only fill in serverName, transport, and url in their cordis.patch.yaml.

Who cleans up connections when a plugin is removed?

The framework does — provided you register through the Context. Wrap persistent connections and tool registrations in ctx.effect and the DISPOSE phase releases them uniformly on uninstall. That is why the tutorial never stashes connections in module-level globals.

Related guides

The rest of the plugin ecosystem, from concepts to a live directory.

Sources & credits

Screenshots come from this public screen recording; every image deep-links back to the exact moment of the source video, and the step-by-step text above is our own retelling.

DSH Plugins is an independent community directory of DeepSeek Harness plugins. Not affiliated with or endorsed by DeepSeek. Third-party plugins are not security-audited — review the source before installing.

New DeepSeek Harness plugins, weekly. No spam.