memorix 1.8.2 → 1.8.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.8.4] - 2026-08-27
8
+
9
+ ### Fixed
10
+ - **Stateless HTTP MCP requests (#255)** -- clients such as Claude Code that
11
+ send the current protocol header on every request can list and call tools
12
+ without echoing a custom project handle. Durable handles are still created
13
+ on `initialize` and remain available when a client opts into them.
14
+ - **MCP protocol self-description (#257)** -- discovery and HTTP response
15
+ headers now advertise the legacy revisions implemented by the pinned SDK,
16
+ instead of claiming complete support for the newer 2026 revision.
17
+ - **MCP stdio smoke contract** -- the release smoke script now checks the
18
+ truthful advertised revision set after the compatibility fix.
19
+
20
+ ### MCP Compatibility
21
+ - The 1.8.4 line supports legacy stateful MCP, versionless stdio tool calls,
22
+ pre-initialize discovery, and the narrow stateless HTTP compatibility path.
23
+ It does not claim full 2026 result metadata, Tasks, subscriptions, or list
24
+ caching support.
25
+
26
+ ## [1.8.3] - 2026-08-25
27
+
28
+ ### Fixed
29
+ - **Stdio MCP startup gate** -- `memorix serve` now reads and bounds
30
+ newline-delimited JSON-RPC input before project initialization completes.
31
+ Early `server/discover` requests receive the complete discovery result, while
32
+ initialize, notifications, responses, and versionless tool requests replay
33
+ through the pinned SDK transport in input order without duplicate handling.
34
+
35
+ ### MCP Compatibility
36
+ - **Stable cold-start discovery** -- real stdio CLI startup now supports
37
+ pre-initialize discovery within the startup window and preserves legacy
38
+ initialize plus versionless `tools/list`/`tools/call` behavior. Tasks,
39
+ subscriptions, list-response caching, and modern non-discovery result
40
+ metadata remain unsupported or partial.
41
+
7
42
  ## [1.8.2] - 2026-08-25
8
43
 
9
44
  ### Fixed
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.8.2" : pkg.version;
3235
+ return true ? "1.8.4" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -205764,6 +205764,257 @@ var init_tool_profile = __esm({
205764
205764
  }
205765
205765
  });
205766
205766
 
205767
+ // src/server/mcp-discovery.ts
205768
+ function getMcpServerInfo() {
205769
+ return {
205770
+ name: "memorix",
205771
+ version: true ? "1.8.4" : "1.0.1"
205772
+ };
205773
+ }
205774
+ function createMcpDiscoverResult(capabilities, serverInfo) {
205775
+ return {
205776
+ resultType: "complete",
205777
+ supportedVersions: [
205778
+ MCP_LEGACY_PROTOCOL_VERSION,
205779
+ MCP_OLDEST_LEGACY_PROTOCOL_VERSION
205780
+ ],
205781
+ capabilities,
205782
+ _meta: {
205783
+ [MCP_SERVER_INFO_META_KEY]: { ...serverInfo }
205784
+ },
205785
+ // v1 has no response-cache negotiation. A zero private TTL prevents clients
205786
+ // from inventing a shared cache for an implementation that cannot invalidate it.
205787
+ ttlMs: 0,
205788
+ cacheScope: "private"
205789
+ };
205790
+ }
205791
+ function createMcpStartupDiscoverResult(serverInfo) {
205792
+ return createMcpDiscoverResult({ tools: { listChanged: true } }, serverInfo);
205793
+ }
205794
+ var MCP_LEGACY_PROTOCOL_VERSION, MCP_OLDEST_LEGACY_PROTOCOL_VERSION, MCP_SERVER_DISCOVER_METHOD, MCP_SERVER_INFO_META_KEY;
205795
+ var init_mcp_discovery = __esm({
205796
+ "src/server/mcp-discovery.ts"() {
205797
+ "use strict";
205798
+ init_esm_shims();
205799
+ MCP_LEGACY_PROTOCOL_VERSION = "2025-11-25";
205800
+ MCP_OLDEST_LEGACY_PROTOCOL_VERSION = "2024-11-05";
205801
+ MCP_SERVER_DISCOVER_METHOD = "server/discover";
205802
+ MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
205803
+ }
205804
+ });
205805
+
205806
+ // src/server/stdio-startup-gate.ts
205807
+ import { PassThrough as PassThrough3 } from "stream";
205808
+ var STDIO_STARTUP_GATE_MAX_BUFFER_SIZE, StdioStartupGate;
205809
+ var init_stdio_startup_gate = __esm({
205810
+ "src/server/stdio-startup-gate.ts"() {
205811
+ "use strict";
205812
+ init_esm_shims();
205813
+ init_mcp_discovery();
205814
+ STDIO_STARTUP_GATE_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
205815
+ StdioStartupGate = class {
205816
+ input = new PassThrough3();
205817
+ stdin;
205818
+ stdout;
205819
+ serverInfo;
205820
+ maxBufferSize;
205821
+ onError;
205822
+ onEnd;
205823
+ queuedMessages = [];
205824
+ pendingInput = "";
205825
+ queuedBytes = 0;
205826
+ started = false;
205827
+ ready = false;
205828
+ stopped = false;
205829
+ stdinEnded = false;
205830
+ endNotified = false;
205831
+ onData = (chunk) => {
205832
+ if (this.stopped) return;
205833
+ if (this.ready) {
205834
+ if (Buffer.byteLength(chunk, "utf8") > this.maxBufferSize) {
205835
+ this.fail(new Error(`stdio input chunk exceeded ${this.maxBufferSize} bytes`));
205836
+ return;
205837
+ }
205838
+ this.input.write(chunk);
205839
+ return;
205840
+ }
205841
+ const text2 = typeof chunk === "string" ? chunk : chunk.toString("utf8");
205842
+ if (this.queuedBytes + Buffer.byteLength(this.pendingInput, "utf8") + Buffer.byteLength(text2, "utf8") > this.maxBufferSize) {
205843
+ this.fail(new Error(`stdio startup input exceeded ${this.maxBufferSize} bytes before a complete JSON-RPC line`));
205844
+ return;
205845
+ }
205846
+ this.pendingInput += text2;
205847
+ let newline = this.pendingInput.indexOf("\n");
205848
+ while (newline >= 0 && !this.stopped) {
205849
+ const line = this.pendingInput.slice(0, newline + 1);
205850
+ this.pendingInput = this.pendingInput.slice(newline + 1);
205851
+ this.handleLine(line);
205852
+ newline = this.pendingInput.indexOf("\n");
205853
+ }
205854
+ if (!this.stopped && Buffer.byteLength(this.pendingInput, "utf8") > this.maxBufferSize) {
205855
+ this.fail(new Error(`stdio startup input exceeded ${this.maxBufferSize} bytes`));
205856
+ }
205857
+ };
205858
+ onStdinError = (error40) => {
205859
+ this.fail(error40);
205860
+ };
205861
+ onStdinEnd = () => {
205862
+ this.stdinEnded = true;
205863
+ if (this.ready) this.finishInput();
205864
+ };
205865
+ constructor(options) {
205866
+ this.stdin = options.stdin;
205867
+ this.stdout = options.stdout;
205868
+ this.serverInfo = { ...options.serverInfo };
205869
+ this.maxBufferSize = options.maxBufferSize ?? STDIO_STARTUP_GATE_MAX_BUFFER_SIZE;
205870
+ this.onError = options.onError;
205871
+ this.onEnd = options.onEnd;
205872
+ }
205873
+ start() {
205874
+ if (this.started || this.stopped) return;
205875
+ this.started = true;
205876
+ this.stdin.on("data", this.onData);
205877
+ this.stdin.on("error", this.onStdinError);
205878
+ this.stdin.on("end", this.onStdinEnd);
205879
+ }
205880
+ /** Release queued messages only after StdioServerTransport has installed its listener. */
205881
+ markReady() {
205882
+ if (this.stopped || this.ready) return;
205883
+ this.ready = true;
205884
+ for (const message of this.queuedMessages) this.input.write(message);
205885
+ this.queuedMessages.length = 0;
205886
+ this.queuedBytes = 0;
205887
+ if (this.pendingInput) {
205888
+ this.input.write(this.pendingInput);
205889
+ this.pendingInput = "";
205890
+ }
205891
+ if (this.stdinEnded) this.finishInput();
205892
+ }
205893
+ stop() {
205894
+ if (this.stopped) return;
205895
+ this.stopped = true;
205896
+ this.stdin.off("data", this.onData);
205897
+ this.stdin.off("error", this.onStdinError);
205898
+ this.stdin.off("end", this.onStdinEnd);
205899
+ this.queuedMessages.length = 0;
205900
+ this.pendingInput = "";
205901
+ this.queuedBytes = 0;
205902
+ if (!this.input.destroyed) this.input.end();
205903
+ }
205904
+ handleLine(line) {
205905
+ const parsed = this.parseLine(line);
205906
+ if (!parsed) return;
205907
+ if ("error" in parsed) {
205908
+ this.writeJsonRpcError(null, parsed.error.code, parsed.error.message);
205909
+ return;
205910
+ }
205911
+ if (!this.ready && this.isDiscoveryRequest(parsed.message)) {
205912
+ const response = {
205913
+ jsonrpc: "2.0",
205914
+ id: parsed.message.id,
205915
+ result: createMcpStartupDiscoverResult(this.serverInfo)
205916
+ };
205917
+ try {
205918
+ this.stdout.write(`${JSON.stringify(response)}
205919
+ `);
205920
+ } catch (error40) {
205921
+ this.fail(error40 instanceof Error ? error40 : new Error(String(error40)));
205922
+ }
205923
+ return;
205924
+ }
205925
+ if (this.ready) {
205926
+ this.input.write(line);
205927
+ return;
205928
+ }
205929
+ const bytes = Buffer.byteLength(line, "utf8");
205930
+ if (this.queuedBytes + bytes > this.maxBufferSize) {
205931
+ this.fail(new Error(`stdio startup queue exceeded ${this.maxBufferSize} bytes`));
205932
+ return;
205933
+ }
205934
+ this.queuedMessages.push(line);
205935
+ this.queuedBytes += bytes;
205936
+ }
205937
+ parseLine(line) {
205938
+ const trimmed = line.endsWith("\n") ? line.slice(0, -1).replace(/\r$/, "") : line;
205939
+ if (!trimmed.trim()) return null;
205940
+ let value;
205941
+ try {
205942
+ value = JSON.parse(trimmed);
205943
+ } catch {
205944
+ return { error: { code: -32700, message: "Parse error" } };
205945
+ }
205946
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
205947
+ return { error: { code: -32600, message: "Invalid Request" } };
205948
+ }
205949
+ const message = value;
205950
+ if (message.jsonrpc !== "2.0") {
205951
+ return { error: { code: -32600, message: "Invalid Request" } };
205952
+ }
205953
+ const hasId = Object.prototype.hasOwnProperty.call(message, "id");
205954
+ const hasMethod = typeof message.method === "string";
205955
+ const hasResult = Object.prototype.hasOwnProperty.call(message, "result");
205956
+ const hasError = Object.prototype.hasOwnProperty.call(message, "error");
205957
+ if (hasMethod && (hasResult || hasError)) {
205958
+ return { error: { code: -32600, message: "Invalid Request" } };
205959
+ }
205960
+ if (!hasMethod && !hasResult && !hasError) {
205961
+ return { error: { code: -32600, message: "Invalid Request" } };
205962
+ }
205963
+ if (hasId && !this.isValidRequestId(message.id)) {
205964
+ return { error: { code: -32600, message: "Invalid Request" } };
205965
+ }
205966
+ if (!hasMethod && !hasId) {
205967
+ return { error: { code: -32600, message: "Invalid Request" } };
205968
+ }
205969
+ return { message };
205970
+ }
205971
+ isDiscoveryRequest(message) {
205972
+ if (message.method !== MCP_SERVER_DISCOVER_METHOD) return false;
205973
+ if (!Object.prototype.hasOwnProperty.call(message, "id")) return false;
205974
+ const params = message.params;
205975
+ return params === void 0 || typeof params === "object" && params !== null && !Array.isArray(params);
205976
+ }
205977
+ isValidRequestId(id) {
205978
+ return id === null || typeof id === "string" || typeof id === "number" && Number.isFinite(id);
205979
+ }
205980
+ writeJsonRpcError(id, code, message) {
205981
+ try {
205982
+ this.stdout.write(`${JSON.stringify({
205983
+ jsonrpc: "2.0",
205984
+ id,
205985
+ error: { code, message }
205986
+ })}
205987
+ `);
205988
+ } catch (error40) {
205989
+ this.fail(error40 instanceof Error ? error40 : new Error(String(error40)));
205990
+ }
205991
+ }
205992
+ finishInput() {
205993
+ if (!this.ready || this.input.destroyed || this.input.writableEnded) return;
205994
+ this.input.end();
205995
+ this.input.once("end", () => this.notifyEnd());
205996
+ }
205997
+ notifyEnd() {
205998
+ if (this.endNotified) return;
205999
+ this.endNotified = true;
206000
+ this.onEnd?.();
206001
+ }
206002
+ fail(error40) {
206003
+ if (this.stopped) return;
206004
+ this.stopped = true;
206005
+ this.stdin.off("data", this.onData);
206006
+ this.stdin.off("error", this.onStdinError);
206007
+ this.stdin.off("end", this.onStdinEnd);
206008
+ this.queuedMessages.length = 0;
206009
+ this.pendingInput = "";
206010
+ this.queuedBytes = 0;
206011
+ if (!this.input.destroyed) this.input.destroy();
206012
+ this.onError?.(error40);
206013
+ }
206014
+ };
206015
+ }
206016
+ });
206017
+
205767
206018
  // src/cli/mcp-root-path.ts
