memorix 1.8.1 → 1.8.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 (40) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cli/index.js +577 -58
  3. package/dist/index.js +832 -321
  4. package/dist/maintenance-runner.js +31 -25
  5. package/dist/media-audio-runner.js +31 -25
  6. package/dist/media-video-runner.js +31 -25
  7. package/dist/memcode-runtime/CHANGELOG.md +36 -0
  8. package/dist/sdk.js +585 -315
  9. package/dist/vector-backfill-runner.js +31 -25
  10. package/docs/1.2.0-PROVIDER-QUALITY.md +15 -6
  11. package/docs/1.3.3-MULTIMODAL-MEDIA-AND-MCP-COMPAT.md +7 -6
  12. package/docs/1.8.0-RELEASE-CANDIDATE-SPEC.md +13 -8
  13. package/docs/API_REFERENCE.md +14 -11
  14. package/docs/CONFIGURATION.md +2 -2
  15. package/docs/DEVELOPMENT.md +3 -2
  16. package/docs/README.md +13 -7
  17. package/package.json +2 -1
  18. package/plugins/antigravity/memorix/plugin.json +2 -1
  19. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  20. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  21. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  22. package/plugins/copilot/memorix/plugin.json +1 -1
  23. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  24. package/plugins/hermes/memorix/plugin.yaml +1 -1
  25. package/plugins/omp/memorix/package.json +1 -1
  26. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/pi/memorix/package.json +1 -1
  28. package/server.json +3 -3
  29. package/src/cli/commands/serve-http.ts +10 -2
  30. package/src/cli/commands/serve.ts +26 -7
  31. package/src/codegraph/external-provider.ts +299 -19
  32. package/src/config/dotenv-loader.ts +9 -4
  33. package/src/config.ts +16 -16
  34. package/src/dashboard/maintenance.ts +4 -1
  35. package/src/llm/provider.ts +17 -4
  36. package/src/server/mcp-compat.ts +63 -0
  37. package/src/server/mcp-diagnostics.ts +13 -4
  38. package/src/server/mcp-discovery.ts +61 -0
  39. package/src/server/stdio-startup-gate.ts +263 -0
  40. package/src/server.ts +4 -4
@@ -104,8 +104,10 @@ function loadEnvFile(filePath) {
104
104
  loadedEnvFiles.push(filePath);
105
105
  }
