Preface¶
To add a capability to DeepSeek Harness (hereinafter referred to as DSH), the conventional approach is to write a harness package: declare the tool schema, implement execute, and package it into a profile. This workflow is suitable for official plugins, but the cost is relatively high for scenarios like “needing to temporarily call a weather API today” or “repeatedly performing the same type of file organization in this workspace”. After making changes, you still need to restart and remount, and the model cannot automatically supplement tools in the middle of a session.
The official repository describes the architecture as “everything is a plugin”: models, tools, skills, sessions, sandboxes, and UI can be attached and detached at the configuration layer without modifying the Harness source code. Someone in the community has integrated the “writing tools” process into the settings page: use the Monaco editor to fill in the name, description, parameters, and JavaScript code, and register them into ctx.tools with hot reload after saving. For the same set of tools, the model can also add, delete, and modify them through custom_tool_create / custom_tools_list / custom_tool_remove. This plugin is called dsh-custom-tool and is included in the independent community plugin directory deepseek-harness-plugin.com. This directory has no official affiliation with DeepSeek / Fangjia and is not an official app store.
This article is organized after cross-checking the plugin directory page, GitHub repository README / README.zh.md, package.json, and dsh.plugin.json: what it is, which command to install, how to write tool code, as well as sandbox boundaries and harness prerequisites.
What It Is¶
dsh-custom-tool is a development and runtime plugin for DeepSeek Harness, maintained by the GitHub organization omdsh-dev, with the repository address omdsh-dev/dsh-custom-tool. The current version is 0.1.2 (consistent in package.json and dsh.plugin.json; the repository tagged v0.1.2 on 2026-08-16). The license is MIT, and the LICENSE copyright statement is for 2026 FSMargoo. The main language is TypeScript; the repository has already committed lib/, so you do not need to build locally when installing via the GitHub tarball as described in the README. As of the query on 2026-08-17, the GitHub API shows 24 stars (the community directory page marked 23 at that time). package.json requires Node.js ^22.19 || >=24.
The directory page’s one-sentence description is: Create and manage sandbox JavaScript tools with the Monaco editor, with the model driving the tool lifecycle. The repository README is more specific: users write tools in the “Custom Tool” page of the settings interface; the model extends and prunes the same tool set through the aforementioned three tool interfaces. Tools are persisted, hot-registered, and written into the model prompt in the next step.
The README lists three pain points it addresses: previously you had to publish a harness package to add capabilities, now you can save and take effect with a single form; the model can persist tools in the middle of a session and share the same validation gate with the UI; the code you write runs in a restricted worker instead of directly entering the current Node process.
Core Features¶
The following items are all from the current repository README, README.zh.md, and dsh.plugin.json, with no additional elaboration.
1. Custom Tool in the Settings Page¶
The plugin adds a “Custom Tool” section in the web settings, with a dedicated navigation icon. You can list, create, edit, enable/disable, and delete tools. Tools created by the model and workspace-scoped tools will have badges. The copy adapts to the Chinese/English language system of the harness and switches with the interface language.
The editor is Monaco (VS Code engine) with TypeScript language service: args generates types according to the parameter schema you declare, env and sandbox globals have type declarations, and completions and diagnostics appear in real time. The editor and TS worker are bundled inline, and the client is a single-file bundle. package.json declares that the browser half is provided via dsh.client, and the README specifies the path as /plugins/dsh-custom-tool/client.js.
2. Persistence and Hot Registration¶
Tools are stored in the custom-tools settings namespace, with the same hierarchical method as other DSH settings: schema default values, composite base, user documents. Changes take effect immediately after submission and are restored from storage after restart.
Enabled tools are registered into ctx.tools the moment the setting write is submitted; disabling or deleting them will immediately unregister the tool. The tool schema is automatically imported into the system prompt by the harness, and the model will see it in the next step.
3. Model Self-Service: Create, List, Delete¶
dsh.plugin.json declares the contributed tools as:
- custom_tool_create: Upsert by name
- custom_tools_list
- custom_tool_remove
These three share the validation gate with the settings UI. The attribution rules are clearly written in the README:
- The model can create, list, and delete tools it created (source: model)
- Creating tools in the global location requires explicit user authorization: custom_tool_create will initiate a DSH approval request (GUI pop-up); creation will fail and close if rejected or unavailable for approval
- The model can autonomously create tools in the workspace location
- The model cannot delete tools created by users (source: user): custom_tool_remove will reject it, and the prompt will guide the model to ask the user to delete them in the settings interface
The settings interface manages all sources, scopes, locations, as well as enabling/disabling and deletion.
4. Two Sets of Scopes, Two Sets of Storage Locations¶
Each tool declares an execution scope. This is the core security contract of the plugin:
global (default) |
workspace |
|
|---|---|---|
| Use Case | Pure computation, external data, workflows | Repetitive file tasks within the workspace |
fetch |
Controlled by allowNetwork |
Controlled by allowNetwork |
console, timers, TextEncoder, URL, etc. |
Available | Available |
fs |
Unavailable | readFile / writeFile / list, limited to the root directory of this session’s workspace |
require / import / process |
Never available | Never available |
The root directory of the workspace scope is the cwd from which the agent is initiated, parsed at call time. Relative paths are resolved from the root; absolute paths must fall within the root; out-of-bounds paths will be explicitly rejected. When there is no session context, the workspace tool will directly report no workspace root and will not run without boundaries.
Isolation is at the lexical level (resolve + prefix check). The README clearly states: symbolic links within the workspace may still point to external resources—the workspace scope treats code as trusted, not a sandbox against malicious hosts.
The storage location is another dimension:
- location: 'global': Stored in the shared settings namespace, available to all workspaces until deleted
- location: 'workspace': Stored in independent files named by the workspace root path hash (the README writes it under the workspace-tools/ directory), only visible to sessions of that workspace
The two dimensions can be freely combined. The README gives an example: a file tool with global location and workspace scope (such as PDF reading) that executes fs on any called workspace.
5. Sandbox Execution and Budget¶
Each call runs in an independent worker thread, with an environment of a brand-new node:vm realm, paired with a whitelist, Node Permission Model, and hard budget. The worker does not inherit environment variables, nor can it access files outside the configured scope or create child processes. After timeout, cancellation, or completion, the worker is terminated.
The adjustable budgets given in the README (the config field of the dsh-custom-tool entry in cordis.yml) have the following default values:
| Field | Default Value | Meaning |
|---|---|---|
timeoutMs |
30000 | Wall clock upper limit for a single call (milliseconds) |
memoryLimitMb |
128 | Old generation heap upper limit for a single call worker (MB) |
maxResultChars |
16000 | Character limit for result rendering text |
maxCodeBytes |
65536 | UTF-8 byte limit for a single tool code |
maxTools |
100 | Upper limit of storable tools |
allowNetwork |
true | Whether to allow tool code to call fetch |
Installation and Enablement¶
The installation command given on the plugin directory page is:
dsh plugin add github:omdsh-dev/dsh-custom-tool
The directory page also reminds users: the plugin runs with the permissions of the current dsh process, and may execute code during installation. Please check the source code repository and license before installing. If you need reproducible installation, fix the commit hash:
dsh plugin add github:omdsh-dev/dsh-custom-tool#<commit>
Replace <commit> with the actual commit hash in the repository, do not copy the placeholder verbatim. The current tag v0.1.2 points to commit 7cb95649dca9b380c9a30af96bdbef87a76a2259.
The repository README, for the web interface, describes installing a fixed version tarball according to the web profile and then restarting:
dsh plugin --profile web add https://github.com/omdsh-dev/dsh-custom-tool/archive/refs/tags/v0.1.2.tar.gz
dsh web
The package declares dsh.bundle.patch (mount host plugin) and dsh.client (provide browser half). lib/ has been committed, so this tarball does not require further building after installation.
Harness Prerequisites (original from the README): The settings namespace must be exposed to the web configuration client via the WEB_SETTINGS_NAMESPACES whitelist in packages/host/apiproxy/src/api-proxy.ts, and the list must include 'custom-tools'. The upstream DSH commit d6ea05b5 has already added this item. Without it, the interface can render, but saving will be silently rejected with the error settings-not-exposed.
Typical Usage¶
Create a Tool in the Settings Page¶
Launch the web interface and open the Custom Tool in settings:
1. Create a new tool, fill in the name, description, parameter schema, scope, and storage location.
2. Write code in Monaco. The code field is an asynchronous function body, with the contract async (args, env) => value, not a complete source file.
3. Save. When enabled, the tool is immediately registered into ctx.tools and will appear in the model prompt in the next step.
4. You can disable or delete it when not needed; disabling will immediately unregister it.
The README gives an example of pulling weather by city (requires allowNetwork to be true, which is the default value):
// args is typed according to the parameter JSON Schema you declared.
const url = `https://api.example.com/weather?city=${encodeURIComponent(args.city)}`
const response = await fetch(url)
if (!response.ok) throw new Error(`upstream returned ${response.status}`)
return await response.json()
The return value must be a JSON value: string, number, boolean, null, array, or plain object. undefined or non-JSON values will cause the call to fail.
Parameters are JSON Schema with an object root, and only accept the DSH subset: type, properties, required, items, enum, const, oneOf, additionalProperties, description, title, default, examples.
Sandbox globals include: fetch (disabled when allowNetwork: false), console, TextEncoder / TextDecoder, URL / URLSearchParams, atob / btoa, structuredClone, AbortController, setTimeout / setInterval and their corresponding clear functions. env is { tool, scope }. The workspace scope additionally provides fs.
There is currently no “test run” button on the interface. The README states that tools should be validated through model calls or headless runtime.
Let the Model Supplement Tools Itself¶
You can directly ask the model to create a reusable tool during a session. The model will use custom_tool_create. If the target is the global location, a DSH approval pop-up will appear; rejection will cause the creation to fail. The workspace location does not require this approval step.
Use custom_tools_list to view the current custom tools. Custom tool names cannot shadow tools already registered by other packages; conflicts will appear in this list as per-tool registration failures.
When deleting, the model can only delete entries with source: model. Tools written by users in the settings page need to be deleted by humans in the UI.
Applicable Scenarios and Notes¶
These scenarios are more suitable:
- Already using dsh web and want to add a few small tools to the current environment without releasing an official plugin package
- Need the model to precipitate recurring steps into callable tools in the middle of a session and make them visible immediately in the next step
- The tool logic is pure computation or controlled fetch; if you need to access files, explicitly use the workspace scope and accept the lexical isolation boundary
- Hope to separate the permissions of user-written tools and model-written tools: humans delete human-created tools, and the model can only operate the batch it created
Please note the following items, all from the directory page or repository README, with no additional elaboration:
1. Check the source code and license before installing. The directory page clearly states: the plugin runs with the permissions of the current dsh process and may execute code during installation. This is a community plugin, not an official DeepSeek component.
2. Confirm that custom-tools has been added to the DSH whitelist. Otherwise, the settings page can open, but saving will fail silently (settings-not-exposed). You need the upstream commit d6ea05b5 or an equivalent change.
3. The sandbox is not a universal isolation tool. require / import / process are unavailable; the global scope has no fs; the workspace path check does not protect against symbolic links. The README treats workspace code as trusted code.
4. Network access is enabled by default. allowNetwork defaults to true. If you do not want tools to access the external network, you need to turn it off in cordis.yml.
5. There are budget limits. The default hard limits given in the documentation are 30 seconds per call, 128 MB heap, 16000 characters for results, 64 KiB for code, and a maximum of 100 tools.
6. Do not treat the directory page as an official store. deepseek-harness-plugin.com is a community directory; the DSH本体 is based on deepseek-ai/deepseek-harness. Follow the installation command from the directory page verbatim; for web fixed-version installation, follow the tarball method in the repository README, do not construct the path by yourself based on the plugin name.
Summary¶
dsh-custom-tool has a very focused function: putting “writing a JavaScript tool” into the DSH settings page, using Monaco for editing, hot registration, and persistence; it also hands the same lifecycle to the model, but protects user tools through approval and attribution rules. The execution side is a worker with a whitelist and budget, rather than running arbitrary scripts directly in the current process.
Directory page and repository links:
- Plugin directory: https://deepseek-harness-plugin.com/zh-CN/plugins/dsh-custom-tool/
- GitHub: https://github.com/omdsh-dev/dsh-custom-tool
- DeepSeek Harness: https://github.com/deepseek-ai/deepseek-harness
- Official introduction: https://www.deepseek.com/harness/