@json-to-office/mcp-server 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -564,6 +564,19 @@ interface RestoredWorkspace extends PersistedMeta {
564
564
  head: PersistedDocument;
565
565
  pins: PersistedDocument[];
566
566
  }
567
+ /**
568
+ * The durable half of a workspace store.
569
+ *
570
+ * Not internally synchronized: `save` and `remove` each read the root, decide
571
+ * what to prune and then act, so two of them running at once can both decide
572
+ * there is room, or one can delete a directory the other is half way through
573
+ * writing. Callers serialize — `createMemoryWorkspaceStore` runs every durable
574
+ * operation through one queue, which is also where the ordering against a
575
+ * close is settled. Reads (`list`, `load`) need no such treatment: metadata is
576
+ * renamed into place after the revision it names, so a concurrent read sees
577
+ * one committed state or the other, and `load` re-reads before it treats a
578
+ * missing document as a torn directory.
579
+ */
567
580
  interface WorkspacePersistence {
568
581
  /** Absolute path of the root. May not exist on disk until first write. */
569
582
  readonly root: string;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { McpServer } from "@modelcontextprotocol/server";
3
3
 
4
4
  // src/lib/version.ts
5
- var SERVER_VERSION = true ? "1.8.0" : "dev-mode";
5
+ var SERVER_VERSION = true ? "1.9.0" : "dev-mode";
6
6
  var SERVER_NAME = "json-to-office";
7
7
  var PACKAGE_NAME = "@json-to-office/mcp-server";
8
8
 
@@ -5265,9 +5265,16 @@ function createWorkspacePersistenceAt(root, limits = {}) {
5265
5265
  },
5266
5266
  async load(handle) {
5267
5267
  if (!isPersistableHandle(handle)) return void 0;
5268
- const meta = await readMeta(handle);
5268
+ let meta = await readMeta(handle);
5269
5269
  if (!meta) return void 0;
5270
- const head = await readRevision(handle, meta.revision);
5270
+ let head = await readRevision(handle, meta.revision);
5271
+ if (!head) {
5272
+ const second = await readMeta(handle);
5273
+ if (second) {
5274
+ meta = second;
5275
+ head = await readRevision(handle, second.revision);
5276
+ }
5277
+ }
5271
5278
  if (!head) {
5272
5279
  await fs7.rm(dirFor(handle), { recursive: true, force: true }).catch(() => void 0);
5273
5280
  return void 0;
@@ -5434,45 +5441,67 @@ function createMemoryWorkspaceStore(options = {}) {
5434
5441
  if (at - entry.touchedAt > limits.idleTtlMs) evict(entry, "ttl");
5435
5442
  }
5436
5443
  }
5444
+ let durable = Promise.resolve();
5445
+ function queued(work) {
5446
+ const running = durable.then(work);
5447
+ durable = running.catch(() => void 0);
5448
+ return running;
5449
+ }
5437
5450
  async function persist(entry) {
5438
5451
  if (!persistence) return void 0;
5439
5452
  try {
5440
- await persistence.save({
5441
- handle: entry.handle,
5442
- format: entry.format,
5443
- createdAt: entry.createdAt,
5444
- updatedAt: entry.updatedAt,
5445
- ...entry.title !== void 0 && { title: entry.title },
5446
- head: {
5447
- revision: entry.revision,
5448
- text: entry.text,
5449
- bytes: entry.bytes
5450
- },
5451
- pins: [...entry.pins.entries()].map(([revision, pin]) => ({
5452
- revision,
5453
- text: pin.text,
5454
- bytes: pin.bytes
5455
- }))
5453
+ const written = await queued(async () => {
5454
+ if (entries.get(entry.handle) !== entry) return false;
5455
+ await saveEntry(entry);
5456
+ return true;
5456
5457
  });
5458
+ if (!written) {
5459
+ entry.persisted = false;
5460
+ return void 0;
5461
+ }
5457
5462
  entry.persisted = true;
5458
5463
  return void 0;
5459
5464
  } catch (error) {
5460
5465
  entry.persisted = false;
5461
- return diagnostic(
5462
- WORKSPACE_ERROR_CODES.NOT_PERSISTED,
5463
- `Revision ${entry.revision} of ${entry.handle} is held in memory but was not written to ${persistence.root}: ${error instanceof Error ? error.message : String(error)}`,
5464
- {
5465
- severity: "warning",
5466
- suggestion: "Export the JSON with jto_workspace_snapshot and keep it yourself; this handle will not survive a lost connection.",
5467
- context: {
5468
- handle: entry.handle,
5469
- revision: entry.revision,
5470
- workspaceRoot: persistence.root
5471
- }
5472
- }
5473
- );
5466
+ return notPersisted(entry, error);
5474
5467
  }
5475
5468
  }
5469
+ async function saveEntry(entry) {
5470
+ if (!persistence) return;
5471
+ await persistence.save({
5472
+ handle: entry.handle,
5473
+ format: entry.format,
5474
+ createdAt: entry.createdAt,
5475
+ updatedAt: entry.updatedAt,
5476
+ ...entry.title !== void 0 && { title: entry.title },
5477
+ head: {
5478
+ revision: entry.revision,
5479
+ text: entry.text,
5480
+ bytes: entry.bytes
5481
+ },
5482
+ pins: [...entry.pins.entries()].map(([revision, pin]) => ({
5483
+ revision,
5484
+ text: pin.text,
5485
+ bytes: pin.bytes
5486
+ }))
5487
+ });
5488
+ }
5489
+ function notPersisted(entry, error) {
5490
+ const root = persistence?.root ?? "";
5491
+ return diagnostic(
5492
+ WORKSPACE_ERROR_CODES.NOT_PERSISTED,
5493
+ `Revision ${entry.revision} of ${entry.handle} is held in memory but was not written to ${root}: ${error instanceof Error ? error.message : String(error)}`,
5494
+ {
5495
+ severity: "warning",
5496
+ suggestion: "Export the JSON with jto_workspace_snapshot and keep it yourself; this handle will not survive a lost connection.",
5497
+ context: {
5498
+ handle: entry.handle,
5499
+ revision: entry.revision,
5500
+ workspaceRoot: root
5501
+ }
5502
+ }
5503
+ );
5504
+ }
5476
5505
  function committed(record, warning) {
5477
5506
  return { ok: true, record, ...warning && { warnings: [warning] } };
5478
5507
  }
@@ -5789,24 +5818,33 @@ function createMemoryWorkspaceStore(options = {}) {
5789
5818
  async close(handle) {
5790
5819
  sweep();
5791
5820
  const entry = entries.get(handle);
5792
- let durable = false;
5793
- if (persistence) {
5794
- try {
5795
- durable = await persistence.remove(handle);
5796
- } catch (error) {
5797
- return failure(
5798
- WORKSPACE_ERROR_CODES.NOT_CLOSED,
5799
- `Workspace ${handle} could not be removed from ${persistence.root}: ${error instanceof Error ? error.message : String(error)}. It is still open and still on disk.`,
5800
- {
5801
- suggestion: "Check the workspace directory is writable, then close again.",
5802
- context: { handle, workspaceRoot: persistence.root }
5803
- }
5804
- );
5805
- }
5821
+ if (!persistence) {
5822
+ if (entry) evict(entry, "closed");
5823
+ return { ok: true, handle, closed: entry !== void 0 };
5824
+ }
5825
+ const root = persistence.root;
5826
+ try {
5827
+ return await queued(async () => {
5828
+ const removed = await persistence.remove(handle);
5829
+ const resident = entries.get(handle);
5830
+ if (resident) evict(resident, "closed");
5831
+ else if (removed) tombstone(handle, "closed", 0);
5832
+ return {
5833
+ ok: true,
5834
+ handle,
5835
+ closed: resident !== void 0 || removed
5836
+ };
5837
+ });
5838
+ } catch (error) {
5839
+ return failure(
5840
+ WORKSPACE_ERROR_CODES.NOT_CLOSED,
5841
+ `Workspace ${handle} could not be removed from ${root}: ${error instanceof Error ? error.message : String(error)}. It is still open and still on disk.`,
5842
+ {
5843
+ suggestion: "Check the workspace directory is writable, then close again.",
5844
+ context: { handle, workspaceRoot: root }
5845
+ }
5846
+ );
5806
5847
  }
5807
- if (entry) evict(entry, "closed");
5808
- else if (durable) tombstone(handle, "closed", 0);
5809
- return { ok: true, handle, closed: entry !== void 0 || durable };
5810
5848
  },
5811
5849
  /**
5812
5850
  * Release memory, keep the disk.