@openviking/dsh-memory-plugin 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -1,6 +1,30 @@
1
1
  # OpenViking Memory for DeepSeek Harness
2
2
 
3
- An installable DeepSeek Harness bundle that adds OpenViking auto-recall, session capture, `viking://` URI protection, and model-invocable memory tools.
3
+ An installable DeepSeek Harness bundle that adds OpenViking auto-recall, session capture, `viking://` URI protection, and the OpenViking MCP tool surface.
4
+
5
+ Published as [`@openviking/dsh-memory-plugin`](https://www.npmjs.com/package/@openviking/dsh-memory-plugin).
6
+
7
+ ## Install
8
+
9
+ DSH is covered by the unified memory-plugin installer, which asks which profile
10
+ to install into (default `web`):
11
+
12
+ ```bash
13
+ bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/memory-plugin-shared/install.sh)
14
+ ```
15
+
16
+ Or add the package to a profile directly:
17
+
18
+ ```bash
19
+ dsh plugin --profile web add @openviking/dsh-memory-plugin
20
+ dsh --profile web --dump-config # should list the openviking-memory group
21
+ ```
22
+
23
+ `dsh plugin` forwards to pnpm inside the profile directory, so the bundle has to
24
+ be a real package. Linking a source checkout (`dsh plugin --profile web add
25
+ ./examples/dsh-memory-plugin`) only works when that checkout has its own
26
+ `node_modules`, because Node resolves the bundle's dsh peers from the source
27
+ tree's realpath rather than from the profile.
4
28
 
5
29
  ## Requirements
6
30
 
@@ -8,15 +32,24 @@ An installable DeepSeek Harness bundle that adds OpenViking auto-recall, session
8
32
  - Node.js `^22.19.0` or `>=24`
9
33
  - A reachable OpenViking server
10
34
 
11
- The bundle has no runtime npm dependencies. Its tool and message structures
12
- come from the DSH constructors (`defineTool` from `@deepseek-ai/dsh-tools`,
13
- `createUserMessage` from `@deepseek-ai/dsh-llm`) behind exact-pinned
14
- peerDependencies that DSH exposes through its profile fallback at boot time, so
15
- the definitions track DSH's contracts instead of hand-built object shapes.
35
+ The bundle has no runtime npm dependencies. Its message structures come from
36
+ `createUserMessage` (`@deepseek-ai/dsh-llm`), its tool surface from
37
+ `@deepseek-ai/dsh-mcp-client`, and its skill provider from
38
+ `@deepseek-ai/dsh-skill-filesystem` — exact-pinned peerDependencies that DSH
39
+ itself already installs, so there is nothing extra to add to a profile.
16
40
  It is tested against `0.1.0-rc.6`; install that exact DSH release because
17
41
  prerelease package dist-tags are not synchronized across the package family.
42
+ `overrides` holds the whole transitive `@deepseek-ai/dsh-*` family at the same
43
+ release so a fresh `npm install` cannot float a subpackage forward.
44
+
45
+ Everything under `shared/` and `skills/` is generated by
46
+ `node examples/memory-plugin-shared/sync.mjs` — edit
47
+ `examples/memory-plugin-shared/lib/` and `examples/skills/`, never the vendored
48
+ copies.
49
+
50
+ ## Design notes
18
51
 
19
- ## Why injection uses pre-step user messages, not the system prompt
52
+ ### Why injection uses pre-step user messages, not the system prompt
20
53
 
21
54
  Recall and profile context enter through the `agent/pre-step` waterfall as
22
55
  durable, source-attributed user messages (`source: { kind: 'plugin', … }`).
@@ -28,27 +61,44 @@ context under such presets with no error. Pre-step injection also makes each
28
61
  injection a session event that replays, is visible to compaction, and never
29
62
  reaches `request/header`.
30
63
 
31
- ## Install
32
-
33
- From the OpenViking repository:
34
-
35
- ```bash
36
- dsh plugin --profile default add ./examples/dsh-memory-plugin
37
- ```
38
-
39
- Or install the published package:
40
-
41
- ```bash
42
- dsh plugin --profile default add @openviking/dsh-memory-plugin
43
- ```
44
-
45
- Confirm that the profile includes the bundle:
46
-
47
- ```bash
48
- dsh --profile default --dump-config
49
- ```
50
-
51
- The package patch mounts the runtime inside a Cordis group with an isolated `openvikingMemory` service.
64
+ ### How the tool surface is mounted
65
+
66
+ `mcp.mjs` mounts `@deepseek-ai/dsh-mcp-client` on `servers/mcp-proxy.mjs`, the
67
+ same stdio proxy every other OpenViking memory integration starts, so the model
68
+ gets the server's full tool set instead of a hand-maintained subset and the
69
+ transport behaves identically across harnesses. Pointing the bridge straight at
70
+ the server's `/mcp` endpoint does not work: with `stateless_http=True` the
71
+ server still answers `GET /mcp` with an idle 200 SSE stream, and once the MCP
72
+ SDK client opens that standalone stream it stops resolving POST responses, so
73
+ `tools/list` never returns. The proxy owns the transport itself and is
74
+ unaffected.
75
+
76
+ The bundle's resolved credentials travel to the proxy through the child
77
+ environment, because DSH scrubs credential-shaped names out of the inherited env
78
+ and a subprocess cannot see the Cordis patch; the proxy then applies the usual
79
+ `OPENVIKING_*` → `ovcli.conf` → `ov.conf` chain and reloads when those files
80
+ change.
81
+
82
+ Two consequences follow from the proxy being one process per profile:
83
+
84
+ - **The actor peer is process-level.** Recall, capture, and commit still resolve
85
+ a peer per session from that session's workspace, but tool calls carry the
86
+ peer resolved at boot. Set `OPENVIKING_PEER_ID` when one process serves
87
+ several workspaces and you need tool calls attributed exactly.
88
+ - **`remember` is not session-scoped.** The server's MCP `remember` stores into
89
+ its own short-lived session rather than the live `dsh-<session-id>` stream —
90
+ the same behavior the Claude Code, Codex, and Cursor integrations have.
91
+ Automatic capture still records the conversation itself.
92
+
93
+ The bridge and the skill provider are mounted last in `apply()`, after every
94
+ lifecycle registration, so a proxy that cannot start holds up nothing above it.
95
+
96
+ ### Why the skill gets its own provider
97
+
98
+ `skills.mjs` registers a second `ctx.skills` provider with
99
+ `includeDefaultRoots: false`, serving only `skills/`. DSH already mounts a
100
+ `filesystem` provider over the project and user skill roots; an isolated
101
+ provider adds the bundle's skill without shadowing or duplicating that catalog.
52
102
 
53
103
  ## Configuration
54
104
 
@@ -88,6 +138,7 @@ The patch can also carry plugin config:
88
138
  scoreThreshold: 0.35
89
139
  captureToolResults: false
90
140
  commitTokenThreshold: 20000
141
+ mcpToolCallTimeoutMs: 60000
91
142
  ```
92
143
 
93
144
  ## Behavior
@@ -97,28 +148,33 @@ The patch can also carry plugin config:
97
148
  - `session/event` captures user, assistant, and optionally tool-result messages without scraping a transcript.
98
149
  - `turn/end` checks the OpenViking pending-token threshold and commits when required.
99
150
  - Failed writes enter the shared OpenViking pending queue for replay at the next session start.
100
- - `tools/pre-execute` blocks DSH filesystem and shell tools from treating `viking://` URIs as local paths.
151
+ - `tools/pre-execute` blocks DSH filesystem and shell tools from treating `viking://` URIs as local paths, pointing the model at the bridged `mcp__openviking__*` tools instead.
101
152
 
102
153
  Each DSH session maps to `dsh-<session-id>` in OpenViking. Workspace-derived actor peers are resolved per session and sent on every session-specific request.
103
154
 
104
155
  ## Tools
105
156
 
106
- The bundle registers:
157
+ The model sees the OpenViking MCP tools under the bridge's server-qualified
158
+ names — `mcp__openviking__search`, `mcp__openviking__read`,
159
+ `mcp__openviking__list`, `mcp__openviking__tree`, `mcp__openviking__grep`,
160
+ `mcp__openviking__glob`, `mcp__openviking__remember`,
161
+ `mcp__openviking__write`, `mcp__openviking__edit`,
162
+ `mcp__openviking__forget`, `mcp__openviking__add_resource`, and the rest of
163
+ whatever the connected server advertises. The list re-syncs when the server
164
+ announces a change, so a server upgrade adds tools without a bundle release.
107
165
 
108
- - `viking_search`
109
- - `viking_read`
110
- - `viking_browse`
111
- - `viking_remember`
112
- - `viking_forget`
113
- - `viking_add_resource`
114
- - `viking_archive_expand`
166
+ `mcp__openviking__forget` performs permanent deletion. The calling model should
167
+ use it only when the user explicitly requests deletion.
115
168
 
116
- `viking_forget` performs permanent deletion. The calling model should use it only when the user explicitly requests deletion.
169
+ The bundle also serves the shared `openviking-memory` skill from `skills/`
170
+ through its own isolated `ctx.skills` provider, so the model gets the same
171
+ guidance on when to search, read, and write that the other integrations ship.
117
172
 
118
173
  ## Testing
119
174
 
120
175
  ```bash
121
176
  npm ci # installs the exact-pinned dsh devDependencies the tests exercise
177
+ npm run check # syntax check every shipped module + package/PLUGIN_VERSION agreement
122
178
  npm test # node --test *.test.mjs — runs in the repo's PR workflow
123
179
  ```
124
180
 
package/config.mjs CHANGED
@@ -1,7 +1,14 @@
1
1
  import { buildUserAgent, resolveOpenVikingCredentials } from "./shared/credentials.mjs";
2
2
  import { resolveEffectivePeerId } from "./shared/workspace-peer.mjs";
3
3
 
4
- export const PLUGIN_VERSION = "0.1.0";
4
+ export const PLUGIN_VERSION = "0.2.0";
5
+
6
+ /**
7
+ * Namespace for the bridged OpenViking MCP tools. DSH publishes every MCP tool
8
+ * as `mcp__<serverName>__<rawName>`, so this string is part of the
9
+ * model-facing contract: changing it renames all of them.
10
+ */
11
+ export const MCP_SERVER_NAME = "openviking";
5
12
 
6
13
  const DEFAULT_CONFIG = Object.freeze({
7
14
  endpoint: "http://127.0.0.1:1933",
@@ -31,6 +38,7 @@ const DEFAULT_CONFIG = Object.freeze({
31
38
  captureToolMaxChars: 1000000,
32
39
  captureAssistantTurns: true,
33
40
  requestTimeoutMs: 10000,
41
+ mcpToolCallTimeoutMs: 60000,
34
42
  });
35
43
 
36
44
  export function resolveConfig(input = {}, env = process.env, cwd = process.cwd()) {
@@ -134,6 +142,12 @@ export function resolveConfig(input = {}, env = process.env, cwd = process.cwd()
134
142
  120000,
135
143
  DEFAULT_CONFIG.requestTimeoutMs,
136
144
  );
145
+ config.mcpToolCallTimeoutMs = clampInteger(
146
+ config.mcpToolCallTimeoutMs,
147
+ 1000,
148
+ 600000,
149
+ DEFAULT_CONFIG.mcpToolCallTimeoutMs,
150
+ );
137
151
  config.captureMode = config.captureMode === "keyword" ? "keyword" : "semantic";
138
152
  config.syncTurns = config.syncTurns !== false;
139
153
  config.captureAssistantTurns = config.captureAssistantTurns !== false;
package/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { OpenVikingClient } from "./client.mjs";
2
2
  import { resolveConfig } from "./config.mjs";
3
3
  import { injectStartupProfile } from "./lifecycle.mjs";
4
+ import { mountOpenVikingMcp } from "./mcp.mjs";
4
5
  import { OpenVikingRuntime } from "./runtime.mjs";
5
- import { registerOpenVikingTools } from "./tools.mjs";
6
+ import { mountOpenVikingSkills } from "./skills.mjs";
6
7
  import { guardVikingUri } from "./uri-guard.mjs";
7
8
 
8
9
  export const name = "openviking-memory";
@@ -18,8 +19,6 @@ export function apply(ctx, input = {}) {
18
19
  "openvikingMemory.disposeAll()",
19
20
  );
20
21
 
21
- registerOpenVikingTools(ctx, client, runtime);
22
-
23
22
  ctx.on("agent/session-start", ({ agent }) => {
24
23
  agent.ctx.effect(
25
24
  () => () => runtime.dispose(agent.session),
@@ -53,4 +52,10 @@ export function apply(ctx, input = {}) {
53
52
  });
54
53
 
55
54
  ctx.on("tools/pre-execute", guardVikingUri);
55
+
56
+ // Mounted last, and deliberately not awaited: the bridge's apply blocks on
57
+ // its first tools/list, so a server that accepts the connection but never
58
+ // answers would otherwise hold up every registration above it.
59
+ mountOpenVikingMcp(ctx, config);
60
+ mountOpenVikingSkills(ctx);
56
61
  }
package/mcp.mjs ADDED
@@ -0,0 +1,43 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
3
+
4
+ import { MCP_SERVER_NAME } from "./config.mjs";
5
+
6
+ /** The same stdio proxy every other OpenViking memory integration starts. */
7
+ export const PROXY_PATH = fileURLToPath(new URL("./servers/mcp-proxy.mjs", import.meta.url));
8
+
9
+ /**
10
+ * Build the dsh-mcp-client config for the OpenViking stdio proxy.
11
+ *
12
+ * The bundle's own credential resolution (`OPENVIKING_*` → `ovcli.conf` →
13
+ * `ov.conf`, plus anything set in the Cordis patch) is forwarded through the
14
+ * child environment: DSH scrubs credential-shaped names out of the inherited
15
+ * env, and the patch is invisible to a subprocess, so values the runtime
16
+ * already resolved have to be passed explicitly.
17
+ */
18
+ export function buildMcpConfig(config) {
19
+ const env = {};
20
+ if (config.endpoint) env.OPENVIKING_URL = config.endpoint;
21
+ if (config.apiKey) env.OPENVIKING_API_KEY = config.apiKey;
22
+ if (config.account) env.OPENVIKING_ACCOUNT = config.account;
23
+ if (config.user) env.OPENVIKING_USER = config.user;
24
+ if (config.peerId) env.OPENVIKING_PEER_ID = config.peerId;
25
+ return {
26
+ transport: "stdio",
27
+ serverName: MCP_SERVER_NAME,
28
+ command: process.execPath,
29
+ args: [PROXY_PATH],
30
+ env,
31
+ toolCallTimeoutMs: config.mcpToolCallTimeoutMs,
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Mount the tool surface. The bridge ships with dsh itself, so it resolves
37
+ * from the host installation and never needs a separate install step.
38
+ * Startup failure is contained: recall, capture, and commit keep working
39
+ * against a server whose MCP endpoint is unreachable.
40
+ */
41
+ export function mountOpenVikingMcp(ctx, config) {
42
+ return ctx.plugin(mcpClient, buildMcpConfig(config));
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openviking/dsh-memory-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "OpenViking memory and context bundle for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
@@ -13,10 +13,13 @@
13
13
  "config.mjs",
14
14
  "capture.mjs",
15
15
  "lifecycle.mjs",
16
+ "mcp.mjs",
16
17
  "runtime.mjs",
17
- "tools.mjs",
18
+ "skills.mjs",
18
19
  "uri-guard.mjs",
20
+ "servers/",
19
21
  "shared/",
22
+ "skills/",
20
23
  "cordis.patch.yml",
21
24
  "README.md"
22
25
  ],
@@ -26,16 +29,40 @@
26
29
  }
27
30
  },
28
31
  "scripts": {
29
- "test": "node --test *.test.mjs"
32
+ "check": "for f in *.mjs shared/*.mjs; do node --check \"$f\" || exit 1; done && npm run check:version",
33
+ "check:version": "node --input-type=module -e \"import {readFileSync} from 'node:fs'; const {PLUGIN_VERSION} = await import('./config.mjs'); const v = JSON.parse(readFileSync('package.json','utf8')).version; if (PLUGIN_VERSION !== v) throw new Error('PLUGIN_VERSION ' + PLUGIN_VERSION + ' does not match package.json version ' + v);\"",
34
+ "test": "node --test *.test.mjs",
35
+ "prepublishOnly": "npm run check && npm test"
30
36
  },
31
37
  "peerDependencies": {
32
38
  "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
33
- "@deepseek-ai/dsh-tools": "0.1.0-rc.6"
39
+ "@deepseek-ai/dsh-mcp-client": "0.1.0-rc.6",
40
+ "@deepseek-ai/dsh-skill-filesystem": "0.1.0-rc.6"
34
41
  },
35
42
  "devDependencies": {
36
43
  "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
44
+ "@deepseek-ai/dsh-mcp-client": "0.1.0-rc.6",
45
+ "@deepseek-ai/dsh-skill-filesystem": "0.1.0-rc.6",
37
46
  "@deepseek-ai/dsh-tools": "0.1.0-rc.6"
38
47
  },
48
+ "overrides": {
49
+ "@deepseek-ai/dsh-agent": "0.1.0-rc.6",
50
+ "@deepseek-ai/dsh-attachment": "0.1.0-rc.6",
51
+ "@deepseek-ai/dsh-brand": "0.1.0-rc.6",
52
+ "@deepseek-ai/dsh-code-runtime": "0.1.0-rc.6",
53
+ "@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-mcp-client": "0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-scope": "0.1.0-rc.6",
57
+ "@deepseek-ai/dsh-session": "0.1.0-rc.6",
58
+ "@deepseek-ai/dsh-skill-filesystem": "0.1.0-rc.6",
59
+ "@deepseek-ai/dsh-subprocess": "0.1.0-rc.6",
60
+ "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
61
+ "@deepseek-ai/dsh-timeout": "0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-user-approval": "0.1.0-rc.6"
65
+ },
39
66
  "engines": {
40
67
  "node": "^22.19.0 || >=24"
41
68
  },
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * stdio -> streamable-HTTP MCP proxy for the OpenViking DSH bundle.
5
+ *
6
+ * DSH's MCP bridge starts this process as a local stdio MCP server. The proxy
7
+ * reads the same OpenViking credential sources as the in-process runtime; the
8
+ * bundle also forwards its resolved config through the child environment, so
9
+ * values that came from the Cordis patch survive the process boundary.
10
+ */
11
+
12
+ import { resolve as resolvePath } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { resolveConfig } from "../config.mjs";
15
+ import { resolveOpenVikingCredentials } from "../shared/credentials.mjs";
16
+ import { createLogger } from "../shared/debug-log.mjs";
17
+ import { buildMcpProxyConfig } from "../shared/mcp-proxy-config.mjs";
18
+ import { createOpenVikingMcpProxy } from "../shared/mcp-proxy-core.mjs";
19
+
20
+ export function readProxyConfig(env = process.env, cwd = process.cwd()) {
21
+ const cfg = resolveConfig({}, env, cwd);
22
+ const creds = resolveOpenVikingCredentials(env);
23
+ return buildMcpProxyConfig({
24
+ baseUrl: cfg.endpoint,
25
+ apiKey: cfg.apiKey,
26
+ account: cfg.account,
27
+ user: cfg.user,
28
+ peerId: cfg.peerId,
29
+ userAgent: cfg.userAgent,
30
+ timeoutMs: cfg.requestTimeoutMs,
31
+ debug: Boolean(env.OV_DEBUG_LOG),
32
+ debugLogPath: env.OV_DEBUG_LOG,
33
+ credentialSource: creds.credentialSource,
34
+ credentialPath: creds.cliPath || creds.ovPath,
35
+ watchedPaths: [creds.cliPath, creds.ovPath, creds.cliPathCandidate],
36
+ env,
37
+ });
38
+ }
39
+
40
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolvePath(process.argv[1])) {
41
+ createOpenVikingMcpProxy({ readConfig: readProxyConfig, loggerFactory: createLogger }).start();
42
+ }
@@ -0,0 +1,90 @@
1
+ // GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
2
+ /**
3
+ * Shared config shaping for the stdio MCP proxy entrypoints.
4
+ *
5
+ * Every harness loads its credentials differently, but the shape
6
+ * `createOpenVikingMcpProxy` consumes is identical everywhere, and so is the
7
+ * set of files whose changes must trigger a credential reload. Keeping that
8
+ * here stops each entrypoint from re-deriving `~` expansion, URL trimming,
9
+ * timeout clamping, and the watch list.
10
+ */
11
+
12
+ import { homedir } from "node:os";
13
+ import { join, resolve as resolvePath } from "node:path";
14
+
15
+ export const DEFAULT_PROXY_TIMEOUT_MS = 15000;
16
+ const MIN_PROXY_TIMEOUT_MS = 1000;
17
+
18
+ export function trimSlash(value) {
19
+ return String(value || "").replace(/\/+$/, "");
20
+ }
21
+
22
+ /** Expand a leading `~` and resolve to an absolute path; "" stays "". */
23
+ export function normalizeConfigPath(value) {
24
+ const raw = String(value || "").trim();
25
+ if (!raw) return "";
26
+ if (raw === "~") return homedir();
27
+ if (raw.startsWith("~/")) return resolvePath(join(homedir(), raw.slice(2)));
28
+ return resolvePath(raw);
29
+ }
30
+
31
+ /**
32
+ * The credential files every harness must watch: the two `OPENVIKING_*_FILE`
33
+ * overrides and the two default locations under `~/.openviking`.
34
+ */
35
+ export function defaultCredentialPaths(env = process.env) {
36
+ return [
37
+ normalizeConfigPath(env.OPENVIKING_CLI_CONFIG_FILE),
38
+ normalizeConfigPath(env.OPENVIKING_CONFIG_FILE),
39
+ join(homedir(), ".openviking", "ovcli.conf"),
40
+ join(homedir(), ".openviking", "ov.conf"),
41
+ ].filter(Boolean);
42
+ }
43
+
44
+ function uniq(values) {
45
+ return [...new Set(values.filter(Boolean))];
46
+ }
47
+
48
+ /**
49
+ * Shape a harness's loaded config into the object `createOpenVikingMcpProxy`
50
+ * expects.
51
+ *
52
+ * `mcpUrl` wins over `baseUrl` when both are given, so a harness that lets the
53
+ * user pin an explicit MCP URL keeps that behavior. `watchedPaths` are the
54
+ * harness's own extra files; the shared defaults are appended and the result
55
+ * deduplicated.
56
+ */
57
+ export function buildMcpProxyConfig({
58
+ baseUrl = "",
59
+ mcpUrl = "",
60
+ apiKey = "",
61
+ account = "",
62
+ user = "",
63
+ peerId = "",
64
+ userAgent = "",
65
+ timeoutMs,
66
+ debug = false,
67
+ debugLogPath = "",
68
+ credentialSource = "auto",
69
+ credentialPath = "",
70
+ watchedPaths = [],
71
+ env = process.env,
72
+ } = {}) {
73
+ return {
74
+ mcpUrl: mcpUrl || `${trimSlash(baseUrl)}/mcp`,
75
+ apiKey: apiKey || "",
76
+ account: account || "",
77
+ user: user || "",
78
+ peerId: peerId || "",
79
+ userAgent: userAgent || "",
80
+ timeoutMs: Math.max(
81
+ MIN_PROXY_TIMEOUT_MS,
82
+ Number(timeoutMs) || DEFAULT_PROXY_TIMEOUT_MS,
83
+ ),
84
+ debug: debug === true,
85
+ debugLogPath,
86
+ credentialSource: credentialSource || "auto",
87
+ credentialPath: credentialPath || "",
88
+ watchedPaths: uniq([...watchedPaths, ...defaultCredentialPaths(env)]),
89
+ };
90
+ }