dsh-mcp-skills 0.2.1 → 0.2.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.
Files changed (2) hide show
  1. package/lib/index.js +57 -15
  2. package/package.json +5 -1
package/lib/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  // @deepseek-ai packages so out-of-tree resolution stays reliable (the modlens
8
8
  // precedent); `yaml` and `fflate` are runtime dependencies, and the MCP SDK is
9
9
  // an optional peer the host installation already provides (see loadMcpSdk).
10
+ import { existsSync } from 'node:fs'
10
11
  import { access, copyFile, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
11
12
  import { homedir } from 'node:os'
12
13
  import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
@@ -19,7 +20,7 @@ export const inject = []
19
20
 
20
21
  const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
21
22
  /** Kept in step with package.json (the smoke test reports it to the server). */
22
- const PLUGIN_VERSION = '0.2.1'
23
+ const PLUGIN_VERSION = '0.2.3'
23
24
  const MCP_CONFIG_FILENAME = 'mcp-servers.yaml'
24
25
  const MCP_PROFILE_ROW_PREFIX = 'mcp-'
25
26
  /** Same grammar as @deepseek-ai/dsh-mcp-client's SERVER_NAME_PATTERN. */
@@ -406,13 +407,32 @@ async function writeMcpServers(ctx, servers) {
406
407
  await syncMcpProfilePatch(ctx, servers)
407
408
  }
408
409
 
410
+ /** Executable directories a GUI-launched harness commonly lacks. */
411
+ const EXTRA_PATH_DIRS = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin', '/bin']
412
+
413
+ /**
414
+ * The environment one stdio server is spawned with.
415
+ *
416
+ * MCP servers are spawned by `@deepseek-ai/dsh-mcp-client` from the harness
417
+ * process environment, and a harness launched from a desktop icon inherits a
418
+ * minimal PATH (macOS gives just `/usr/bin:/bin:/usr/sbin:/sbin`). A configured
419
+ * `npx` / `uvx` then fails with ENOENT even though the user's shell resolves it,
420
+ * so the well-known executable directories are prepended when they exist.
421
+ * An explicit `PATH` in the server config still wins.
422
+ */
423
+ function serverEnv(env) {
424
+ const current = (process.env.PATH ?? '').split(':').filter((entry) => entry !== '')
425
+ const missing = EXTRA_PATH_DIRS.filter((dir) => existsSync(dir) && !current.includes(dir))
426
+ return { ...(missing.length > 0 ? { PATH: [...missing, ...current].join(':') } : {}), ...env }
427
+ }
428
+
409
429
  /**
410
430
  * The transport-shaped fields of one managed server, shared by the generated
411
431
  * patch row and the connection smoke test so both always agree.
412
432
  */
413
433
  function serverConnection(server) {
414
434
  return server.transport === 'stdio'
415
- ? { command: server.command, args: server.args ?? [], env: server.env ?? {}, cwd: server.cwd || process.cwd() }
435
+ ? { command: server.command, args: server.args ?? [], env: serverEnv(server.env ?? {}), cwd: server.cwd || process.cwd() }
416
436
  : { url: server.url, headers: server.headers ?? {} }
417
437
  }
418
438
 
@@ -520,28 +540,50 @@ function scrubbedParentEnv() {
520
540
  * The MCP SDK is an optional peer dependency the host installation provides,
521
541
  * loaded on demand so a missing copy disables only the connection smoke test
522
542
  * instead of breaking the whole plugin at import time.
543
+ *
544
+ * The package was renamed across host releases, so both spellings are tried:
545
+ * `@modelcontextprotocol/client` (current) and `@modelcontextprotocol/sdk`
546
+ * (older). Both expose the same Client/transport API.
523
547
  */
524
548
  let mcpSdkPromise
525
549
  function loadMcpSdk() {
526
550
  if (mcpSdkPromise === undefined) {
527
- mcpSdkPromise = Promise.all([
528
- import('@modelcontextprotocol/sdk/client/index.js'),
529
- import('@modelcontextprotocol/sdk/client/stdio.js'),
530
- import('@modelcontextprotocol/sdk/client/streamableHttp.js'),
531
- ])
532
- .then(([index, stdio, streamableHttp]) => ({
533
- Client: index.Client,
534
- StdioClientTransport: stdio.StdioClientTransport,
535
- StreamableHTTPClientTransport: streamableHttp.StreamableHTTPClientTransport,
536
- }))
537
- .catch((error) => {
551
+ mcpSdkPromise = loadCurrentMcpSdk().catch((currentError) =>
552
+ loadLegacyMcpSdk().catch((legacyError) => {
538
553
  mcpSdkPromise = undefined // let a later call retry after a transient failure
539
- throw new ManageError('mcp-sdk-unavailable', `no MCP SDK in this installation: ${error instanceof Error ? error.message : String(error)}`)
540
- })
554
+ const reason = legacyError instanceof Error ? legacyError.message : String(legacyError)
555
+ const current = currentError instanceof Error ? currentError.message : String(currentError)
556
+ throw new ManageError('mcp-sdk-unavailable', `no MCP SDK in this installation (tried @modelcontextprotocol/client: ${current}; @modelcontextprotocol/sdk: ${reason})`)
557
+ }),
558
+ )
541
559
  }
542
560
  return mcpSdkPromise
543
561
  }
544
562
 
563
+ /** The current `@modelcontextprotocol/client` spelling. */
564
+ async function loadCurrentMcpSdk() {
565
+ const [main, stdio] = await Promise.all([import('@modelcontextprotocol/client'), import('@modelcontextprotocol/client/stdio')])
566
+ return {
567
+ Client: main.Client,
568
+ StdioClientTransport: stdio.StdioClientTransport,
569
+ StreamableHTTPClientTransport: main.StreamableHTTPClientTransport,
570
+ }
571
+ }
572
+
573
+ /** The older `@modelcontextprotocol/sdk` spelling. */
574
+ async function loadLegacyMcpSdk() {
575
+ const [index, stdio, streamableHttp] = await Promise.all([
576
+ import('@modelcontextprotocol/sdk/client/index.js'),
577
+ import('@modelcontextprotocol/sdk/client/stdio.js'),
578
+ import('@modelcontextprotocol/sdk/client/streamableHttp.js'),
579
+ ])
580
+ return {
581
+ Client: index.Client,
582
+ StdioClientTransport: stdio.StdioClientTransport,
583
+ StreamableHTTPClientTransport: streamableHttp.StreamableHTTPClientTransport,
584
+ }
585
+ }
586
+
545
587
  /** Connect to a server and list its tools (connection smoke test). */
546
588
  async function testMcpConnection(server) {
547
589
  const { Client, StdioClientTransport, StreamableHTTPClientTransport } = await loadMcpSdk()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-skills",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "DeepSeek Harness web plugin: settings pages for managing user skills and MCP servers",
5
5
  "type": "module",
6
6
  "repository": {
@@ -41,9 +41,13 @@
41
41
  "yaml": "^2.4.0"
42
42
  },
43
43
  "peerDependencies": {
44
+ "@modelcontextprotocol/client": "^2.0.0",
44
45
  "@modelcontextprotocol/sdk": "^1.30.0"
45
46
  },
46
47
  "peerDependenciesMeta": {
48
+ "@modelcontextprotocol/client": {
49
+ "optional": true
50
+ },
47
51
  "@modelcontextprotocol/sdk": {
48
52
  "optional": true
49
53
  }