@json-to-office/mcp-server 1.2.0 → 1.4.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/cli.js +161 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.js +161 -15
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
8
8
|
|
|
9
9
|
// src/lib/version.ts
|
|
10
|
-
var SERVER_VERSION = true ? "1.
|
|
10
|
+
var SERVER_VERSION = true ? "1.4.0" : "dev-mode";
|
|
11
11
|
var SERVER_NAME = "json-to-office";
|
|
12
12
|
var PACKAGE_NAME = "@json-to-office/mcp-server";
|
|
13
13
|
|
|
@@ -170,6 +170,9 @@ function outputSchema(properties, required = []) {
|
|
|
170
170
|
import {
|
|
171
171
|
runWithDiagnosticSink
|
|
172
172
|
} from "@json-to-office/jto-ops";
|
|
173
|
+
import {
|
|
174
|
+
RENDERER_DEPENDENCY_MISSING
|
|
175
|
+
} from "@json-to-office/shared";
|
|
173
176
|
import { ValueErrorType } from "@sinclair/typebox/errors";
|
|
174
177
|
var ERROR_CODES = {
|
|
175
178
|
/** An exception escaped a tool handler. Always a bug here, never the caller's. */
|
|
@@ -313,7 +316,11 @@ function toolResult(payload) {
|
|
|
313
316
|
structuredContent: payload
|
|
314
317
|
};
|
|
315
318
|
}
|
|
316
|
-
var HOST_DEPENDENCY_ERRORS = /* @__PURE__ */ new Set([
|
|
319
|
+
var HOST_DEPENDENCY_ERRORS = /* @__PURE__ */ new Set([RENDERER_DEPENDENCY_MISSING]);
|
|
320
|
+
function stackAllowed() {
|
|
321
|
+
const flag = process.env.JTO_MCP_DEBUG_STACKS;
|
|
322
|
+
return flag === "1" || flag === "true";
|
|
323
|
+
}
|
|
317
324
|
function hostNote(text, tone = "muted") {
|
|
318
325
|
return {
|
|
319
326
|
// Never `error`. The body has already decided `ok` by the time a note
|
|
@@ -364,7 +371,7 @@ async function guarded(body) {
|
|
|
364
371
|
return withHostNotes(
|
|
365
372
|
failure(code, message2, {
|
|
366
373
|
context: {
|
|
367
|
-
...error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
374
|
+
...stackAllowed() && error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
368
375
|
}
|
|
369
376
|
}),
|
|
370
377
|
notes
|
|
@@ -607,7 +614,7 @@ function register(server, deps) {
|
|
|
607
614
|
"jto_info",
|
|
608
615
|
{
|
|
609
616
|
title: "Server info",
|
|
610
|
-
description: "Versions, supported formats
|
|
617
|
+
description: "Versions, supported formats with each renderer and whether its backend loads here, workspace availability, output-root and size limits, and whether the optional host dependencies (LibreOffice and poppler for jto_preview, a Highcharts export server for the DOCX `highcharts` component) are present on this host. Call this first.",
|
|
611
618
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
612
619
|
inputSchema: S({
|
|
613
620
|
type: "object",
|
|
@@ -659,10 +666,32 @@ function register(server, deps) {
|
|
|
659
666
|
rendererIds: {
|
|
660
667
|
type: "array",
|
|
661
668
|
items: { type: "string" },
|
|
662
|
-
description: "Defaults first."
|
|
669
|
+
description: "Defaults first. Registered, which is not the same as usable \u2014 read `renderers` before picking one."
|
|
670
|
+
},
|
|
671
|
+
renderers: {
|
|
672
|
+
type: "array",
|
|
673
|
+
description: "Every registered renderer with whether its backend loads on this host. A renderer with `available: false` will fail every render until `installHint` is run.",
|
|
674
|
+
items: {
|
|
675
|
+
type: "object",
|
|
676
|
+
properties: {
|
|
677
|
+
id: { type: "string" },
|
|
678
|
+
default: { type: "boolean" },
|
|
679
|
+
available: { type: "boolean" },
|
|
680
|
+
reason: { type: "string" },
|
|
681
|
+
installHint: { type: "string" }
|
|
682
|
+
},
|
|
683
|
+
required: ["id", "default", "available"],
|
|
684
|
+
additionalProperties: false
|
|
685
|
+
}
|
|
663
686
|
}
|
|
664
687
|
},
|
|
665
|
-
required: [
|
|
688
|
+
required: [
|
|
689
|
+
"name",
|
|
690
|
+
"extension",
|
|
691
|
+
"label",
|
|
692
|
+
"rendererIds",
|
|
693
|
+
"renderers"
|
|
694
|
+
],
|
|
666
695
|
additionalProperties: false
|
|
667
696
|
}
|
|
668
697
|
},
|
|
@@ -718,9 +747,21 @@ function register(server, deps) {
|
|
|
718
747
|
const formats = await Promise.all(
|
|
719
748
|
FORMAT_NAMES.map(async (name) => {
|
|
720
749
|
const adapter = deps.getAdapter(name);
|
|
721
|
-
let
|
|
750
|
+
let renderers = [];
|
|
722
751
|
try {
|
|
723
|
-
|
|
752
|
+
renderers = (await adapter.rendererStatuses()).map(
|
|
753
|
+
(status) => ({
|
|
754
|
+
id: status.id,
|
|
755
|
+
default: status.default,
|
|
756
|
+
available: status.available,
|
|
757
|
+
...status.reason !== void 0 && {
|
|
758
|
+
reason: status.reason
|
|
759
|
+
},
|
|
760
|
+
...status.installHint !== void 0 && {
|
|
761
|
+
installHint: status.installHint
|
|
762
|
+
}
|
|
763
|
+
})
|
|
764
|
+
);
|
|
724
765
|
} catch (error) {
|
|
725
766
|
diagnostics.push(
|
|
726
767
|
diagnostic(
|
|
@@ -730,11 +771,32 @@ function register(server, deps) {
|
|
|
730
771
|
)
|
|
731
772
|
);
|
|
732
773
|
}
|
|
774
|
+
for (const renderer of renderers) {
|
|
775
|
+
if (renderer.available) continue;
|
|
776
|
+
diagnostics.push(
|
|
777
|
+
diagnostic(
|
|
778
|
+
ERROR_CODES.DEPENDENCY_MISSING,
|
|
779
|
+
`The "${renderer.id}" ${name} renderer is registered but cannot load on this host, so every render through it will fail.`,
|
|
780
|
+
{
|
|
781
|
+
severity: "warning",
|
|
782
|
+
...renderer.installHint && {
|
|
783
|
+
suggestion: `Install its backend: ${renderer.installHint}. Until then use one of: ${renderers.filter((entry) => entry.available).map((entry) => `"${entry.id}"`).join(", ")}.`
|
|
784
|
+
},
|
|
785
|
+
context: {
|
|
786
|
+
format: name,
|
|
787
|
+
renderer: renderer.id,
|
|
788
|
+
...renderer.reason && { reason: renderer.reason }
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
}
|
|
733
794
|
return {
|
|
734
795
|
name: adapter.name,
|
|
735
796
|
extension: adapter.extension,
|
|
736
797
|
label: adapter.label,
|
|
737
|
-
rendererIds
|
|
798
|
+
rendererIds: renderers.map((renderer) => renderer.id),
|
|
799
|
+
renderers
|
|
738
800
|
};
|
|
739
801
|
})
|
|
740
802
|
);
|
|
@@ -1122,8 +1184,16 @@ async function catalogFormat(format, deps, diagnostics) {
|
|
|
1122
1184
|
const schemas = formatSchemas(format);
|
|
1123
1185
|
const adapter = deps.getAdapter(format);
|
|
1124
1186
|
let rendererIds = [];
|
|
1187
|
+
const availability = /* @__PURE__ */ new Map();
|
|
1188
|
+
const installHints = /* @__PURE__ */ new Map();
|
|
1189
|
+
let probed = false;
|
|
1125
1190
|
try {
|
|
1126
|
-
|
|
1191
|
+
for (const status of await adapter.rendererStatuses()) {
|
|
1192
|
+
rendererIds.push(status.id);
|
|
1193
|
+
availability.set(status.id, status.available);
|
|
1194
|
+
if (status.installHint) installHints.set(status.id, status.installHint);
|
|
1195
|
+
}
|
|
1196
|
+
probed = true;
|
|
1127
1197
|
} catch (error) {
|
|
1128
1198
|
diagnostics.push(
|
|
1129
1199
|
diagnostic(
|
|
@@ -1239,6 +1309,14 @@ async function catalogFormat(format, deps, diagnostics) {
|
|
|
1239
1309
|
renderers: orderedIds.map((id, index) => ({
|
|
1240
1310
|
id,
|
|
1241
1311
|
default: index === 0,
|
|
1312
|
+
// Three cases, and they are not the same. A probed renderer answers for
|
|
1313
|
+
// itself. A profile the cores never registered has no status but is
|
|
1314
|
+
// already reported as drift above, so calling it unavailable would be a
|
|
1315
|
+
// second, worse description of that. And a probe that threw knows
|
|
1316
|
+
// nothing about any of them — reporting those as usable would contradict
|
|
1317
|
+
// the diagnostic pushed beside them.
|
|
1318
|
+
available: availability.get(id) ?? probed,
|
|
1319
|
+
...installHints.has(id) && { installHint: installHints.get(id) },
|
|
1242
1320
|
components: [...byRenderer.get(id)?.components.keys() ?? []].sort(),
|
|
1243
1321
|
unsupported: allNames.filter((name) => !byRenderer.get(id)?.components.has(name)).sort()
|
|
1244
1322
|
})),
|
|
@@ -1308,6 +1386,14 @@ function register2(server, deps) {
|
|
|
1308
1386
|
properties: {
|
|
1309
1387
|
id: { type: "string" },
|
|
1310
1388
|
default: { type: "boolean" },
|
|
1389
|
+
available: {
|
|
1390
|
+
type: "boolean",
|
|
1391
|
+
description: "Whether this renderer's backend loads on this host. A renderer that is registered but unavailable accepts the components below and then fails every render."
|
|
1392
|
+
},
|
|
1393
|
+
installHint: {
|
|
1394
|
+
type: "string",
|
|
1395
|
+
description: "The command that would make an unavailable renderer available."
|
|
1396
|
+
},
|
|
1311
1397
|
components: {
|
|
1312
1398
|
type: "array",
|
|
1313
1399
|
items: { type: "string" }
|
|
@@ -1318,7 +1404,13 @@ function register2(server, deps) {
|
|
|
1318
1404
|
description: "Components another renderer of this format accepts and this one does not."
|
|
1319
1405
|
}
|
|
1320
1406
|
},
|
|
1321
|
-
required: [
|
|
1407
|
+
required: [
|
|
1408
|
+
"id",
|
|
1409
|
+
"default",
|
|
1410
|
+
"available",
|
|
1411
|
+
"components",
|
|
1412
|
+
"unsupported"
|
|
1413
|
+
],
|
|
1322
1414
|
additionalProperties: false
|
|
1323
1415
|
}
|
|
1324
1416
|
},
|
|
@@ -1758,6 +1850,39 @@ function withRenderer(document, renderer) {
|
|
|
1758
1850
|
}
|
|
1759
1851
|
return { ...document, renderer };
|
|
1760
1852
|
}
|
|
1853
|
+
function effectiveRenderer(document, override2) {
|
|
1854
|
+
if (override2 !== void 0) return override2;
|
|
1855
|
+
if (typeof document === "object" && document !== null) {
|
|
1856
|
+
const declared = document.renderer;
|
|
1857
|
+
if (typeof declared === "string") return declared;
|
|
1858
|
+
}
|
|
1859
|
+
return void 0;
|
|
1860
|
+
}
|
|
1861
|
+
async function rendererAvailability(adapter, document, override2) {
|
|
1862
|
+
const wanted = effectiveRenderer(document, override2);
|
|
1863
|
+
let statuses;
|
|
1864
|
+
try {
|
|
1865
|
+
statuses = await adapter.rendererStatuses();
|
|
1866
|
+
} catch {
|
|
1867
|
+
return void 0;
|
|
1868
|
+
}
|
|
1869
|
+
const status = wanted ? statuses.find((entry) => entry.id === wanted) : statuses.find((entry) => entry.default);
|
|
1870
|
+
if (!status || status.available) return void 0;
|
|
1871
|
+
const usable = statuses.filter((entry) => entry.available).map((entry) => `"${entry.id}"`);
|
|
1872
|
+
return diagnostic(
|
|
1873
|
+
ERROR_CODES.DEPENDENCY_MISSING,
|
|
1874
|
+
`This document validates against the "${status.id}" ${adapter.name} renderer, but that renderer cannot load on this host \u2014 generating with it will fail.`,
|
|
1875
|
+
{
|
|
1876
|
+
severity: "warning",
|
|
1877
|
+
suggestion: status.installHint ? `Install its backend: ${status.installHint}.${usable.length > 0 ? ` Or render with ${usable.join(" or ")}.` : ""}` : `Render with ${usable.join(" or ")} instead.`,
|
|
1878
|
+
context: {
|
|
1879
|
+
format: adapter.name,
|
|
1880
|
+
renderer: status.id,
|
|
1881
|
+
...status.reason && { reason: status.reason }
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1761
1886
|
|
|
1762
1887
|
// src/lib/workspace-store.ts
|
|
1763
1888
|
var unavailable = () => failure(
|
|
@@ -1867,7 +1992,7 @@ function register4(server, deps) {
|
|
|
1867
1992
|
"jto_validate",
|
|
1868
1993
|
{
|
|
1869
1994
|
title: "Validate a document",
|
|
1870
|
-
description: "Check a document against its format schema and report every defect as a path-addressed diagnostic. Paths are RFC 6901 JSON Pointers into the document you passed, so they can be used directly as patch targets; codes are the stable `E_`/`W_` vocabulary, e.g. `E_REQUIRED_PROPERTY`, `E_UNEXPECTED_PROPERTY`, `E_TYPE_MISMATCH`, `E_UNKNOWN_COMPONENT`. `ok` mirrors the gate generation applies: schema and semantic errors block it \u2014 the semantic rules the published JSON Schema cannot state, such as a text component needing one of `text`/`runs`, are checked here and only here \u2014 while renderer-profile findings (code `W_UNSUPPORTED_RENDERER_FEATURE`) come back as warnings because the renderer, not the schema, has the last word on those. A broken document is a normal result with `ok: false`, never an error.",
|
|
1995
|
+
description: "Check a document against its format schema and report every defect as a path-addressed diagnostic. Paths are RFC 6901 JSON Pointers into the document you passed, so they can be used directly as patch targets; codes are the stable `E_`/`W_` vocabulary, e.g. `E_REQUIRED_PROPERTY`, `E_UNEXPECTED_PROPERTY`, `E_TYPE_MISMATCH`, `E_UNKNOWN_COMPONENT`. `ok` mirrors the gate generation applies: schema and semantic errors block it \u2014 the semantic rules the published JSON Schema cannot state, such as a text component needing one of `text`/`runs`, are checked here and only here \u2014 while renderer-profile findings (code `W_UNSUPPORTED_RENDERER_FEATURE`) come back as warnings because the renderer, not the schema, has the last word on those. A broken document is a normal result with `ok: false`, never an error. A renderer whose backend is not installed on this host is reported here too, as a `E_DEPENDENCY_MISSING` warning \u2014 the document may be fine and the host merely incomplete.",
|
|
1871
1996
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1872
1997
|
inputSchema: S({
|
|
1873
1998
|
type: "object",
|
|
@@ -1928,7 +2053,15 @@ function register4(server, deps) {
|
|
|
1928
2053
|
const result = adapter.validateDocument(
|
|
1929
2054
|
withRenderer(resolved.document, args.renderer)
|
|
1930
2055
|
);
|
|
1931
|
-
const
|
|
2056
|
+
const unavailable2 = await rendererAvailability(
|
|
2057
|
+
adapter,
|
|
2058
|
+
resolved.document,
|
|
2059
|
+
args.renderer
|
|
2060
|
+
);
|
|
2061
|
+
const all = [
|
|
2062
|
+
...validationDiagnostics(result.errors),
|
|
2063
|
+
...unavailable2 ? [unavailable2] : []
|
|
2064
|
+
];
|
|
1932
2065
|
const counts = countDiagnostics(all);
|
|
1933
2066
|
const { kept, truncated } = capDiagnostics(
|
|
1934
2067
|
all,
|
|
@@ -2818,6 +2951,9 @@ import crypto2 from "crypto";
|
|
|
2818
2951
|
import { promises as fs6 } from "fs";
|
|
2819
2952
|
import os2 from "os";
|
|
2820
2953
|
import path5 from "path";
|
|
2954
|
+
import {
|
|
2955
|
+
RENDERER_DEPENDENCY_MISSING as RENDERER_DEPENDENCY_MISSING2
|
|
2956
|
+
} from "@json-to-office/shared";
|
|
2821
2957
|
import { getFontStager } from "@json-to-office/jto-ops";
|
|
2822
2958
|
|
|
2823
2959
|
// src/preview/cache-key.ts
|
|
@@ -3149,6 +3285,15 @@ function renderFailure(stage, detail, context = {}) {
|
|
|
3149
3285
|
{ suggestion, context: { stage, ...context } }
|
|
3150
3286
|
);
|
|
3151
3287
|
}
|
|
3288
|
+
function buildFailure(error) {
|
|
3289
|
+
if (error instanceof Error && error.name === RENDERER_DEPENDENCY_MISSING2) {
|
|
3290
|
+
return failure(ERROR_CODES.DEPENDENCY_MISSING, message(error), {
|
|
3291
|
+
suggestion: "Install the renderer's backend, or re-run with a renderer jto_info reports as available. The document is not at fault.",
|
|
3292
|
+
context: { stage: "build" }
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
return renderFailure("build", message(error));
|
|
3296
|
+
}
|
|
3152
3297
|
var PNG_SIGNATURE = Buffer.from([
|
|
3153
3298
|
137,
|
|
3154
3299
|
80,
|
|
@@ -3355,9 +3500,10 @@ async function renderPreview(options) {
|
|
|
3355
3500
|
converters = versions;
|
|
3356
3501
|
} catch (error) {
|
|
3357
3502
|
if (signal?.aborted) return cancelled2();
|
|
3503
|
+
const build = buildFailure(error);
|
|
3358
3504
|
return failureFrom([
|
|
3359
|
-
...validationDiagnostics2(options.getAdapter(format), document),
|
|
3360
|
-
...
|
|
3505
|
+
...build.diagnostics[0]?.code === ERROR_CODES.DEPENDENCY_MISSING ? [] : validationDiagnostics2(options.getAdapter(format), document),
|
|
3506
|
+
...build.diagnostics
|
|
3361
3507
|
]);
|
|
3362
3508
|
}
|
|
3363
3509
|
const generateMs = elapsed(generateStarted);
|