Drive the Studio with DeepSeek Harness (preview)
Guide · Core Studio 3.0 · Verified 2026-09-01
Status: proven on 2026-08-31 (DS-2 legs, evidence in evidence/). A
DeepSeek Harness (dsh) agent drives Core Studio’s metadata
surface through the Studio’s MCP server — reads flow standalone, and every
write passes through the Studio’s human approval gate, never dsh’s.
Every behavioral claim below is backed by a committed evidence leg (
evidence/leg1-wiring…evidence/leg4-audit). Where a leg could not prove something, the text says so.
1. What you get
- A dsh agent (web UI or headless) that can find, list, search, and read D365 F&O metadata in the Studio’s live local index, plus xref queries (references, closure, stats) — 12 read tools.
- Four gated write tools (
metadata_write,edit_apply,dotnet_build,dotnet_add_fo_reference) that pause on a human approval card in the running Studio desktop. No approval, no write — structurally. - A tamper-evident audit log of every call the agent makes.
2. Prerequisites
| Requirement | Detail |
|---|---|
| Core Studio | repo build or installed; the MCP server ships at packages/mcp-server/dist/bin.js (installed product: resources\mcp-server\run.cmd) |
| Pairing config | generated by the desktop (Settings → Connect AI agents) → %APPDATA%\xpp-studio\mcp-server.json. The read tools need the index built (open the AOT root in the desktop once) |
| Node.js ≥ 22.13 | for the MCP server (node:sqlite) |
| dsh | npx @deepseek-ai/dsh web — proven with 0.1.1-rc.2 |
| dsh MCP client | @deepseek-ai/[email protected] — first-party, opt-in |
| A provider key | any OpenAI-compatible endpoint dsh supports; the key lives in an environment variable, never in a config file (see §6) |
3. The exact wiring (proven)
dsh consumes external MCP servers natively — no adapter, no shim. The bridge is a first-party plugin; the mount is one Cordis patch row.
-
Install the plugin into your profile (once per profile):
Terminal window -
Add the mount to
$DSH_HOME/cordis.patch.yml(home level → every profile) or to a single profile’sprofiles/<name>/cordis.patch.yml:- insert:- id: mcp-studioname: '@deepseek-ai/dsh-mcp-client'config:serverName: studio # tools appear as mcp__studio__<tool>transport: stdiocommand: C:\Program Files\nodejs\node.exeargs:- C:\xpp-studio\packages\mcp-server\dist\bin.js # or the installed run.cmd- '--config'- C:\Users\<you>\AppData\Roaming\xpp-studio\mcp-server.jsontoolCallTimeoutMs: 120000 # the approval card can pause a call; give the human roomfailOnStartupError: true -
Configure the model provider in
$DSH_HOME/settings.yaml— the key is an env var reference, never a literal:llm-pi-ai:providers:my-gateway:apiKeyEnv: MY_PROVIDER_KEY # env var NAME onlyapi: openai-completionsbaseURL: https://<your-openai-compatible-endpoint>/v1compat: # documented pi-ai switches for gatewayssupportsDeveloperRole: false # that reject the developer role andmaxTokensField: max_tokens # max_completion_tokensmodels:- id: <your-model>agent-default-model:provider: my-gatewaymodel: <your-model> -
Drive it:
dsh web(browser UI) ordsh --profile headless "list the D365 models via mcp__studio__metadata_list_models".
Verified first contact (headless, one tool round):
{"count":3,"models":[{"name":"(root)"},{"name":"ApplicationFoundation"},{"name":"ApplicationSuite"}]}4. The safety model — OUR gate, not theirs
dsh has its own permission layer (presets, an approval policy, a repeat-call guard). None of it is load-bearing for your metadata. The enforcement that matters lives in the Studio’s MCP server and desktop:
| Layer | What it does | Proven by |
|---|---|---|
| Brokered approval gate | Write tools block on a human card in the running desktop (diff preview, 10-minute walk-away bound resolving as denial). There is no parameter, flag, or config that bypasses it from the agent side | leg 3b (denied → zero writes), leg 3c (granted → lands, audit ok) |
| Desktop-down refusal | Desktop not running → immediate typed refusal, reads keep working | leg 3a (write_requires_desktop) |
| Scope confinement | Paths must stay inside the pinned AOT root; .. segments and outside-root absolute paths are refused before any disk access | leg 3d (3/3 refused) |
| Rate limit | Per-connection token bucket (60 burst, 1/s refill); exhaustion is a typed, retryable result | leg 3e (12 throttled of a 70-frame flood, exact text recorded) |
| Tamper-evident audit | Append-only hash-chained JSONL (prevHash/hash, fixed genesis); every tools/call recorded with verdict class; argument VALUES never logged (SHA-256 + byte size only) | leg 4 (full log verifies clean; edit/delete/reorder all detected) |
The write flow the agent experiences:
tools/call metadata_write …→ the call pauses while the desktop shows the approval card (that is whytoolCallTimeoutMsshould be generous).- Human approves → the write lands via the never-drop writer + reindex;
audit verdict
ok. - Human denies (or walks away) →
isError: true, textwrite denied; the workspace is untouched. - Desktop down →
isError: true, textwrite_requires_desktop: metadata_write is brokered to CORE Enterprise Studio's approval gate — start Core Studio with this AOT root open and retry. Read tools work standalone.
Typed refusal inventory (verbatim, from the legs)
| Refusal | Exact text (verbatim) |
|---|---|
| Desktop down | write_requires_desktop: metadata_write is brokered to Core Studio's approval gate — start Core Studio with this AOT root open and retry. Read tools work standalone. |
| Denied at the gate | write denied |
| Outside-root absolute path | metadata_read failed: path outside the AOT root: C:/Windows/System32/drivers/etc/hosts (and the same for C:/AOSService/…) |
| Traversal segment | metadata_read failed: invalid path: '..' is not allowed |
| Rate limited | rate_limited: xref_stats refused — this MCP connection exhausted its call budget; retry shortly (the bucket refills continuously) |
| Missing parent dir (granted write) | metadata_write failed: ENOENT: no such file or directory, open '…\Ds2Scratch\AxClass\Ds2GateProbe.xml' — the writer requires a pre-existing model/module folder; nothing partial is written |
5. What the legs proved, precisely
| Leg | Result | Evidence |
|---|---|---|
| 1 Wiring | dsh consumes external MCP natively via dsh-mcp-client; first-contact read succeeded on the first attempt | evidence/leg1-wiring/ |
| 2 Read funnel (desktop UP) | 7 calls — list models, find elements, list elements, read, FTS source search, xref references, xref stats — every oracle matched the known sample estate (3 models; SampleClass→AxClass at its file; 5 ApplicationSuite elements by name; 25 xref edges for SalesFormLetter; schema v5 stats) | evidence/leg2-reads/ |
| 3a Unapproved write, desktop DOWN | typed write_requires_desktop; audit verdict recorded | evidence/leg3-gate/3a-desktop-down/ |
| 3b Write, approval DENIED (desktop UP) | real card answered “no” through the desktop’s own consent IPC; write denied; target path never created; audit write_denied | evidence/leg3-gate/3b-desktop-up-denied/ |
| 3c Write, approval GRANTED (desktop UP) | landed; read-back verified the exact element (type/name/methods); sha256 recorded; audit ok. (First attempt also proved the missing-folder ENOENT above.) | evidence/leg3-gate/3c2-desktop-up-granted-lands/ |
| 3d Scope escape ×3 | all refused structurally; audit tool_error ×3 | evidence/leg3-gate/3d-scope-escape/ |
| 3e Rate limit | agent-paced burst: dsh’s own repeat-call guard capped the agent at 20 identical calls — below the Studio’s 60-burst bucket, 0 throttled (recorded verbatim; the harness guard fires first at agent pace). Protocol-level flood on one raw connection: 58 ok + 12 throttled, typed text above | evidence/leg3-gate/3e-rate-limit-*/ |
| 4 Audit trail | the session’s 43 calls all recorded with verdict classes (ok 34, tool_error 5, rate_limited 2, write_requires_desktop 1, write_denied 1); the server’s own verifyAuditChain verifies the full log clean; mid-log edit, deletion, and reorder each detected with precise line numbers | evidence/leg4-audit/ |
The gate held in 100% of the hostile cases. No write ever landed unapproved.
6. Credential hygiene (do it this way)
- The provider key is an environment variable for the session only
(
apiKeyEnvnames it; the value never enters a config file, a log, or this repo). The DS-2 evidence redacts to fingerprint form (sk-kimi…). - The dsh stdio bridge scrubs credential-looking ambient variables before spawning the MCP server; the Studio server needs none.
- The pairing config contains a machine-local pairing token — treat the file as private, never transmit it.
7. Honest limits
- dsh is a developer preview. DeepSeek states compatibility-breaking
changes are expected; the wiring above is pinned to
dsh 0.1.1-rc.2+dsh-mcp-client 0.1.1-rc.2. Re-verify after upgrades (theevidence/harness/run-leg.mjsrunner re-runs any leg in one command). - Approval is human-paced. A write call blocks until the card is
answered (10-minute desktop walk-away bound → denial). Set
toolCallTimeoutMsaccordingly; unattended “bulk write” agent runs are not a supported shape. - Writes need an existing folder.
metadata_writedoes not create model/module directories — target an existing model folder (or create the structure through the desktop first). - The audit log is bounded, not complete under flood. Verdict records are batched through a capped queue; a burst faster than the disk is counted-dropped by design (availability outranks log completeness — the drop count makes gaps visible). At agent pace every call is recorded.
- Rate limiting is per connection. Each dsh plugin instance (and each raw client) gets its own bucket.
- Reads are only as fresh as the index. The read tools serve the desktop-built index of the pinned AOT root; switching roots requires regenerating the pairing config from the desktop.
- One dsh-side behavior to know: its repeat-call guard stops identical rapid-fire tool bursts (~20) before the Studio’s bucket is ever reached — observed, recorded, and harmless to the safety story (both layers are fail-closed).
- The MCP surface is metadata + xref + scaffold/compile only: no deploy, debug, test-run, OData, or environment-management tools — by design.
8. Reproducing the legs
All evidence tooling lives in evidence/harness/ (labeled evidence tooling,
not product code): run-leg.mjs (one bounded headless dsh leg + session-log
extraction with a fail-closed credential scrub), boot-desktop.mjs (dev
desktop on the pairing root with CDP), approval-driver.mjs (answers the
real approval card through the desktop’s own consent IPC),
rate-flood.mjs (protocol-level throttle proof), audit-tamper.mjs
(chain verification + tamper battery).