DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Carrot On A Stick — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
C

dsh-carrot-on-a-stick

Carrot On A Stick

通过 MCP 操作 DeepSeek Harness (dsh):将 dsh 会话、任务执行和工作区管理作为 MCP 服务器提供给任何 MCP 客户端。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:Leawind/dsh-carrot-on-a-stick#fd83351a22c32c5255bb331515d9ee71dc4cbc80
README兼容性版本

兼容性与来源证明

Carrot On A Stick 以 dsh-carrot-on-a-stick 发布,当前版本为 0.1.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/9/24

版本

0.1.0stable
2026/9/24

相关插件

正在加载相关插件…

最新版
0.1.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/9/24
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 productivity-workflow 分类下经过校验的插件。

Acp App@deepseek-ai/dsh-acp-appdsh ACP 配置文件包:基于 dsh-base 的仅限自动化的 JSON-RPC stdio 和进程生命周期管理Web All@linxin666/dsh-web-allDSH Web UI 全家桶聚合插件:一键安装全部功能插件(task-board / git-graph / pet / remote-web-ui / web-ui-settings / skin-center / community-plugins / compat shim)。compat 桥接层已并入本包(src/client),无需独立 compat npm 包。Codex Ui@michengai/dsh-codex-uiDSH Codex UI — 为 DeepSeek Harness Web 提供 Codex 风格侧栏、工作区会话树、全局搜索和轮次导航Deepseek Ipptdeepseek-ipptiPolloWork PPT Studio 及其精选幻灯片模板,作为原生 DeepSeek Harness 对话视图。

README

DSH Carrot on a Stick

Operate DeepSeek Harness (dsh) over MCP: run an MCP server inside the dsh process and expose session execution, task queues, and workspace management to any MCP client.

📖 English (this page) · 中文

What it does

dsh ships a complete agent runtime — model routing, tool sandbox, presets, persistent sessions — but it is a Cordis application that external programs cannot call directly. dsh-carrot-on-a-stick turns it inside out: the plugin starts an MCP server (StreamableHTTP) inside the dsh process and bridges the live harness through ctx.agents / ctx.agentPresets / ctx.tools.

Your MCP client is the commander; dsh is the executor. Works with Claude Code, Codex CLI, Cursor, another dsh instance (via the official dsh-mcp-client), or any automation script.

MCP client (Claude Code / Codex / another dsh / …)
   │  agent_run / task_* / session_* / model_list / select_model (HTTP + Bearer)
   ▼
dsh-carrot-on-a-stick (MCP server, 127.0.0.1:8090)
   │  ctx.agents.create → mount preset
   ▼
dsh agent — full toolset: bash, fs, todo, web…

agent_run lifecycle (progress + cancellation are spec-native):

sequenceDiagram
    participant C as MCP Client
    participant S as dsh-carrot-on-a-stick
    participant A as dsh Agent
    C->>S: tools/call agent_run (task, cwd, _meta.progressToken)
    S->>S: lock (cwd/session) → pool hit or create
    S->>A: followup(userMessage)
    S-->>C: notifications/progress (started, events=0)
    loop every progressIntervalMs
        S-->>C: notifications/progress (events N)
    end
    Note over C,A: cancel (notifications/cancelled) → official agent.cancel({kind:user})
    A-->>S: turn/end (completed | error | cancelled)
    S-->>C: CallToolResult (isError?, sessionId, changes/verification/leftovers…)

Tools

