diff --git a/.gitignore b/.gitignore
index e3d220f9d6..b825021816 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,3 +79,7 @@ docs/superpowers/
# Reference DLLs fetched by tools/compile-check.sh (not redistributable)
.compile-refs/
+.compile-out/
+
+# Local-only scratch files marked by name
+*.DO-NOT-COMMIT.*
diff --git a/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs
new file mode 100644
index 0000000000..f03161e823
--- /dev/null
+++ b/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MCPForUnity.Editor.Constants;
+using MCPForUnity.Editor.Helpers;
+using MCPForUnity.Editor.Models;
+using MCPForUnity.Editor.Services;
+using UnityEditor;
+
+namespace MCPForUnity.Editor.Clients.Configurators
+{
+ ///
+ /// DeepSeek Harness (dsh) has no client config file. MCP servers are mounted
+ /// through its plugin manager as a configuration-only bundle (a manifest plus a
+ /// cordis.patch.yml row for @deepseek-ai/dsh-mcp-client), so setup is
+ /// manual like the other UI-driven clients: the snippet below holds the two bundle
+ /// files and the dsh plugin add command that installs them. After a DSH
+ /// reload the tools surface as mcp__unityMCP__<tool>. From there DSH's
+ /// own plugin manager owns enable/disable and removal.
+ ///
+ public class DeepSeekHarnessConfigurator : McpClientConfiguratorBase
+ {
+ public const string ClientName = "DeepSeek Harness";
+
+ private const string ServerName = "unityMCP";
+ private const string BundleName = "@local/unity-mcp-dsh";
+
+ public DeepSeekHarnessConfigurator() : base(new McpClient
+ {
+ name = ClientName,
+ windowsConfigPath = BundlePatchPath(),
+ macConfigPath = BundlePatchPath(),
+ linuxConfigPath = BundlePatchPath()
+ })
+ { }
+
+ public override bool SupportsAutoConfigure => false;
+
+ /// DSH is installed once it has run at least once (its home directory exists).
+ public override bool IsInstalled => Directory.Exists(DshHome());
+
+ public override string GetConfigPath() => BundlePatchPath();
+
+ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
+ {
+ client.SetStatus(McpStatus.NotConfigured, "DeepSeek Harness setup is manual; DSH's plugin manager owns the bundle.");
+ return client.status;
+ }
+
+ public override void Configure()
+ {
+ throw new InvalidOperationException(
+ "DeepSeek Harness setup is manual. Use the Manual Configuration snippet below: " +
+ "it holds the two bundle files and the 'dsh plugin add' command to run.");
+ }
+
+ public override IList GetInstallationSteps() => new List
+ {
+ "Install DeepSeek Harness and run any 'dsh' command once (creates ~/.dsh)",
+ "Create the two files shown in the Manual Configuration snippet below",
+ "Quit DeepSeek Harness, run the 'dsh plugin add' command from the snippet, then reopen it",
+ "Restart DeepSeek Harness; Unity tools appear as mcp__unityMCP__",
+ "DSH's plugin manager can enable/disable or remove the bundle afterwards"
+ };
+
+ public override string GetManualSnippet()
+ {
+ try
+ {
+ string bundleDir = BundleDir();
+ string profile = ProfileName();
+ var sb = new StringBuilder();
+ sb.AppendLine("# DeepSeek Harness setup. $DSH_HOME defaults to ~/.dsh.");
+ sb.AppendLine("# 'desktop' is the Desktop app's own profile: quit DeepSeek Harness, run the");
+ sb.AppendLine("# commands below, then reopen it. With a standalone dsh (npm install), pick");
+ sb.AppendLine("# another profile name instead; 'desktop' is reserved for the Desktop app.");
+ sb.AppendLine();
+ sb.AppendLine("# --- " + bundleDir + "/package.json ---");
+ sb.AppendLine("{");
+ sb.AppendLine(" \"name\": \"@local/unity-mcp-dsh\",");
+ sb.AppendLine(" \"version\": \"1.0.0\",");
+ sb.AppendLine(" \"private\": true,");
+ sb.AppendLine(" \"type\": \"module\",");
+ sb.AppendLine(" \"dsh\": { \"bundle\": { \"patch\": \"./cordis.patch.yml\" } }");
+ sb.AppendLine("}");
+ sb.AppendLine();
+ sb.AppendLine("# --- " + bundleDir + "/cordis.patch.yml ---");
+ sb.Append(Row());
+ sb.AppendLine();
+ sb.AppendLine("# Install:");
+ sb.AppendLine("# dsh plugin --profile " + ShellQuote(profile) + " add " + ShellQuote(bundleDir));
+ sb.AppendLine("# Remove:");
+ sb.AppendLine("# dsh plugin --profile " + ShellQuote(profile) + " remove " + ShellQuote(BundleName));
+ return sb.ToString();
+ }
+ catch (Exception ex)
+ {
+ return "# error: " + ex.Message;
+ }
+ }
+
+ /// Renders the patch row for the transport currently selected in Unity.
+ private static string Row()
+ {
+ bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport;
+ var sb = new StringBuilder();
+ sb.AppendLine("# >>> MCP for Unity (unityMCP) managed block >>>");
+ sb.AppendLine("- insert:");
+ sb.AppendLine(" - id: unitymcp");
+ sb.AppendLine(" name: '@deepseek-ai/dsh-mcp-client'");
+ sb.AppendLine(" config:");
+ sb.AppendLine(" serverName: " + ServerName);
+
+ if (useHttp)
+ {
+ sb.AppendLine(" transport: streamable-http");
+ sb.AppendLine(" url: " + YamlQuote(HttpEndpointUtility.GetMcpRpcUrl()));
+ if (HttpEndpointUtility.IsRemoteScope())
+ {
+ string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);
+ if (!string.IsNullOrEmpty(apiKey))
+ {
+ sb.AppendLine(" headers:");
+ sb.AppendLine(" " + AuthConstants.ApiKeyHeader + ": " + YamlQuote(apiKey));
+ }
+ }
+ }
+ else
+ {
+ var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
+ if (string.IsNullOrWhiteSpace(uvxPath))
+ {
+ throw new InvalidOperationException("uvx not found. Install uv/uvx or set the override in Advanced Settings.");
+ }
+
+ sb.AppendLine(" transport: stdio");
+ sb.AppendLine(" command: " + YamlQuote(uvxPath));
+ sb.AppendLine(" args:");
+ foreach (string flag in AssetPathUtility.GetUvxDevFlagsList())
+ {
+ sb.AppendLine(" - " + YamlQuote(flag));
+ }
+ foreach (string arg in AssetPathUtility.GetBetaServerFromArgsList())
+ {
+ sb.AppendLine(" - " + YamlQuote(arg));
+ }
+ sb.AppendLine(" - " + YamlQuote(packageName));
+ sb.AppendLine(" - " + YamlQuote("--transport"));
+ sb.AppendLine(" - " + YamlQuote("stdio"));
+ if (MCPServiceLocator.Platform.IsWindows())
+ {
+ sb.AppendLine(" env:");
+ sb.AppendLine(" SystemRoot: " + YamlQuote(MCPServiceLocator.Platform.GetSystemRoot()));
+ }
+ }
+
+ sb.AppendLine(" failOnStartupError: true");
+ sb.AppendLine("# <<< MCP for Unity (unityMCP) <<<");
+ return sb.ToString();
+ }
+
+ /// DSH home: $DSH_HOME when set, else ~/.dsh.
+ private static string DshHome()
+ {
+ string envHome = Environment.GetEnvironmentVariable("DSH_HOME");
+ if (!string.IsNullOrEmpty(envHome)) return envHome;
+
+ string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ return string.IsNullOrEmpty(home) ? string.Empty : Path.Combine(home, ".dsh");
+ }
+
+ /// Target profile: DSH_PROFILE when set, else desktop.
+ private static string ProfileName()
+ {
+ string envProfile = Environment.GetEnvironmentVariable("DSH_PROFILE");
+ return string.IsNullOrEmpty(envProfile) ? "desktop" : envProfile;
+ }
+
+ private static string BundleDir() => Path.Combine(DshHome(), "bundles", ProfileName(), "unity-mcp");
+
+ private static string BundlePatchPath() => Path.Combine(BundleDir(), "cordis.patch.yml");
+
+ /// Single-quotes a YAML scalar; embedded single quotes are doubled.
+ private static string YamlQuote(string value) => "'" + (value ?? string.Empty).Replace("'", "''") + "'";
+
+ /// Double-quotes a shell argument so cmd.exe metacharacters stay literal.
+ private static string ShellQuote(string value) => "\"" + (value ?? string.Empty).Replace("\"", "\\\"") + "\"";
+ }
+}
diff --git a/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs.meta b/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs.meta
new file mode 100644
index 0000000000..2e9188f8d9
--- /dev/null
+++ b/MCPForUnity/Editor/Clients/Configurators/DeepSeekHarnessConfigurator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2f9d6c1e8a3b4d7f9e5a1c2b3d4e5f60
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/website/docs/getting-started/clients.md b/website/docs/getting-started/clients.md
index 245d17a25d..83a34f9b98 100644
--- a/website/docs/getting-started/clients.md
+++ b/website/docs/getting-started/clients.md
@@ -27,13 +27,14 @@ MCP for Unity auto-configures every client the package detects on your machine.
| **OpenClaw** | HTTP / stdio | yes | yes | yes | Requires `openclaw-mcp-bridge` plugin enabled. Follows MCP for Unity's transport choice. |
| **Antigravity** | HTTP | yes | yes | varies | Requires an MCP toggle in Antigravity settings. |
| **Pi** | stdio / HTTP | yes | yes | yes | Needs an MCP extension (Pi ships no MCP client of its own): `pi install npm:pi-mcp-adapter`. Writes the shared `~/.config/mcp/mcp.json`. |
+| **DeepSeek Harness** | HTTP / stdio | no (manual Configure) | yes | yes | Manual setup: the snippet holds a configuration-only DSH bundle (`$DSH_HOME/bundles//unity-mcp/`) installed with `dsh plugin add`. Tools appear as `mcp__unityMCP__`. |
## How to pick
- **You want it to just work**: Claude Desktop. Stdio means no port conflicts and no firewall prompts.
- **You're building a multi-agent or remote workflow**: anything HTTP. Multiple clients can share one Python server; see [Multi-Instance Routing](/guides/multi-instance).
- **You're already in your IDE**: Cursor, VS Code Copilot, or Cline — keeps you in flow.
-- **You want a terminal**: Claude Code, Copilot CLI, Codex, Gemini CLI, Qwen Code, or Pi.
+- **You want a terminal**: Claude Code, Copilot CLI, Codex, Gemini CLI, Qwen Code, Pi, or DeepSeek Harness.
## Manual configuration
diff --git a/website/docs/guides/client-configurators.md b/website/docs/guides/client-configurators.md
index 8cbdf101de..f981d45544 100644
--- a/website/docs/guides/client-configurators.md
+++ b/website/docs/guides/client-configurators.md
@@ -173,6 +173,24 @@ Some clients cannot be handled by the generic JSON configurator alone.
- The bridge exposes a single proxy tool such as `unityMCP__call`, which then forwards to Unity MCP tool names.
- OpenClaw support follows the currently selected MCP for Unity transport (via `openclaw-mcp-bridge`).
+### DeepSeek Harness (native DSH bundle)
+
+- Uses a custom configurator (`DeepSeekHarnessConfigurator`) because DeepSeek Harness (`dsh`) consumes MCP servers through its `@deepseek-ai/dsh-mcp-client` plugin, registered as a Cordis row — not a JSON/TOML config file.
+- Setup is manual, like the other UI-driven clients. The snippet renders a configuration-only bundle (`$DSH_HOME/bundles//unity-mcp/` with a `package.json` manifest and a `cordis.patch.yml` row) plus the `dsh plugin add` / `dsh plugin remove` commands. Run them with DSH closed (its `desktop` profile is Electron-managed) using the Desktop-installed `dsh`, then reopen it. After a DSH reload the tools surface as `mcp__unityMCP__`.
+- The row shape (confirmed against DSH's `mcp-memory` guide and plugin-manager patch schema):
+ ```yaml
+ - insert:
+ - id: unitymcp
+ name: '@deepseek-ai/dsh-mcp-client'
+ config:
+ serverName: unityMCP
+ transport: streamable-http # or stdio with command/args
+ url: http://127.0.0.1:/mcp
+ failOnStartupError: true
+ ```
+- Installing as a bundle (instead of editing `$DSH_HOME/cordis.patch.yml`, a global layer that collides with rows from other sources) keeps the row profile-scoped, and DSH's own plugin manager owns enable/disable and removal afterwards.
+- Detection is fail-closed: DSH counts as installed only once it has run at least once (its home directory exists).
+
---
## Adding a new MCP client (typical JSON case)