dsh-baton-mcp 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/host/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-baton-host",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "DeepSeek Harness plugin for dsh-baton: adding it is the whole install. Switched on, it registers the dsh-baton MCP server for Claude Code and Codex, then takes the baton over a local, owner-only control socket, runs it in a specific session through the host's own session controller, and hands the result back when the turn is done.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -20,7 +20,7 @@
20
20
  "LICENSE"
21
21
  ],
22
22
  "dependencies": {
23
- "dsh-baton-mcp": "0.5.2"
23
+ "dsh-baton-mcp": "0.5.3"
24
24
  },
25
25
  "license": "MIT",
26
26
  "dsh": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-baton-mcp",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "mcpName": "io.github.JialaoLiu/dsh-baton",
5
5
  "description": "Pass the baton from Claude Code, Codex or any MCP client to a local DeepSeek Harness: an MCP server that hands off tasks, continues a specific session, shows the run live in Harness and hands the result back when it is done.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/JialaoLiu/dsh-baton",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.5.2",
10
+ "version": "0.5.3",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "dsh-baton-mcp",
15
- "version": "0.5.2",
15
+ "version": "0.5.3",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
package/src/register.mjs CHANGED
@@ -32,7 +32,8 @@ export function editClaudeConfig(text, entry) {
32
32
 
33
33
  export function readClaudeEntry(text) {
34
34
  const e = text.trim() ? JSON.parse(text).mcpServers?.[NAME] : null
35
- return e?.command ? { command: e.command, args: e.args ?? [] } : null
35
+ if (!e?.command) return null
36
+ return { command: e.command, args: e.args ?? [], ...(e.env && Object.keys(e.env).length ? { env: e.env } : {}) }
36
37
  }
37
38
 
38
39
  const tomlString = (s) => (/['\n\r]/.test(s) ? JSON.stringify(s) : `'${s}'`)
@@ -61,22 +62,29 @@ export function editCodexConfig(text, entry) {
61
62
  return out ? out + '\n' : ''
62
63
  }
63
64
 
64
- // The command and args of [mcp_servers.dsh-baton], as setup writes them (single-line strings).
65
+ // The command, args and env of [mcp_servers.dsh-baton] (and its .env table), as setup
66
+ // writes them (single-line strings).
65
67
  export function readCodexEntry(text) {
66
68
  const str = String.raw`'[^'\n]*'|"(?:[^"\\\n]|\\.)*"`
67
69
  const unquote = (s) => (s.startsWith("'") ? s.slice(1, -1) : JSON.parse(s))
68
- let inTable = false
70
+ let table = null
69
71
  let command = null, args = []
72
+ const env = {}
70
73
  for (const line of text.split(/\r?\n/)) {
71
- if (ANY_TABLE.test(line)) inTable = /^\s*\[\s*mcp_servers\s*\.\s*(["']?)dsh-baton\1\s*\]/.test(line)
72
- else if (inTable) {
74
+ if (ANY_TABLE.test(line)) {
75
+ const m = line.match(/^\s*\[\s*mcp_servers\s*\.\s*(["']?)dsh-baton\1\s*(\.\s*env\s*)?\]/)
76
+ table = m ? (m[2] ? 'env' : 'main') : null
77
+ } else if (table === 'main') {
73
78
  const c = line.match(new RegExp(`^\\s*command\\s*=\\s*(${str})`))
74
79
  if (c) command = unquote(c[1])
75
80
  const a = line.match(/^\s*args\s*=\s*\[(.*)\]/)
76
81
  if (a) args = [...a[1].matchAll(new RegExp(str, 'g'))].map((m) => unquote(m[0]))
82
+ } else if (table === 'env') {
83
+ const kv = line.match(new RegExp(`^\\s*([\\w-]+)\\s*=\\s*(${str})`))
84
+ if (kv) env[kv[1]] = unquote(kv[2])
77
85
  }
78
86
  }
79
- return command ? { command, args } : null
87
+ return command ? { command, args, ...(Object.keys(env).length ? { env } : {}) } : null
80
88
  }
81
89
 
82
90
  // Write only when the content changes, keep one backup, and replace the file atomically.
@@ -124,10 +132,10 @@ export function nodeVersionOk(version) {
124
132
  return major > MIN_NODE[0] || (major === MIN_NODE[0] && minor >= MIN_NODE[1])
125
133
  }
126
134
 
127
- // A Node.js for the MCP server when the caller is not one itself: the Harness plugin runs
128
- // on the app's own binary, whose path changes with every Harness update. So: the first
129
- // `node` on PATH, then the usual install folders, never one inside the Harness app, and
130
- // only a new enough one.
135
+ // The user's own Node.js for the MCP server, preferred over Harness's runtime (see
136
+ // harnessRuntime): the first `node` on PATH, then the usual install folders, never the
137
+ // launcher scripts inside the Harness app (they only work from Harness itself), and only
138
+ // a new enough one.
131
139
  export function findNode({ env = process.env, platform = process.platform, exclude = [], version = nodeVersion } = {}) {
132
140
  const exe = platform === 'win32' ? 'node.exe' : 'node'
133
141
  const p = platform === 'win32' ? path.win32 : path.posix
@@ -212,7 +220,17 @@ export function autoRegister({ root, home = os.homedir(), env = process.env, nod
212
220
  serverRoot = app
213
221
  }
214
222
  const script = path.join(serverRoot, 'src', 'server.mjs')
215
- const works = (e) => e && fs.existsSync(e.command) && e.args?.[0] && fs.existsSync(e.args[0])
223
+ // Whether an entry can start a server. A bare command (`npx -y dsh-baton-mcp`, as MCP
224
+ // directories suggest) is the user's own choice and resolved on the client's PATH: kept.
225
+ // Harness's own binary only works in Node mode.
226
+ const bare = (cmd) => !/[\\/]/.test(cmd)
227
+ const works = (e) => {
228
+ if (!e?.command) return false
229
+ // `node /old/path/server.mjs` with that file gone is still broken.
230
+ if (bare(e.command)) return !e.args?.[0] || bare(e.args[0]) || fs.existsSync(e.args[0])
231
+ if (!fs.existsSync(e.command) || !e.args?.[0] || !fs.existsSync(e.args[0])) return false
232
+ return !fallback || path.resolve(e.command) !== path.resolve(fallback.command) || e.env?.ELECTRON_RUN_AS_NODE === '1'
233
+ }
216
234
  const list = clients(env, home)
217
235
  let launch = node ? { command: node } : null
218
236
  for (const c of list) {
@@ -221,7 +239,7 @@ export function autoRegister({ root, home = os.homedir(), env = process.env, nod
221
239
  const text = fs.existsSync(c.file) ? fs.readFileSync(c.file, 'utf8') : ''
222
240
  const current = c.read(text)
223
241
  if (works(current)) {
224
- const same = path.resolve(current.args[0]) === path.resolve(script)
242
+ const same = !bare(current.command) && path.resolve(current.args[0]) === path.resolve(script)
225
243
  result.clients.push({ id: c.id, label: c.label, state: same ? 'unchanged' : 'kept', script: current.args[0] })
226
244
  continue
227
245
  }