ToolPurpose
echoconnectivity check
dsh_list_toolslist the host-global tool registry (name + description; model tools are preset-scoped, usually empty)
model_listlist currently routable providers, model ids, reasoning efforts, and the default selection (look here before picking a model)
agent_runrun a task synchronously, structured result; sessionId continues a session; provider/model/reasoningEffort pick this run's model; detail controls result size; cancellable via the MCP notifications/cancelled (the host agent's official cancel is invoked); reports notifications/progress heartbeats when the caller passes _meta.progressToken
task_inboxpush a structured task (task + context + cwd + model) into the async queue, returns taskId
task_resultfetch a queued task's result; detail=status is a lightweight poll that never re-injects the payload
task_listlist queued/running/finished tasks (queue observability)
task_cancelcancel a queued or running task (a running one is cancelled through the host's official agent.cancel)
session_listlist known sessions (live + persisted, newest first) to pick sessionIds for continuation
session_historyread a live session's transcript summary (user/assistant/tool turns, newest first, truncated)
select_modelswitch the model of an existing session (official sessionController.selectModel path)
attach_sessionattach a session to the workspace of its cwd
rename_sessionrename an existing session

Model selection

Priority: per-call arguments > plugin config (provider+model) > host default selection (ctx.agentDefaultModel.currentSelection(), the same source the Web UI uses when creating a session). Supplying only one half completes it from the lower-priority source; if the pair still cannot be resolved the call fails loudly instead of running with an empty model (an empty model leaves the persona's {{model}} variable unset and fails the whole turn at assembly time).

reasoningEffort follows the model's source: call argument > plugin config > the host default selection's own effort. But when the caller or the plugin config pins provider+model explicitly, the host's effort (chosen for some other model) is not inherited — the host rejects unsupported explicit efforts instead of clamping or aliasing, so inheriting one would break a deployment that used to work.

Three ways to change models:

GoalHow
Run this one task on another modelpass provider+model (plus optional reasoningEffort) to agent_run / task_inbox
Switch models mid-conversation, keeping historyselect_model (official sessionController.selectModel: validated, writes a durable notice, takes effect on the next step; history is preserved)
Pin one model for the whole deploymentset provider+model in the plugin config and allowModelOverride: false to refuse caller overrides

Two source semantics worth knowing:

  • The resident session pool is keyed by cwd + model triple. Task 1 on model A and task 2 on model B in the same directory are two separate sessions (no shared context), so which model a session uses is always predictable; to switch models inside one session, use select_model.
  • Results now carry model: {provider, model, reasoningEffort?} so the caller never has to guess who answered.

model_list's source names the catalog's origin: sessionController = the official view (same data source as the Web UI's model selector; includes default, routableProviders, per-provider load failures, and reasoning efforts); llm = fallback (llm.listProviders() + per-provider listModels(), for deployments without sessionController, e.g. headless, and without reasoning metadata). It also reports the plugin's own model config and allowModelOverride, so a caller can see at a glance whether it may choose.

Result detail levels (token budget) — the whole point of this plugin is saving the caller's (operator's) context: execution details stay inside dsh, and read-back is projected by detail:

  • summary (default, a few hundred tokens): the changes/verification/leftovers three-line summary + the answer tail (the summary JSON sits at the end) + tool-name list + error
  • normal (~2k tokens): the above + truncated tool-call arguments and results
  • full (up to tens of thousands of tokens, for debugging): the full text
  • task_result also has a status level: polling returns only {taskId, status, error?} — fetch the summary once after completion instead of re-injecting the payload on every poll

When continuing the same sessionId, the executor already remembers prior turns — send only the delta in context.

Every result is structured: sessionId / model / assistantText / toolCalls / toolResults / changes / verification / leftovers — ready to be persisted by the caller.

Sessions are reused per cwd + model (LRU, default 8) to avoid reloading project context on every call.

Typical workflows

1. One-off task, synchronous — get a structured summary back:

{ "name": "agent_run", "arguments": { "task": "fix the failing test in src/auth", "cwd": "/workspace/app" } }

Continue it later with "sessionId": "<from the result>" — send only the delta in context.

2. Fire-and-forget queue — submit, poll cheaply, cancel if needed:

{ "name": "task_inbox", "arguments": { "task": "…", "cwd": "/workspace/app", "provider": "deepseek-official", "model": "…" } }
→ { "taskId": "…" }
{ "name": "task_result", "arguments": { "taskId": "…", "detail": "status" } }   // poll: no payload re-injection
{ "name": "task_cancel", "arguments": { "taskId": "…" } }                        // optional
{ "name": "task_result", "arguments": { "taskId": "…" } }                        // fetch the summary once done

task_list shows everything queued/running/finished at a glance.

3. Discover and steer sessions — find the right session, check its model, read what happened:

{ "name": "session_list", "arguments": { "limit": 10 } }                // sessionId / title / cwd / model
{ "name": "session_history", "arguments": { "sessionId": "…" } }        // recent turns, truncated
{ "name": "select_model", "arguments": { "sessionId": "…", "provider": "…", "model": "…" } }

4. Long synchronous runs — agent_run supports MCP-native progress and cancellation: clients that pass _meta.progressToken receive notifications/progress heartbeats, and any client can cancel via the standard notifications/cancelled (both wired to the host agent's official cancel). No custom polling protocol required.

Install & run

Requires the dsh host to run on Node.js >= 18 (the plugin declares engines accordingly).

The plugin must be installed into a dsh profile directory (the loader resolves plugin names from there; --patch alone from a repo checkout will not find the local package — see finding 1 in the E2E report):

git clone https://github.com/Leawind/dsh-carrot-on-a-stick.git
cd dsh-carrot-on-a-stick
npm install && npm run build
npm pack                                        # produces dsh-carrot-on-a-stick-<ver>.tgz

# install into the profile (Windows note: use the tarball; pnpm mangles file:D:/... specifiers)
pnpm -C ~/.dsh/profiles/<profile> add -w <path-to-tarball>

export DEEPSEEK_API_KEY=...                     # model credentials (or use what ~/.dsh already stores)
dsh --profile <profile> --patch ./cordis.yml --no-open --port 3081

The MCP server listens on 127.0.0.1:8090 (StreamableHTTP). Point any MCP client at http://127.0.0.1:8090/mcp.

Client configuration

Generic (any streamable-http capable MCP client):

{
  "mcpServers": {
    "dsh": {
      "url": "http://127.0.0.1:8090/mcp",
      "headers": { "Authorization": "Bearer <your-secret-token>" }
    }
  }
}

Let another dsh operate this one (add to the peer profile's cordis.patch.yml, using the official dsh-mcp-client):

- id: dsh-carrot-on-a-stick
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: dsh
    transport: streamable-http
    url: http://127.0.0.1:8090/mcp
    headers:
      Authorization: 'Bearer <your-secret-token>'

cordis.yml (patch format)

- insert:
    - id: dsh-carrot-on-a-stick
      name: 'dsh-carrot-on-a-stick'
      config:
        http: true
        port: 8090
        host: 127.0.0.1        # localhost only by default; add auth before exposing
        # authToken: 'your-secret-token'   # Bearer token auth (constant-time compare)
        # workspaceRoots: ['/workspace']   # cwd whitelist (separator/case-safe cross-platform)
        # allowedHosts: ['my-box.lan']     # extra allowed Host header values (DNS-rebinding guard)
        # preset: 'standard'               # agent preset to mount
        # model: ''                        # empty = follow dsh user/default settings
        # provider: ''                     # pair with model; empty = follow host default
        # reasoningEffort: ''              # default reasoning effort (empty = adapter default)
        # allowModelOverride: true         # false = pin the model, refuse caller overrides
        # sessionTtlMs: 86400000           # idle MCP transport sessions are reaped after this (0 = never)
FieldDefaultMeaning
host / port127.0.0.1 / 8090MCP server bind address; a listen failure (port in use, …) fails plugin startup loudly
authToken—Bearer token; enforced on every request when set (constant-time compare)
workspaceRoots—cwd whitelist; agents may only work inside the listed directories (subdirs included)
allowedHosts—extra allowed Host-header values; the bind host and loopback aliases are always allowed, everything else gets 403
provider / modelfollow host user settings (agentDefaultModel)spawned-agent model selection; configure as a pair — a partial setting is completed from the host default
reasoningEffortadapter defaultdefault reasoning effort (adapter-defined id, see reasoningEfforts in model_list)
allowModelOverridetruewhether callers (agent_run/task_inbox/select_model) may override the model; false pins it and refuses overrides explicitly
presetstandardagent preset to mount
defaultDetailsummarydefault detail level for agent_run/task_result (overridable per call via detail)
reattachOrphansfalsebulk-attach ungrouped sessions to workspaces at startup (writes user data; the attach_session tool remains available anytime)
maxQueue / taskTtlMs / maxAgents100 / 10 min / 8queue capacity, result TTL, session-pool LRU limit
taskTimeoutMs0 (off)auto-timeout per agent turn; on expiry the host's official fires and the result's notes the timeout. Raise it for long-task deployments

Every result is structured: sessionId / model / changes / verification / leftovers / error / toolCallCount … (projected by detail level); error carries non-normal turn endings (model failure / cancel / blocked), so a silent empty "success" can no longer happen. Empty error/taskId fields are omitted rather than sent as empty strings. Tool-level failures (unknown taskId, model override refused, service unavailable, cwd outside workspaceRoots, a turn that ended in error) come back as tool results with isError: true (the MCP-spec-recommended shape) — strict clients and models can recognize them without parsing the payload.

Zero host copies

The plugin has zero runtime dependencies on @deepseek-ai/*: every dsh capability is reached through injected host services (ctx.agents / ctx.tools / ctx.agentPresets …), types only augment the compiler via import type (erased at build), and @deepseek-ai/* packages are devDependencies. A plugin therefore can never drag a mismatched copy of a host package into the process — the root cause behind the upstream-era scopeOf symbol mismatch that silently left agents tool-less. Event reads go through the public session.snapshotEvents() API and message construction uses a local, field-for-field equivalent of the host's createUserMessage.

Security

⚠️ This plugin exposes local execution capability (equivalent to RCE). It binds 127.0.0.1 only by default. When enabling:

  1. Set authToken — against other local processes and DNS-rebinding attacks (constant-time compare);
  2. Set workspaceRoots — constrain where agents may work;
  3. Never bind 0.0.0.0 or expose to LAN/WAN without a reverse proxy + TLS + auth.

Built-in guards: a Host-header allowlist (bind host + loopback aliases by default, guarding against DNS rebinding; a missing Host header gets 400), an Origin-header check on the same allowlist (requests that carry a cross-origin or unparseable Origin — e.g. Origin: null — get 403; non-browser MCP clients that send no Origin are unaffected), a /mcp-only HTTP surface (everything else 404), 401 answers with a WWW-Authenticate: Bearer challenge, and idle transport sessions are reaped after sessionTtlMs (24 h default). Tools also carry spec-metadata (title, annotations.readOnlyHint etc., the 2025-06-18 protocol fields) so clients can label and sandbox-check them. Note: queuePersistPath writes task payloads (task text, caller context, results) to a plaintext file — point it at a location with appropriate filesystem permissions.

Web settings panel

The bundle also injects a settings section into the dsh web UI (Settings → dsh-carrot-on-a-stick):

  • live status badge (listening / soft-stopped / http disabled) and uptime;
  • the MCP endpoint (click to copy) and start / stop buttons — soft stop drains and closes cleanly, start re-listens (verified by tests with a real handshake after restart);
  • connected MCP clients: session id, user-agent, connected-at, last activity, request count;
  • model / preset / auth / session-TTL / queue-persistence summary and queue counters (active / done / failed / cancelled).

The panel talks to the host over same-origin routes under /_dsh/dsh-carrot-on-a-stick/*; mutating routes require same-origin markers, so no extra ports or CORS exposure are needed.

Provenance

The initial source of this project was copied from chushixixin/dsh-harness-mcp-server (MIT, thanks @chushixixin) and then evolved as an independent project — no git fork relationship, no upstream contributions planned. Key changes:

  • drops the Hermes-specific framing — targets any MCP client;
  • tracks current dsh releases (see Roadmap);
  • independent name and repository: dsh-carrot-on-a-stick.

Roadmap / known limitations (against dsh 0.1.5-rc.2)

The 0.2.0 compatibility issues were fixed in 0.3.0; 0.3.1 completed live-host E2E verification (all green — see docs/e2e-0.1.5-rc.2.zh.md) and fixed what it uncovered: the {{model}} prompt variable (model selection now completed via agentDefaultModel), turn-failure surfacing, pool-session flush, startup reattach off by default, and corrected install docs. 0.5.0 completed the model-selection surface: model_list (official catalog / llm fallback), per-call overrides on agent_run + task_inbox, select_model (in-session switch), reasoningEffort, the allowModelOverride gate, model reported in every result, and a session pool keyed by cwd + model. The current (unreleased) batch is a large consolidated update covering six areas, developed in internal milestones (see the CHANGELOG for the per-milestone detail):

  • Protocol conformance: tool errors carry isError: true, Origin-header check + WWW-Authenticate challenge join the DNS-rebinding guards, tools expose title + annotations, transport sessions are reaped (sessionTtlMs), 10 MB request-body cap;
  • Cancellation: agent_run honours MCP notifications/cancelled and client timeouts (both wired to the host's official agent.cancel);
  • Observability: notifications/progress heartbeats (spec _meta.progressToken), task_list, read-only session_list (with current model) and session_history (paginated);
  • Async queue: task_cancel, opt-in persistence (queuePersistPath, atomic writes), honest running/interrupted statuses;
  • Resilience: lock-table cleanup, LRU eviction skips busy sessions, corrupted persistence file tolerated at startup;
  • A full protocol audit is documented in docs/protocol-audit-2026-09-24.zh.md.

What remains:

  • The task queue lives in process memory; a restart loses it — opt-in persistence (queuePersistPath) added; without it, the queue is still memory-only.
  • No server-side timeout for agent_run / task_inbox — the opt-in taskTimeoutMs (off by default) fires the host's official cancel; callers can also cancel actively (notifications/cancelled for agent_run, task_cancel for queue tasks).
  • The queue cannot be listed or cancelled either — done (task_list / task_cancel).
  • Read-only query surface is still incomplete — session_list + session_history (live sessions) join attach_session / rename_session / select_model; reading the full log of persisted-only sessions needs a host-side load API.
  • preset remains deployment-level (one persona per MCP server instance); it cannot be chosen per call.
  • Tool calls inside spawned sessions go through the host approval policy (sensitive operations under ask may pop a dialog or fail closed; the read-only E2E operation was unaffected).
  • dsh_list_tools only lists the host-global registry; listing an agent's actually-visible tools needs a host-side API (the ScopeKey is a private symbol, unreachable under the zero-copy principle).
  • select_model requires the web profile's sessionController; where that service is absent (e.g. headless) only the catalog fallback and per-call overrides work, and the tool says so explicitly.
  • Protocol-native task augmentation (2025-11-25 draft, SDK marks the interfaces experimental): agent_run as a spec-native task with tasks/get / tasks/result polling. Deliberately deferred — our task_inbox / task_result / task_cancel already cover the workflow for all clients; revisit when the spec leaves draft.
  • When dsh releases new versions, the devDependencies need syncing (compile-time only; the zero-runtime-dependency design is unaffected).

Development

npm install
npm run build    # standalone build (plain tsc) -> lib/
npm run smoke    # fake-ctx smoke on ports 8099/8098/8096/8095/8094/8093/8092/8091/8089/8088/8087/8086/8085 (132 checks, real MCP protocol round-trips incl. official SDK client)
                 # + a port-conflict case (apply must fail loudly)

Live-host E2E (needs a local dsh with model credentials; costs a few tokens): boot a dedicated profile as described in docs/e2e-0.1.5-rc.2.zh.md, then run E2E_WITH_AGENT=1 node e2e.mjs.

License

MIT — see LICENSE (upstream copyright notice retained).

agent.cancel({kind:'hook'})
error
progressIntervalMs5000 (min 250)heartbeat interval for notifications/progress on agent_run — only active when the caller passes _meta.progressToken
queuePersistPath— (off)persist the task queue to this file: every change is written, and on startup done/error/cancelled tasks come back with their results, queued tasks re-execute, and running tasks are honestly marked interrupted by restart
sessionTtlMs86400000 (24 h)reap idle MCP transport sessions after this long; clients get 404 on the stale session id and re-initialize per the spec (0 = never reap)
@deepseek-ai/*