106
106
  function loadDotenv(projectRoot, options = {}) {
107
- if (dotenvLoaded && dotenvProjectRoot === (projectRoot ?? null)) return;
108
- if (dotenvLoaded && dotenvProjectRoot !== (projectRoot ?? null)) {
107
+ const userHomeDir = options.userHomeDir ?? homedir();
108
+ const normalizedProjectRoot = projectRoot ?? null;
109
+ if (dotenvLoaded && dotenvProjectRoot === normalizedProjectRoot && dotenvHomeDir === userHomeDir) return;
110
+ if (dotenvLoaded && (dotenvProjectRoot !== normalizedProjectRoot || dotenvHomeDir !== userHomeDir)) {
109
111
  resetDotenv();
110
112
  }
111
113
  loadedEnvFiles.length = 0;
@@ -114,9 +116,10 @@ function loadDotenv(projectRoot, options = {}) {
114
116
  if (projectRoot) {
115
117
  loadEnvFile(getProjectDotenvPath(projectRoot));
116
118
  }
117
- loadEnvFile(getGlobalDotenvPath(options.userHomeDir ?? homedir()));
119
+ loadEnvFile(getGlobalDotenvPath(userHomeDir));
118
120
  dotenvLoaded = true;
119
- dotenvProjectRoot = projectRoot ?? null;
121
+ dotenvProjectRoot = normalizedProjectRoot;
122
+ dotenvHomeDir = userHomeDir;
120
123
  }
121
124
  function resetDotenv() {
122
125
  for (const key of injectedKeys) {
@@ -125,13 +128,14 @@ function resetDotenv() {
125
128
  injectedKeys.clear();
126
129
  dotenvLoaded = false;
127
130
  dotenvProjectRoot = null;
131
+ dotenvHomeDir = null;
128
132
  loadedEnvFiles.length = 0;
129
133
  loadedEnvKeys.clear();
130
134
  }
131
135
  function getLoadedEnvFiles() {
132
136
  return loadedEnvFiles;
133
137
  }
134
- var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
138
+ var dotenvLoaded, dotenvProjectRoot, dotenvHomeDir, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
135
139
  var init_dotenv_loader = __esm({
136
140
  "src/config/dotenv-loader.ts"() {
137
141
  "use strict";
@@ -139,6 +143,7 @@ var init_dotenv_loader = __esm({
139
143
  init_config_paths();
140
144
  dotenvLoaded = false;
141
145
  dotenvProjectRoot = null;
146
+ dotenvHomeDir = null;
142
147
  loadedEnvFiles = [];
143
148
  loadedEnvKeys = /* @__PURE__ */ new Set();
144
149
  loadedEnvValues = /* @__PURE__ */ new Map();
@@ -2372,33 +2377,33 @@ __export(config_exports, {
2372
2377
  resetConfigCache: () => resetConfigCache,
2373
2378
  resetDotenv: () => resetDotenv
2374
2379
  });
2375
- function getLLMApiKey() {
2376
- return getResolvedMemoryLane().llm.apiKey;
2380
+ function getLLMApiKey(options = {}) {
2381
+ return getResolvedMemoryLane(options).llm.apiKey;
2377
2382
  }
2378
- function getLLMProvider() {
2379
- const provider2 = getResolvedMemoryLane().llm.provider;
2383
+ function getLLMProvider(options = {}) {
2384
+ const provider2 = getResolvedMemoryLane(options).llm.provider;
2380
2385
  if (provider2) return provider2;
2381
2386
  if (process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) return "anthropic";
2382
2387
  if (process.env.OPENROUTER_API_KEY && !process.env.OPENAI_API_KEY) return "openrouter";
2383
2388
  return "openai";
2384
2389
  }
2385
- function getLLMModel(providerDefault) {
2386
- return getResolvedMemoryLane().llm.model || providerDefault;
2390
+ function getLLMModel(providerDefault, options = {}) {
2391
+ return getResolvedMemoryLane(options).llm.model || providerDefault;
2387
2392
  }
2388
- function getLLMBaseUrl(providerDefault) {
2389
- return getResolvedMemoryLane().llm.baseUrl || providerDefault;
2393
+ function getLLMBaseUrl(providerDefault, options = {}) {
2394
+ return getResolvedMemoryLane(options).llm.baseUrl || providerDefault;
2390
2395
  }
2391
- function getAgentLLMApiKey() {
2392
- return getResolvedAgentLane().apiKey;
2396
+ function getAgentLLMApiKey(options = {}) {
2397
+ return getResolvedAgentLane(options).apiKey;
2393
2398
  }
2394
- function getAgentLLMProvider() {
2395
- return getResolvedAgentLane().provider || getLLMProvider();
2399
+ function getAgentLLMProvider(options = {}) {
2400
+ return getResolvedAgentLane(options).provider || getLLMProvider(options);
2396
2401
  }
2397
- function getAgentLLMModel(providerDefault) {
2398
- return getResolvedAgentLane().model || getLLMModel(providerDefault);
2402
+ function getAgentLLMModel(providerDefault, options = {}) {
2403
+ return getResolvedAgentLane(options).model || getLLMModel(providerDefault, options);
2399
2404
  }
2400
- function getAgentLLMBaseUrl(providerDefault) {
2401
- return getResolvedAgentLane().baseUrl || getLLMBaseUrl(providerDefault);
2405
+ function getAgentLLMBaseUrl(providerDefault, options = {}) {
2406
+ return getResolvedAgentLane(options).baseUrl || getLLMBaseUrl(providerDefault, options);
2402
2407
  }
2403
2408
  function getEmbeddingMode() {
2404
2409
  const provider2 = getResolvedEmbeddingLane().provider?.toLowerCase()?.trim();
@@ -4657,18 +4662,19 @@ async function* callLLMWithToolsStream(messages, tools) {
4657
4662
  }
4658
4663
  function initLLM(options = {}) {
4659
4664
  const scope = options.scope ?? "memory";
4660
- const apiKey = scope === "agent" ? getAgentLLMApiKey() : getLLMApiKey();
4665
+ const configOptions = { projectRoot: options.projectRoot, homeDir: options.homeDir };
4666
+ const apiKey = scope === "agent" ? getAgentLLMApiKey(configOptions) : getLLMApiKey(configOptions);
4661
4667
  if (!apiKey) {
4662
4668
  currentConfig = null;
4663
4669
  return null;
4664
4670
  }
4665
- const provider2 = scope === "agent" ? getAgentLLMProvider() : getLLMProvider();
4671
+ const provider2 = scope === "agent" ? getAgentLLMProvider(configOptions) : getLLMProvider(configOptions);
4666
4672
  const defaults2 = PROVIDER_DEFAULTS[provider2] ?? PROVIDER_DEFAULTS.openai;
4667
4673
  currentConfig = {
4668
4674
  provider: provider2,
4669
4675
  apiKey,
4670
- model: scope === "agent" ? getAgentLLMModel(defaults2.model) : getLLMModel(defaults2.model),
4671
- baseUrl: scope === "agent" ? getAgentLLMBaseUrl(defaults2.baseUrl) : getLLMBaseUrl(defaults2.baseUrl)
4676
+ model: scope === "agent" ? getAgentLLMModel(defaults2.model, configOptions) : getLLMModel(defaults2.model, configOptions),
4677
+ baseUrl: scope === "agent" ? getAgentLLMBaseUrl(defaults2.baseUrl, configOptions) : getLLMBaseUrl(defaults2.baseUrl, configOptions)
4672
4678
  };
4673
4679
  return currentConfig;
4674
4680
  }
@@ -58,8 +58,9 @@ Memorix only considers the external provider when all of these are true:
58
58
  root;
59
59
  4. the index has no reported pending changes or worktree mismatch;
60
60
  5. the bounded command completes before its configured timeout; and
61
- 6. the returned JSON passes Memorix's schema, path, count, and output-size
62
- validation.
61
+ 6. the returned context JSON, or the official v1.5.0 `explore` output after a
62
+ context-command failure, passes Memorix's schema, path, relation, count,
63
+ and output-size validation.
63
64
 
64
65
  Memorix never runs `codegraph init`, `index`, or `sync` on the user's behalf.
65
66
  It does not transmit the repository to a remote service. Its default request is
@@ -90,7 +91,7 @@ external_context = "auto"
90
91
  # external_command = "C:\\tools\\codegraph.cmd"
91
92
 
92
93
  # A bounded local query. Values outside the safe range are clamped.
93
- external_timeout_ms = 1200
94
+ external_timeout_ms = 3000
94
95
  ~~~
95
96
 
96
97
  The equivalent YAML keys are `externalContext`, `externalCommand`, and
@@ -114,12 +115,19 @@ The status is visible through `memorix codegraph status --json`, `memorix
114
115
  doctor --json`, and Project Context JSON. Prompt output keeps it compact: it
115
116
  adds semantic start points only when a valid external outline is available, and
116
117
  adds one caution when a requested external outline was not safe to use.
118
+ An external status of `ready` means only that the index is initialized and
119
+ current; it does not prove that the installed CLI exposes `context`. The task
120
+ diagnostics therefore record `context` and `explore` independently.
117
121
 
118
122
  ## Validation and Fallback
119
123
 
120
124
  The external adapter executes without a shell, pins the working directory to
121
- the actual project root, uses a bounded timeout and stdout limit, and accepts
122
- JSON only. It rejects a result when:
125
+ the actual project root, uses a bounded timeout and stdout limit, and tries
126
+ `context` first. CodeGraph v1.5.0 does not register that command, so a failed
127
+ or invalid context result triggers the official `explore --path <root>
128
+ --max-files 4 <task>` command. JSON context output and the stable explore
129
+ outline/relationship markers are accepted only after the same strict path and
130
+ relation validation. It rejects a result when:
123
131
 
124
132
  - the status belongs to another project root;
125
133
  - the index is uninitialized, has pending changes, or reports a worktree
@@ -145,7 +153,8 @@ runner. It verifies:
145
153
  3. Stale, mismatched, malformed, timed-out, and oversized external output
146
154
  safely falls back to Lite.
147
155
  4. Raw code blocks and credentials do not enter the default Workset.
148
- 5. `codegraph status` and `doctor` identify the active/fallback provider.
156
+ 5. `codegraph status` and `doctor` identify the active/fallback provider, and
157
+ distinguish a healthy index from a usable task-query command.
149
158
 
150
159
  The optional real smoke can be run on a Windows project that already has an
151
160
  indexed CodeGraph installation:
@@ -158,15 +158,16 @@ layer, not Memorix product identity.
158
158
  - Product operations receive a resolved project/workspace binding and explicit
159
159
  actor metadata; they do not read an HTTP session map directly.
160
160
  - The old `Mcp-Session-Id` map remains isolated in `serve-http` as a legacy
161
- adapter until the current SDK has a stable released implementation of the
162
- 2026-07-28 protocol.
161
+ adapter. A low-level `server/discover` adapter now covers the required
162
+ 2026-07-28 discovery result without upgrading the pinned v1 SDK.
163
163
  - Asset IDs, media job IDs, project IDs, and attachment targets are explicit
164
164
  durable identifiers. They are never inferred from a live MCP connection.
165
165
  - New media tools work through stdio, legacy streamable HTTP, and direct CLI
166
166
  invocation with identical project binding behavior.
167
- - No release depends on an unreleased MCP SDK 2.x alpha. The current server
168
- remains compatible with existing Codex, Claude Code, OpenCode, and other
169
- configured clients.
167
+ - No release depends on the MCP TypeScript SDK v2. The current server remains
168
+ compatible with existing legacy clients and supports versionless list/call
169
+ through the v1 SDK's legacy envelopes. The adapter does not claim modern
170
+ result caching, Tasks, subscriptions, or complete 2026 conformance.
170
171
 
171
172
  ## Non-goals
172
173
 
@@ -177,7 +178,7 @@ layer, not Memorix product identity.
177
178
  - No automatic audio/video transcription, frame extraction, or arbitrary media
178
179
  understanding in 1.3.3. The asset model makes those future derivations safe.
179
180
  - No forced migration to a protocol version that the published MCP TypeScript
180
- SDK does not yet support.
181
+ SDK does not yet support; discovery is a narrow compatibility adapter.
181
182
  - No additional swarm of low-level MCP tools; CLI is the complete management
182
183
  surface and MCP exposes only small agent-facing operations.
183
184
 
@@ -17,14 +17,19 @@ Status: accepted implementation contract for Memorix 1.8.0.
17
17
 
18
18
  ## Compatibility Contract
19
19
 
20
- The MCP 2026-07-28 protocol is the external stateless request contract.
21
- Memorix also accepts the legacy stateful Streamable HTTP contract with
22
- `Mcp-Session-Id`. Stateless requests carry an explicit `Mcp-Project-Handle`
23
- that is persisted in SQLite and survives service restart; the handle, rather
24
- than a transport session id, owns project routing. The pinned SDK's accepted
25
- wire version is normalized internally at the HTTP boundary, while responses
26
- continue to advertise current protocol metadata. Unsupported Tasks/polling is
27
- reported by capability diagnostics rather than simulated.
20
+ Memorix preserves the legacy 2025-era initialize contract, including stdio and
21
+ stateful Streamable HTTP with `Mcp-Session-Id`. Memorix 1.8.3 also registers a
22
+ low-level `server/discover` compatibility adapter for the 2026-07-28 request.
23
+ Its `DiscoverResult` is complete for the required identity, versions,
24
+ capabilities, and private zero-TTL cache hint. Versionless `tools/list` and
25
+ `tools/call` are accepted by the pinned v1 SDK and retain its legacy result
26
+ envelopes. The stdio startup gate reads bounded newline-delimited input before
27
+ project initialization, answers discovery immediately, and replays all other
28
+ messages in order; EOF is deferred until the queued input is released. HTTP
29
+ stateless project handles remain a compatibility adapter: they
30
+ own project routing, but do not turn the v1 SDK into a complete 2026 runtime.
31
+ Tasks, subscriptions, and list-response caching are unsupported and are not
32
+ advertised or simulated.
28
33
 
29
34
  Antigravity is the primary setup target. Gemini CLI remains a compatibility and
30
35
  migration path. GitHub issue #204 (WorkBuddy) is intentionally not merged or
@@ -147,17 +147,20 @@ preserved. A memory without explicit feedback keeps the legacy relevance and
147
147
  retention score. The first feedback event opts that candidate into the
148
148
  feedback projection.
149
149
 
150
- ### HTTP MCP Compatibility
151
-
152
- Legacy clients use stateful Streamable HTTP with `Mcp-Session-Id`. Current
153
- stateless clients send `Mcp-Protocol-Version: 2026-07-28` and receive a durable
154
- `Mcp-Project-Handle`. Send that handle on subsequent requests; it is persisted
155
- in SQLite and remains valid across service restart until its TTL expires.
156
- `/protocol-diagnostics` reports the current/legacy contract and explicitly
157
- reports Tasks and polling as unsupported when the pinned SDK cannot provide a
158
- durable implementation. When MCP discovery is unavailable, use one bounded
159
- `memorix context --fallback` request for a task; repeated fallback probing is
160
- rejected by the local budget.
150
+ ### MCP Compatibility
151
+
152
+ Legacy clients use stateful Streamable HTTP with `Mcp-Session-Id` and the
153
+ 2025-era `initialize` handshake. The 1.8.3 stdio server starts a bounded
154
+ newline-delimited JSON-RPC gate before project initialization: it answers the
155
+ 2026-07-28 `server/discover` request before `initialize`, then replays other
156
+ requests, notifications, and responses in order into the pinned SDK transport.
157
+ Versionless `tools/list` and `tools/call` use the SDK's legacy result envelopes.
158
+ HTTP stateless clients may use a durable
159
+ `Mcp-Project-Handle`; `/protocol-diagnostics` labels this as compatibility-only.
160
+ Modern result metadata is discovery-only with a private zero-TTL hint. Tasks,
161
+ subscriptions, and list-response caching are unsupported. When MCP discovery
162
+ is unavailable, use one bounded `memorix context --fallback` request for a
163
+ task; repeated fallback probing is rejected by the local budget.
161
164
 
162
165
  The built-in Lite provider indexes common code files with lightweight file, symbol, and import facts. It is a structural fallback, not a language-server-quality graph. When a project already has a healthy local CodeGraph index, `[codegraph].external_context = "auto"` may add a validated, bounded semantic outline to the Workset. Memorix never initializes, syncs, or exports that external index, and never stores its raw source output. `memorix codegraph status --json`, `memorix doctor --json`, and Project Context JSON identify the actual provider quality.
163
166
 
@@ -74,7 +74,7 @@ max_file_bytes = 2097152
74
74
  # Optional: use an already-indexed local CodeGraph only when it is healthy.
75
75
  external_context = "auto"
76
76
  # external_command = "C:\\tools\\codegraph.cmd"
77
- external_timeout_ms = 1200
77
+ external_timeout_ms = 3000
78
78
 
79
79
  [server]
80
80
  transport = "stdio"
@@ -334,7 +334,7 @@ Common keys:
334
334
  - `max_file_bytes = 2097152` (2 MiB per source file by default)
335
335
  - `external_context = "auto"` (`"off"` keeps the built-in Lite provider only)
336
336
  - `external_command = "C:\\tools\\codegraph.cmd"` (optional path when CodeGraph is not on `PATH`)
337
- - `external_timeout_ms = 1200` (bounded local semantic-query timeout)
337
+ - `external_timeout_ms = 3000` (bounded local semantic-query timeout; values are clamped to 5 seconds)
338
338
 
339
339
  Legacy YAML uses `codegraph.excludePatterns`, `codegraph.maxFileBytes`,
340
340
  `codegraph.externalContext`, `codegraph.externalCommand`, and
@@ -13,8 +13,8 @@ Memorix is a TypeScript project built around:
13
13
 
14
14
  ## Current Development Baseline
15
15
 
16
- The current release work targets the **1.8.1 release line** and package metadata
17
- is kept at `1.8.1` for this release commit.
16
+ The current release work targets the **1.8.3 release line** and package metadata
17
+ is kept at `1.8.3` for this release commit.
18
18
 
19
19
  Contributors should assume the following areas are part of the current release line:
20
20
 
@@ -96,6 +96,7 @@ npm run test:watch
96
96
 
97
97
  ```bash
98
98
  memorix serve
99
+ node scripts/mcp-stdio-smoke.mjs
99
100
  memorix background start
100
101
  memorix status
101
102
  ```
package/docs/README.md CHANGED
@@ -63,6 +63,7 @@ The public docs are organized by user intent:
63
63
  | Topic | Document |
64
64
  | --- | --- |
65
65
  | MCP / CLI commands | [API_REFERENCE.md](API_REFERENCE.md) |
66
+ | MCP protocol discovery and compatibility boundaries | [API_REFERENCE.md](API_REFERENCE.md), [1.3.3 MCP Compatibility](1.3.3-MULTIMODAL-MEDIA-AND-MCP-COMPAT.md), and [1.8.0 Release Specification](1.8.0-RELEASE-CANDIDATE-SPEC.md) |
66
67
  | Plugin, rules, hooks, skills, and MCP support matrix | [INTEGRATIONS.md](INTEGRATIONS.md) |
67
68
  | TypeScript SDK | [../README.md#sdk](../README.md#sdk) |
68
69
  | Workspace and rules sync | [API_REFERENCE.md § Workspace and Rules](API_REFERENCE.md#8-workspace-and-rules-tools) |
@@ -93,7 +94,7 @@ The public docs are organized by user intent:
93
94
  | 1.2 honest Lite and optional semantic CodeGraph provider contract | [1.2 Provider Quality](1.2.0-PROVIDER-QUALITY.md) |
94
95
  | 1.3 long-term memory model, source boundary, and lifecycle | [1.3 Memory Architecture](1.3-MEMORY-ARCHITECTURE.md) |
95
96
  | Active context-control work | [1.2.2 Memory Control Plane](1.2.2-MEMORY-CONTROL-PLANE.md) |
96
- | 1.8 context-control acceptance contract | [1.8.0 Release Specification](1.8.0-RELEASE-CANDIDATE-SPEC.md) |
97
+ | 1.8.3 stdio discovery startup and compatibility acceptance contract | [1.8.0 Release Specification](1.8.0-RELEASE-CANDIDATE-SPEC.md) |
97
98
  | Historical cloud sync and multi-agent research | [CLOUD_SYNC_AND_MULTI_AGENT_RESEARCH.md](CLOUD_SYNC_AND_MULTI_AGENT_RESEARCH.md) |
98
99
  | Known issues and old roadmap notes | [KNOWN_ISSUES_AND_ROADMAP.md](KNOWN_ISSUES_AND_ROADMAP.md) |
99
100
 
@@ -121,7 +122,7 @@ Historical/deep-reference documents may describe older designs. If they conflict
121
122
 
122
123
  ## Current Product Line
123
124
 
124
- The current product line is **1.8.1**. The authoritative acceptance contract is
125
+ The current product line is **1.8.3**. The authoritative acceptance contract is
125
126
  [1.8.0 Release Specification](1.8.0-RELEASE-CANDIDATE-SPEC.md).
126
127
  The current baseline has:
127
128
 
@@ -140,8 +141,13 @@ The current baseline has:
140
141
  - `~/.memorix/config.toml` and project `memorix.toml` are the user-facing configuration model
141
142
  - legacy `memorix.yml`, `.env`, and `config.json` files are still read for compatibility, but new setups should use TOML
142
143
 
143
- For MCP compatibility, 1.8 preserves legacy stateful Streamable HTTP and
144
- supports 2026-07-28 stateless requests with durable project handles. Tasks and
145
- polling are reported as unsupported when the pinned SDK cannot provide a real
146
- durable implementation. Antigravity is the primary setup target; Gemini CLI is
147
- the compatibility path. See the release specification for the exact contract.
144
+ For MCP compatibility, 1.8.3 preserves legacy 2025-era initialize and
145
+ stateful Streamable HTTP behavior. `memorix serve` starts a bounded stdio
146
+ startup gate: pre-initialize 2026-07-28 `server/discover` is answered with a
147
+ complete discovery result, and other early JSON-RPC messages replay in order
148
+ through the pinned SDK transport. Versionless `tools/list` and `tools/call`
149
+ continue through the SDK's legacy result envelopes. Modern result metadata is
150
+ limited to the discovery response (`ttlMs: 0`, private cache scope); Tasks,
151
+ subscriptions, and list-response caching are unsupported. Stateless HTTP
152
+ project handles are a compatibility adapter, not a claim of complete 2026
153
+ conformance.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.8.1",
4
+ "version": "1.8.3",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -62,6 +62,7 @@
62
62
  "update-models": "npm --workspace @memorix/ai run update-models",
63
63
  "start": "node dist/index.js",
64
64
  "test": "vitest run --no-file-parallelism --maxWorkers=1 --minWorkers=1",
65
+ "test:mcp-stdio-smoke": "node scripts/mcp-stdio-smoke.mjs",
65
66
  "test:watch": "vitest",
66
67
  "test:llm-live": "MEMORIX_RUN_LIVE_LLM_TESTS=1 vitest run tests/integration/formation-llm-quality.test.ts",
67
68
  "test:e2e": "vitest run tests/e2e/",
@@ -1,3 +1,4 @@
1
1
  {
2
- "name": "memorix"
2
+ "name": "memorix",
3
+ "version": "1.8.3"
3
4
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.8.1",
4
+ "version": "1.8.3",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,5 +1,5 @@
1
1
  name: memorix
2
- version: "1.1.1"
2
+ version: "1.8.3"
3
3
  description: Shared workspace memory bridge for Hermes Agent.
4
4
  author: AVIDS2
5
5
  license: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Memorix shared workspace memory package for Oh-my-Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -2,12 +2,12 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.AVIDS2/memorix",
4
4
  "title": "Memorix",
5
- "description": "Local-first persistent project memory for AI coding agents across MCP clients and sessions.",
5
+ "description": "Local-first project memory with legacy MCP and 2026 discovery compatibility.",
6
6
  "repository": {
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.8.1",
10
+ "version": "1.8.3",
11
11
  "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.8.1",
25
+ "version": "1.8.3",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -40,6 +40,12 @@ export const EXPIRED_SESSION_TTL_MS = 10 * 60 * 1000;
40
40
  /** Pinned SDK protocol accepted internally while the HTTP boundary speaks the current stateless contract. */
41
41
  export const MCP_SDK_COMPAT_PROTOCOL_VERSION = '2025-11-25';
42
42
 
43
+ /** Modern discovery is allowed before a durable project handle exists. */
44
+ export function isServerDiscoverRequest(body: unknown): boolean {
45
+ return Boolean(body && typeof body === 'object' && !Array.isArray(body)
46
+ && (body as { method?: unknown }).method === 'server/discover');
47
+ }
48
+
43
49
  export function parseSessionTimeoutMs(raw: string | undefined): number {
44
50
  const value = raw?.trim();
45
51
  if (!value) return DEFAULT_SESSION_TIMEOUT_MS;
@@ -427,7 +433,7 @@ export default defineCommand({
427
433
  ? req.headers['mcp-project-root']
428
434
  : undefined;
429
435
  const root = persisted?.projectRoot ?? requestedRoot ?? projectRoot;
430
- if (!persisted && !isInitializeRequest(body)) {
436
+ if (!persisted && !isInitializeRequest(body) && !isServerDiscoverRequest(body)) {
431
437
  res.writeHead(400, { 'Content-Type': 'application/json' });
432
438
  res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Stateless MCP requests require Mcp-Project-Handle; initialize first.' }, id: null }));
433
439
  return;
@@ -505,7 +511,9 @@ export default defineCommand({
505
511
 
506
512
  const requestedProtocol = req.headers['mcp-protocol-version'];
507
513
  const statelessRequested = req.headers['mcp-stateless'] === 'true'
508
- || requestedProtocol === CURRENT_MCP_PROTOCOL_VERSION;
514
+ || requestedProtocol === CURRENT_MCP_PROTOCOL_VERSION
515
+ || typeof req.headers['mcp-project-handle'] === 'string'
516
+ || isServerDiscoverRequest(body);
509
517
  if (!sessionId && statelessRequested) {
510
518
  await handleStatelessPost(req, res, body);
511
519
  return;
@@ -4,6 +4,8 @@
4
4
 
5
5
  import { defineCommand } from 'citty';
6
6
  import { resolveToolProfile } from '../../server/tool-profile.js';
7
+ import { getMcpServerInfo } from '../../server/mcp-discovery.js';
8
+ import { StdioStartupGate } from '../../server/stdio-startup-gate.js';
7
9
  import { mcpFileUriToPath } from '../mcp-root-path.js';
8
10
 
9
11
  export default defineCommand({
@@ -29,6 +31,28 @@ export default defineCommand({
29
31
  },
30
32
  },
31
33
  run: async ({ args }) => {
34
+ // Take ownership of stdin before project/config/runtime initialization. The
35
+ // gate answers handshake-free discovery and queues every other JSON-RPC line
36
+ // until the SDK transport is connected, so cold starts cannot lose requests.
37
+ const startupGate = new StdioStartupGate({
38
+ stdin: process.stdin,
39
+ stdout: process.stdout,
40
+ serverInfo: getMcpServerInfo(),
41
+ onError: (error) => {
42
+ console.error(`[memorix] stdio startup gate error: ${error.message}`);
43
+ // The stream is no longer safe to replay after an input-limit or I/O
44
+ // failure. Exit instead of leaving a half-connected MCP process alive.
45
+ process.exit(1);
46
+ },
47
+ onEnd: () => {
48
+ console.error('[memorix] stdin closed — exiting');
49
+ // The gate has already ended the transport input when ready. Set the
50
+ // exit code and let pending JSON-RPC stdout writes drain naturally.
51
+ process.exitCode = 0;
52
+ },
53
+ });
54
+ startupGate.start();
55
+
32
56
  const { StdioServerTransport } = await import(
33
57
  '@modelcontextprotocol/sdk/server/stdio.js'
34
58
  );
@@ -37,12 +61,6 @@ export default defineCommand({
37
61
  const { homedir } = await import('node:os');
38
62
  const { resolveServeProject } = await import('./serve-shared.js');
39
63
 
40
- // Auto-exit when stdio pipe breaks (IDE closed) to prevent orphaned processes
41
- process.stdin.on('end', () => {
42
- console.error('[memorix] stdin closed — exiting');
43
- process.exit(0);
44
- });
45
-
46
64
  // Priority: explicit --cwd arg > MEMORIX_PROJECT_ROOT env > INIT_CWD (npm lifecycle) > process.cwd()
47
65
  let safeCwd: string;
48
66
  try { safeCwd = process.cwd(); } catch { safeCwd = homedir(); }
@@ -85,8 +103,9 @@ export default defineCommand({
85
103
  ? { toolProfile, deferProjectRuntimeInit: true }
86
104
  : { allowUntrackedFallback: allowUntracked, deferProjectInitUntilBound: !allowUntracked, deferProjectRuntimeInit: true, toolProfile };
87
105
  const { server, projectId, deferredInit, switchProject } = await createMemorixServer(projectRoot, undefined, undefined, serverOptions);
88
- const transport = new StdioServerTransport();
106
+ const transport = new StdioServerTransport(startupGate.input, process.stdout);
89
107
  await server.connect(transport);
108
+ startupGate.markReady();
90
109
 
91
110
  console.error(`[memorix] MCP Server running on stdio (project: ${projectId})`);
92
111
  console.error(`[memorix] Project root: ${detected?.rootPath ?? projectRoot}`);