205768
206019
  import path37 from "path";
205769
206020
  function mcpFileUriToPath(uri, platform2 = process.platform) {
@@ -219212,40 +219463,22 @@ var init_request_context = __esm({
219212
219463
  // src/server/mcp-compat.ts
219213
219464
  function buildMcpDiscoverResult(server, serverInfo) {
219214
219465
  const getCapabilities = server.server.getCapabilities.bind(server.server);
219215
- return {
219216
- resultType: "complete",
219217
- supportedVersions: [
219218
- MCP_MODERN_PROTOCOL_VERSION,
219219
- MCP_LEGACY_PROTOCOL_VERSION,
219220
- MCP_OLDEST_LEGACY_PROTOCOL_VERSION
219221
- ],
219222
- capabilities: getCapabilities(),
219223
- _meta: {
219224
- [MCP_SERVER_INFO_META_KEY]: { ...serverInfo }
219225
- },
219226
- // v1 has no response-cache negotiation. A zero private TTL is the
219227
- // conservative result and prevents clients from inventing a shared cache.
219228
- ttlMs: 0,
219229
- cacheScope: "private"
219230
- };
219466
+ return createMcpDiscoverResult(getCapabilities(), serverInfo);
219231
219467
  }
219232
219468
  function registerMcpDiscovery(server, serverInfo) {
219233
219469
  if (registeredServers.has(server)) return;
219234
219470
  server.server.setRequestHandler(ServerDiscoverRequestSchema, async () => buildMcpDiscoverResult(server, serverInfo));
219235
219471
  registeredServers.add(server);
219236
219472
  }
219237
- var MCP_MODERN_PROTOCOL_VERSION, MCP_LEGACY_PROTOCOL_VERSION, MCP_OLDEST_LEGACY_PROTOCOL_VERSION, MCP_SERVER_DISCOVER_METHOD, MCP_SERVER_INFO_META_KEY, ServerDiscoverRequestSchema, registeredServers;
219473
+ var ServerDiscoverRequestSchema, registeredServers;
219238
219474
  var init_mcp_compat = __esm({
219239
219475
  "src/server/mcp-compat.ts"() {
219240
219476
  "use strict";
219241
219477
  init_esm_shims();
219242
219478
  init_types8();
219243
219479
  init_v4();
219244
- MCP_MODERN_PROTOCOL_VERSION = "2026-07-28";
219245
- MCP_LEGACY_PROTOCOL_VERSION = "2025-11-25";
219246
- MCP_OLDEST_LEGACY_PROTOCOL_VERSION = "2024-11-05";
219247
- MCP_SERVER_DISCOVER_METHOD = "server/discover";
219248
- MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
219480
+ init_mcp_discovery();
219481
+ init_mcp_discovery();
219249
219482
  ServerDiscoverRequestSchema = RequestSchema.extend({
219250
219483
  method: external_exports.literal(MCP_SERVER_DISCOVER_METHOD),
219251
219484
  params: external_exports.looseObject({}).optional()
@@ -261633,10 +261866,7 @@ The path should point to a directory containing a .git folder.`
261633
261866
  isError: true
261634
261867
  };
261635
261868
  };
261636
- const serverInfo = {
261637
- name: "memorix",
261638
- version: true ? "1.8.2" : "1.0.1"
261639
- };
261869
+ const serverInfo = getMcpServerInfo();
261640
261870
  const server = existingServer ?? new McpServer(serverInfo);
261641
261871
  registerMcpDiscovery(server, serverInfo);
261642
261872
  const originalRegisterTool = server.registerTool.bind(server);
@@ -266372,6 +266602,8 @@ var init_serve = __esm({
266372
266602
  init_esm_shims();
266373
266603
  init_dist2();
266374
266604
  init_tool_profile();
266605
+ init_mcp_discovery();
266606
+ init_stdio_startup_gate();
266375
266607
  init_mcp_root_path();
266376
266608
  serve_default = defineCommand({
266377
266609
  meta: {
@@ -266396,15 +266628,25 @@ var init_serve = __esm({
266396
266628
  }
266397
266629
  },
266398
266630
  run: async ({ args }) => {
266631
+ const startupGate = new StdioStartupGate({
266632
+ stdin: process.stdin,
266633
+ stdout: process.stdout,
266634
+ serverInfo: getMcpServerInfo(),
266635
+ onError: (error40) => {
266636
+ console.error(`[memorix] stdio startup gate error: ${error40.message}`);
266637
+ process.exit(1);
266638
+ },
266639
+ onEnd: () => {
266640
+ console.error("[memorix] stdin closed \u2014 exiting");
266641
+ process.exitCode = 0;
266642
+ }
266643
+ });
266644
+ startupGate.start();
266399
266645
  const { StdioServerTransport: StdioServerTransport2 } = await Promise.resolve().then(() => (init_stdio2(), stdio_exports));
266400
266646
  const { createMemorixServer: createMemorixServer2 } = await Promise.resolve().then(() => (init_server4(), server_exports2));
266401
266647
  const { detectProject: detectProject2, findGitInSubdirs: findGitInSubdirs2, isSystemDirectory: isSystemDirectory2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
266402
266648
  const { homedir: homedir36 } = await import("os");
266403
266649
  const { resolveServeProject: resolveServeProject2 } = await Promise.resolve().then(() => (init_serve_shared(), serve_shared_exports));
266404
- process.stdin.on("end", () => {
266405
- console.error("[memorix] stdin closed \u2014 exiting");
266406
- process.exit(0);
266407
- });
266408
266650
  let safeCwd;
266409
266651
  try {
266410
266652
  safeCwd = process.cwd();
@@ -266439,8 +266681,9 @@ var init_serve = __esm({
266439
266681
  const toolProfile = resolveToolProfile({ explicit: args.mode, envValue: process.env.MEMORIX_MODE, fallback: "micro" });
266440
266682
  const serverOptions = detected ? { toolProfile, deferProjectRuntimeInit: true } : { allowUntrackedFallback: allowUntracked, deferProjectInitUntilBound: !allowUntracked, deferProjectRuntimeInit: true, toolProfile };
266441
266683
  const { server, projectId, deferredInit, switchProject } = await createMemorixServer2(projectRoot, void 0, void 0, serverOptions);
266442
- const transport = new StdioServerTransport2();
266684
+ const transport = new StdioServerTransport2(startupGate.input, process.stdout);
266443
266685
  await server.connect(transport);
266686
+ startupGate.markReady();
266444
266687
  console.error(`[memorix] MCP Server running on stdio (project: ${projectId})`);
266445
266688
  console.error(`[memorix] Project root: ${detected?.rootPath ?? projectRoot}`);
266446
266689
  const tryRootsSwitch = async () => {
@@ -269036,7 +269279,7 @@ var init_serve_http = __esm({
269036
269279
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
269037
269280
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
269038
269281
  res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id, Mcp-Project-Handle, Mcp-Protocol-Version, Mcp-Method, Mcp-Name");
269039
- res.setHeader("Mcp-Protocol-Version", req.headers["mcp-protocol-version"] || CURRENT_MCP_PROTOCOL_VERSION);
269282
+ res.setHeader("Mcp-Protocol-Version", MCP_SDK_COMPAT_PROTOCOL_VERSION);
269040
269283
  res.setHeader("Mcp-Method", req.method || "UNKNOWN");
269041
269284
  res.setHeader("Mcp-Name", MCP_SERVER_NAME);
269042
269285
  }
@@ -269050,11 +269293,6 @@ var init_serve_http = __esm({
269050
269293
  }
269051
269294
  const requestedRoot = typeof req.headers["mcp-project-root"] === "string" ? req.headers["mcp-project-root"] : void 0;
269052
269295
  const root = persisted?.projectRoot ?? requestedRoot ?? projectRoot;
269053
- if (!persisted && !isInitializeRequest2(body) && !isServerDiscoverRequest(body)) {
269054
- res.writeHead(400, { "Content-Type": "application/json" });
269055
- res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Stateless MCP requests require Mcp-Project-Handle; initialize first." }, id: null }));
269056
- return;
269057
- }
269058
269296
  const { createMemorixServer: createMemorixServer3 } = await Promise.resolve().then(() => (init_server4(), server_exports2));
269059
269297
  const { createProjectBindingController: createProjectBindingController3 } = await Promise.resolve().then(() => (init_request_context(), request_context_exports));
269060
269298
  const binding = createProjectBindingController3(root);
@@ -269081,7 +269319,7 @@ var init_serve_http = __esm({
269081
269319
  try {
269082
269320
  await server.connect(transport);
269083
269321
  let effectiveHandle = persisted;
269084
- if (!effectiveHandle && projectId !== "__unresolved__") {
269322
+ if (!effectiveHandle && projectId !== "__unresolved__" && isInitializeRequest2(body)) {
269085
269323
  effectiveHandle = statelessBindingStore.create({
269086
269324
  projectId,
269087
269325
  projectRoot: root,