@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/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.
|
|
5
|
+
var SERVER_VERSION = true ? "1.4.0" : "dev-mode";
|
|
6
6
|
var SERVER_NAME = "json-to-office";
|
|
7
7
|
var PACKAGE_NAME = "@json-to-office/mcp-server";
|
|
8
8
|
|
|
@@ -165,6 +165,9 @@ function outputSchema(properties, required = []) {
|
|
|
165
165
|
import {
|
|
166
166
|
runWithDiagnosticSink
|
|
167
167
|
} from "@json-to-office/jto-ops";
|
|
168
|
+
import {
|
|
169
|
+
RENDERER_DEPENDENCY_MISSING
|
|
170
|
+
} from "@json-to-office/shared";
|
|
168
171
|
import { ValueErrorType } from "@sinclair/typebox/errors";
|
|
169
172
|
var ERROR_CODES = {
|
|
170
173
|
/** An exception escaped a tool handler. Always a bug here, never the caller's. */
|
|
@@ -308,7 +311,11 @@ function toolResult(payload) {
|
|
|
308
311
|
structuredContent: payload
|
|
309
312
|
};
|
|
310
313
|
}
|
|
311
|
-
var HOST_DEPENDENCY_ERRORS = /* @__PURE__ */ new Set([
|
|
314
|
+
var HOST_DEPENDENCY_ERRORS = /* @__PURE__ */ new Set([RENDERER_DEPENDENCY_MISSING]);
|
|
315
|
+
function stackAllowed() {
|
|
316
|
+
const flag = process.env.JTO_MCP_DEBUG_STACKS;
|
|
317
|
+
return flag === "1" || flag === "true";
|
|
318
|
+
}
|
|
312
319
|
function hostNote(text, tone = "muted") {
|
|
313
320
|
return {
|
|
314
321
|
// Never `error`. The body has already decided `ok` by the time a note
|
|
@@ -359,7 +366,7 @@ async function guarded(body) {
|
|
|
359
366
|
return withHostNotes(
|
|
360
367
|
failure(code, message2, {
|
|
361
368
|
context: {
|
|
362
|
-
...error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
369
|
+
...stackAllowed() && error instanceof Error && error.stack !== void 0 && { stack: error.stack }
|
|
363
370
|
}
|
|
364
371
|
}),
|
|
365
372
|
notes
|
|
@@ -602,7 +609,7 @@ function register(server, deps) {
|
|
|
602
609
|
"jto_info",
|
|
603
610
|
{
|
|
604
611
|
title: "Server info",
|
|
605
|
-
description: "Versions, supported formats
|
|
612
|
+
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.",
|
|
606
613
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
607
614
|
inputSchema: S({
|
|
608
615
|
type: "object",
|
|
@@ -654,10 +661,32 @@ function register(server, deps) {
|
|
|
654
661
|
rendererIds: {
|
|
655
662
|
type: "array",
|
|
656
663
|
items: { type: "string" },
|
|
657
|
-
description: "Defaults first."
|
|
664
|
+
description: "Defaults first. Registered, which is not the same as usable \u2014 read `renderers` before picking one."
|
|
665
|
+
},
|
|
666
|
+
renderers: {
|
|
667
|
+
type: "array",
|
|
668
|
+
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.",
|
|
669
|
+
items: {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {
|
|
672
|
+
id: { type: "string" },
|
|
673
|
+
default: { type: "boolean" },
|
|
674
|
+
available: { type: "boolean" },
|
|
675
|
+
reason: { type: "string" },
|
|
676
|
+
installHint: { type: "string" }
|
|
677
|
+
},
|
|
678
|
+
required: ["id", "default", "available"],
|
|
679
|
+
additionalProperties: false
|
|
680
|
+
}
|
|
658
681
|
}
|
|
659
682
|
},
|
|
660
|
-
required: [
|
|
683
|
+
required: [
|
|
684
|
+
"name",
|
|
685
|
+
"extension",
|
|
686
|
+
"label",
|
|
687
|
+
"rendererIds",
|
|
688
|
+
"renderers"
|
|
689
|
+
],
|
|
661
690
|
additionalProperties: false
|
|
662
691
|
}
|
|
663
692
|
},
|
|
@@ -713,9 +742,21 @@ function register(server, deps) {
|
|
|
713
742
|
const formats = await Promise.all(
|
|
714
743
|
FORMAT_NAMES.map(async (name) => {
|
|
715
744
|
const adapter = deps.getAdapter(name);
|
|
716
|
-
let
|
|
745
|
+
let renderers = [];
|
|
717
746
|
try {
|
|
718
|
-
|
|
747
|
+
renderers = (await adapter.rendererStatuses()).map(
|
|
748
|
+
(status) => ({
|
|
749
|
+
id: status.id,
|
|
750
|
+
default: status.default,
|
|
751
|
+
available: status.available,
|
|
752
|
+
...status.reason !== void 0 && {
|
|
753
|
+
reason: status.reason
|
|
754
|
+
},
|
|
755
|
+
...status.installHint !== void 0 && {
|
|
756
|
+
installHint: status.installHint
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
);
|
|
719
760
|
} catch (error) {
|
|
720
761
|
diagnostics.push(
|
|
721
762
|
diagnostic(
|
|
@@ -725,11 +766,32 @@ function register(server, deps) {
|
|
|
725
766
|
)
|
|
726
767
|
);
|
|
727
768
|
}
|
|
769
|
+
for (const renderer of renderers) {
|
|
770
|
+
if (renderer.available) continue;
|
|
771
|
+
diagnostics.push(
|
|
772
|
+
diagnostic(
|
|
773
|
+
ERROR_CODES.DEPENDENCY_MISSING,
|
|
774
|
+
`The "${renderer.id}" ${name} renderer is registered but cannot load on this host, so every render through it will fail.`,
|
|
775
|
+
{
|
|
776
|
+
severity: "warning",
|
|
777
|
+
...renderer.installHint && {
|
|
778
|
+
suggestion: `Install its backend: ${renderer.installHint}. Until then use one of: ${renderers.filter((entry) => entry.available).map((entry) => `"${entry.id}"`).join(", ")}.`
|
|
779
|
+
},
|
|
780
|
+
context: {
|
|
781
|
+
format: name,
|
|
782
|
+
renderer: renderer.id,
|
|
783
|
+
...renderer.reason && { reason: renderer.reason }
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
)
|
|
787
|
+
);
|
|
788
|
+
}
|
|
728
789
|
return {
|
|
729
790
|
name: adapter.name,
|
|
730
791
|
extension: adapter.extension,
|
|
731
792
|
label: adapter.label,
|
|
732
|
-
rendererIds
|
|
793
|
+
rendererIds: renderers.map((renderer) => renderer.id),
|
|
794
|
+
renderers
|
|
733
795
|
};
|
|
734
796
|
})
|
|
735
797
|
);
|
|
@@ -1117,8 +1179,16 @@ async function catalogFormat(format, deps, diagnostics) {
|
|
|
1117
1179
|
const schemas = formatSchemas(format);
|
|
1118
1180
|
const adapter = deps.getAdapter(format);
|
|
1119
1181
|
let rendererIds = [];
|
|
1182
|
+
const availability = /* @__PURE__ */ new Map();
|
|
1183
|
+
const installHints = /* @__PURE__ */ new Map();
|
|
1184
|
+
let probed = false;
|
|
1120
1185
|
try {
|
|
1121
|
-
|
|
1186
|
+
for (const status of await adapter.rendererStatuses()) {
|
|
1187
|
+
rendererIds.push(status.id);
|
|
1188
|
+
availability.set(status.id, status.available);
|
|
1189
|
+
if (status.installHint) installHints.set(status.id, status.installHint);
|
|
1190
|
+
}
|
|
1191
|
+
probed = true;
|
|
1122
1192
|
} catch (error) {
|
|
1123
1193
|
diagnostics.push(
|
|
1124
1194
|
diagnostic(
|
|
@@ -1234,6 +1304,14 @@ async function catalogFormat(format, deps, diagnostics) {
|
|
|
1234
1304
|
renderers: orderedIds.map((id, index) => ({
|
|
1235
1305
|
id,
|
|
1236
1306
|
default: index === 0,
|
|
1307
|
+
// Three cases, and they are not the same. A probed renderer answers for
|
|
1308
|
+
// itself. A profile the cores never registered has no status but is
|
|
1309
|
+
// already reported as drift above, so calling it unavailable would be a
|
|
1310
|
+
// second, worse description of that. And a probe that threw knows
|
|
1311
|
+
// nothing about any of them — reporting those as usable would contradict
|
|
1312
|
+
// the diagnostic pushed beside them.
|
|
1313
|
+
available: availability.get(id) ?? probed,
|
|
1314
|
+
...installHints.has(id) && { installHint: installHints.get(id) },
|
|
1237
1315
|
components: [...byRenderer.get(id)?.components.keys() ?? []].sort(),
|
|
1238
1316
|
unsupported: allNames.filter((name) => !byRenderer.get(id)?.components.has(name)).sort()
|
|
1239
1317
|
})),
|
|
@@ -1303,6 +1381,14 @@ function register2(server, deps) {
|
|
|
1303
1381
|
properties: {
|
|
1304
1382
|
id: { type: "string" },
|
|
1305
1383
|
default: { type: "boolean" },
|
|
1384
|
+
available: {
|
|
1385
|
+
type: "boolean",
|
|
1386
|
+
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."
|
|
1387
|
+
},
|
|
1388
|
+
installHint: {
|
|
1389
|
+
type: "string",
|
|
1390
|
+
description: "The command that would make an unavailable renderer available."
|
|
1391
|
+
},
|
|
1306
1392
|
components: {
|
|
1307
1393
|
type: "array",
|
|
1308
1394
|
items: { type: "string" }
|
|
@@ -1313,7 +1399,13 @@ function register2(server, deps) {
|
|
|
1313
1399
|
description: "Components another renderer of this format accepts and this one does not."
|
|
1314
1400
|
}
|
|
1315
1401
|
},
|
|
1316
|
-
required: [
|
|
1402
|
+
required: [
|
|
1403
|
+
"id",
|
|
1404
|
+
"default",
|
|
1405
|
+
"available",
|
|
1406
|
+
"components",
|
|
1407
|
+
"unsupported"
|
|
1408
|
+
],
|
|
1317
1409
|
additionalProperties: false
|
|
1318
1410
|
}
|
|
1319
1411
|
},
|
|
@@ -1756,6 +1848,39 @@ function withRenderer(document, renderer) {
|
|
|
1756
1848
|
}
|
|
1757
1849
|
return { ...document, renderer };
|
|
1758
1850
|
}
|
|
1851
|
+
function effectiveRenderer(document, override2) {
|
|
1852
|
+
if (override2 !== void 0) return override2;
|
|
1853
|
+
if (typeof document === "object" && document !== null) {
|
|
1854
|
+
const declared = document.renderer;
|
|
1855
|
+
if (typeof declared === "string") return declared;
|
|
1856
|
+
}
|
|
1857
|
+
return void 0;
|
|
1858
|
+
}
|
|
1859
|
+
async function rendererAvailability(adapter, document, override2) {
|
|
1860
|
+
const wanted = effectiveRenderer(document, override2);
|
|
1861
|
+
let statuses;
|
|
1862
|
+
try {
|
|
1863
|
+
statuses = await adapter.rendererStatuses();
|
|
1864
|
+
} catch {
|
|
1865
|
+
return void 0;
|
|
1866
|
+
}
|
|
1867
|
+
const status = wanted ? statuses.find((entry) => entry.id === wanted) : statuses.find((entry) => entry.default);
|
|
1868
|
+
if (!status || status.available) return void 0;
|
|
1869
|
+
const usable = statuses.filter((entry) => entry.available).map((entry) => `"${entry.id}"`);
|
|
1870
|
+
return diagnostic(
|
|
1871
|
+
ERROR_CODES.DEPENDENCY_MISSING,
|
|
1872
|
+
`This document validates against the "${status.id}" ${adapter.name} renderer, but that renderer cannot load on this host \u2014 generating with it will fail.`,
|
|
1873
|
+
{
|
|
1874
|
+
severity: "warning",
|
|
1875
|
+
suggestion: status.installHint ? `Install its backend: ${status.installHint}.${usable.length > 0 ? ` Or render with ${usable.join(" or ")}.` : ""}` : `Render with ${usable.join(" or ")} instead.`,
|
|
1876
|
+
context: {
|
|
1877
|
+
format: adapter.name,
|
|
1878
|
+
renderer: status.id,
|
|
1879
|
+
...status.reason && { reason: status.reason }
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1759
1884
|
|
|
1760
1885
|
// src/lib/workspace-store.ts
|
|
1761
1886
|
var unavailable = () => failure(
|
|
@@ -1879,7 +2004,7 @@ function register4(server, deps) {
|
|
|
1879
2004
|
"jto_validate",
|
|
1880
2005
|
{
|
|
1881
2006
|
title: "Validate a document",
|
|
1882
|
-
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.",
|
|
2007
|
+
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.",
|
|
1883
2008
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
1884
2009
|
inputSchema: S({
|
|
1885
2010
|
type: "object",
|
|
@@ -1940,7 +2065,15 @@ function register4(server, deps) {
|
|
|
1940
2065
|
const result = adapter.validateDocument(
|
|
1941
2066
|
withRenderer(resolved.document, args.renderer)
|
|
1942
2067
|
);
|
|
1943
|
-
const
|
|
2068
|
+
const unavailable2 = await rendererAvailability(
|
|
2069
|
+
adapter,
|
|
2070
|
+
resolved.document,
|
|
2071
|
+
args.renderer
|
|
2072
|
+
);
|
|
2073
|
+
const all = [
|
|
2074
|
+
...validationDiagnostics(result.errors),
|
|
2075
|
+
...unavailable2 ? [unavailable2] : []
|
|
2076
|
+
];
|
|
1944
2077
|
const counts = countDiagnostics(all);
|
|
1945
2078
|
const { kept, truncated } = capDiagnostics(
|
|
1946
2079
|
all,
|
|
@@ -2830,6 +2963,9 @@ import crypto2 from "crypto";
|
|
|
2830
2963
|
import { promises as fs6 } from "fs";
|
|
2831
2964
|
import os2 from "os";
|
|
2832
2965
|
import path5 from "path";
|
|
2966
|
+
import {
|
|
2967
|
+
RENDERER_DEPENDENCY_MISSING as RENDERER_DEPENDENCY_MISSING2
|
|
2968
|
+
} from "@json-to-office/shared";
|
|
2833
2969
|
import { getFontStager } from "@json-to-office/jto-ops";
|
|
2834
2970
|
|
|
2835
2971
|
// src/preview/cache-key.ts
|
|
@@ -3161,6 +3297,15 @@ function renderFailure(stage, detail, context = {}) {
|
|
|
3161
3297
|
{ suggestion, context: { stage, ...context } }
|
|
3162
3298
|
);
|
|
3163
3299
|
}
|
|
3300
|
+
function buildFailure(error) {
|
|
3301
|
+
if (error instanceof Error && error.name === RENDERER_DEPENDENCY_MISSING2) {
|
|
3302
|
+
return failure(ERROR_CODES.DEPENDENCY_MISSING, message(error), {
|
|
3303
|
+
suggestion: "Install the renderer's backend, or re-run with a renderer jto_info reports as available. The document is not at fault.",
|
|
3304
|
+
context: { stage: "build" }
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
return renderFailure("build", message(error));
|
|
3308
|
+
}
|
|
3164
3309
|
var PNG_SIGNATURE = Buffer.from([
|
|
3165
3310
|
137,
|
|
3166
3311
|
80,
|
|
@@ -3367,9 +3512,10 @@ async function renderPreview(options) {
|
|
|
3367
3512
|
converters = versions;
|
|
3368
3513
|
} catch (error) {
|
|
3369
3514
|
if (signal?.aborted) return cancelled2();
|
|
3515
|
+
const build = buildFailure(error);
|
|
3370
3516
|
return failureFrom([
|
|
3371
|
-
...validationDiagnostics2(options.getAdapter(format), document),
|
|
3372
|
-
...
|
|
3517
|
+
...build.diagnostics[0]?.code === ERROR_CODES.DEPENDENCY_MISSING ? [] : validationDiagnostics2(options.getAdapter(format), document),
|
|
3518
|
+
...build.diagnostics
|
|
3373
3519
|
]);
|
|
3374
3520
|
}
|
|
3375
3521
|
const generateMs = elapsed(generateStarted);
|