@openviking/dsh-memory-plugin 0.1.0 → 0.2.1
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 +97 -41
- package/config.mjs +15 -1
- package/index.mjs +8 -3
- package/mcp.mjs +43 -0
- package/package.json +32 -5
- package/servers/mcp-proxy.mjs +42 -0
- package/shared/mcp-proxy-config.mjs +90 -0
- package/shared/mcp-proxy-core.mjs +504 -0
- package/skills/openviking-memory/SKILL.md +81 -0
- package/skills.mjs +23 -0
- package/uri-guard.mjs +18 -14
- package/tools.mjs +0 -246
package/README.md
CHANGED
|
@@ -1,22 +1,55 @@
|
|
|
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
|
|
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
|
|
|
7
|
-
- `@deepseek-ai/dsh` `0.1.0-rc.6`
|
|
31
|
+
- `@deepseek-ai/dsh` `0.1.0-rc.6` or newer in the `0.1.x` release line
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
peerDependencies that DSH
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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` — peerDependencies that DSH itself already
|
|
39
|
+
installs, so there is nothing extra to add to a profile. The supported peer
|
|
40
|
+
range starts at `0.1.0-rc.6` and stops before `0.2.0`, allowing current DSH
|
|
41
|
+
releases whose core packages resolve to newer release candidates. The local
|
|
42
|
+
devDependencies and `overrides` stay pinned to rc.6 so CI continues to exercise
|
|
43
|
+
the minimum supported contract.
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
The
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
4
|
+
export const PLUGIN_VERSION = "0.2.1";
|
|
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 {
|
|
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
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
33
|
-
"@deepseek-ai/dsh-
|
|
38
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0",
|
|
39
|
+
"@deepseek-ai/dsh-mcp-client": ">=0.1.0-rc.6 <0.2.0",
|
|
40
|
+
"@deepseek-ai/dsh-skill-filesystem": ">=0.1.0-rc.6 <0.2.0"
|
|
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
|
+
}
|