dsh-agent-plugins-market 0.8.3 → 0.9.0

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.
@@ -24,6 +24,7 @@ import { DEFAULT_STARTUP_TIMEOUT_MS, DEFAULT_TOOL_CALL_TIMEOUT_MS } from './mcp-
24
24
  import { resolveCwd } from '../../catalog/validate.js';
25
25
  import { qualifiedSuiteId, suiteDataDir } from '../../catalog/paths.js';
26
26
  import { applyOverride } from './mcp-overrides.js';
27
+ import { isUserMcpSuite } from './mcp-direct-config.js';
27
28
  import { PLUGIN_ROOT_VARIABLES, PLUGIN_DATA_VARIABLES } from '../../model/layouts.js';
28
29
  /** The max length the bridge accepts for a serverName. */
29
30
  const SERVER_NAME_MAX = 32;
@@ -236,7 +237,7 @@ async function toResolvedMount(suite, serverKey, server, pluginDataRoot, resolve
236
237
  return { failure: missingFailure(serverKey, missing) };
237
238
  const sseConfig = {
238
239
  transport: 'sse',
239
- serverName: deriveServerName(suite.id, serverKey),
240
+ serverName: deriveServerName(suite, serverKey),
240
241
  url: url.value,
241
242
  headers: headers.values,
242
243
  ...(server.auth === undefined ? {} : { auth: mapAuth(server.auth) }),
@@ -246,7 +247,7 @@ async function toResolvedMount(suite, serverKey, server, pluginDataRoot, resolve
246
247
  return { request: { suiteId: qualifiedSuiteId(suite.sourceId, suite.id), serverKey, config: sseConfig } };
247
248
  }
248
249
  const expand = expander(suite, dataDir, resolver, portable);
249
- const serverName = deriveServerName(suite.id, serverKey);
250
+ const serverName = deriveServerName(suite, serverKey);
250
251
  if (server.type === 'stdio') {
251
252
  const args = await expand.all(server.args ?? []);
252
253
  const env = await expand.map(server.env ?? {});
@@ -413,7 +414,9 @@ function sanitizeToken(raw) {
413
414
  return cleaned === '' ? 'server' : cleaned;
414
415
  }
415
416
  /**
416
- * Derive a bridge serverName from the suite and server ids:
417
+ * Derive the bridge serverName of one suite server.
418
+ *
419
+ * A package's servers are namespaced under their suite:
417
420
  * `${suiteId}__${serverKey}` sanitized, clamped to 32 chars with a
418
421
  * deterministic 12-hex suffix when the join exceeds the budget.
419
422
  *
@@ -421,11 +424,16 @@ function sanitizeToken(raw) {
421
424
  * suite/server pair are the same server for the model, so the second mount
422
425
  * is skipped with a `duplicate-mount` diagnostic instead of registering a
423
426
  * shadow copy under a mangled name.
427
+ *
428
+ * The user's own declaration file is the exception. Its servers are the
429
+ * user's, not a package's, so each mounts under its own server key alone —
430
+ * the model reads `mcp__<serverKey>__<tool>`, the same name that declaration
431
+ * carries in every other MCP client.
424
432
  */
425
- export function deriveServerName(suiteId, serverKey) {
426
- const candidate = `${sanitizeToken(suiteId)}__${sanitizeToken(serverKey)}`;
433
+ export function deriveServerName(suite, serverKey) {
434
+ const candidate = isUserMcpSuite(suite) ? sanitizeToken(serverKey) : `${sanitizeToken(suite.id)}__${sanitizeToken(serverKey)}`;
427
435
  if (candidate.length <= SERVER_NAME_MAX)
428
436
  return candidate;
429
- const hash = createHash('sha256').update(`${suiteId}\u0000${serverKey}`).digest('hex').slice(0, 12);
437
+ const hash = createHash('sha256').update(`${suite.id}\u0000${serverKey}`).digest('hex').slice(0, 12);
430
438
  return `${candidate.slice(0, SERVER_NAME_MAX - 13)}-${hash}`;
431
439
  }
@@ -4,6 +4,17 @@ import { MCP_SCHEMA_ID, validateMcpJson } from '../../catalog/validate.js';
4
4
  import { effectiveSurfaces } from '../../model/types.js';
5
5
  export const USER_MCP_SOURCE = '@user-mcp';
6
6
  export const USER_MCP_SUITE = 'user-mcp';
7
+ /**
8
+ * Whether one suite is the user's own declaration file rather than a package.
9
+ *
10
+ * These servers are local data the user authored, so they are also the one
11
+ * suite whose servers own the top-level MCP namespace: they mount under their
12
+ * own server key instead of a suite-namespaced one (see `deriveServerName` in
13
+ * `mcp-config.ts`).
14
+ */
15
+ export function isUserMcpSuite(suite) {
16
+ return suite.sourceId === USER_MCP_SOURCE && suite.id === USER_MCP_SUITE;
17
+ }
7
18
  /**
8
19
  * Load the user's own MCP declaration file.
9
20
  *
@@ -1,4 +1,5 @@
1
1
  /** Redact secret-shaped MCP configuration before it crosses a status/detail boundary. */
2
+ import { REDACTED_VALUE } from '../../contracts/mcp.js';
2
3
  /**
3
4
  * Secret-shaped key matcher. This is a heuristic, not a security boundary: it
4
5
  * exists so a token cannot leak into a status response, a detail preview, or a
@@ -63,7 +64,7 @@ export function redactUrl(raw) {
63
64
  const [name = '', value = ''] = splitOnce(part, '=');
64
65
  if (hasPlaceholder(value))
65
66
  return `${name}=${value}`;
66
- return isSensitiveKey(name) ? `${name}=[redacted]` : `${name}=${value}`;
67
+ return isSensitiveKey(name) ? `${name}=${REDACTED_VALUE}` : `${name}=${value}`;
67
68
  })
68
69
  .join('&');
69
70
  return safeQuery === '' ? base : `${base}?${safeQuery}`;
@@ -87,7 +88,7 @@ function redactArgs(values) {
87
88
  return values.map(value => {
88
89
  if (redactNext) {
89
90
  redactNext = false;
90
- return '[redacted]';
91
+ return REDACTED_VALUE;
91
92
  }
92
93
  if (typeof value !== 'string')
93
94
  return value;
@@ -99,7 +100,7 @@ function redactArgs(values) {
99
100
  redactNext = true;
100
101
  return value;
101
102
  }
102
- return `${value.slice(0, equals + 1)}[redacted]`;
103
+ return `${value.slice(0, equals + 1)}${REDACTED_VALUE}`;
103
104
  });
104
105
  }
105
106
  function redactValue(value, key = '') {
@@ -109,7 +110,7 @@ function redactValue(value, key = '') {
109
110
  if (isSensitiveKey(key)) {
110
111
  if (typeof value === 'string' && hasPlaceholder(value))
111
112
  return value;
112
- return '[redacted]';
113
+ return REDACTED_VALUE;
113
114
  }
114
115
  if (key === 'url' && typeof value === 'string')
115
116
  return redactUrl(value);
@@ -1,6 +1,19 @@
1
1
  import { credentialRefsInServer, effectiveMcpServers, deriveServerName } from './mcp-config.js';
2
2
  import { redactMcpConfig, redactUrl } from './mcp-redaction.js';
3
3
  import { qualifiedSuiteId } from '../../catalog/paths.js';
4
+ /**
5
+ * Whether one effective configuration supplies its own Authorization header.
6
+ *
7
+ * Such a server authenticates with that header: the browser authorization a
8
+ * re-authorize would restart is not the path it uses, so the action is not
9
+ * offered for it.
10
+ */
11
+ export function declaresAuthHeader(config) {
12
+ const headers = config?.['headers'];
13
+ if (typeof headers !== 'object' || headers === null)
14
+ return false;
15
+ return Object.keys(headers).some(name => name.toLowerCase() === 'authorization');
16
+ }
4
17
  /** Build status rows from discovered plugin MCP definitions and observed tool names. */
5
18
  export function buildMcpStatus(suites, diagnostics, observed, overrides = new Map()) {
6
19
  const entries = [];
@@ -11,9 +24,10 @@ export function buildMcpStatus(suites, diagnostics, observed, overrides = new Ma
11
24
  if (suite.mcp === undefined)
12
25
  continue;
13
26
  for (const [serverKey, server] of Object.entries(suite.mcp.servers)) {
14
- // Server names and every status key are source-qualified: two sources
15
- // may ship the same suite id, and the inventory must not conflate them.
16
- const serverName = deriveServerName(suite.id, serverKey);
27
+ // Every status key is source-qualified: two sources may ship the same
28
+ // suite id, and the inventory must not conflate them. The derived
29
+ // serverName itself is shared by design — see `deriveServerName`.
30
+ const serverName = deriveServerName(suite, serverKey);
17
31
  knownServerNames.add(serverName);
18
32
  knownDefinitions.set(serverName, { suite, serverKey, server });
19
33
  }
@@ -27,7 +41,7 @@ export function buildMcpStatus(suites, diagnostics, observed, overrides = new Ma
27
41
  continue;
28
42
  const suiteKey = qualifiedSuiteId(suite.sourceId, suite.id);
29
43
  for (const { serverKey, server: effective, override, enabled, credentialRefs: refs, policy } of effectiveMcpServers(suite, overrides.get(suiteKey))) {
30
- const serverName = deriveServerName(suite.id, serverKey);
44
+ const serverName = deriveServerName(suite, serverKey);
31
45
  const tools = observedByServer.get(serverName) ?? [];
32
46
  claimedServers.add(serverName);
33
47
  const diagnostic = diagnosticsByKey.get(`${suiteKey}\u0000${serverKey}`);
@@ -15,7 +15,7 @@ import { loadLspServers, saveLspServers } from './lsp/lsp-direct-config.js';
15
15
  import { addUserMcpServer, loadUserMcpSuite, USER_MCP_SOURCE, USER_MCP_SUITE } from './mcp/mcp-direct-config.js';
16
16
  import { applyOverride, loadSuiteOverrides, mergeOverridePatch, parseMcpPolicyPatch, saveSuiteOverrides, withoutPolicyFields } from './mcp/mcp-overrides.js';
17
17
  import { redactMcpConfig, redactMcpOverrides } from './mcp/mcp-redaction.js';
18
- import { buildMcpStatus } from './mcp/mcp-status.js';
18
+ import { buildMcpStatus, declaresAuthHeader } from './mcp/mcp-status.js';
19
19
  import { resolveRegion } from './regions.js';
20
20
  import { applyLspOverrides, lspConfig, restoreRedactedConfig, saveLspOverride, validateServerLsp, validateServerMcp } from './server-config.js';
21
21
  /** The host compatibility client mounts servers without a startup timeout or tool filters. */
@@ -52,6 +52,7 @@ export class McpService {
52
52
  (entry.kind === 'plugin' || entry.managed === true) &&
53
53
  (entry.transport === 'sse' || entry.transport === 'streamable-http') &&
54
54
  auth?.enabled !== false &&
55
+ !declaresAuthHeader(entry.config) &&
55
56
  !['disabled', 'foreign', 'orphaned'].includes(entry.state);
56
57
  }
57
58
  return payload;
@@ -4,6 +4,7 @@ import { MCP_SCHEMA_ID, validateAgainstSchema, validateMcpJson } from '../catalo
4
4
  import { parseLspServers } from '../catalog/lsp-spec.js';
5
5
  import { qualifiedSuiteId } from '../catalog/paths.js';
6
6
  import { redactMcpConfig } from './mcp/mcp-redaction.js';
7
+ import { REDACTED_VALUE } from '../contracts/mcp.js';
7
8
  /**
8
9
  * The errors the closed-set package shape produces. A user-owned service does
9
10
  * not obey that shape, and the per-transport checks still require what it needs.
@@ -24,7 +25,7 @@ export class McpConfigError extends Error {
24
25
  }
25
26
  /** Restore only unchanged redacted leaves, rejecting invented masked values. */
26
27
  export function restoreRedactedConfig(input, original, redacted = redactMcpConfig(original)) {
27
- if (typeof input === 'string' && input.includes('[redacted]')) {
28
+ if (typeof input === 'string' && input.includes(REDACTED_VALUE)) {
28
29
  if (input !== redacted)
29
30
  throw new Error('masked values must remain unchanged or be replaced');
30
31
  return original;
@@ -1 +1,7 @@
1
- export {};
1
+ /**
2
+ * What a secret-shaped value reads as once it crosses a status or editor
3
+ * boundary. The client compares against it to tell a hidden literal from a
4
+ * value it may show, so the placeholder is one shared constant rather than a
5
+ * string literal on each side.
6
+ */
7
+ export const REDACTED_VALUE = '[redacted]';
@@ -100,9 +100,11 @@ export function renderCatalogText(entries, update) {
100
100
  '<system-reminder>',
101
101
  update ? CATALOG_UPDATED : CATALOG_INTRO,
102
102
  CATALOG_WHEN_TO_DELEGATE,
103
+ '',
103
104
  '<available_subagents>',
104
105
  ...lines,
105
106
  '</available_subagents>',
107
+ '',
106
108
  CATALOG_PROMPT,
107
109
  CATALOG_USAGE,
108
110
  '</system-reminder>'
@@ -125,7 +125,9 @@ export declare function resolveMcpPolicy(declared: McpServerPolicy, override: Mc
125
125
  /** Find external credential references used by one MCP server definition. */
126
126
  export declare function credentialRefsInServer(server: McpServer): string[];
127
127
  /**
128
- * Derive a bridge serverName from the suite and server ids:
128
+ * Derive the bridge serverName of one suite server.
129
+ *
130
+ * A package's servers are namespaced under their suite:
129
131
  * `${suiteId}__${serverKey}` sanitized, clamped to 32 chars with a
130
132
  * deterministic 12-hex suffix when the join exceeds the budget.
131
133
  *
@@ -133,5 +135,10 @@ export declare function credentialRefsInServer(server: McpServer): string[];
133
135
  * suite/server pair are the same server for the model, so the second mount
134
136
  * is skipped with a `duplicate-mount` diagnostic instead of registering a
135
137
  * shadow copy under a mangled name.
138
+ *
139
+ * The user's own declaration file is the exception. Its servers are the
140
+ * user's, not a package's, so each mounts under its own server key alone —
141
+ * the model reads `mcp__<serverKey>__<tool>`, the same name that declaration
142
+ * carries in every other MCP client.
136
143
  */
137
- export declare function deriveServerName(suiteId: string, serverKey: string): string;
144
+ export declare function deriveServerName(suite: Pick<Suite, 'sourceId' | 'id'>, serverKey: string): string;
@@ -1,6 +1,15 @@
1
1
  import { type McpSuiteConfig, type Suite } from '../../model/types.js';
2
2
  export declare const USER_MCP_SOURCE = "@user-mcp";
3
3
  export declare const USER_MCP_SUITE = "user-mcp";
4
+ /**
5
+ * Whether one suite is the user's own declaration file rather than a package.
6
+ *
7
+ * These servers are local data the user authored, so they are also the one
8
+ * suite whose servers own the top-level MCP namespace: they mount under their
9
+ * own server key instead of a suite-namespaced one (see `deriveServerName` in
10
+ * `mcp-config.ts`).
11
+ */
12
+ export declare function isUserMcpSuite(suite: Pick<Suite, 'sourceId' | 'id'>): boolean;
4
13
  /**
5
14
  * Load the user's own MCP declaration file.
6
15
  *
@@ -1,4 +1,3 @@
1
- /** Redact secret-shaped MCP configuration before it crosses a status/detail boundary. */
2
1
  export declare function redactMcpConfig(value: unknown): unknown;
3
2
  export declare function redactMcpOverrides(value: Record<string, Record<string, unknown>>): Record<string, Record<string, unknown>>;
4
3
  /** Whether one map key is treated as secret-shaped. */
@@ -12,5 +12,13 @@ export interface McpDiagnostic {
12
12
  causes?: string[];
13
13
  credentialRefs?: string[];
14
14
  }
15
+ /**
16
+ * Whether one effective configuration supplies its own Authorization header.
17
+ *
18
+ * Such a server authenticates with that header: the browser authorization a
19
+ * re-authorize would restart is not the path it uses, so the action is not
20
+ * offered for it.
21
+ */
22
+ export declare function declaresAuthHeader(config: Record<string, unknown> | undefined): boolean;
15
23
  /** Build status rows from discovered plugin MCP definitions and observed tool names. */
16
24
  export declare function buildMcpStatus(suites: Suite[], diagnostics: McpDiagnostic[], observed: readonly McpToolSnapshot[], overrides?: Map<string, McpSuiteOverrides>): McpStatusPayload;
@@ -8,6 +8,13 @@
8
8
  import type { McpStatusCode } from './mcp-status.js';
9
9
  /** The MCP mount backend the market uses for suite servers. */
10
10
  export type McpBackend = 'builtin' | 'host';
11
+ /**
12
+ * What a secret-shaped value reads as once it crosses a status or editor
13
+ * boundary. The client compares against it to tell a hidden literal from a
14
+ * value it may show, so the placeholder is one shared constant rather than a
15
+ * string literal on each side.
16
+ */
17
+ export declare const REDACTED_VALUE = "[redacted]";
11
18
  /** One MCP mount failure as the registries record it for the status surface. */
12
19
  export interface McpMountDiagnostic {
13
20
  suiteId: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-agent-plugins-market",
3
3
  "description": "One-stop skills, subagent, MCP and LSP manager inside DeepSeek Harness (DSH), compatible with Claude Code, Codex, Cursor, Kimi and other agent plugin layouts.",
4
- "version": "0.8.3",
4
+ "version": "0.9.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -75,17 +75,17 @@
75
75
  ],
76
76
  "peerDependencies": {
77
77
  "@deepseek-ai/cordis": "~4.0.4",
78
- "@deepseek-ai/dsh-attachment": "^0.1.7-rc.2",
79
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
80
- "@deepseek-ai/dsh-hooks-claude-code": "^0.1.7-rc.2",
81
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
82
- "@deepseek-ai/dsh-mcp-client": "^0.1.7-rc.2",
83
- "@deepseek-ai/dsh-skill": "^0.1.7-rc.2",
84
- "@deepseek-ai/dsh-subprocess": "^0.1.7-rc.2",
85
- "@deepseek-ai/dsh-timeout": "^0.1.7-rc.2",
86
- "@deepseek-ai/dsh-tools": "^0.1.7-rc.2",
87
- "@deepseek-ai/dsh-util-code-language": "^0.1.7-rc.2",
88
- "@deepseek-ai/dsh-util-workspace-path": "^0.1.7-rc.2"
78
+ "@deepseek-ai/dsh-attachment": "^0.2.0-rc.2",
79
+ "@deepseek-ai/dsh-credentials": "^0.2.0-rc.2",
80
+ "@deepseek-ai/dsh-hooks-claude-code": "^0.2.0-rc.2",
81
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
82
+ "@deepseek-ai/dsh-mcp-client": "^0.2.0-rc.2",
83
+ "@deepseek-ai/dsh-skill": "^0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-subprocess": "^0.2.0-rc.2",
85
+ "@deepseek-ai/dsh-timeout": "^0.2.0-rc.2",
86
+ "@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-util-code-language": "^0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-util-workspace-path": "^0.2.0-rc.2"
89
89
  },
90
90
  "peerDependenciesMeta": {
91
91
  "@deepseek-ai/dsh-hooks-claude-code": {
@@ -99,11 +99,11 @@
99
99
  }
100
100
  },
101
101
  "dependencies": {
102
- "@deepseek-ai/dsh-atomic-write": "^0.1.7-rc.2",
103
- "@deepseek-ai/dsh-home-paths": "^0.1.7-rc.2",
104
- "@deepseek-ai/dsh-lsp": "^0.1.7-rc.2",
105
- "@deepseek-ai/dsh-lsp-stdio": "^0.1.7-rc.2",
106
- "@deepseek-ai/dsh-tool-lsp": "^0.1.7-rc.2",
102
+ "@deepseek-ai/dsh-atomic-write": "^0.2.0-rc.2",
103
+ "@deepseek-ai/dsh-home-paths": "^0.2.0-rc.2",
104
+ "@deepseek-ai/dsh-lsp": "^0.2.0-rc.2",
105
+ "@deepseek-ai/dsh-lsp-stdio": "^0.2.0-rc.2",
106
+ "@deepseek-ai/dsh-tool-lsp": "^0.2.0-rc.2",
107
107
  "@deepseek-ai/schemastery": "^3.18.4",
108
108
  "@modelcontextprotocol/sdk": "^1.30.0",
109
109
  "ajv": "^8.17.1",
@@ -121,22 +121,22 @@
121
121
  "@codemirror/view": "^6.43.12",
122
122
  "@deepseek-ai/cordis": "~4.0.4",
123
123
  "@deepseek-ai/cosmokit": "^1.8.5",
124
- "@deepseek-ai/dsh-attachment": "0.1.7-rc.2",
125
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
126
- "@deepseek-ai/dsh-client-ui-plugin-manager": "0.1.7-rc.2",
127
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
128
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
129
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
130
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.2",
131
- "@deepseek-ai/dsh-hooks-claude-code": "0.1.7-rc.2",
132
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
133
- "@deepseek-ai/dsh-mcp-client": "0.1.7-rc.2",
134
- "@deepseek-ai/dsh-skill": "0.1.7-rc.2",
135
- "@deepseek-ai/dsh-subprocess": "0.1.7-rc.2",
136
- "@deepseek-ai/dsh-timeout": "0.1.7-rc.2",
137
- "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
138
- "@deepseek-ai/dsh-util-code-language": "0.1.7-rc.2",
139
- "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.2",
124
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
125
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
126
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.0-rc.2",
127
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
128
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
129
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
130
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
131
+ "@deepseek-ai/dsh-hooks-claude-code": "0.2.0-rc.2",
132
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
133
+ "@deepseek-ai/dsh-mcp-client": "0.2.0-rc.2",
134
+ "@deepseek-ai/dsh-skill": "0.2.0-rc.2",
135
+ "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
136
+ "@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
137
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
138
+ "@deepseek-ai/dsh-util-code-language": "0.2.0-rc.2",
139
+ "@deepseek-ai/dsh-util-workspace-path": "0.2.0-rc.2",
140
140
  "@eslint/js": "^10.0.1",
141
141
  "@shikijs/langs": "^4.4.3",
142
142
  "@tsdown/css": "^0.22.14",